Compare commits

...
Author SHA1 Message Date
Siddarth Kumar c5cbb79bc7 chore: commit wip stuff 2024-03-22 14:08:26 +05:30
Yevheniia Berdnyk 05a810b051 e2e: updated method for a profile picture changing 2024-02-29 02:25:59 +02:00
Ibrahem Khalil 7db35a3cc8 Fix statusbar color on dark screens (#18785) 2024-02-28 18:53:13 +02:00
Siddarth Kumar 1301bbc0d9 chore: bump status-go version to v0.176.2 (#19036) 2024-02-28 20:42:10 +05:30
Anton Iakimov 5f4d4bfc11 ci: cleanup more build artifacts
Should be cleaning artifacts for more than 1+1 build for each branch.
2024-02-28 15:50:47 +01:00
flexsurfer 26dd51c5da [#19004] Password input cursor is jumping when typing password fast (#19029) 2024-02-28 15:23:15 +01:00
Parvesh Monu bd7ced5f25 Fix quo preview navigation (#19003) 2024-02-28 18:34:53 +05:30
Icaro Motta 61f8c51d85 Fix: only run unit tests in test-unit target (#18947) 2024-02-28 09:51:53 -03:00
Mohsen 781b83d13d [#18961] feat: add new contact profile skeleton (#19001) 2024-02-28 15:02:24 +03:00
flexsurfer b7bffb3bd3 migrating to react state. step 2 (#18905) 2024-02-28 11:49:49 +01:00
flexsurfer 047e45d2a3 migrating to react state. step 1 (#18901) 2024-02-28 11:48:23 +01:00
Flavio Fraschetti acc6a3c072 Add the Contract Tests to the mobile CI/CD pipeline (#19017)
* Add the Contract Tests to the mobile CI/CD pipeline

* Increase timeout to 20 minutes
2024-02-28 06:47:45 -03:00
Andrea Maria Piana 7171c7085d Bump version to 0.176.1 (#19024) 2024-02-28 08:35:33 +00:00
Lungu Cristian 6d227b61e1 Add react hooks use-state and use-memo (#19016)
* feat: added use-state and use-memo

* feat: add reagent functional compiler

* test: fixed recovery-phrase tests
2024-02-27 15:36:12 +02:00
Siddarth Kumar bf1a734c4e fix: cleanup wallet push notification subscription (#19014)
fixes #19012

### Summary

The legacy wallet code was removed here -> https://github.com/status-im/status-mobile/pull/18749
as part of that purge we also cleaned up re-frame subscriptions for wallet push notifications.

Since the new wallet is under active development we would not need this toggle in settings just yet.

This commit fixes the app crashing on settings UI.

## Platforms
- Android
- iOS
2024-02-27 17:10:37 +05:30
68 changed files with 1397 additions and 1239 deletions
+1
View File
@@ -34,3 +34,4 @@ STICKERS_TEST_ENABLED=1
LOCAL_PAIRING_ENABLED=1
TEST_STATEOFUS=1
FAST_CREATE_COMMUNITY_ENABLED=1
FLAG_NEW_CONTACT_UI_ENABLED=0
+1 -1
View File
@@ -359,7 +359,7 @@ test-watch-for-repl: ##@test Watch all Clojure tests and support REPL connection
"until [ -f $$SHADOW_OUTPUT_TO ] ; do sleep 1 ; done ; node --require ./test-resources/override.js $$SHADOW_OUTPUT_TO --repl"
test-unit: export SHADOW_OUTPUT_TO := target/unit_test/test.js
test-unit: export SHADOW_NS_REGEXP := ^(?!tests\.integration-test)(?!tests-im\.contract-test).*-test$$
test-unit: export SHADOW_NS_REGEXP := ^(?!tests\.integration-test)(?!tests\.contract-test).*-test$$
test-unit: ##@test Run unit tests
test-unit: _test-clojure
+1 -1
View File
@@ -15,7 +15,7 @@ pipeline {
buildDiscarder(logRotator(
numToKeepStr: '10',
daysToKeepStr: '20',
artifactNumToKeepStr: '10',
artifactNumToKeepStr: '1',
))
/* Allows combined build to copy */
copyArtifactPermission('/status-mobile/*')
+1 -1
View File
@@ -13,7 +13,7 @@ pipeline {
buildDiscarder(logRotator(
numToKeepStr: '10',
daysToKeepStr: '30',
artifactNumToKeepStr: '10',
artifactNumToKeepStr: '1',
))
}
+1
View File
@@ -23,6 +23,7 @@ pipeline {
buildDiscarder(logRotator(
numToKeepStr: '10',
daysToKeepStr: '30',
artifactNumToKeepStr: '1',
))
}
+1 -1
View File
@@ -23,7 +23,7 @@ pipeline {
buildDiscarder(logRotator(
numToKeepStr: '10',
daysToKeepStr: '20',
artifactNumToKeepStr: '10',
artifactNumToKeepStr: '1',
))
/* Allows combined build to copy */
copyArtifactPermission('/status-mobile/*')
+10 -2
View File
@@ -10,12 +10,12 @@ pipeline {
options {
timestamps()
/* Prevent Jenkins jobs from running forever */
timeout(time: 15, unit: 'MINUTES')
timeout(time: 20, unit: 'MINUTES')
/* Limit builds retained */
buildDiscarder(logRotator(
numToKeepStr: '10',
daysToKeepStr: '20',
artifactNumToKeepStr: '10',
artifactNumToKeepStr: '1',
))
/* Abort old PR builds. */
disableConcurrentBuilds(
@@ -62,6 +62,14 @@ pipeline {
}
}
}
stage('Contract Tests') {
steps {
sh """#!/bin/bash
set -eo pipefail
make test-contract 2>&1 | tee -a ${LOG_FILE}
"""
}
}
stage('Integration Tests') {
steps {
sh """#!/bin/bash
+5
View File
@@ -29,6 +29,11 @@ pipeline {
options {
disableConcurrentBuilds()
buildDiscarder(logRotator(
numToKeepStr: '10',
daysToKeepStr: '30',
artifactNumToKeepStr: '1',
))
}
stages {
+5
View File
@@ -47,6 +47,11 @@ pipeline {
options {
disableConcurrentBuilds()
timeout(time: 90, unit: 'MINUTES')
buildDiscarder(logRotator(
numToKeepStr: '10',
daysToKeepStr: '30',
artifactNumToKeepStr: '1',
))
}
stages {
+5
View File
@@ -28,6 +28,11 @@ pipeline {
options {
disableConcurrentBuilds()
buildDiscarder(logRotator(
numToKeepStr: '10',
daysToKeepStr: '30',
artifactNumToKeepStr: '1',
))
}
+1
View File
@@ -34,6 +34,7 @@ pipeline {
buildDiscarder(logRotator(
numToKeepStr: '20',
daysToKeepStr: '30',
artifactNumToKeepStr: '1',
))
}
+3 -1
View File
@@ -74,6 +74,8 @@
</dict>
<key>NSCameraUsageDescription</key>
<string>Status uses camera to take pictures and scan QR codes.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Status needs access to the local network to give you the ability to connect with peers.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Location access is required for some DApps to function properly.</string>
<key>NSLocationWhenInUseUsageDescription</key>
@@ -133,7 +135,7 @@
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<false/>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
<string>A00000080400010101</string>
@@ -4,10 +4,25 @@
#import "Statusgo.h"
#import "Utils.h"
@interface NetworkManager() <NSNetServiceBrowserDelegate>
@property (nonatomic, strong) NSNetServiceBrowser *serviceBrowser;
@property (nonatomic, copy) RCTResponseSenderBlock callback;
@end
@implementation NetworkManager
RCT_EXPORT_MODULE();
- (instancetype)init {
if ((self = [super init])) {
self.serviceBrowser = [[NSNetServiceBrowser alloc] init];
self.serviceBrowser.delegate = self;
}
return self;
}
RCT_EXPORT_METHOD(startSearchForLocalPairingPeers:(RCTResponseSenderBlock)callback) {
NSString *result = StatusgoStartSearchForLocalPairingPeers();
callback(@[result]);
@@ -95,6 +110,39 @@ RCT_EXPORT_METHOD(callPrivateRPC:(NSString *)payload
});
}
RCT_EXPORT_METHOD(requestLocalNetworkAccess:(RCTResponseSenderBlock)callback) {
self.callback = callback;
// Start browsing for services using TCP on the local network.
dispatch_async(dispatch_get_main_queue(), ^{
[self.serviceBrowser searchForServicesOfType:@"_http._tcp." inDomain:@""];
});
}
// NSNetServiceBrowser delegate method
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didNotSearch:(NSDictionary<NSString *,NSNumber *> *)errorDict {
RCTLogInfo(@"Failed to search for services: %@", errorDict);
if (self.callback) {
self.callback(@[@"Failed to search for services"]);
}
}
// NSNetServiceBrowser delegate method
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing {
RCTLogInfo(@"Found service: %@", service);
// we stop the search after finding the first service or wait until the search is finished.
[browser stop];
if (self.callback) {
self.callback(@[[NSNull null], @"Requested local network access and found service"]);
}
}
// NSNetServiceBrowser delegate method
- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)browser {
RCTLogInfo(@"Stopped searching for services");
}
#pragma mark - Recover
RCT_EXPORT_METHOD(recover:(NSString *)message
-15
View File
@@ -72,18 +72,3 @@
{:events [:show-tooltip]}
[_ tooltip-id]
{:show-tooltip tooltip-id})
(rf/defn show-profile
{:events [:chat.ui/show-profile]}
[{:keys [db]} identity ens-name]
(let [my-public-key (get-in db [:profile/profile :public-key])]
(if (not= my-public-key identity)
{:db (-> db
(assoc :contacts/identity identity)
(assoc :contacts/ens-name ens-name))
:dispatch [:contacts/build-contact
{:pubkey identity
:ens ens-name
:success-fn (fn [_]
{:dispatch [:open-modal :profile]})}]}
{:dispatch [:navigate-to :my-profile]})))
@@ -8,24 +8,6 @@
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn local-notifications
[]
(let [{:keys [enabled?]} (rf/sub [:push-notifications/wallet-transactions])
{:keys [notifications-enabled?]} (rf/sub [:profile/profile])]
[:<>
[quo/separator
{:color (:ui-02 @colors/theme)
:style {:margin-vertical 8}}]
[quo/list-header (i18n/label :t/local-notifications)]
[list.item/list-item
{:size :small
:title (i18n/label :t/notifications-transactions)
:accessibility-label :notifications-button
:active (and notifications-enabled? enabled?)
:on-press #(rf/dispatch [:push-notifications.wallet/switch-transactions
(not enabled?)])
:accessory :switch}]]))
(defn notifications-settings-ios
[]
(let [{:keys [notifications-enabled?
@@ -63,8 +45,7 @@
:on-press #(rf/dispatch
[:push-notifications/switch-block-mentions
(not push-notifications-block-mentions?)])
:accessory :switch}]
[local-notifications]]))
:accessory :switch}]]))
(defn notifications-settings-android
[]
@@ -76,8 +57,7 @@
:subtitle (i18n/label :t/local-notifications-subtitle)
:active notifications-enabled?
:on-press #(rf/dispatch [:push-notifications/switch (not notifications-enabled?)])
:accessory :switch}]
[local-notifications]]))
:accessory :switch}]]))
(defn notifications-settings
[]
+1 -1
View File
@@ -53,7 +53,7 @@
:FlatList {}
:SectionList {}
:Text {}
:StatusBar {}
:StatusBar {:setBarStyle identity}
:ScrollView {}
:SafeAreaView {}
:KeyboardAvoidingView {}
+5
View File
@@ -258,6 +258,11 @@
(log/debug "[native-module] set-soft-input-mode")
(.setSoftInputMode ^js (ui-helper) mode))
(defn request-local-network-access
[callback]
(log/debug "[native-module] request-local-network-access")
(.requestLocalNetworkAccess ^js (network) callback))
(defn call-rpc
[payload callback]
(log/debug "[native-module] call-rpc")
@@ -6,8 +6,7 @@
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.platform :as platform]
[reagent.core :as reagent]))
[react-native.platform :as platform]))
(defn remove-http-https-www
[value]
@@ -68,78 +67,86 @@
[:cursor-color :placeholder-text-color :editable :on-change-text :on-focus
:on-blur :on-clear :value :disabled? :blur? :customization-color :theme])
(defn- view-internal
[{:keys [default-value]
:or {default-value ""}}]
(let [state (reagent/atom :default)
value (reagent/atom default-value)
set-active #(reset! state :active)
set-default #(reset! state :default)
set-value #(reset! value %)
ref (atom nil)
clear-input (fn []
(.clear ^js @ref)
(reset! value ""))
focus-input (fn []
(set-active)
(.focus ^js @ref))]
(fn [{:keys [disabled? blur? on-change-text customization-color
on-clear on-focus on-blur theme get-ref locked?
favicon favicon-color favicon-size]
:as props}]
(let [clean-props (apply dissoc props props-to-remove)]
[rn/view {:style style/root-container}
(when (and (seq @value) (= @state :default))
[rn/touchable-opacity
{:style style/default-container
:on-press focus-input}
(when favicon
[icon/icon favicon
{:accessibility-label :browser-input-favicon
:color favicon-color
:container-style style/favicon-icon-container
:size favicon-size}])
[rn/text
{:accessibility-label :browser-input-label
:style (style/text)} (remove-http-https-www @value)]
(when locked?
[lock-icon
{:blur? blur?
:theme theme}])])
[rn/view {:style (style/active-container (or (empty? @value) (= @state :active)))}
[rn/text-input
(merge
clean-props
{:accessibility-label :browser-input
:auto-capitalize :none
:auto-correct false
:cursor-color (cursor-color customization-color theme)
:editable (not disabled?)
:keyboard-appearance (colors/theme-colors :light :dark theme)
:keyboard-type :web-search
:on-blur (fn []
(set-default)
(when on-blur (on-blur)))
:on-change-text (fn [new-text]
(set-value new-text)
(when on-change-text (on-change-text new-text)))
:on-focus (fn []
(set-active)
(when on-focus (on-focus)))
:placeholder-text-color (placeholder-color @state blur? theme)
:ref (fn [r]
(reset! ref r)
(when get-ref (get-ref r)))
:selection-color (when platform/ios?
(cursor-color customization-color theme))
:select-text-on-focus true
:style (style/input disabled?)})]
(when (seq @value)
[clear-button
{:blur? blur?
:on-press (fn []
(clear-input)
(when on-clear (on-clear)))
:theme theme}])]]))))
(def view (quo.theme/with-theme view-internal))
(defn view
[{:keys [disabled? blur? on-change-text customization-color
on-clear on-focus on-blur get-ref locked?
favicon favicon-color favicon-size default-value]
:or {default-value ""}
:as props}]
(let [ref (rn/use-ref-atom nil)
on-ref (rn/use-callback
(fn [r]
(reset! ref r)
(when get-ref (get-ref r)))
[get-ref])
theme (quo.theme/use-theme)
[state set-state] (rn/use-state :default)
[value set-value] (rn/use-state default-value)
on-clear (rn/use-callback
(fn []
(.clear ^js @ref)
(set-value "")
(when on-clear (on-clear)))
[on-clear])
focus-input (rn/use-callback
(fn []
(set-state :active)
(.focus ^js @ref)))
on-blur (rn/use-callback
(fn []
(set-state :default)
(when on-blur (on-blur)))
[on-blur])
on-change-text (rn/use-callback
(fn [new-text]
(set-value new-text)
(when on-change-text (on-change-text new-text)))
[on-change-text])
on-focus (rn/use-callback
(fn []
(set-state :active)
(when on-focus (on-focus)))
[on-focus])
clean-props (apply dissoc props props-to-remove)]
[rn/view {:style style/root-container}
(when (and (seq value) (= state :default))
[rn/touchable-opacity
{:style style/default-container
:on-press focus-input}
(when favicon
[icon/icon favicon
{:accessibility-label :browser-input-favicon
:color favicon-color
:container-style style/favicon-icon-container
:size favicon-size}])
[rn/text
{:accessibility-label :browser-input-label
:style (style/text)}
(remove-http-https-www value)]
(when locked?
[lock-icon {:blur? blur? :theme theme}])])
[rn/view {:style (style/active-container (or (empty? value) (= state :active)))}
[rn/text-input
(merge
clean-props
{:accessibility-label :browser-input
:auto-capitalize :none
:auto-correct false
:cursor-color (cursor-color customization-color theme)
:editable (not disabled?)
:keyboard-appearance (colors/theme-colors :light :dark theme)
:keyboard-type :web-search
:on-blur on-blur
:on-change-text on-change-text
:on-focus on-focus
:placeholder-text-color (placeholder-color state blur? theme)
:ref on-ref
:selection-color (when platform/ios?
(cursor-color customization-color theme))
:select-text-on-focus true
:style (style/input disabled?)})]
(when (seq value)
[clear-button
{:blur? blur?
:on-press on-clear
:theme theme}])]]))
+99 -99
View File
@@ -7,10 +7,9 @@
[quo.foundations.customization-colors :as customization-colors]
[quo.theme :as theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[reagent.core :as reagent]))
[react-native.core :as rn]))
(defn- button-internal
(defn button
"with label
[button opts \"label\"]
opts
@@ -29,102 +28,103 @@
:theme :light/:dark
only icon
[button {:icon-only? true} :i/close-circle]"
[_ _]
(let [pressed-state? (reagent/atom false)]
(fn
[{:keys [on-press on-long-press disabled? type background size icon-left icon-right icon-top
customization-color theme accessibility-label icon-only? container-style inner-style
pressed? on-press-in on-press-out allow-multiple-presses?]
:or {type :primary
size 40
customization-color (if (= type :primary) :blue nil)}}
children]
(let [{:keys [icon-color background-color label-color border-color blur-type
blur-overlay-color border-radius overlay-customization-color]}
(button-properties/get-values {:customization-color customization-color
:background background
:type type
:theme theme
:pressed? (if pressed? pressed? @pressed-state?)
:icon-only? icon-only?})
icon-size (when (= 24 size) 12)]
[rn/touchable-without-feedback
{:disabled disabled?
:accessibility-label accessibility-label
:on-press-in (fn []
(reset! pressed-state? true)
(when on-press-in (on-press-in)))
:on-press-out (fn []
(reset! pressed-state? nil)
(when on-press-out (on-press-out)))
:on-press on-press
:allow-multiple-presses? allow-multiple-presses?
:on-long-press on-long-press}
[{:keys [on-press on-long-press disabled? type background size icon-left icon-right icon-top
customization-color accessibility-label icon-only? container-style inner-style
pressed? on-press-in on-press-out allow-multiple-presses?]
:or {type :primary
size 40
customization-color (if (= type :primary) :blue nil)}}
children]
(let [[pressed-state? set-pressed-state] (rn/use-state false)
theme (theme/use-theme-value)
{:keys [icon-color background-color label-color border-color blur-type
blur-overlay-color border-radius overlay-customization-color]}
(button-properties/get-values {:customization-color customization-color
:background background
:type type
:theme theme
:pressed? (if pressed? pressed? pressed-state?)
:icon-only? icon-only?})
icon-size (when (= 24 size) 12)
on-press-in-cb (rn/use-callback
(fn []
(set-pressed-state true)
(when on-press-in (on-press-in))))
on-press-out-cb (rn/use-callback
(fn []
(set-pressed-state nil)
(when on-press-out (on-press-out))))]
[rn/touchable-without-feedback
{:disabled disabled?
:accessibility-label accessibility-label
:on-press-in on-press-in-cb
:on-press-out on-press-out-cb
:on-press on-press
:allow-multiple-presses? allow-multiple-presses?
:on-long-press on-long-press}
[rn/view
{:style (merge
(style/shape-style-container size border-radius)
container-style)}
[rn/view
{:style (merge
(style/style-container {:size size
:disabled? disabled?
:border-radius border-radius
:background-color background-color
:border-color border-color
:icon-only? icon-only?
:icon-top icon-top
:icon-left icon-left
:icon-right icon-right})
inner-style)}
(when overlay-customization-color
[customization-colors/overlay
{:customization-color overlay-customization-color
:theme theme
:pressed? (if pressed? pressed? pressed-state?)}])
(when (= background :photo)
[blur/view
{:blur-radius 20
:blur-type blur-type
:overlay-color blur-overlay-color
:style style/blur-view}])
(when icon-top
[rn/view
{:style (merge
(style/shape-style-container size border-radius)
container-style)}
[rn/view
{:style (merge
(style/style-container {:size size
:disabled? disabled?
:border-radius border-radius
:background-color background-color
:border-color border-color
:icon-only? icon-only?
:icon-top icon-top
:icon-left icon-left
:icon-right icon-right})
inner-style)}
(when overlay-customization-color
[customization-colors/overlay
{:customization-color overlay-customization-color
:theme theme
:pressed? (if pressed? pressed? @pressed-state?)}])
(when (= background :photo)
[blur/view
{:blur-radius 20
:blur-type blur-type
:overlay-color blur-overlay-color
:style style/blur-view}])
(when icon-top
[rn/view
[quo.icons/icon icon-top
{:container-style {:margin-bottom 2}
:color icon-color
:size icon-size}]])
(when icon-left
[rn/view
{:style (style/icon-left-icon-style
{:size size
:icon-size icon-size})}
[quo.icons/icon icon-left
{:color icon-color
:size icon-size}]])
[rn/view
(cond
icon-only?
[quo.icons/icon children
{:color label-color
:size icon-size}]
[quo.icons/icon icon-top
{:container-style {:margin-bottom 2}
:color icon-color
:size icon-size}]])
(when icon-left
[rn/view
{:style (style/icon-left-icon-style
{:size size
:icon-size icon-size})}
[quo.icons/icon icon-left
{:color icon-color
:size icon-size}]])
[rn/view
(cond
icon-only?
[quo.icons/icon children
{:color label-color
:size icon-size}]
(string? children)
[text/text
{:size (when (#{56 24} size) :paragraph-2)
:weight :medium
:number-of-lines 1
:style {:color label-color}}
children]
(string? children)
[text/text
{:size (when (#{56 24} size) :paragraph-2)
:weight :medium
:number-of-lines 1
:style {:color label-color}}
children]
(vector? children)
children)]
(when icon-right
[rn/view
{:style (style/icon-right-icon-style
{:size size
:icon-size icon-size})}
[quo.icons/icon icon-right
{:color icon-color
:size icon-size}]])]]]))))
(def button (theme/with-theme button-internal))
(vector? children)
children)]
(when icon-right
[rn/view
{:style (style/icon-right-icon-style
{:size size
:icon-size icon-size})}
[quo.icons/icon icon-right
{:color icon-color
:size icon-size}]])]]]))
@@ -3,28 +3,24 @@
[quo.components.buttons.composer-button.style :as style]
[quo.components.icon :as quo.icons]
[quo.theme :as theme]
[react-native.core :as rn]
[reagent.core :as reagent]))
[react-native.core :as rn]))
(defn- view-internal
[_ _]
(let [pressed? (reagent/atom false)]
(fn
[{:keys [on-press on-long-press disabled? theme blur? icon accessibility-label container-style]}]
[rn/pressable
{:accessibility-label (or accessibility-label :composer-button)
:on-press on-press
:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? nil)
:on-long-press on-long-press
:disabled disabled?
:style (merge (style/main {:pressed? @pressed?
:blur? blur?
:theme theme
:disabled? disabled?})
container-style)}
[quo.icons/icon icon
{:color (style/get-label-color {:blur? blur?
:theme theme})}]])))
(def view (theme/with-theme view-internal))
(defn view
[{:keys [on-press on-long-press disabled? blur? icon accessibility-label container-style]}]
(let [[pressed? set-pressed] (rn/use-state false)
theme (theme/use-theme-value)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed nil))]
[rn/pressable
{:accessibility-label (or accessibility-label :composer-button)
:on-press on-press
:on-press-in on-press-in
:on-press-out on-press-out
:on-long-press on-long-press
:disabled disabled?
:style (merge (style/main {:pressed? pressed?
:blur? blur?
:theme theme
:disabled? disabled?})
container-style)}
[quo.icons/icon icon {:color (style/get-label-color {:blur? blur? :theme theme})}]]))
@@ -5,8 +5,7 @@
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]))
[react-native.core :as rn]))
(defn- get-button-color
[{:keys [type pressed? customization-color theme]}]
@@ -45,48 +44,48 @@
:color (get-icon-and-text-color type theme)
:container-style (style/container type)}])
(defn- view-internal
(defn view
"[dynamic-button opts]
opts
{:type :jump-to/:mention/:notification-down/:notification-up/:search/:search-with-label/:scroll-to-bottom
:on-press fn
:count mentions or notifications count
:customization-color customize jump-to and mention button color}"
[_]
(let [pressed? (reagent/atom false)]
(fn [{:keys [type label on-press customization-color style theme] :as args}]
[rn/touchable-opacity
{:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false)
:on-press on-press
:active-opacity 1
:hit-slop {:top 5 :bottom 5 :left 5 :right 5}
:pointer-events :auto
:style {:height 24}
:accessibility-label type}
[rn/view
{:style (merge
{:flex-direction :row
:height 24
:border-radius 12
:background-color (get-button-color {:type type
:pressed? @pressed?
:customization-color (or customization-color
:primary)
:theme theme})}
style)}
(when (#{:mention :search :search-with-label :scroll-to-bottom} type)
[icon-view type])
(when (#{:jump-to :mention :notification-down :notification-up :search-with-label} type)
[text/text
{:weight :medium
:size :paragraph-2
:style (assoc (style/text type) :color (get-icon-and-text-color type theme))}
(case type
:jump-to label
:search-with-label label
(:mention :notification-down :notification-up) (str (:count args)))])
(when (#{:jump-to :notification-down :notification-up} type)
[icon-view type theme])]])))
(def view (quo.theme/with-theme view-internal))
[{:keys [type label on-press customization-color style] :as args}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
button-color (get-button-color {:type type
:pressed? pressed?
:customization-color (or customization-color :primary)
:theme theme})
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))]
[rn/touchable-opacity
{:on-press-in on-press-in
:on-press-out on-press-out
:on-press on-press
:active-opacity 1
:hit-slop {:top 5 :bottom 5 :left 5 :right 5}
:pointer-events :auto
:style {:height 24}
:accessibility-label type}
[rn/view
{:style (merge
{:flex-direction :row
:height 24
:border-radius 12
:background-color button-color}
style)}
(when (#{:mention :search :search-with-label :scroll-to-bottom} type)
[icon-view type])
(when (#{:jump-to :mention :notification-down :notification-up :search-with-label} type)
[text/text
{:weight :medium
:size :paragraph-2
:style (assoc (style/text type) :color (get-icon-and-text-color type theme))}
(case type
:jump-to label
:search-with-label label
(:mention :notification-down :notification-up) (str (:count args)))])
(when (#{:jump-to :notification-down :notification-up} type)
[icon-view type theme])]]))
@@ -6,29 +6,25 @@
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[utils.i18n :as i18n]))
(defn- view-internal
[_]
(let [pressed? (reagent/atom false)
on-press-in #(reset! pressed? true)
on-press-out #(reset! pressed? nil)]
(fn
[{:keys [on-press on-long-press disabled? theme container-style]}]
[rn/pressable
{:accessibility-label :log-out-button
:on-press on-press
:on-press-in on-press-in
:on-press-out on-press-out
:on-long-press on-long-press
:disabled disabled?
:style (merge (style/main {:pressed? @pressed?
:theme theme
:disabled? disabled?})
container-style)}
[icon/icon :i/log-out {:color (if pressed? colors/white-opa-40 colors/white-opa-70)}]
[text/text {:weight :medium :size :paragraph-1}
(i18n/label :t/logout)]])))
(def view (quo.theme/with-theme view-internal))
(defn view
[{:keys [on-press on-long-press disabled? container-style]}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed nil))]
[rn/pressable
{:accessibility-label :log-out-button
:on-press on-press
:on-press-in on-press-in
:on-press-out on-press-out
:on-long-press on-long-press
:disabled disabled?
:style (merge (style/main {:pressed? pressed?
:theme theme
:disabled? disabled?})
container-style)}
[icon/icon :i/log-out {:color (if pressed? colors/white-opa-40 colors/white-opa-70)}]
[text/text {:weight :medium :size :paragraph-1}
(i18n/label :t/logout)]]))
@@ -1,9 +1,5 @@
(ns quo.components.buttons.slide-button.animations
(:require
[oops.core :as oops]
[quo.components.buttons.slide-button.utils :as utils]
[react-native.gesture :as gesture]
[react-native.reanimated :as reanimated]))
(:require [react-native.reanimated :as reanimated]))
(def ^:private extrapolation
{:extrapolateLeft "clamp"
@@ -66,36 +62,6 @@
:damping 30
:stiffness 400}))
(defn- complete-animation
[sliding-complete?]
(reset! sliding-complete? true))
(defn reset-track-position
[x-pos]
(animate-spring x-pos 0))
;; Gestures
(defn drag-gesture
[x-pos
gestures-disabled?
disabled?
track-width
sliding-complete?]
(let [gestures-enabled? (not (or disabled? @gestures-disabled?))]
(-> (gesture/gesture-pan)
(gesture/with-test-ID :slide-button-gestures)
(gesture/enabled gestures-enabled?)
(gesture/min-distance 0)
(gesture/on-update (fn [event]
(let [x-translation (oops/oget event "translationX")
clamped-x (utils/clamp-value x-translation 0 track-width)
reached-end? (>= clamped-x track-width)]
(reanimated/set-shared-value x-pos clamped-x)
(when (and reached-end? (not @sliding-complete?))
(reset! gestures-disabled? true)
(complete-animation sliding-complete?)))))
(gesture/on-end (fn [event]
(let [x-translation (oops/oget event "translationX")
reached-end? (>= x-translation track-width)]
(when (not reached-end?)
(reset-track-position x-pos))))))))
+100 -90
View File
@@ -11,94 +11,35 @@
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]))
[react-native.reanimated :as reanimated]))
(defn- f-slider
[{:keys [disabled?]}]
(let [track-width (reagent/atom nil)
sliding-complete? (reagent/atom false)
gestures-disabled? (reagent/atom disabled?)
on-track-layout (fn [evt]
(let [width (oops/oget evt "nativeEvent.layout.width")]
(reset! track-width width)))]
(fn [{:keys [on-reset
on-complete
track-text
track-icon
disabled?
customization-color
size
container-style
theme
type
blur?]}]
(let [x-pos (reanimated/use-shared-value 0)
dimensions (partial utils/get-dimensions
(or @track-width constants/default-width)
size)
interpolate-track (partial animations/interpolate-track
x-pos
(dimensions :usable-track)
(dimensions :thumb))
custom-color (if (= type :danger) :danger customization-color)]
(rn/use-effect (fn []
(when @sliding-complete?
(on-complete)))
[@sliding-complete?])
(rn/use-effect (fn []
(when on-reset
(reset! sliding-complete? false)
(reset! gestures-disabled? false)
(animations/reset-track-position x-pos)
(on-reset)))
[on-reset])
[gesture/gesture-detector
{:gesture (animations/drag-gesture x-pos
gestures-disabled?
disabled?
(dimensions :usable-track)
sliding-complete?)}
[reanimated/view
{:test-ID :slide-button-track
:style (merge (style/track {:disabled? disabled?
:customization-color custom-color
:height (dimensions :track-height)
:blur? blur?})
container-style)
:on-layout (when-not (some? @track-width)
on-track-layout)}
[reanimated/view {:style (style/track-cover interpolate-track)}
[rn/view {:style (style/track-cover-text-container @track-width)}
[icon/icon track-icon
{:color (utils/text-color custom-color theme blur?)
:size 20}]
[rn/view {:width 4}]
[text/text
{:weight :medium
:size :paragraph-1
:style (style/track-text custom-color theme blur?)}
track-text]]]
[reanimated/view
{:style (style/thumb-container {:interpolate-track interpolate-track
:thumb-size (dimensions :thumb)
:customization-color custom-color
:theme theme
:blur? blur?})}
[reanimated/view {:style (style/arrow-icon-container interpolate-track)}
[icon/icon :arrow-right
{:color colors/white
:size 20}]]
[reanimated/view
{:style (style/action-icon interpolate-track
(dimensions :thumb))}
[icon/icon track-icon
{:color colors/white
:size 20}]]]]]))))
(defn drag-gesture
[x-pos gestures-disabled? set-gestures-disabled disabled? track-width sliding-complete?
set-sliding-complete
on-complete reset-fn]
(let [gestures-enabled? (not (or disabled? gestures-disabled?))]
(-> (gesture/gesture-pan)
(gesture/with-test-ID :slide-button-gestures)
(gesture/enabled gestures-enabled?)
(gesture/min-distance 0)
(gesture/on-update (fn [event]
(let [x-translation (oops/oget event "translationX")
clamped-x (utils/clamp-value x-translation 0 track-width)
reached-end? (>= clamped-x track-width)]
(reanimated/set-shared-value x-pos clamped-x)
(when (and reached-end? (not sliding-complete?))
(set-gestures-disabled true)
(set-sliding-complete true)
(when on-complete (on-complete reset-fn))))))
(gesture/on-end (fn [event]
(let [x-translation (oops/oget event "translationX")
reached-end? (>= x-translation track-width)]
(when (not reached-end?)
(animations/reset-track-position x-pos))))))))
(defn- view-internal
(defn view
"Options
- `on-complete` Callback called when the sliding is complete
- `on-complete` Callback called when the sliding is complete, returns reset fn as a parameter
- `disabled?` Boolean that disables the button
(_and gestures_)
- `size` :size/s-40`/`:size/s-48`
@@ -106,9 +47,78 @@
- `track-icon` Key of the icon shown on the track
(e.g. `:face-id`)
- `customization-color` Customization color
- `on-reset` A callback which can be used to reset the component and run required functionality
"
[props]
[:f> f-slider props])
(def view (quo.theme/with-theme view-internal))
[{:keys [on-complete track-text track-icon disabled? customization-color size
container-style type blur?]}]
(let [theme (quo.theme/use-theme-value)
x-pos (reanimated/use-shared-value 0)
[track-width set-track-width] (rn/use-state nil)
[sliding-complete?
set-sliding-complete] (rn/use-state false)
[gestures-disabled?
set-gestures-disabled] (rn/use-state disabled?)
on-track-layout (rn/use-callback
#(set-track-width (oops/oget % "nativeEvent.layout.width")))
reset-fn (rn/use-callback
(fn []
(set-sliding-complete false)
(set-gestures-disabled false)
(animations/reset-track-position x-pos)))
dimensions (rn/use-callback
(partial utils/get-dimensions
(or track-width constants/default-width)
size)
[track-width])
interpolate-track (rn/use-callback
(partial animations/interpolate-track
x-pos
(dimensions :usable-track)
(dimensions :thumb))
[dimensions])
custom-color (if (= type :danger) :danger customization-color)
gesture (rn/use-memo #(drag-gesture x-pos
gestures-disabled?
set-gestures-disabled
disabled?
(dimensions :usable-track)
sliding-complete?
set-sliding-complete
on-complete
reset-fn)
[gestures-disabled? sliding-complete? disabled?])]
[gesture/gesture-detector
{:gesture gesture}
[reanimated/view
{:test-ID :slide-button-track
:style (merge (style/track {:disabled? disabled?
:customization-color custom-color
:height (dimensions :track-height)
:blur? blur?})
container-style)
:on-layout on-track-layout}
[reanimated/view {:style (style/track-cover interpolate-track)}
[rn/view {:style (style/track-cover-text-container track-width)}
[icon/icon track-icon
{:color (utils/text-color custom-color theme blur?)
:size 20}]
[rn/view {:width 4}]
[text/text
{:weight :medium
:size :paragraph-1
:style (style/track-text custom-color theme blur?)}
track-text]]]
[reanimated/view
{:style (style/thumb-container {:interpolate-track interpolate-track
:thumb-size (dimensions :thumb)
:customization-color custom-color
:theme theme
:blur? blur?})}
[reanimated/view {:style (style/arrow-icon-container interpolate-track)}
[icon/icon :arrow-right
{:color colors/white
:size 20}]]
[reanimated/view
{:style (style/action-icon interpolate-track (dimensions :thumb))}
[icon/icon track-icon
{:color colors/white
:size 20}]]]]]))
@@ -4,26 +4,24 @@
[quo.components.icon :as quo.icons]
[quo.foundations.colors :as colors]
[quo.theme :as theme]
[react-native.core :as rn]
[reagent.core :as reagent]))
[react-native.core :as rn]))
(defn- view-internal
[]
(let [pressed? (reagent/atom false)]
(fn
[{:keys [on-press on-long-press disabled? icon accessibility-label container-style theme]}]
[rn/pressable
{:accessibility-label (or accessibility-label :wallet-button)
:on-press on-press
:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? nil)
:on-long-press on-long-press
:disabled disabled?
:style (merge (style/main {:pressed? @pressed?
:theme theme
:disabled? disabled?})
container-style)}
[quo.icons/icon icon
{:color (colors/theme-colors colors/neutral-100 colors/white theme)}]])))
(def view (theme/with-theme view-internal))
(defn view
[{:keys [on-press on-long-press disabled? icon accessibility-label container-style]}]
(let [theme (theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed nil))]
[rn/pressable
{:accessibility-label (or accessibility-label :wallet-button)
:on-press on-press
:on-press-in on-press-in
:on-press-out on-press-out
:on-long-press on-long-press
:disabled disabled?
:style (merge (style/main {:pressed? pressed?
:theme theme
:disabled? disabled?})
container-style)}
[quo.icons/icon icon
{:color (colors/theme-colors colors/neutral-100 colors/white theme)}]]))
+31 -31
View File
@@ -8,36 +8,36 @@
[quo.components.calendar.calendar.years-list.view :as years-list]
[quo.theme :as theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[utils.number :as utils.number]))
(defn- view-internal
[]
(let [selected-year (reagent/atom (utils/current-year))
selected-month (reagent/atom (utils/current-month))
on-change-year #(reset! selected-year %)
on-change-month (fn [new-date]
(reset! selected-year (utils.number/parse-int (:year new-date)))
(reset! selected-month (utils.number/parse-int (:month new-date))))]
(fn [{:keys [on-change start-date end-date theme]}]
[rn/view
{:style (style/container theme)}
[years-list/view
{:on-change-year on-change-year
:year @selected-year}]
[rn/view
{:style style/container-main}
[month-picker/view
{:year @selected-year
:month @selected-month
:on-change on-change-month}]
[weekdays-header/view]
[days-grid/view
{:year @selected-year
:month @selected-month
:start-date start-date
:end-date end-date
:on-change on-change
:customization-color :blue}]]])))
(def view (theme/with-theme view-internal))
(defn view
[{:keys [on-change start-date end-date]}]
(let [theme (theme/use-theme-value)
[selected-year set-selected-year] (rn/use-state (utils/current-year))
[selected-month set-selected-month] (rn/use-state (utils/current-month))
on-change-year (rn/use-callback #(set-selected-year %))
on-change-month (rn/use-callback
(fn [new-date]
(set-selected-year
(utils.number/parse-int (:year new-date)))
(set-selected-month
(utils.number/parse-int (:month new-date)))))]
[rn/view
{:style (style/container theme)}
[years-list/view
{:on-change-year on-change-year
:year selected-year}]
[rn/view
{:style style/container-main}
[month-picker/view
{:year selected-year
:month selected-month
:on-change on-change-month}]
[weekdays-header/view]
[days-grid/view
{:year selected-year
:month selected-month
:start-date start-date
:end-date end-date
:on-change on-change
:customization-color :blue}]]]))
@@ -3,13 +3,7 @@
[quo.components.colors.color.constants :as constants]
[quo.components.colors.color.view :as color]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[reagent.core :as reagent]))
(defn- on-change-handler
[selected color-name on-change]
(reset! selected color-name)
(when on-change (on-change color-name)))
[react-native.core :as rn]))
(defn get-item-layout
[_ index]
@@ -18,15 +12,34 @@
:offset (* (+ constants/color-size 8) index)
:index index})
(defn- view-internal
(defn view
"Options
- `default-selected` Default selected color name.
- `on-change` Callback called when a color is selected `(fn [color-name])`.
- `blur?` Boolean to enable blur background support.}"
[{:keys [default-selected blur? on-change feng-shui? container-style]}]
(let [selected (reagent/atom default-selected)
{window-width :width} (rn/get-window)
ref (atom nil)]
(let [[selected set-selected] (rn/use-state default-selected)
{window-width :width} (rn/get-window)
ref (rn/use-ref-atom nil)
on-ref (rn/use-callback #(reset! ref %))
render-fn (rn/use-callback
(fn [color idx]
[color/view
{:selected? (= color selected)
:on-press (fn [color-name]
(.scrollToIndex ^js @ref
#js
{:animated true
:index idx
:viewPosition 0.5})
(set-selected color-name)
(when on-change (on-change color-name)))
:blur? blur?
:key color
:color color
:idx idx
:window-width window-width}])
[selected blur? on-change @ref])]
(rn/use-mount
(fn []
(js/setTimeout
@@ -40,32 +53,14 @@
:viewPosition 0.5})))))
50)))
[rn/flat-list
{:ref #(reset! ref %)
{:ref on-ref
;; TODO: using :feng-shui? temporarily while b & w is being developed.
;; https://github.com/status-im/status-mobile/discussions/16676
:data (if feng-shui?
(conj colors/account-colors :feng-shui)
colors/account-colors)
:render-fn (fn [color idx]
[color/view
{:selected? (= color @selected)
:on-press (fn [e]
(.scrollToIndex ^js @ref
#js
{:animated true
:index idx
:viewPosition 0.5})
(on-change-handler selected e on-change))
:blur? blur?
:key color
:color color
:idx idx
:window-width window-width}])
:render-fn render-fn
:get-item-layout get-item-layout
:horizontal true
:shows-horizontal-scroll-indicator false
:content-container-style container-style}]))
(defn view
[props]
[:f> view-internal props])
@@ -35,12 +35,11 @@
(defn image
"Same as rn/image but cache the image source in a js/Set, so the image won't
flicker when re-render on android"
[]
(let [loaded-source (reagent/atom nil)
on-source-loaded #(reset! loaded-source %)]
(fn [props]
(if platform/ios?
[rn/image props]
[:<>
[rn/image (assoc props :source @loaded-source)]
[caching-image props on-source-loaded]]))))
[props]
(let [[loaded-source set-loaded-source] (rn/use-state nil)
on-source-loaded (rn/use-callback #(set-loaded-source %))]
(if platform/ios?
[rn/image props]
[:<>
[rn/image (assoc props :source loaded-source)]
[caching-image props on-source-loaded]])))
@@ -3,25 +3,23 @@
[quo.components.dropdowns.network-dropdown.style :as style]
[quo.components.list-items.preview-list.view :as preview-list]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]))
[react-native.core :as rn]))
(defn- internal-view
[_ _]
(let [pressed? (reagent/atom false)]
(fn
[{:keys [on-press state] :as props} networks]
[rn/pressable
{:style (style/dropdown-container (merge props {:pressed? @pressed?}))
:accessibility-label :network-dropdown
:disabled (= state :disabled)
:on-press on-press
:on-press-in (fn [] (reset! pressed? true))
:on-press-out (fn [] (reset! pressed? false))}
[preview-list/view
{:type :network
:list-size (count networks)
:size :size-20}
networks]])))
(def view (quo.theme/with-theme internal-view))
(defn view
[{:keys [on-press state] :as props} networks]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))]
[rn/pressable
{:style (style/dropdown-container (merge props {:pressed? pressed? :theme theme}))
:accessibility-label :network-dropdown
:disabled (= state :disabled)
:on-press on-press
:on-press-in on-press-in
:on-press-out on-press-out}
[preview-list/view
{:type :network
:list-size (count networks)
:size :size-20}
networks]]))
@@ -14,7 +14,6 @@
(def initial-spacing 56)
(def end-spacing 22)
(def y-axis-label-width -33)
(def inspecting? (reagent/atom false))
(defn- pointer
[customization-color]
@@ -32,14 +31,9 @@
:pointer-color customization-color
:pointer-strip-enable-gradient true})
(defn- get-pointer-props
[pointer-props]
(let [pointer-index (.-pointerIndex ^js pointer-props)]
(reset! inspecting? (not= pointer-index -1))))
(defn- get-line-color
[state theme]
(if @inspecting?
[state theme inspecting?]
(if inspecting?
(colors/theme-colors colors/neutral-80-opa-40
colors/white-opa-20
theme)
@@ -51,11 +45,13 @@
colors/danger-60
theme))))
(defn- view-internal
[{:keys [data state customization-color theme reference-value reference-prefix decimal-separator]
(defn view
[{:keys [data state customization-color reference-value reference-prefix decimal-separator]
:or {reference-prefix "$"
decimal-separator :dot}}]
(let [data (if (> (count data) max-data-points)
(let [theme (quo.theme/use-theme-value)
[inspecting? set-inspecting] (rn/use-state false)
data (if (> (count data) max-data-points)
(utils/downsample-data data max-data-points)
data)
highest-value (utils/find-highest-value data)
@@ -64,7 +60,11 @@
max-value (- (utils/calculate-rounded-max highest-value) min-value)
step-value (/ max-value 4)
width (:width (rn/get-window))
line-color (get-line-color state theme)
line-color (get-line-color state theme inspecting?)
get-pointer-props (rn/use-callback
(fn [pointer-props]
(let [pointer-index (.-pointerIndex ^js pointer-props)]
(set-inspecting (not= pointer-index -1)))))
rules-color (colors/theme-colors colors/neutral-80-opa-10
colors/white-opa-5
theme)
@@ -119,7 +119,7 @@
:show-strip-on-focus true
:reference-line-1-config {:color rules-color}
:reference-line-1-position 0
:show-reference-line-2 (and (not @inspecting?)
:show-reference-line-2 (and (not inspecting?)
(<= reference-value highest-value)
(>= reference-value lowest-value))
:reference-line-2-config {:color y-axis-label-text-color
@@ -138,5 +138,3 @@
:x-axis-label-text-style (style/x-axis-label-text (/ width (count x-axis-label-texts))
y-axis-label-text-color)
:x-axis-label-texts x-axis-label-texts}]]))
(def view (quo.theme/with-theme view-internal))
@@ -20,7 +20,7 @@
(h/fire-event :on-focus (h/get-by-label-text :address-text-input))
(h/has-prop (h/get-by-label-text :address-text-input)
:placeholder-text-color
colors/neutral-40)))
colors/neutral-30)))
(h/test "on focus with blur? true"
(with-redefs [clipboard/get-string #(% "")]
@@ -30,27 +30,22 @@
(h/fire-event :on-focus (h/get-by-label-text :address-text-input))
(h/has-prop (h/get-by-label-text :address-text-input)
:placeholder-text-color
colors/neutral-80-opa-40)))
colors/neutral-80-opa-20)))
(h/test "scanned value is properly set"
(let [on-change-text (h/mock-fn)
scanned-value "scanned-value"]
(h/test "default value is properly set"
(let [default-value "default-value"]
(with-redefs [clipboard/get-string #(% "")]
(h/render [address-input/address-input
{:scanned-value scanned-value
:on-change-text on-change-text
:ens-regex ens-regex}])
(-> (h/wait-for #(h/get-by-label-text :clear-button))
(.then (fn []
(h/was-called-with on-change-text scanned-value)
(h/has-prop (h/get-by-label-text :address-text-input)
:default-value
scanned-value)))))))
{:default-value default-value
:ens-regex ens-regex}])
(h/has-prop (h/get-by-label-text :address-text-input)
:value
default-value))))
(h/test "clear icon is shown when input has text"
(with-redefs [clipboard/get-string #(% "")]
(h/render [address-input/address-input
{:scanned-value "scanned value"
{:default-value "default value"
:ens-regex ens-regex}])
(-> (h/wait-for #(h/get-by-label-text :clear-button-container))
(.then #(h/is-truthy (h/get-by-label-text :clear-button))))))
@@ -58,7 +53,7 @@
(h/test "on blur with text and blur? false"
(with-redefs [clipboard/get-string #(% "")]
(h/render [address-input/address-input
{:scanned-value "scanned value"
{:default-value "default value"
:ens-regex ens-regex}])
(-> (h/wait-for #(h/get-by-label-text :clear-button))
(.then (fn []
@@ -71,7 +66,7 @@
(h/test "on blur with text blur? true"
(with-redefs [clipboard/get-string #(% "")]
(h/render [address-input/address-input
{:scanned-value "scanned value"
{:default-value "default value"
:blur? true
:ens-regex ens-regex}])
(-> (h/wait-for #(h/get-by-label-text :clear-button))
@@ -106,7 +101,7 @@
(let [on-clear (h/mock-fn)]
(with-redefs [clipboard/get-string #(% "")]
(h/render [address-input/address-input
{:scanned-value "scanned value"
{:default-value "default value"
:on-clear on-clear
:ens-regex ens-regex}])
(-> (h/wait-for #(h/get-by-label-text :clear-button))
@@ -148,7 +143,7 @@
(-> (h/wait-for #(h/get-by-label-text :clear-button))
(.then (fn []
(h/has-prop (h/get-by-label-text :address-text-input)
:default-value
:value
clipboard)))))))
(h/test "ENS loading state and call on-detect-ens"
+132 -122
View File
@@ -8,7 +8,6 @@
[react-native.clipboard :as clipboard]
[react-native.core :as rn]
[react-native.platform :as platform]
[reagent.core :as reagent]
[utils.i18n :as i18n]))
(defn- icon-color
@@ -55,124 +54,135 @@
(and (not= status :default) (not blur?))
(colors/theme-colors colors/neutral-30 colors/neutral-60 theme)))
(defn- f-address-input-internal
[]
(let [status (reagent/atom :default)
value (reagent/atom "")
focused? (atom false)]
(fn [{:keys [scanned-value theme blur? on-change-text on-blur on-focus on-clear on-scan
on-detect-ens on-detect-address on-detect-unclassified address-regex ens-regex
valid-ens-or-address? container-style]}]
(let [on-change (fn [text]
(when (not= @value text)
(let [address? (when address-regex
(boolean (re-matches address-regex text)))
ens? (when ens-regex
(boolean (re-matches ens-regex text)))]
(if (> (count text) 0)
(reset! status :typing)
(reset! status :active))
(reset! value text)
(when on-change-text
(on-change-text text))
(when (and on-detect-ens ens?)
(reset! status :loading)
(on-detect-ens text #(reset! status :typing)))
(when (and address? on-detect-address)
(reset! status :loading)
(on-detect-address text))
(when (and (not address?)
(not ens?)
on-detect-unclassified)
(on-detect-unclassified text)))))
on-paste (fn []
(clipboard/get-string
(fn [clipboard]
(when-not (empty? clipboard)
(on-change clipboard)
(reset! value clipboard)))))
on-clear (fn []
(reset! value "")
(reset! status (if @focused? :active :default))
(when on-change-text
(on-change-text ""))
(when on-clear
(on-clear)))
on-scan #(when on-scan
(on-scan))
on-focus (fn []
(when (= (count @value) 0)
(reset! status :active))
(reset! focused? true)
(when on-focus (on-focus)))
on-blur (fn []
(when (= @status :active)
(reset! status :default))
(reset! focused? false)
(when on-blur (on-blur)))
placeholder-text-color (get-placeholder-text-color @status theme blur?)]
(rn/use-effect (fn []
(when-not (empty? scanned-value)
(on-change scanned-value)))
[scanned-value])
[rn/view {:style (style/container container-style)}
[rn/text-input
{:accessibility-label :address-text-input
:style (style/input-text theme)
:placeholder (i18n/label :t/name-ens-or-address)
:placeholder-text-color placeholder-text-color
:default-value @value
:auto-complete (when platform/ios? :off)
:auto-capitalize :none
:auto-correct false
:spell-check false
:keyboard-appearance (quo.theme/theme-value :light :dark theme)
:on-focus on-focus
:on-blur on-blur
:on-change-text on-change}]
(when (or (= @status :default)
(= @status :active))
[rn/view
{:style style/buttons-container
:accessibility-label :paste-scan-buttons-container}
[button/button
{:accessibility-label :paste-button
:type :outline
:size 24
:container-style {:margin-right 8}
:inner-style (style/accessory-button blur? theme)
:on-press on-paste}
(i18n/label :t/paste)]
[button/button
{:accessibility-label :scan-button
:icon-only? true
:type :outline
:size 24
:inner-style (style/accessory-button blur? theme)
:on-press on-scan}
:main-icons/scan]])
(when (= @status :typing)
[rn/view
{:style style/buttons-container
:accessibility-label :clear-button-container}
[clear-button
{:on-press on-clear
:blur? blur?
:theme theme}]])
(when (and (= @status :loading) (not valid-ens-or-address?))
[rn/view
{:style style/buttons-container
:accessibility-label :loading-button-container}
[loading-icon blur? theme]])
(when (and (= @status :loading) valid-ens-or-address?)
[rn/view
{:style style/buttons-container
:accessibility-label :positive-button-container}
[positive-state-icon theme]])]))))
(defn address-input-internal
[props]
[:f> f-address-input-internal props])
(def address-input
(quo.theme/with-theme address-input-internal))
(defn address-input
[{:keys [default-value blur? on-change-text on-blur on-focus on-clear on-scan
on-detect-ens on-detect-address on-detect-unclassified address-regex ens-regex
valid-ens-or-address? container-style]}]
(let [theme (quo.theme/use-theme-value)
[status set-status] (rn/use-state :default)
value (rn/use-ref-atom nil)
[_ trigger-render-value] (rn/use-state @value)
[focused? set-focused] (rn/use-state false)
on-change (rn/use-callback
(fn [text]
(let [address? (when address-regex
(boolean (re-matches address-regex text)))
ens? (when ens-regex
(boolean (re-matches ens-regex text)))]
(reset! value text)
(if (> (count text) 0)
(set-status :typing)
(set-status :active))
(when on-change-text
(on-change-text text))
(when (and on-detect-ens ens?)
(set-status :loading)
(on-detect-ens text #(set-status :typing)))
(when (and address? on-detect-address)
(set-status :loading)
(on-detect-address text))
(when (and (not address?)
(not ens?)
on-detect-unclassified)
(on-detect-unclassified text)))))
set-value (rn/use-callback
(fn [new-value]
(reset! value new-value)
(on-change new-value)
(trigger-render-value new-value)))
on-paste (rn/use-callback
(fn []
(clipboard/get-string
(fn [clipboard]
(when-not (empty? clipboard)
(set-value clipboard))))))
on-clear (rn/use-callback
(fn []
(set-value "")
(set-status (if focused? :active :default))
(when on-change-text
(on-change-text ""))
(when on-clear
(on-clear)))
[focused?])
on-clear (rn/use-callback
(fn []
(set-value "")
(set-status (if focused? :active :default))
(when on-change-text
(on-change-text ""))
(when on-clear
(on-clear)))
[focused?])
on-scan (when on-scan (rn/use-callback #(on-scan set-value)))
on-focus (rn/use-callback
(fn []
(when (= (count @value) 0)
(set-status :active))
(set-focused true)
(when on-focus (on-focus))))
on-blur (rn/use-callback
(fn []
(when (= status :active)
(set-status :default))
(set-focused false)
(when on-blur (on-blur)))
[status])
placeholder-text-color (rn/use-memo #(get-placeholder-text-color status theme blur?)
[status theme blur?])]
(rn/use-mount #(on-change (or default-value "")))
[rn/view {:style (style/container container-style)}
[rn/text-input
{:accessibility-label :address-text-input
:style (style/input-text theme)
:placeholder (i18n/label :t/name-ens-or-address)
:placeholder-text-color placeholder-text-color
:value @value
:auto-complete (when platform/ios? :off)
:auto-capitalize :none
:auto-correct false
:spell-check false
:keyboard-appearance (quo.theme/theme-value :light :dark theme)
:on-focus on-focus
:on-blur on-blur
:on-change-text on-change}]
(when (or (= status :default)
(= status :active))
[rn/view
{:style style/buttons-container
:accessibility-label :paste-scan-buttons-container}
[button/button
{:accessibility-label :paste-button
:type :outline
:size 24
:container-style {:margin-right 8}
:inner-style (style/accessory-button blur? theme)
:on-press on-paste}
(i18n/label :t/paste)]
(when on-scan
[button/button
{:accessibility-label :scan-button
:icon-only? true
:type :outline
:size 24
:inner-style (style/accessory-button blur? theme)
:on-press on-scan}
:main-icons/scan])])
(when (= status :typing)
[rn/view
{:style style/buttons-container
:accessibility-label :clear-button-container}
[clear-button
{:on-press on-clear
:blur? blur?
:theme theme}]])
(when (and (= status :loading) (not valid-ens-or-address?))
[rn/view
{:style style/buttons-container
:accessibility-label :loading-button-container}
[loading-icon blur? theme]])
(when (and (= status :loading) valid-ens-or-address?)
[rn/view
{:style style/buttons-container
:accessibility-label :positive-button-container}
[positive-state-icon theme]])]))
+101 -103
View File
@@ -5,8 +5,7 @@
[quo.components.markdown.text :as text]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.platform :as platform]
[reagent.core :as reagent]))
[react-native.platform :as platform]))
(defn- label-&-counter
[{:keys [label current-chars char-limit variant-colors]}]
@@ -61,99 +60,104 @@
:container-style])
(defn- base-input
[{:keys [on-change-text on-char-limit-reach weight default-value]}]
(let [status (reagent/atom :default)
internal-on-focus #(reset! status :focus)
internal-on-blur #(reset! status :default)
multiple-lines? (reagent/atom false)
set-multiple-lines! #(let [height (oops/oget % "nativeEvent.contentSize.height")
;; In Android height comes with padding
min-height (if platform/android? 40 22)]
(if (> height min-height)
(reset! multiple-lines? true)
(reset! multiple-lines? false)))
char-count (reagent/atom (count default-value))
update-char-limit! (fn [new-text char-limit]
(when on-change-text (on-change-text new-text))
(let [amount-chars (count new-text)]
(reset! char-count amount-chars)
(when (and (>= amount-chars char-limit) on-char-limit-reach)
(on-char-limit-reach amount-chars))))]
(fn [{:keys [blur? theme error? right-icon left-icon disabled? small? button
label char-limit multiline? clearable? on-focus on-blur container-style]
:as props}]
(let [status-kw (cond
disabled? :disabled
error? :error
:else @status)
colors-by-status (style/status-colors status-kw blur? theme)
variant-colors (style/variants-colors blur? theme)
clean-props (apply dissoc props custom-props)]
[rn/view {:style container-style}
(when (or label char-limit)
[label-&-counter
{:variant-colors variant-colors
:label label
:current-chars @char-count
:char-limit char-limit}])
[rn/view {:style (style/input-container colors-by-status small? disabled?)}
(when-let [{:keys [icon-name]} left-icon]
[left-accessory
{:variant-colors variant-colors
:small? small?
:icon-name icon-name}])
[rn/text-input
(cond-> {:style (style/input colors-by-status small? @multiple-lines? weight)
:accessibility-label :input
:placeholder-text-color (:placeholder colors-by-status)
:keyboard-appearance (quo.theme/theme-value :light :dark theme)
:cursor-color (:cursor variant-colors)
:editable (not disabled?)
:on-focus (fn []
(when on-focus (on-focus))
(internal-on-focus))
:on-blur (fn []
(when on-blur (on-blur))
(internal-on-blur))}
:always (merge clean-props)
multiline? (assoc :multiline true
:on-content-size-change set-multiple-lines!)
char-limit (assoc :on-change-text #(update-char-limit! % char-limit)))]
(when-let [{:keys [on-press icon-name style-fn]} right-icon]
[right-accessory
{:variant-colors variant-colors
:small? small?
:disabled? disabled?
:icon-style-fn style-fn
:icon-name icon-name
:on-press (fn []
(when clearable? (reset! char-count 0))
(on-press))}])
(when-let [{:keys [on-press text]} button]
[right-button
{:colors-by-status colors-by-status
:variant-colors variant-colors
:small? small?
:disabled? disabled?
:on-press on-press
:text text}])]]))))
[{:keys [blur? theme error? right-icon left-icon disabled? small? button
label char-limit multiline? clearable? on-focus on-blur container-style
on-change-text on-char-limit-reach weight default-value]
:as props}]
(let [[status set-status] (rn/use-state :default)
internal-on-focus (rn/use-callback #(set-status :focus))
internal-on-blur (rn/use-callback #(set-status :default))
[multiple-lines?
set-multiple-lines] (rn/use-state false)
on-content-size-change (rn/use-callback
(fn [event]
(let [height (oops/oget event "nativeEvent.contentSize.height")
;; In Android height comes with padding
min-height (if platform/android? 40 22)]
(if (> height min-height)
(set-multiple-lines true)
(set-multiple-lines false)))))
[char-count
set-char-count] (rn/use-state (count default-value))
on-change-text (rn/use-callback
(fn [new-text]
(when on-change-text (on-change-text new-text))
(let [amount-chars (count new-text)]
(set-char-count amount-chars)
(when (and (>= amount-chars char-limit) on-char-limit-reach)
(on-char-limit-reach amount-chars)))))
status-kw (cond
disabled? :disabled
error? :error
:else status)
colors-by-status (style/status-colors status-kw blur? theme)
variant-colors (style/variants-colors blur? theme)
clean-props (apply dissoc props custom-props)]
[rn/view {:style container-style}
(when (or label char-limit)
[label-&-counter
{:variant-colors variant-colors
:label label
:current-chars char-count
:char-limit char-limit}])
[rn/view {:style (style/input-container colors-by-status small? disabled?)}
(when-let [{:keys [icon-name]} left-icon]
[left-accessory
{:variant-colors variant-colors
:small? small?
:icon-name icon-name}])
[rn/text-input
(cond-> {:style (style/input colors-by-status small? multiple-lines? weight)
:accessibility-label :input
:placeholder-text-color (:placeholder colors-by-status)
:keyboard-appearance (quo.theme/theme-value :light :dark theme)
:cursor-color (:cursor variant-colors)
:editable (not disabled?)
:on-focus (fn []
(when on-focus (on-focus))
(internal-on-focus))
:on-blur (fn []
(when on-blur (on-blur))
(internal-on-blur))}
:always (merge clean-props)
multiline? (assoc :multiline true
:on-content-size-change on-content-size-change)
char-limit (assoc :on-change-text on-change-text))]
(when-let [{:keys [on-press icon-name style-fn]} right-icon]
[right-accessory
{:variant-colors variant-colors
:small? small?
:disabled? disabled?
:icon-style-fn style-fn
:icon-name icon-name
:on-press (fn []
(when clearable? (set-char-count 0))
(on-press))}])
(when-let [{:keys [on-press text]} button]
[right-button
{:colors-by-status colors-by-status
:variant-colors variant-colors
:small? small?
:disabled? disabled?
:on-press on-press
:text text}])]]))
(defn- password-input
[{:keys [default-shown?]
:or {default-shown? false}}]
(let [password-shown? (reagent/atom default-shown?)]
(fn [props]
[base-input
(assoc props
:accessibility-label :password-input
:auto-capitalize :none
:auto-complete :password
:secure-text-entry (not @password-shown?)
:right-icon {:style-fn style/password-icon
:icon-name (if @password-shown? :i/hide-password :i/reveal)
:on-press #(swap! password-shown? not)})])))
:or {default-shown? false}
:as props}]
(let [[password-shown? set-password-shown] (rn/use-state default-shown?)]
[base-input
(assoc props
:accessibility-label :password-input
:auto-capitalize :none
:auto-complete :password
:secure-text-entry (not password-shown?)
:right-icon {:style-fn style/password-icon
:icon-name (if password-shown? :i/hide-password :i/reveal)
:on-press #(set-password-shown (not password-shown?))})]))
(defn input-internal
(defn input
"This input supports the following properties:
- :type - Can be `:text`(default) or `:password`.
- :blur? - Boolean to set the blur color variant.
@@ -177,21 +181,15 @@
- :on-change-text
...
"
[{:keys [type clearable? on-clear on-change-text icon-name]
[{:keys [type clearable? on-clear icon-name]
:or {type :text}
:as props}]
(let [base-props (cond-> props
icon-name (assoc-in [:left-icon :icon-name] icon-name)
clearable? (assoc :right-icon
{:style-fn style/clear-icon
:icon-name :i/clear
:on-press #(when on-clear (on-clear))})
on-change-text (assoc :on-change-text
(fn [new-text]
(on-change-text new-text)
(reagent/flush))))]
icon-name (assoc-in [:left-icon :icon-name] icon-name)
clearable? (assoc :right-icon
{:style-fn style/clear-icon
:icon-name :i/clear
:on-press #(when on-clear (on-clear))}))]
(if (= type :password)
[password-input base-props]
[base-input base-props])))
(def input (quo.theme/with-theme input-internal))
@@ -39,7 +39,7 @@
{:keys [ok-words error-words]} (group-by #(if (string? %) :ok-words :error-words)
children-text-nodes)]
(h/is-equal (apply str ok-words) "Text with some that the ")
(h/is-truthy (= (map #(-> % :props :argv second) error-words)
(h/is-truthy (= (map #(-> % :props :argv first) error-words)
["error" "words" "don't" "satisfy" "predicate"]))))
(h/test "Marked when words exceed the limit given"
@@ -55,7 +55,7 @@
(h/is-equal (string/trim (apply str ok-words))
"these are ok words,")
(h/is-equal (->> error-words
(map #(-> % :props :argv second))
(map #(-> % :props :argv first))
(interpose " ")
(apply str))
"these words exceed the limit")))))
@@ -3,8 +3,7 @@
[clojure.string :as string]
[quo.components.inputs.recovery-phrase.style :as style]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]))
[react-native.core :as rn]))
(def ^:private custom-props
[:customization-color :theme :blur? :cursor-color :multiline :on-focus :on-blur
@@ -38,42 +37,42 @@
:idx 0})
:result)))
(defn recovery-phrase-input-internal
[_ _]
(let [state (reagent/atom :default)
set-focused #(reset! state :focused)
set-default #(reset! state :default)]
(fn [{:keys [customization-color theme blur? on-focus on-blur mark-errors?
error-pred-current-word error-pred-written-words word-limit
container-style]
:or {customization-color :blue
word-limit ##Inf
error-pred-current-word (constantly false)
error-pred-written-words (constantly false)}
:as props}
text]
(let [extra-props (apply dissoc props custom-props)]
[rn/view {:style (style/container container-style)}
[rn/text-input
(merge {:accessibility-label :recovery-phrase-input
:style (style/input)
:placeholder-text-color (style/placeholder-color @state theme blur?)
:cursor-color (style/cursor-color customization-color theme)
:keyboard-appearance (quo.theme/theme-value :light :dark theme)
:multiline true
:on-focus (fn []
(set-focused)
(when on-focus (on-focus)))
:on-blur (fn []
(set-default)
(when on-blur (on-blur)))}
extra-props)
(if mark-errors?
(mark-error-words {:pred-last-word error-pred-current-word
:pred-previous-words error-pred-written-words
:text text
:word-limit word-limit
:theme theme})
text)]]))))
(def recovery-phrase-input (quo.theme/with-theme recovery-phrase-input-internal))
(defn recovery-phrase-input
[{:keys [customization-color blur? on-focus on-blur mark-errors?
error-pred-current-word error-pred-written-words word-limit
container-style]
:or {customization-color :blue
word-limit ##Inf
error-pred-current-word (constantly false)
error-pred-written-words (constantly false)}
:as props}
text]
(let [theme (quo.theme/use-theme-value)
[state set-state] (rn/use-state :default)
on-focus (rn/use-callback
(fn []
(set-state :focused)
(when on-focus (on-focus))))
on-blur (rn/use-callback
(fn []
(set-state :default)
(when on-blur (on-blur))))
extra-props (apply dissoc props custom-props)]
[rn/view {:style (style/container container-style)}
[rn/text-input
(merge {:accessibility-label :recovery-phrase-input
:style (style/input)
:placeholder-text-color (style/placeholder-color state theme blur?)
:cursor-color (style/cursor-color customization-color theme)
:keyboard-appearance (quo.theme/theme-value :light :dark theme)
:multiline true
:on-focus on-focus
:on-blur on-blur}
extra-props)
(if mark-errors?
(mark-error-words {:pred-last-word error-pred-current-word
:pred-previous-words error-pred-written-words
:text text
:word-limit word-limit
:theme theme})
text)]]))
@@ -4,8 +4,7 @@
[quo.components.icon :as icon]
[quo.components.inputs.search-input.style :as style]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[reagent.core :as reagent]))
[react-native.core :as rn]))
(def ^:private tag-separator [rn/view {:style style/tag-separator}])
@@ -41,54 +40,52 @@
text-input))
(defn search-input
[{:keys [value]}]
(let [state (reagent/atom :default)
set-active #(reset! state :active)
set-default #(reset! state :default)
scroll-view-ref (atom nil)
use-value? (boolean value)]
(fn [{:keys [value tags disabled? blur? on-change-text customization-color
on-clear on-focus on-blur override-theme container-style]
:or {customization-color :blue}
:as props}
& children]
(let [clean-props (apply dissoc props props-to-remove)]
[rn/view
{:accessibility-label :search-input
:style (style/container container-style)}
[rn/scroll-view
{:ref #(reset! scroll-view-ref %)
:style style/scroll-container
:content-container-style style/scroll-content
:horizontal true
:shows-horizontal-scroll-indicator false}
(when (seq tags)
[inner-tags tags])
(add-children
[rn/text-input
(cond-> {:style (style/input-text disabled?)
:cursor-color (style/cursor customization-color override-theme)
:placeholder-text-color (style/placeholder-color @state blur? override-theme)
:editable (not disabled?)
:on-key-press #(handle-backspace % @scroll-view-ref)
:keyboard-appearance (colors/theme-colors :light :dark override-theme)
:on-change-text (fn [new-text]
(when on-change-text
(on-change-text new-text))
(reagent/flush))
:on-focus (fn []
(set-active)
(when on-focus (on-focus)))
:on-blur (fn []
(set-default)
(when on-blur (on-blur)))}
use-value? (assoc :value value)
(seq clean-props) (merge clean-props))]
(when-not use-value? children))]
(when (or (seq value) (seq children))
[clear-button
{:on-press on-clear
:blur? blur?
:override-theme override-theme}])]))))
[{:keys [value tags disabled? blur? on-change-text customization-color
on-clear on-focus on-blur override-theme container-style]
:or {customization-color :blue}
:as props}
& children]
(let [[state set-state] (rn/use-state :default)
on-focus (rn/use-callback
(fn []
(set-state :active)
(when on-focus (on-focus))))
on-blur (rn/use-callback
(fn []
(set-state :default)
(when on-blur (on-blur))))
scroll-view-ref (rn/use-ref-atom nil)
on-scroll-view-ref (rn/use-callback #(reset! scroll-view-ref %))
on-key-press (rn/use-callback #(handle-backspace % @scroll-view-ref))
use-value? (boolean value)
clean-props (apply dissoc props props-to-remove)]
[rn/view
{:accessibility-label :search-input
:style (style/container container-style)}
[rn/scroll-view
{:ref on-scroll-view-ref
:style style/scroll-container
:content-container-style style/scroll-content
:horizontal true
:shows-horizontal-scroll-indicator false}
(when (seq tags)
[inner-tags tags])
(add-children
[rn/text-input
(cond-> {:style (style/input-text disabled?)
:cursor-color (style/cursor customization-color override-theme)
:placeholder-text-color (style/placeholder-color state blur? override-theme)
:editable (not disabled?)
:on-key-press on-key-press
:keyboard-appearance (colors/theme-colors :light :dark override-theme)
:on-change-text on-change-text
:on-focus on-focus
:on-blur on-blur}
use-value? (assoc :value value)
(seq clean-props) (merge clean-props))]
(when-not use-value? children))]
(when (or (seq value) (seq children))
[clear-button
{:on-press on-clear
:blur? blur?
:override-theme override-theme}])]))
+65 -77
View File
@@ -4,8 +4,7 @@
[quo.components.inputs.title-input.style :as style]
[quo.components.markdown.text :as text]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]))
[react-native.core :as rn]))
(defn- pad-0
[value]
@@ -13,81 +12,70 @@
(str 0 value)
value))
(defn- view-internal
[{:keys [blur?
on-change-text
auto-focus
placeholder
max-length
default-value
return-key-type
size
theme
on-focus
on-blur
container-style]
(defn view
[{:keys [blur? on-change-text auto-focus placeholder max-length default-value return-key-type
size on-focus on-blur container-style customization-color disabled?]
:or {max-length 0
auto-focus false
default-value ""}}]
(let [focused? (reagent/atom auto-focus)
value (reagent/atom default-value)
input-ref (atom nil)
on-change (fn [v]
(reset! value v)
(when on-change-text
(on-change-text v)))]
(fn [{:keys [customization-color disabled?]}]
[rn/view
{:style (merge (style/container disabled?) container-style)}
[rn/view {:style style/text-input-container}
[rn/text-input
{:style
(text/text-style
{:size (or size :heading-1)
:weight :semi-bold
:style (style/title-text theme)})
:default-value default-value
:accessibility-label :profile-title-input
:keyboard-appearance (quo.theme/theme-value :light :dark theme)
:return-key-type return-key-type
:on-focus (fn []
(when (fn? on-focus)
(on-focus))
(reset! focused? true))
:on-blur (fn []
(when (fn? on-blur)
(on-blur))
(reset! focused? false))
:auto-focus auto-focus
:input-mode :text
:on-change-text on-change
:editable (not disabled?)
:max-length max-length
:placeholder placeholder
:ref #(reset! input-ref %)
:selection-color (style/get-selection-color customization-color blur? theme)
:placeholder-text-color (if @focused?
(style/get-focused-placeholder-color blur? theme)
(style/get-placeholder-color blur? theme))}]]
[rn/view
{:style (style/counter-container @focused?)}
(if @focused?
[text/text
[text/text
{:style (style/char-count blur? theme)
:size :paragraph-2}
(pad-0
(str
(count @value)))]
[text/text
{:style (style/char-count blur? theme)
:size :paragraph-2}
(str "/"
(pad-0
(str max-length)))]]
[rn/pressable
{:on-press #(when-not disabled?
(.focus ^js @input-ref))}
[icon/icon :i/edit {:color (style/get-char-count-color blur? theme)}]])]])))
(def view (quo.theme/with-theme view-internal))
(let [theme (quo.theme/use-theme-value)
[focused? set-focused] (rn/use-state auto-focus)
[value set-value] (rn/use-state default-value)
input-ref (rn/use-ref-atom nil)
on-inpur-ref (rn/use-callback #(reset! input-ref %))
on-press (rn/use-callback
#(when-not disabled? (.focus ^js @input-ref))
[disabled?])
on-change (rn/use-callback
(fn [v]
(set-value v)
(when on-change-text (on-change-text v))))
on-focus (rn/use-callback
(fn []
(when (fn? on-focus) (on-focus))
(set-focused true)))
on-blur (rn/use-callback
(fn []
(when (fn? on-blur) (on-blur))
(set-focused false)))]
[rn/view
{:style (merge (style/container disabled?) container-style)}
[rn/view {:style style/text-input-container}
[rn/text-input
{:style (text/text-style
{:size (or size :heading-1)
:weight :semi-bold
:style (style/title-text theme)})
:default-value default-value
:accessibility-label :profile-title-input
:keyboard-appearance (quo.theme/theme-value :light :dark theme)
:return-key-type return-key-type
:on-focus on-focus
:on-blur on-blur
:auto-focus auto-focus
:input-mode :text
:on-change-text on-change
:editable (not disabled?)
:max-length max-length
:placeholder placeholder
:ref on-inpur-ref
:selection-color (style/get-selection-color customization-color blur? theme)
:placeholder-text-color (if focused?
(style/get-focused-placeholder-color blur? theme)
(style/get-placeholder-color blur? theme))}]]
[rn/view
{:style (style/counter-container focused?)}
(if focused?
[text/text
[text/text
{:style (style/char-count blur? theme)
:size :paragraph-2}
(pad-0
(str
(count value)))]
[text/text
{:style (style/char-count blur? theme)
:size :paragraph-2}
(str "/" (pad-0 (str max-length)))]]
[rn/pressable {:on-press on-press}
[icon/icon :i/edit {:color (style/get-char-count-color blur? theme)}]])]]))
+8
View File
@@ -122,6 +122,8 @@
(def memo react/memo)
(def use-state react/useState)
(def create-ref react/createRef)
(def use-ref react/useRef)
@@ -179,6 +181,10 @@
([handler deps]
(react/useCallback handler (get-js-deps deps))))
(defn use-memo
[handler deps]
(react/useMemo handler (get-js-deps deps)))
(def layout-animation (.-LayoutAnimation ^js react-native))
(def configure-next (.-configureNext ^js layout-animation))
@@ -204,3 +210,5 @@
(def linking (.-Linking react-native))
(defn open-url [link] (.openURL ^js linking link))
(def set-status-bar-style react-native/StatusBar.setBarStyle)
+73 -80
View File
@@ -9,7 +9,6 @@
[react-native.gesture :as gesture]
[react-native.hooks :as hooks]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[status-im.common.bottom-sheet.style :as style]
[utils.re-frame :as rf]))
@@ -57,83 +56,77 @@
(show translate-y bg-opacity)
(hide translate-y bg-opacity window-height on-close))))))
(defn- f-view
[_ _]
(let [sheet-height (reagent/atom 0)
item-height (reagent/atom 0)]
(fn [{:keys [hide? insets theme]}
{:keys [content selected-item padding-bottom-override border-radius on-close shell?
gradient-cover? customization-color hide-handle? blur-radius]
:or {border-radius 12}}]
(let [{window-height :height} (rn/get-window)
bg-opacity (reanimated/use-shared-value 0)
translate-y (reanimated/use-shared-value window-height)
sheet-gesture (get-sheet-gesture translate-y
bg-opacity
window-height
on-close)
selected-item-smaller-than-sheet? (< @item-height
(- window-height
@sheet-height
(:top insets)
(:bottom insets)
bottom-margin))
top (- window-height (:top insets) @sheet-height)
bottom (if selected-item-smaller-than-sheet?
(+ @sheet-height bottom-margin)
(:bottom insets))]
(rn/use-effect
#(if hide?
(hide translate-y bg-opacity window-height on-close)
(show translate-y bg-opacity))
[hide?])
(hooks/use-back-handler (fn []
(when (fn? on-close)
(on-close))
(rf/dispatch [:hide-bottom-sheet])
true))
[rn/view {:style {:flex 1}}
;; backdrop
[rn/pressable
{:on-press #(rf/dispatch [:hide-bottom-sheet])
:style {:flex 1}}
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:opacity bg-opacity}
{:flex 1 :background-color colors/neutral-100-opa-70})}]]
;; sheet
[gesture/gesture-detector {:gesture sheet-gesture}
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:transform [{:translateY translate-y}]}
(style/sheet insets window-height selected-item))}
(when shell?
[blur/ios-view
{:style style/shell-bg
:blur-radius (or blur-radius 20)
:blur-type :transparent
:overlay-color :transparent}])
(when selected-item
[rn/view
{:on-layout #(reset! item-height (.-nativeEvent.layout.height ^js %))
:style
(style/selected-item theme top bottom selected-item-smaller-than-sheet? border-radius)}
[selected-item]])
(defn view
[{:keys [hide? insets]}
{:keys [content selected-item padding-bottom-override border-radius on-close shell?
gradient-cover? customization-color hide-handle? blur-radius]
:or {border-radius 12}}]
(let [theme (quo.theme/use-theme-value)
sheet-height (rn/use-ref-atom 0)
item-height (rn/use-ref-atom 0)
{window-height :height} (rn/get-window)
bg-opacity (reanimated/use-shared-value 0)
translate-y (reanimated/use-shared-value window-height)
sheet-gesture (get-sheet-gesture translate-y
bg-opacity
window-height
on-close)
selected-item-smaller-than-sheet? (< @item-height
(- window-height
@sheet-height
(:top insets)
(:bottom insets)
bottom-margin))
top (- window-height (:top insets) @sheet-height)
bottom (if selected-item-smaller-than-sheet?
(+ @sheet-height bottom-margin)
(:bottom insets))]
(rn/use-effect
#(if hide?
(hide translate-y bg-opacity window-height on-close)
(show translate-y bg-opacity))
[hide?])
(hooks/use-back-handler (fn []
(when (fn? on-close)
(on-close))
(rf/dispatch [:hide-bottom-sheet])
true))
[rn/view {:style {:flex 1}}
;; backdrop
[rn/pressable
{:on-press #(rf/dispatch [:hide-bottom-sheet])
:style {:flex 1}}
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:opacity bg-opacity}
{:flex 1 :background-color colors/neutral-100-opa-70})}]]
;; sheet
[gesture/gesture-detector {:gesture sheet-gesture}
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:transform [{:translateY translate-y}]}
(style/sheet insets window-height selected-item))}
(when shell?
[blur/ios-view
{:style style/shell-bg
:blur-radius (or blur-radius 20)
:blur-type :transparent
:overlay-color :transparent}])
(when selected-item
[rn/view
{:on-layout #(reset! item-height (.-nativeEvent.layout.height ^js %))
:style
(style/selected-item theme top bottom selected-item-smaller-than-sheet? border-radius)}
[selected-item]])
[rn/view
{:style (style/sheet-content theme padding-bottom-override insets shell? bottom-margin)
:on-layout #(reset! sheet-height (.-nativeEvent.layout.height ^js %))}
(when (and gradient-cover? customization-color)
[rn/view {:style style/gradient-bg}
[quo/gradient-cover
{:customization-color customization-color
:opacity 0.4}]])
(when-not hide-handle?
[quo/drawer-bar])
[content]]]]]))))
(defn- internal-view
[args sheet]
[:f> f-view args sheet])
(def view (quo.theme/with-theme internal-view))
[rn/view
{:style (style/sheet-content theme padding-bottom-override insets shell? bottom-margin)
:on-layout #(reset! sheet-height (.-nativeEvent.layout.height ^js %))}
(when (and gradient-cover? customization-color)
[rn/view {:style style/gradient-bg}
[quo/gradient-cover
{:customization-color customization-color
:opacity 0.4}]])
(when-not hide-handle?
[quo/drawer-bar])
[content]]]]]))
@@ -0,0 +1,11 @@
(ns status-im.common.scalable-avatar.style)
(defn wrapper
[{:keys [scale margin-top margin border-color]}]
[{:transform [{:scale scale}]
:margin-top margin-top
:margin-left margin
:margin-bottom margin}
{:border-width 4
:border-color border-color
:border-radius 100}])
@@ -1,16 +1,14 @@
(ns status-im.contexts.profile.settings.header.avatar
(ns status-im.common.scalable-avatar.view
(:require [quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.reanimated :as reanimated]
[status-im.contexts.profile.settings.header.style :as style]))
[status-im.common.scalable-avatar.style :as style]))
(def scroll-animation-input-range [0 50])
(def header-extrapolation-option
{:extrapolateLeft "clamp"
:extrapolateRight "clamp"})
(defn f-avatar
[{:keys [scroll-y full-name online? profile-picture customization-color]}]
[{:keys [scroll-y full-name online? profile-picture customization-color border-color]}]
(let [image-scale-animation (reanimated/interpolate scroll-y
scroll-animation-input-range
[1 0.4]
@@ -21,14 +19,13 @@
header-extrapolation-option)
image-side-margin-animation (reanimated/interpolate scroll-y
scroll-animation-input-range
[0 -20]
header-extrapolation-option)
theme (quo.theme/get-theme)]
[-4 -20]
header-extrapolation-option)]
[reanimated/view
{:style (style/avatar-container theme
image-scale-animation
image-top-margin-animation
image-side-margin-animation)}
{:style (style/wrapper {:scale image-scale-animation
:margin-top image-top-margin-animation
:margin image-side-margin-animation
:border-color border-color})}
[quo/user-avatar
{:full-name full-name
:online? online?
@@ -75,3 +75,9 @@
:width picture-diameter
:height picture-diameter
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)})
(defn cover-background
[cover-color]
{:background-color cover-color
:height 167
:margin-bottom -16})
+8 -4
View File
@@ -98,9 +98,9 @@
(defn scroll-page
[_ _ _]
(let [scroll-height (reagent/atom negative-scroll-position-0)]
(fn [{:keys [theme cover-image logo on-scroll
(fn [{:keys [theme cover-image cover-color logo on-scroll
collapsed? height top-nav title-colum background-color navigate-back? page-nav-props
overlay-shown? sticky-header]}
overlay-shown? sticky-header children-style]}
children]
[:<>
[:f> f-scroll-page-header
@@ -125,6 +125,8 @@
"nativeEvent.contentOffset.y")))
(when on-scroll
(on-scroll @scroll-height)))}
(when cover-color
[rn/view {:style (style/cover-background cover-color)}])
(when cover-image
[rn/view {:style {:height (if collapsed? 110 151)}}
[rn/image
@@ -136,8 +138,10 @@
:flex 1}}]])
(when children
[rn/view
{:style (style/children-container {:border-radius (diff-with-max-min @scroll-height 16 0)
:background-color background-color})}
{:style (style/children-container (merge
children-style
{:border-radius (diff-with-max-min @scroll-height 16 0)
:background-color background-color}))}
(when (and (not collapsed?) cover-image)
[:f> f-display-picture @scroll-height logo theme])
children])]])))
@@ -12,7 +12,7 @@
[utils.re-frame :as rf]
[utils.security.core :as security]))
(defn get-error-message
(defn- get-error-message
[error]
(if (and (some? error)
(or (= error "file is not a database")
@@ -21,31 +21,62 @@
(i18n/label :t/oops-wrong-password)
error))
(defn- on-change-password
[entered-password]
(debounce/debounce-and-dispatch [:profile/on-password-input-changed
{:password (security/mask-data entered-password)
:error ""}]
100))
(defn- error-info
[error-message processing shell?]
(let [theme (quo.theme/use-theme-value)
on-press (rn/use-callback
(fn []
(rn/dismiss-keyboard!)
(rf/dispatch [:show-bottom-sheet
{:content #(forgot-password-doc/view {:shell? shell?})
:theme theme
:shell? shell?}]))
[theme])]
[rn/view {:style style/error-message}
[quo/info-message
{:type :error
:size :default
:icon :i/info}
error-message]
[rn/pressable
{:hit-slop {:top 6 :bottom 20 :left 0 :right 0}
:disabled processing
:on-press on-press}
[rn/text
{:style {:text-decoration-line :underline
:color (colors/resolve-color :danger theme)}
:size :paragraph-2
:suppress-highlighting true}
(i18n/label :t/forgot-password)]]]))
(defn- view-internal
[{:keys [default-password theme shell? on-press-biometrics blur?]}]
(defn view
[{:keys [shell? on-press-biometrics blur?]}]
(let [{:keys [error processing]} (rf/sub [:profile/login])
error-message (get-error-message error)
error? (boolean (seq error-message))]
error-message (rn/use-memo #(get-error-message error) [error])
error? (boolean (seq error-message))
default-value (rn/use-ref-atom "") ;;bug on Android
;;https://github.com/status-im/status-mobile/issues/19004
on-change-password (rn/use-callback
(fn [entered-password]
(reset! default-value entered-password)
(debounce/debounce-and-dispatch [:profile/on-password-input-changed
{:password (security/mask-data
entered-password)
:error ""}]
100)))]
[:<>
[rn/view {:style {:flex-direction :row}}
[quo/input
{:container-style {:flex 1}
:type :password
:default-value @default-value
:blur? blur?
:disabled? processing
:placeholder (i18n/label :t/type-your-password)
:auto-focus true
:error? error?
:label (i18n/label :t/profile-password)
:on-change-text on-change-password
:default-value (security/safe-unmask-data default-password)}]
:on-change-text on-change-password}]
(when on-press-biometrics
[quo/button
{:container-style style/auth-button
@@ -55,26 +86,5 @@
:type :outline}
:i/face-id])]
(when error?
[rn/view {:style style/error-message}
[quo/info-message
{:type :error
:size :default
:icon :i/info}
error-message]
[rn/pressable
{:hit-slop {:top 6 :bottom 20 :left 0 :right 0}
:disabled processing
:on-press (fn []
(rn/dismiss-keyboard!)
(rf/dispatch [:show-bottom-sheet
{:content #(forgot-password-doc/view {:shell? shell?})
:theme theme
:shell? shell?}]))}
[rn/text
{:style {:text-decoration-line :underline
:color (colors/resolve-color :danger theme)}
:size :paragraph-2
:suppress-highlighting true}
(i18n/label :t/forgot-password)]]])]))
[error-info error-message processing shell?])]))
(def view (quo.theme/with-theme view-internal))
@@ -3,45 +3,32 @@
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.common.standard-authentication.standard-auth.authorize :as authorize]
[status-im.constants :as constants]
[utils.re-frame :as rf]))
(defn- view-internal
[_]
(let [reset-slider? (reagent/atom false)
on-close (fn []
(js/setTimeout
#(reset! reset-slider? true)
200))
(defn view
[{:keys [track-text customization-color auth-button-label on-auth-success on-auth-fail
auth-button-icon-left size blur? container-style]
:or {container-style {:flex 1}}}]
(let [theme (quo.theme/use-theme-value)
auth-method (rf/sub [:auth-method])
biometric-auth? (= auth-method constants/auth-method-biometric)]
(fn [{:keys [track-text
customization-color
auth-button-label
on-auth-success
on-auth-fail
auth-button-icon-left
size
theme
blur?
container-style]
:or {container-style {:flex 1}}}]
[rn/view {:style container-style}
[quo/slide-button
{:size size
:customization-color customization-color
:on-reset (when @reset-slider? #(reset! reset-slider? false))
:on-complete #(authorize/authorize {:on-close on-close
:auth-button-icon-left auth-button-icon-left
:theme theme
:blur? blur?
:biometric-auth? biometric-auth?
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail
:auth-button-label auth-button-label})
:track-icon (if biometric-auth? :i/face-id :password)
:track-text track-text}]])))
(def view (quo.theme/with-theme view-internal))
biometric-auth? (= auth-method constants/auth-method-biometric)
on-complete (rn/use-callback
(fn [reset]
(authorize/authorize {:on-close #(js/setTimeout reset 200)
:auth-button-icon-left auth-button-icon-left
:theme theme
:blur? blur?
:biometric-auth? biometric-auth?
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail
:auth-button-label auth-button-label}))
[theme])]
[quo/slide-button
{:container-style container-style
:size size
:customization-color customization-color
:on-complete on-complete
:track-icon (if biometric-auth? :i/face-id :password)
:track-text track-text}]))
+18
View File
@@ -14,6 +14,7 @@
[status-im.contexts.chat.messenger.messages.delete-message-for-me.events :as delete-for-me]
[status-im.contexts.chat.messenger.messages.delete-message.events :as delete-message]
[status-im.contexts.chat.messenger.messages.list.state :as chat.state]
[status-im.feature-flags :as ff]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.datetime :as datetime]
@@ -431,3 +432,20 @@
[:chat/inputs current-chat-id :metadata :sending-image]
dissoc
(:uri original))})))
(rf/reg-event-fx :chat.ui/show-profile
(fn [{:keys [db]} [public-key ens-name]]
(let [my-public-key (get-in db [:profile/profile :public-key])]
(if (not= my-public-key public-key)
{:db (-> db
(assoc :contacts/identity public-key)
(assoc :contacts/ens-name ens-name))
:dispatch [:contacts/build-contact
{:pubkey public-key
:ens ens-name
:success-fn (fn [_]
{:dispatch [:open-modal
(if (ff/enabled? ::ff/profile.new-contact-ui)
:contact-profile
:profile)]})}]}
{:dispatch [:navigate-to :my-profile]}))))
@@ -10,6 +10,7 @@
[status-im.contexts.onboarding.new-to-status.style :as style]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[native-module.core :as native-module]
[utils.re-frame :as rf]))
(defn sign-in-options
@@ -95,7 +96,8 @@
(defn new-to-status
[]
(let [{:keys [top]} (safe-area/get-insets)]
(let [{:keys [top]} (safe-area/get-insets)
_ (native-module/request-local-network-access (fn [response] (log/info "request-local-network-access ->" response)))]
[rn/view {:style style/content-container}
[quo/page-nav
{:margin-top top
@@ -48,7 +48,7 @@
:disabled? (:disabled? @state)
:blur? @blur?
:type (:type @state)
:on-complete (fn []
:on-complete (fn [_]
(js/setTimeout (fn [] (reset! complete? true))
1000)
(js/alert "I don't wanna slide anymore"))}]
@@ -25,8 +25,8 @@
:blur? (:blur? @state)
:show-blur-background? true}
[quo/address-input
(merge @state
{:on-scan #(js/alert "Not implemented yet")
(merge (dissoc @state :scanned-value)
{:on-scan (fn [on-result] (on-result (:scanned-value @state)))
:ens-regex constants/regx-ens
:on-detect-ens (fn [_]
(swap! state assoc :valid-ens-or-address? false)
+1 -1
View File
@@ -535,7 +535,7 @@
[quo/button
{:type :outline
:container-style {:margin-vertical 8}
:on-press #(rf/dispatch [:navigate-to category-name])}
:on-press #(rf/dispatch [:open-modal category-name])}
(name category-name)]))])))
(defn- main-screen
@@ -0,0 +1,17 @@
(ns status-im.contexts.profile.contact.header.style
(:require [quo.foundations.colors :as colors]
[react-native.reanimated :as reanimated]))
(def avatar-wrapper
{:margin-top -40
:padding-left 20
:align-items :flex-start})
(defn header-container
[border-radius theme margin-top]
(reanimated/apply-animations-to-style
{:border-top-left-radius border-radius
:border-top-right-radius border-radius}
{:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
:padding-horizontal 20
:margin-top margin-top}))
@@ -0,0 +1,34 @@
(ns status-im.contexts.profile.contact.header.view
(:require [quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme]
[react-native.core :as rn]
[status-im.common.scalable-avatar.view :as avatar]
[status-im.contexts.profile.contact.header.style :as style]
[status-im.contexts.profile.utils :as profile.utils]
[utils.re-frame :as rf]))
(defn view
[{:keys [scroll-y]}]
(let [{:keys [public-key customization-color
emoji-hash bio]
:as profile} (rf/sub [:contacts/current-contact])
customization-color (or customization-color :blue)
full-name (profile.utils/displayed-name profile)
profile-picture (profile.utils/photo profile)
online? (rf/sub [:visibility-status-updates/online? public-key])
theme (quo.theme/use-theme-value)]
[rn/view {:style style/header-container}
[rn/view {:style style/avatar-wrapper}
[avatar/view
{:scroll-y scroll-y
:full-name full-name
:online? online?
:profile-picture profile-picture
:border-color (colors/theme-colors colors/white colors/neutral-95 theme)
:customization-color customization-color}]]
[quo/page-top
{:title full-name
:description :text
:description-text bio
:emoji-dash emoji-hash}]]))
@@ -0,0 +1,24 @@
(ns status-im.contexts.profile.contact.view
(:require [quo.foundations.colors :as colors]
[quo.theme]
[react-native.reanimated :as reanimated]
[status-im.common.not-implemented :as not-implemented]
[status-im.common.scroll-page.view :as scroll-page]
[status-im.contexts.profile.contact.header.view :as contact-header]
[utils.re-frame :as rf]))
(defn view
[]
(let [{:keys [customization-color]} (rf/sub [:contacts/current-contact])
scroll-y (reanimated/use-shared-value 0)
theme (quo.theme/use-theme-value)]
[scroll-page/scroll-page
{:navigate-back? true
:height 148
:on-scroll #(reanimated/set-shared-value scroll-y %)
;TODO remove colors/primary-50 when #18733 merged.
:cover-color (or customization-color colors/primary-50)
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
:page-nav-props {:right-side [{:icon-name :i/options
:on-press not-implemented/alert}]}}
[contact-header/view {:scroll-y scroll-y}]]))
@@ -5,7 +5,6 @@
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im.common.confirmation-drawer.view :as confirmation-drawer]
[status-im.common.standard-authentication.core :as standard-authentication]
[status-im.config :as config]
@@ -133,8 +132,8 @@
[:profile.login/login-with-biometric-if-available key-uid])
(when-not keycard-pairing (set-hide-profiles)))}]))
(defn- f-profiles-section
[{:keys [set-hide-profiles]}]
(defn- profiles-section
[{:keys [hide-profiles]}]
(let [profiles (vals (rf/sub [:profile/profiles-overview]))
translate-x (reanimated/use-shared-value @translate-x-atom)]
(rn/use-mount (fn []
@@ -165,42 +164,38 @@
:key-fn :key-uid
:content-container-style {:padding-bottom 20}
:render-data {:last-index (dec (count profiles))
:set-hide-profiles set-hide-profiles}
:set-hide-profiles hide-profiles}
:render-fn profile-card}]]))
(defn profiles-section
[props]
[:f> f-profiles-section props])
(defn password-input
[]
(let [password (rf/sub [:profile/login-password])
auth-method (rf/sub [:auth-method])]
(let [auth-method (rf/sub [:auth-method])
on-press-biometrics (when (= auth-method constants/auth-method-biometric)
(rn/use-callback
(fn []
(rf/dispatch [:biometric/authenticate
{:on-success #(rf/dispatch
[:profile.login/biometric-success])
:on-fail #(rf/dispatch
[:profile.login/biometric-auth-fail
%])}]))))]
[standard-authentication/password-input
{:shell? true
:blur? true
:on-press-biometrics (when (= auth-method constants/auth-method-biometric)
(fn []
(rf/dispatch [:biometric/authenticate
{:on-success #(rf/dispatch
[:profile.login/biometric-success])
:on-fail #(rf/dispatch
[:profile.login/biometric-auth-fail
%])}])))
:default-password password}]))
:on-press-biometrics on-press-biometrics}]))
(defn login-section
[{:keys [set-show-profiles]}]
(let [processing (rf/sub [:profile/login-processing])
{:keys [key-uid name customization-color]} (rf/sub [:profile/login-profile])
sign-in-enabled? (rf/sub [:sign-in-enabled?])
profile-picture (rf/sub [:profile/login-profiles-picture key-uid])
login-multiaccount #(rf/dispatch [:profile.login/login])]
[{:keys [show-profiles]}]
(let [processing (rf/sub [:profile/login-processing])
{:keys [key-uid name
customization-color]} (rf/sub [:profile/login-profile])
sign-in-enabled? (rf/sub [:sign-in-enabled?])
profile-picture (rf/sub [:profile/login-profiles-picture key-uid])
login-multiaccount (rn/use-callback #(rf/dispatch [:profile.login/login]))]
[rn/keyboard-avoiding-view
{:style style/login-container
:keyboardVerticalOffset (- (safe-area/get-bottom))}
[rn/view
{:style style/multi-profile-button-container}
[rn/view {:style style/multi-profile-button-container}
(when config/quo-preview-enabled?
[quo/button
{:size 32
@@ -217,7 +212,7 @@
:type :grey
:background :blur
:icon-only? true
:on-press set-show-profiles
:on-press show-profiles
:disabled? processing
:accessibility-label :show-profiles}
:i/multi-profile]]
@@ -241,16 +236,13 @@
:container-style {:margin-bottom (+ (safe-area/get-bottom) 12)}}
(i18n/label :t/log-in)]]))
;; we had to register it here, because of hotreload, overwise on hotreload it will be reseted
(defonce show-profiles? (reagent/atom false))
(defn view
[]
(let [set-show-profiles #(reset! show-profiles? true)
set-hide-profiles #(reset! show-profiles? false)]
(fn []
[:<>
[background/view true]
(if @show-profiles?
[profiles-section {:set-hide-profiles set-hide-profiles}]
[login-section {:set-show-profiles set-show-profiles}])])))
(let [[show-profiles? set-show-profiles] (rn/use-state false)
show-profiles (rn/use-callback #(set-show-profiles true))
hide-profiles (rn/use-callback #(set-show-profiles false))]
[:<>
[background/view true]
(if show-profiles?
[profiles-section {:hide-profiles hide-profiles}]
[login-section {:show-profiles show-profiles}])]))
@@ -1,9 +1,10 @@
(ns status-im.contexts.profile.settings.header.view
(:require [clojure.string :as string]
[quo.core :as quo]
[quo.theme :as quo.theme]
[quo.foundations.colors :as colors]
[quo.theme]
[react-native.core :as rn]
[status-im.contexts.profile.settings.header.avatar :as header.avatar]
[status-im.common.scalable-avatar.view :as avatar]
[status-im.contexts.profile.settings.header.header-shape :as header.shape]
[status-im.contexts.profile.settings.header.style :as style]
[status-im.contexts.profile.settings.header.utils :as header.utils]
@@ -13,28 +14,31 @@
(defn- f-view
[{:keys [theme scroll-y]}]
(let [{:keys [public-key emoji-hash] :as profile} (rf/sub [:profile/profile-with-image])
online? (rf/sub [:visibility-status-updates/online?
public-key])
status (rf/sub
[:visibility-status-updates/visibility-status-update
public-key])
customization-color (rf/sub [:profile/customization-color])
bio (:bio profile)
full-name (profile.utils/displayed-name profile)
profile-picture (profile.utils/photo profile)
emoji-string (string/join emoji-hash)
{:keys [status-title status-icon]} (header.utils/visibility-status-type-data status)]
(let [{:keys [public-key emoji-hash bio] :as profile} (rf/sub [:profile/profile-with-image])
online? (rf/sub [:visibility-status-updates/online?
public-key])
status (rf/sub
[:visibility-status-updates/visibility-status-update
public-key])
customization-color (rf/sub [:profile/customization-color])
full-name (profile.utils/displayed-name profile)
profile-picture (profile.utils/photo profile)
emoji-string (string/join emoji-hash)
{:keys [status-title status-icon]} (header.utils/visibility-status-type-data status)
border-theme (quo.theme/get-theme)]
[:<>
[header.shape/view
{:scroll-y scroll-y
:customization-color customization-color
:theme theme}]
[rn/view {:style style/avatar-row-wrapper}
[header.avatar/view
[avatar/view
{:scroll-y scroll-y
:display-name full-name
:online? online?
:border-color (colors/theme-colors colors/border-avatar-light
colors/neutral-80-opa-80
border-theme)
:customization-color customization-color
:profile-picture profile-picture}]
[rn/view {:style {:margin-bottom 4}}
@@ -21,10 +21,14 @@
(h/fire-event :change-text
(h/get-by-label-text :add-address-to-watch)
"0x12E838Ae1f769147b12956485dc56e57138f3AC8")
(h/is-truthy (h/get-by-translation-text :t/address-already-in-use)))
(-> (h/wait-for #(h/get-by-translation-text :t/address-already-in-use))
(.then (fn []
(h/is-truthy (h/get-by-translation-text :t/address-already-in-use))))))
(h/test "validation messages show for invalid address"
(h/render [add-address-to-watch/view])
(h/is-falsy (h/query-by-label-text :error-message))
(h/fire-event :change-text (h/get-by-label-text :add-address-to-watch) "0x12E838Ae1f769147b")
(h/is-truthy (h/get-by-translation-text :t/invalid-address))))
(-> (h/wait-for #(h/get-by-translation-text :t/invalid-address))
(.then (fn []
(h/is-truthy (h/get-by-translation-text :t/invalid-address)))))))
+1
View File
@@ -57,6 +57,7 @@
(react-native-shake/add-shake-listener #(re-frame/dispatch [:shake-event]))
(universal-links/initialize)
(interceptors/register-global-interceptors)
; (native-module/request-local-network-access (fn [response] (log/info "request-local-network-access ->" response)))
;; Shell
(async-storage/get-item :selected-stack-id #(shell.utils/change-selected-stack-id % nil nil))
+2 -1
View File
@@ -13,7 +13,8 @@
{::wallet.edit-default-keypair (enabled-in-env? :FLAG_EDIT_DEFAULT_KEYPAIR_ENABLED)
::wallet.bridge-token (enabled-in-env? :FLAG_BRIDGE_TOKEN_ENABLED)
::wallet.remove-account (enabled-in-env? :FLAG_REMOVE_ACCOUNT_ENABLED)
::wallet.network-filter (enabled-in-env? :FLAG_NETWORK_FILTER_ENABLED)}))
::wallet.network-filter (enabled-in-env? :FLAG_NETWORK_FILTER_ENABLED)
::profile.new-contact-ui (enabled-in-env? :FLAG_NEW_CONTACT_UI_ENABLED)}))
(defn feature-flags [] @feature-flags-config)
+16 -3
View File
@@ -1,5 +1,7 @@
(ns status-im.navigation.effects
(:require
[quo.theme]
[react-native.core :as rn]
[react-native.navigation :as navigation]
[status-im.navigation.options :as options]
[status-im.navigation.roots :as roots]
@@ -8,10 +10,20 @@
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
(defn- set-status-bar-color
[theme]
(rn/set-status-bar-style
(if (= theme :dark)
"light-content"
"dark-content")
true))
(rf/reg-fx :set-view-id-fx
(fn [view-id]
(rf/dispatch [:screens/on-will-focus view-id])
(when-let [{:keys [on-focus]} (get views/screens view-id)]
(when-let [{:keys [on-focus options]} (get views/screens view-id)]
(set-status-bar-color (or (:theme options)
(quo.theme/get-theme)))
(when on-focus
(rf/dispatch on-focus)))))
@@ -117,11 +129,12 @@
(defn open-modal
[component]
(let [{:keys [options]} (get views/screens component)
sheet? (:sheet? options)]
(let [{:keys [options name]} (get views/screens component)
sheet? (:sheet? options)]
(if @state/dissmissing
(reset! state/dissmissing component)
(do
(set-view-id name) ; TODO https://github.com/status-im/status-mobile/issues/18811
(reset! state/curr-modal true)
(swap! state/modals conj component)
(navigation/show-modal
-3
View File
@@ -137,9 +137,6 @@
:decelerate
:factor 1.5}}]}}})
(def camera-screen
{:navigationBar {:backgroundColor colors/black}})
(defn merge-top-bar
[root-options options]
(let [options (:topBar options)]
+15 -1
View File
@@ -1,6 +1,7 @@
(ns status-im.navigation.screens
(:require
[legacy.status-im.ui.screens.screens :as old-screens]
[quo.foundations.colors :as colors]
[status-im.common.emoji-picker.view :as emoji-picker]
[status-im.common.lightbox.view :as lightbox]
[status-im.config :as config]
@@ -36,6 +37,7 @@
[status-im.contexts.preview.quo.component-preview.view :as component-preview]
[status-im.contexts.preview.quo.main :as quo.preview]
[status-im.contexts.preview.status-im.main :as status-im-preview]
[status-im.contexts.profile.contact.view :as contact-profile]
[status-im.contexts.profile.edit.accent-colour.view :as edit-accent-colour]
[status-im.contexts.profile.edit.bio.view :as edit-bio]
[status-im.contexts.profile.edit.name.view :as edit-name]
@@ -141,7 +143,8 @@
:component photo-selector/photo-selector}
{:name :camera-screen
:options options/camera-screen
:options {:navigationBar {:backgroundColor colors/black}
:theme :dark}
:component camera-screen/camera-screen}
{:name :new-contact
@@ -202,6 +205,10 @@
:options options/transparent-modal-screen-options
:component edit-bio/view}
{:name :contact-profile
:options {:modalPresentationStyle :overCurrentContext}
:component contact-profile/view}
{:name :new-to-status
:options {:theme :dark
:layout options/onboarding-transparent-layout
@@ -428,6 +435,13 @@
:options options/transparent-modal-screen-options
:component settings-password/view}]
[{:name :shell
:options {:theme :dark}}
{:name :communities-stack}
{:name :chats-stack}
{:name :wallet-stack}
{:name :browser-stack}]
(when js/goog.DEBUG
[{:name :dev-component-preview
:options {:sheet? true}
+14 -6
View File
@@ -17,6 +17,8 @@
[status-im.setup.hot-reload :as reloader]
[utils.re-frame :as rf]))
(def functional-compiler (reagent/create-compiler {:function-components true}))
(defn get-screens
[]
(reduce
@@ -72,7 +74,8 @@
(when js/goog.DEBUG
[:<>
[reloader/reload-view]
[schema.view/view]])]))))
[schema.view/view]])]))
functional-compiler))
(def bottom-sheet
(reagent/reactify-component
@@ -90,7 +93,8 @@
:keyboard-vertical-offset (- (max 20 (:bottom insets)))}
(when sheet
[bottom-sheet/view {:insets insets :hide? hide?}
sheet])]]))))
sheet])]]))
functional-compiler))
(def toasts (reagent/reactify-component toasts/toasts))
@@ -103,7 +107,8 @@
[inactive]
[popover/popover]
(when js/goog.DEBUG
[reloader/reload-view])])))
[reloader/reload-view])])
functional-compiler))
(def visibility-status-popover-comp
(reagent/reactify-component
@@ -113,7 +118,8 @@
[inactive]
[visibility-status-views/visibility-status-popover]
(when js/goog.DEBUG
[reloader/reload-view])])))
[reloader/reload-view])])
functional-compiler))
(def sheet-comp-old
(reagent/reactify-component
@@ -121,7 +127,8 @@
^{:key (str "sheet-old" @reloader/cnt)}
[:<>
[inactive]
[bottom-sheets-old/bottom-sheet]])))
[bottom-sheets-old/bottom-sheet]])
functional-compiler))
(def signing-comp
(reagent/reactify-component
@@ -131,4 +138,5 @@
[inactive]
[signing/signing]
(when js/goog.DEBUG
[reloader/reload-view])])))
[reloader/reload-view])])
functional-compiler))
@@ -1,6 +1,7 @@
(ns test-helpers.component-tests-preload
{:dev/always true}
(:require
[reagent.core :as reagent]
;; NOTE: Do NOT sort i18n-resources because it MUST be loaded first.
[status-im.setup.i18n-resources :as i18n-resources]
#_{:clj-kondo/ignore [:unsorted-required-namespaces]}
@@ -15,6 +16,7 @@
should never be directly required. However, it will be loaded automatically
before any component test runs."
[]
(reagent/set-default-compiler! (reagent/create-compiler {:function-components true}))
(interceptors/register-global-interceptors)
(i18n/set-language "en")
(i18n-resources/load-language "en"))
+3 -3
View File
@@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "v0.175.4",
"commit-sha1": "5304406079a5e3e1cfea285fef892ac56f2c3aad",
"src-sha256": "08xprnmpw0nz2zjm1wp8ypjhdyfvgmh7pqrlk69prgr23xa31ady"
"version": "v0.176.2",
"commit-sha1": "00f50a4112ef80cc29737f8edf34cdb1cf181709",
"src-sha256": "12z9qf0hhb2pc7ask6f86pah0wjp3kyxyv32rpfnkjl42maw0bw6"
}
@@ -59,6 +59,7 @@ class TestActivityCenterContactRequestMultipleDevicePR(MultipleSharedDeviceTestC
self.errors.append("Push notification with text was received for new message in activity centre")
self.device_1.click_system_back_button()
self.device_1.driver.activate_app(app_package)
self.device_1.wait_for_application_to_be_running(app_package)
self.device_1.just_fyi('Device1 verifies pending contact request')
self.home_1.contacts_tab.click()
@@ -271,8 +271,6 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
self.home_1.jump_to_messages_home()
self.home_1.profile_button.click()
self.profile_1.edit_profile_picture(image_index=2)
self.profile_1.navigate_back_to_home_view()
self.profile_1.chats_tab.click()
self.chat_2.just_fyi("Send messages with non-latin symbols")
self.home_1.jump_to_card_by_text(self.username_2)
+16 -4
View File
@@ -6,6 +6,7 @@ import time
from datetime import datetime
from appium.webdriver import WebElement
from appium.webdriver.applicationstate import ApplicationState
from appium.webdriver.common.touch_action import TouchAction
from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver.support import expected_conditions
@@ -658,13 +659,24 @@ class BaseView(object):
self.click_on_floating_jump_to()
self.element_by_text(text).click()
def wait_for_application_to_be_running(self, app_package: str, wait_time: int = 3):
for _ in range(wait_time):
if self.driver.query_app_state(app_package) == ApplicationState.RUNNING_IN_FOREGROUND:
return
time.sleep(1)
raise TimeoutException(msg="Status app is not running in foreground after %s sec" % wait_time)
def wait_for_application_to_not_run(self, app_package: str, wait_time: int = 3):
for _ in range(wait_time):
if self.driver.query_app_state(app_package) == ApplicationState.NOT_RUNNING:
return
time.sleep(1)
raise TimeoutException(msg="Status app is not terminated after %s sec" % wait_time)
def reopen_app(self, password=common_password, sign_in=True):
app_package = self.driver.current_package
self.driver.terminate_app(app_package)
for _ in range(3):
if self.driver.query_app_state(app_package) == 1:
break
time.sleep(1)
self.wait_for_application_to_not_run(app_package=app_package)
self.driver.activate_app(app_package)
if sign_in:
sign_in_view = self.get_sign_in_view()
+11 -6
View File
@@ -199,9 +199,9 @@ class ProfileView(BaseView):
self.online_indicator = Button(self.driver, accessibility_id="online-profile-photo-dot")
self.edit_picture_button = Button(self.driver, accessibility_id="edit-profile-photo-button")
self.confirm_edit_button = Button(self.driver, accessibility_id="done-button")
self.select_from_gallery_button = Button(self.driver, translation_id="profile-pic-pick")
self.select_from_gallery_button_old = Button(self.driver, translation_id="profile-pic-pick")
self.capture_button = Button(self.driver, translation_id="image-source-make-photo")
self.take_photo_button = Button(self.driver, accessibility_id="take-photo")
self.take_photo_button_old = Button(self.driver, accessibility_id="take-photo")
self.crop_photo_button = Button(self.driver, accessibility_id="Crop")
self.decline_photo_crop = Button(self.driver, accessibility_id="Navigate up")
self.shutter_button = Button(self.driver, accessibility_id="Shutter")
@@ -354,6 +354,12 @@ class ProfileView(BaseView):
self.confirm_logout_button = Button(self.driver, translation_id="logout", uppercase=True)
# New profile
self.edit_profile_button = Button(self.driver, accessibility_id="icon, Edit Profile, label-component, icon")
self.change_profile_photo_button = Button(
self.driver,
xpath="//*[@content-desc='user-avatar']/following-sibling::android.view.ViewGroup[@content-desc='icon']")
self.take_photo_button = Button(self.driver, accessibility_id="take-photo-button")
self.select_from_gallery_button = Button(self.driver, accessibility_id="select-from-gallery-button")
self.profile_password_button = Button(self.driver, accessibility_id="icon, Password, label-component, icon")
self.profile_legacy_button = Button(self.driver,
accessibility_id="icon, Legacy settings, label-component, icon")
@@ -438,10 +444,8 @@ class ProfileView(BaseView):
self.driver.info("## Setting custom profile image", device=False)
if not AbstractTestCase().environment == 'sauce':
raise NotImplementedError('Test case is implemented to run on SauceLabs only')
## pointing to legacy profile until new feature is implemented
self.logout_button.scroll_to_element()
self.profile_legacy_button.click()
self.profile_picture.click()
self.edit_profile_button.click()
self.change_profile_photo_button.click()
if update_by == "Gallery":
self.select_from_gallery_button.click()
self.select_photo_from_gallery_by_index(image_index)
@@ -454,6 +458,7 @@ class ProfileView(BaseView):
self.accept_photo_button.click()
self.crop_photo_button.click()
self.driver.info("## Custom profile image has been set", device=False)
self.click_system_back_button()
def take_photo(self):
self.take_photo_button.click()