diff --git a/doc/decisions/UI-related/7-august-2023-paste-button-ux.md b/doc/decisions/UI-related/7-august-2023-paste-button-ux.md new file mode 100644 index 0000000000..122f1f5026 --- /dev/null +++ b/doc/decisions/UI-related/7-august-2023-paste-button-ux.md @@ -0,0 +1,31 @@ +### How should the paste button behave when clipboard is empty? + +This question first came up as a review comment to this PR +https://github.com/status-im/status-mobile/pull/16852 + +There were 2 considerations : +- Either keep the paste button disabled when there is nothing in the clipboard OR +- Always keep the paste button enabled + +There were positives and negatives for both approaches. + +Positives of keeping paste button disabled when there is nothing in clipboard would require us to +check the value of clipboard as soon as the component is mounted (i.e when the user first sees +the screen). In iOS this means a native permissions dialog would appear requesting for permissions +to paste from the clipboard. + +Negatives of this approach is that as soon as any user navigates to this screen they are greeted with +this popup which can be annoying sometimes. + +Positives of keeping paste button always enabled is that we can trigger a request to the clipboard on +tap of the paste button which would trigger the native permissions dialog requesting for permissions +to paste from the clipboard. +In this case seeing this dialog is okay because the user has initiated a paste action. + +Negatives of this approach is that in the event the clipboard is empty the user will still see the +system dialog and on approving nothing will be pasted (because the clipboard was empty). +This behaviour can be confusing. + +On consulting the Design Team via discord it was concluded that out of the two approaches +having the paste button always enabled is a better UX overall. + diff --git a/src/status_im2/contexts/syncing/enter_sync_code/style.cljs b/src/status_im2/contexts/syncing/enter_sync_code/style.cljs new file mode 100644 index 0000000000..1d81f9f676 --- /dev/null +++ b/src/status_im2/contexts/syncing/enter_sync_code/style.cljs @@ -0,0 +1,63 @@ +(ns status-im2.contexts.syncing.enter-sync-code.style + (:require [quo2.foundations.colors :as colors] + [quo2.foundations.typography :as typography])) + +(def container-text-input + {:flex-direction :row + :justify-content :space-between + :padding-horizontal 20}) + +(defn text-input-container + [invalid?] + {:padding-left 12 + :padding-right 7 + :min-height 38 + :flex 1 + :flex-direction :row + :border-width 1 + :border-radius 12 + :border-color (if invalid? + colors/danger-50-opa-40 + colors/neutral-60)}) + +(defn text-input + [] + (merge typography/monospace + typography/paragraph-1 + {:flex 1 + :padding-top 6 + :padding-bottom 8 + :color colors/white + :text-align-vertical :top})) + +(def label-texts-container + {:flex-direction :row + :height 18 + :margin-bottom 8}) + +(def button-paste + {:margin-top 8}) + +(def clear-icon + {:size 20 + :color colors/neutral-80-opa-30}) + +(def right-icon-touchable-area + {:margin-left 8 + :padding-right 4 + :padding-top 6 + :margin-bottom 4}) + +(def label-pairing + {:color colors/white-opa-40}) + +(def label-container + {:flex-direction :row + :margin-left 20 + :line-height 18 + :margin-top 20 + :margin-bottom 8}) + +(def continue-button-container + {:margin-top 12 + :padding-horizontal 22}) diff --git a/src/status_im2/contexts/syncing/enter_sync_code/view.cljs b/src/status_im2/contexts/syncing/enter_sync_code/view.cljs new file mode 100644 index 0000000000..77a9c234e2 --- /dev/null +++ b/src/status_im2/contexts/syncing/enter_sync_code/view.cljs @@ -0,0 +1,76 @@ +(ns status-im2.contexts.syncing.enter-sync-code.view + (:require [clojure.string :as string] + [quo2.core :as quo] + [quo2.foundations.colors :as colors] + [reagent.core :as reagent] + [react-native.core :as rn] + [react-native.clipboard :as clipboard] + [utils.i18n :as i18n] + [status-im2.contexts.syncing.enter-sync-code.style :as style] + [utils.debounce :as debounce] + [utils.re-frame :as rf] + [status-im2.contexts.syncing.utils :as sync-utils])) + +(defn view + [] + (let [sync-code-value (reagent/atom "")] + (fn [] + (let [invalid? false + show-paste-button? (string/blank? @sync-code-value) + profile-color (rf/sub [:profile/customization-color])] + [:<> + [rn/view + {:style style/label-container} + [quo/text + {:style style/label-pairing + :weight :medium + :size :paragraph-2} + (i18n/label :t/type-pairing-code)]] + [rn/view {:style style/container-text-input} + [rn/view {:style (style/text-input-container invalid?)} + [rn/text-input + {:style (style/text-input) + :value @sync-code-value + :placeholder (i18n/label :t/scan-sync-code-placeholder) + :on-change-text (fn [scan-code] + (reset! sync-code-value scan-code) + (reagent/flush)) + :blur-on-submit true + :return-key-type :done + :accessibility-label :enter-sync-code-input + :auto-capitalize :none + :placeholder-text-color colors/white-opa-40 + :multiline true}] + (if show-paste-button? + [quo/button + {:on-press (fn [_] + (clipboard/get-string #(reset! sync-code-value %))) + :type :outline + :container-style style/button-paste + :size 24} + (i18n/label :t/paste)] + + [rn/pressable + {:accessibility-label :input-right-icon + :style style/right-icon-touchable-area + :on-press (fn [_] + (reset! sync-code-value nil))} + [quo/icon :i/clear style/clear-icon]])]] + [quo/button + {:type :primary + :disabled? (string/blank? @sync-code-value) + :customization-color profile-color + :container-style style/continue-button-container + :on-press (fn [_] + (if (sync-utils/valid-connection-string? @sync-code-value) + (debounce/debounce-and-dispatch + [:syncing/input-connection-string-for-bootstrapping + @sync-code-value] + 300) + (rf/dispatch [:toasts/upsert + {:icon :i/info + :icon-color colors/danger-50 + :theme :dark + :text (i18n/label + :t/error-this-is-not-a-sync-qr-code)}])))} + (i18n/label :t/confirm)]])))) diff --git a/src/status_im2/contexts/syncing/scan_sync_code/style.cljs b/src/status_im2/contexts/syncing/scan_sync_code/style.cljs index 0e41763e2f..bf9c4f7792 100644 --- a/src/status_im2/contexts/syncing/scan_sync_code/style.cljs +++ b/src/status_im2/contexts/syncing/scan_sync_code/style.cljs @@ -133,11 +133,6 @@ {:color colors/white-opa-70 :margin-bottom 16}) -(def enter-sync-code-container - {:margin-top 20 - :justify-content :center - :align-items :center}) - (defn bottom-container [translate-y padding-bottom] (reanimated/apply-animations-to-style diff --git a/src/status_im2/contexts/syncing/scan_sync_code/view.cljs b/src/status_im2/contexts/syncing/scan_sync_code/view.cljs index fb3c9cd0cb..4d9a5f5739 100644 --- a/src/status_im2/contexts/syncing/scan_sync_code/view.cljs +++ b/src/status_im2/contexts/syncing/scan_sync_code/view.cljs @@ -20,7 +20,8 @@ [utils.debounce :as debounce] [utils.i18n :as i18n] [utils.re-frame :as rf] - [utils.transforms :as transforms])) + [utils.transforms :as transforms] + [status-im2.contexts.syncing.enter-sync-code.view :as enter-sync-code])) ;; Android allow local network access by default. So, we need this check on iOS only. (defonce preflight-check-passed? (reagent/atom (if platform/ios? false true))) @@ -199,15 +200,6 @@ [viewfinder qr-view-finder] [camera-and-local-network-access-permission-view])) -(defn- enter-sync-code-tab - [] - [rn/view {:style style/enter-sync-code-container} - [quo/text - {:size :paragraph-1 - :weight :medium - :style {:color colors/white}} - "Yet to be implemented"]]) - (defn- f-bottom-view [insets translate-y] [rn/touchable-without-feedback @@ -374,7 +366,7 @@ {})} (case @active-tab 1 [scan-qr-code-tab @qr-view-finder] - 2 [enter-sync-code-tab] + 2 [enter-sync-code/view] nil)] [rn/view {:style style/flex-spacer}] (when show-bottom-view? diff --git a/translations/en.json b/translations/en.json index ebddc56855..1f8c36b101 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2331,5 +2331,7 @@ "here-is-a-cat-in-a-box-instead": "Here’s a cat in a box instead", "accounts-count": "{{count}} accounts", "enter-eth": "Enter any ETH address or ENS name.", - "eth-or-ens": "ETH address or ENS name." + "eth-or-ens": "ETH address or ENS name.", + "type-pairing-code": "Type or paste pairing code", + "scan-sync-code-placeholder": "cs2:4FH..." }