355 lines
13 KiB
Clojure
355 lines
13 KiB
Clojure
(ns status-im.ui.components.react
|
|
(:require-macros [status-im.utils.views :as views])
|
|
(:require [goog.object :as object]
|
|
[reagent.core :as reagent]
|
|
[status-im.ui.components.styles :as styles]
|
|
[status-im.utils.utils :as utils]
|
|
[status-im.utils.core :as utils.core]
|
|
[status-im.utils.platform :as platform]
|
|
[status-im.i18n :as i18n]
|
|
[status-im.react-native.js-dependencies :as js-dependencies]
|
|
[status-im.ui.components.colors :as colors]
|
|
[status-im.ui.components.typography :as typography]))
|
|
|
|
(defn get-react-property [name]
|
|
(if js-dependencies/react-native
|
|
(or (object/get js-dependencies/react-native name) {})
|
|
#js {}))
|
|
|
|
(defn lazy-get-react-property [name]
|
|
(let [react-property (atom nil)]
|
|
(fn []
|
|
(if @react-property
|
|
@react-property
|
|
(reset! react-property (get-react-property name))))))
|
|
|
|
(defn adapt-class [class]
|
|
(when class
|
|
(reagent/adapt-react-class class)))
|
|
|
|
(defn get-class [name]
|
|
(let [react-class (atom nil)]
|
|
(fn []
|
|
(if @react-class
|
|
@react-class
|
|
(reset! react-class
|
|
(adapt-class (get-react-property name)))))))
|
|
|
|
(def native-modules (.-NativeModules js-dependencies/react-native))
|
|
(def device-event-emitter (.-DeviceEventEmitter js-dependencies/react-native))
|
|
|
|
(defn dismiss-keyboard! [] ((js-dependencies/dismiss-keyboard)))
|
|
|
|
(def splash-screen (.-SplashScreen native-modules))
|
|
|
|
;; React Components
|
|
|
|
(def app-registry (get-react-property "AppRegistry"))
|
|
(def app-state (lazy-get-react-property "AppState"))
|
|
(def net-info (lazy-get-react-property "NetInfo"))
|
|
(def view ((get-class "View")))
|
|
(def safe-area-view (get-class "SafeAreaView"))
|
|
(def progress-bar (get-class "ProgressBarAndroid"))
|
|
|
|
(def status-bar-class (when-not platform/desktop? (get-react-property "StatusBar")))
|
|
|
|
(def scroll-view-class (get-class "ScrollView"))
|
|
(def web-view (get-class "WebView"))
|
|
(def keyboard-avoiding-view-class (get-class "KeyboardAvoidingView"))
|
|
|
|
(def refresh-control (get-class "RefreshControl"))
|
|
|
|
(def text-class ((get-class "Text")))
|
|
(def text-input-class (get-class "TextInput"))
|
|
(def image-class (get-class "Image"))
|
|
(def picker-obj (lazy-get-react-property "Picker"))
|
|
(defn picker-class [] (adapt-class (picker-obj)))
|
|
(defn picker-item-class [] (adapt-class (.-Item (picker-obj))))
|
|
|
|
(defn valid-source? [source]
|
|
(or (not (map? source))
|
|
(not (contains? source :uri))
|
|
(and (contains? source :uri)
|
|
(:uri source))))
|
|
|
|
(defn image [{:keys [source] :as props}]
|
|
(when (valid-source? source)
|
|
(let [source (if (fn? source) (source) source)]
|
|
[(image-class) (assoc props :source source)])))
|
|
|
|
(def switch-class (get-class "Switch"))
|
|
|
|
(defn switch [props]
|
|
[(switch-class) props])
|
|
|
|
(def touchable-highlight-class (get-class "TouchableHighlight"))
|
|
(def touchable-without-feedback-class (get-class "TouchableWithoutFeedback"))
|
|
(def touchable-opacity-class (get-class "TouchableOpacity"))
|
|
(def activity-indicator-class (get-class "ActivityIndicator"))
|
|
|
|
(defn activity-indicator [props]
|
|
[(activity-indicator-class) props])
|
|
|
|
(def modal (get-class "Modal"))
|
|
|
|
(def pan-responder (lazy-get-react-property "PanResponder"))
|
|
(def animated (lazy-get-react-property "Animated"))
|
|
|
|
(defn animated-view-class []
|
|
(reagent/adapt-react-class (.-View (animated))))
|
|
|
|
(defn animated-view [props & content]
|
|
(vec (conj content props (animated-view-class))))
|
|
|
|
(def dimensions (lazy-get-react-property "Dimensions"))
|
|
(def keyboard (lazy-get-react-property "Keyboard"))
|
|
(def linking (lazy-get-react-property "Linking"))
|
|
(def desktop-notification (.-DesktopNotification (.-NativeModules js-dependencies/react-native)))
|
|
|
|
(def max-font-size-multiplier 1.25)
|
|
|
|
(defn prepare-text-props [props]
|
|
(-> props
|
|
(update :style typography/get-style)
|
|
(assoc :max-font-size-multiplier max-font-size-multiplier)))
|
|
|
|
(defn prepare-nested-text-props [props]
|
|
(-> props
|
|
(update :style typography/get-nested-style)
|
|
(assoc :nested? true)))
|
|
|
|
;; Accessor methods for React Components
|
|
(defn text
|
|
"For nested text elements, use nested-text instead"
|
|
([text-element]
|
|
(text {} text-element))
|
|
([options text-element]
|
|
[text-class (prepare-text-props options) text-element]))
|
|
|
|
(defn nested-text
|
|
"Returns nested text elements with proper styling and typography
|
|
Do not use the nested? option, it is for internal usage of the function only"
|
|
[options & nested-text-elements]
|
|
(let [options-with-style (if (:nested? options)
|
|
(prepare-nested-text-props options)
|
|
(prepare-text-props options))]
|
|
(reduce (fn [acc text-element]
|
|
(conj acc
|
|
(if (string? text-element)
|
|
text-element
|
|
(let [[options & nested-text-elements] text-element]
|
|
(apply nested-text (prepare-nested-text-props options)
|
|
nested-text-elements)))))
|
|
[text-class (dissoc options-with-style :nested?)]
|
|
nested-text-elements)))
|
|
|
|
(defn text-input
|
|
[options text]
|
|
[(text-input-class)
|
|
(merge
|
|
{:underline-color-android :transparent
|
|
:max-font-size-multiplier max-font-size-multiplier
|
|
:placeholder-text-color colors/text-gray
|
|
:placeholder (i18n/label :t/type-a-message)
|
|
:value text}
|
|
(-> options
|
|
(update :style typography/get-style)
|
|
(update :style dissoc :line-height)))])
|
|
|
|
(defn i18n-text
|
|
[{:keys [style key]}]
|
|
[text {:style style} (i18n/label key)])
|
|
|
|
(defn icon
|
|
([n] (icon n styles/icon-default))
|
|
([n style]
|
|
[image {:source {:uri (keyword (str "icon_" (name n)))}
|
|
:resizeMode "contain"
|
|
:style style}]))
|
|
|
|
(defn touchable-opacity [props content]
|
|
[(touchable-opacity-class) props content])
|
|
|
|
(defn touchable-highlight [props content]
|
|
[(touchable-highlight-class)
|
|
(merge {:underlay-color :transparent} props)
|
|
content])
|
|
|
|
(defn touchable-without-feedback [props content]
|
|
[(touchable-without-feedback-class)
|
|
props
|
|
content])
|
|
|
|
(defn get-dimensions [name]
|
|
(js->clj (.get (dimensions) name) :keywordize-keys true))
|
|
|
|
(defn list-item [component]
|
|
(reagent/as-element component))
|
|
|
|
(defn value->picker-item [{:keys [value label]}]
|
|
[(picker-item-class) {:value (or value "") :label (or label value "")}])
|
|
|
|
(defn picker [{:keys [style on-change selected enabled data]}]
|
|
(into
|
|
[(picker-class) (merge (when style {:style style})
|
|
(when enabled {:enabled enabled})
|
|
(when on-change {:on-value-change on-change})
|
|
(when selected {:selected-value selected}))]
|
|
(map value->picker-item data)))
|
|
|
|
;; Image picker
|
|
|
|
(def image-picker-class js-dependencies/image-crop-picker)
|
|
|
|
(defn show-access-error [o]
|
|
(when (= "E_PERMISSION_MISSING" (object/get o "code"))
|
|
(utils/show-popup (i18n/label :t/error)
|
|
(i18n/label :t/photos-access-error))))
|
|
|
|
(defn show-image-picker
|
|
([images-fn]
|
|
(show-image-picker images-fn nil))
|
|
([images-fn media-type]
|
|
(let [image-picker (.-default (image-picker-class))]
|
|
(-> image-picker
|
|
(.openPicker (clj->js {:multiple false :mediaType (or media-type "any")}))
|
|
(.then images-fn)
|
|
(.catch show-access-error)))))
|
|
|
|
;; Clipboard
|
|
|
|
(def sharing
|
|
(.-Share js-dependencies/react-native))
|
|
|
|
(defn copy-to-clipboard [text]
|
|
(.setString (.-Clipboard js-dependencies/react-native) text))
|
|
|
|
(defn get-from-clipboard [clbk]
|
|
(let [clipboard-contents (.getString (.-Clipboard js-dependencies/react-native))]
|
|
(.then clipboard-contents #(clbk %))))
|
|
|
|
;; HTTP Bridge
|
|
|
|
(def http-bridge js-dependencies/http-bridge)
|
|
|
|
;; KeyboardAvoidingView
|
|
|
|
(defn keyboard-avoiding-view [props & children]
|
|
(let [view-element (if platform/ios?
|
|
[(keyboard-avoiding-view-class) (merge {:behavior :padding} props)]
|
|
[view props])]
|
|
(vec (concat view-element children))))
|
|
|
|
(defn scroll-view [props & children]
|
|
(vec (conj children props (scroll-view-class))))
|
|
|
|
(views/defview with-activity-indicator
|
|
[{:keys [timeout style enabled? preview]} comp]
|
|
(views/letsubs
|
|
[loading (reagent/atom true)]
|
|
{:component-did-mount (fn []
|
|
(if (or (nil? timeout)
|
|
(> 100 timeout))
|
|
(reset! loading false)
|
|
(utils/set-timeout #(reset! loading false)
|
|
timeout)))}
|
|
(if (and (not enabled?) @loading)
|
|
(or preview
|
|
[view {:style (or style {:justify-content :center
|
|
:align-items :center})}
|
|
[activity-indicator {:animating true}]])
|
|
comp)))
|
|
|
|
(defn navigation-wrapper
|
|
"Wraps component so that it will be shown only when current-screen is one of views"
|
|
[{:keys [component views current-view hide?]
|
|
:or {hide? false}}]
|
|
(let [current-view? (if (set? views)
|
|
(views current-view)
|
|
(= views current-view))
|
|
|
|
style (if current-view?
|
|
{:flex 1
|
|
:zIndex 0}
|
|
{:opacity 0
|
|
:flex 0
|
|
:zIndex -1})
|
|
|
|
component' (if (fn? component) [component] component)]
|
|
|
|
(when (or (not hide?) (and hide? current-view?))
|
|
(if hide?
|
|
component'
|
|
[view style (if (fn? component) [component] component)]))))
|
|
|
|
(defn with-empty-preview [comp]
|
|
[with-activity-indicator
|
|
{:preview [view {}]}
|
|
comp])
|
|
|
|
;; Platform-specific View
|
|
|
|
(defmulti create-main-screen-view #(cond
|
|
platform/iphone-x? :iphone-x
|
|
platform/ios? :ios
|
|
platform/android? :android))
|
|
|
|
(defmethod create-main-screen-view :iphone-x [current-view]
|
|
(fn [props & children]
|
|
(let [props (merge props
|
|
{:background-color
|
|
(case current-view
|
|
(:wallet
|
|
:wallet-send-transaction
|
|
:wallet-transaction-sent
|
|
:wallet-request-transaction
|
|
:wallet-send-transaction-chat
|
|
:wallet-send-assets
|
|
:wallet-request-assets
|
|
:choose-recipient
|
|
:recent-recipients
|
|
:wallet-send-transaction-modal
|
|
:wallet-transaction-sent-modal
|
|
:wallet-send-transaction-request
|
|
:wallet-transaction-fee
|
|
:wallet-sign-message-modal
|
|
:contact-code
|
|
:wallet-onboarding-setup
|
|
:wallet-modal
|
|
:wallet-onboarding-setup-modal
|
|
:wallet-settings-hook)
|
|
colors/blue
|
|
|
|
(:qr-viewer
|
|
:recipient-qr-code)
|
|
"#2f3031"
|
|
|
|
colors/white)})
|
|
bottom-background (when (#{:wallet
|
|
:recent-recipients
|
|
:wallet-send-assets
|
|
:wallet-request-assets
|
|
:wallet-modal} current-view)
|
|
[view {:background-color colors/white
|
|
:position :absolute
|
|
:bottom 0
|
|
:right 0
|
|
:left 0
|
|
:height 100
|
|
:z-index -1000}])
|
|
children (conj children bottom-background)]
|
|
(apply vector (safe-area-view) props children))))
|
|
|
|
(defmethod create-main-screen-view :default [_]
|
|
view)
|
|
|
|
(views/defview main-screen-modal-view [current-view & components]
|
|
(views/letsubs []
|
|
(let [main-screen-view (create-main-screen-view current-view)]
|
|
[main-screen-view styles/flex
|
|
[(if (= current-view :chat-modal)
|
|
view
|
|
keyboard-avoiding-view)
|
|
{:flex 1 :flex-direction :column}
|
|
(apply vector view styles/flex components)]])))
|