Added extensions loading panel
Signed-off-by: Julien Eluard <julien.eluard@gmail.com>
This commit is contained in:
parent
f82285b1f4
commit
ae976e205f
1
.env
1
.env
|
@ -14,3 +14,4 @@ DEBUG_WEBVIEW=1
|
|||
INSTABUG_SURVEYS=1
|
||||
GROUP_CHATS_ENABLED=0
|
||||
CACHED_WEBVIEWS_ENABLED=1
|
||||
EXTENSIONS=1
|
||||
|
|
3
.env.e2e
3
.env.e2e
|
@ -9,4 +9,5 @@ POW_TIME=1
|
|||
DEFAULT_NETWORK=testnet_rpc
|
||||
INSTABUG_TOKEN=758630ed52864cbad9c5eeeac596c60c
|
||||
DEBUG_WEBVIEW=1
|
||||
GROUP_CHATS_ENABLED=1
|
||||
GROUP_CHATS_ENABLED=1
|
||||
EXTENSIONS=0
|
||||
|
|
|
@ -13,3 +13,4 @@ DEBUG_WEBVIEW=1
|
|||
GROUP_CHATS_ENABLED=0
|
||||
MAINNET_WARNING_ENABLED=1
|
||||
CACHED_WEBVIEWS_ENABLED=1
|
||||
EXTENSIONS=0
|
||||
|
|
|
@ -12,3 +12,4 @@ INSTABUG_TOKEN=758630ed52864cbad9c5eeeac596c60c
|
|||
DEBUG_WEBVIEW=1
|
||||
GROUP_CHATS_ENABLED=0
|
||||
MAINNET_WARNING_ENABLED=1
|
||||
EXTENSIONS=1
|
||||
|
|
|
@ -13,3 +13,4 @@ DEBUG_WEBVIEW=1
|
|||
INSTABUG_SURVEYS=1
|
||||
GROUP_CHATS_ENABLED=0
|
||||
MAINNET_WARNING_ENABLED=1
|
||||
EXTENSIONS=0
|
||||
|
|
|
@ -13,4 +13,5 @@ TESTFAIRY_TOKEN=969f6c921cb435cea1d41d1ea3f5b247d6026d55
|
|||
INSTABUG_TOKEN=758630ed52864cbad9c5eeeac596c60c
|
||||
DEBUG_WEBVIEW=0
|
||||
GROUP_CHATS_ENABLED=0
|
||||
MAINNET_WARNING_ENABLED=1
|
||||
MAINNET_WARNING_ENABLED=1
|
||||
EXTENSIONS=0
|
||||
|
|
3
deps.edn
3
deps.edn
|
@ -10,7 +10,8 @@
|
|||
com.andrewmcveigh/cljs-time {:mvn/version "0.5.2"}
|
||||
com.taoensso/timbre {:mvn/version "4.10.0"}
|
||||
hickory {:mvn/version "0.7.1"}
|
||||
com.cognitect/transit-cljs {:mvn/version "0.8.248"}}
|
||||
com.cognitect/transit-cljs {:mvn/version "0.8.248"}
|
||||
status-im/pluto {:mvn/version "iteration-2-SNAPSHOT"}}
|
||||
|
||||
:aliases
|
||||
{:dev {:extra-deps
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
[com.andrewmcveigh/cljs-time "0.5.2"]
|
||||
[com.taoensso/timbre "4.10.0"]
|
||||
[hickory "0.7.1"]
|
||||
[com.cognitect/transit-cljs "0.8.248"]]
|
||||
[com.cognitect/transit-cljs "0.8.248"]
|
||||
[status-im/pluto "iteration-2-SNAPSHOT"]]
|
||||
:plugins [[lein-cljsbuild "1.1.7"]
|
||||
[lein-re-frisk "0.5.8"]
|
||||
[lein-cljfmt "0.5.7"]
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
(ns status-im.chat.commands.core
|
||||
(:require [clojure.set :as set]
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[clojure.set :as set]
|
||||
[clojure.string :as string]
|
||||
[pluto.host :as host]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.chat.constants :as chat-constants]
|
||||
[status-im.chat.commands.protocol :as protocol]
|
||||
|
@ -8,6 +10,7 @@
|
|||
[status-im.chat.models :as chat-model]
|
||||
[status-im.chat.models.input :as input-model]
|
||||
[status-im.chat.models.message :as message-model]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.handlers-macro :as handlers-macro]))
|
||||
|
||||
(def register
|
||||
|
@ -80,7 +83,7 @@
|
|||
(defn index-commands
|
||||
"Takes collecton of things implementing the command protocol, and
|
||||
correctly indexes them by their composite ids and access scopes."
|
||||
[commands {:keys [db]}]
|
||||
[commands]
|
||||
(let [id->command (reduce (fn [acc command]
|
||||
(assoc acc (command-id command)
|
||||
{:type command
|
||||
|
@ -100,9 +103,66 @@
|
|||
access-scopes)))
|
||||
{}
|
||||
id->command)]
|
||||
{:db (assoc db
|
||||
:id->command id->command
|
||||
:access-scope->command-id access-scope->command-id)}))
|
||||
{:id->command id->command
|
||||
:access-scope->command-id access-scope->command-id}))
|
||||
|
||||
(defn load-commands
|
||||
"Takes collection of things implementing the command protocol and db,
|
||||
correctly indexes them and adds them to db in a way that preserves existing commands"
|
||||
[commands {:keys [db]}]
|
||||
(let [{:keys [id->command access-scope->command-id]} (index-commands commands)]
|
||||
{:db (-> db
|
||||
(update :id->command merge id->command)
|
||||
(update :access-scope->command-id #(merge-with (fnil into #{}) % access-scope->command-id)))}))
|
||||
|
||||
(defn remove-command
|
||||
"Remove command form db, correctly updating all indexes"
|
||||
[command {:keys [db]}]
|
||||
(let [id (command-id command)]
|
||||
{:db (-> db
|
||||
(update :id->command dissoc id)
|
||||
(update :access-scope->command-id (fn [access-scope->command-id]
|
||||
(reduce (fn [acc [scope command-ids-set]]
|
||||
(if (command-ids-set id)
|
||||
(if (= 1 (count command-ids-set))
|
||||
acc
|
||||
(assoc acc scope (disj command-ids-set id)))
|
||||
(assoc acc scope command-ids-set)))
|
||||
{}
|
||||
access-scope->command-id))))}))
|
||||
|
||||
(def command-hook
|
||||
"Hook for extensions"
|
||||
(reify host/AppHook
|
||||
(id [_] :commands)
|
||||
(properties [_] {:scope #{:personal-chats :public-chats}
|
||||
:description :string
|
||||
:short-preview :view
|
||||
:preview :view
|
||||
:parameters [{:id :keyword
|
||||
:type {:one-of #{:text :phone :password :number}}
|
||||
:placeholder :string
|
||||
:suggestions? :component}]})
|
||||
(hook-in [_ id {:keys [description scope parameters preview short-preview]} cofx]
|
||||
(let [new-command (reify protocol/Command
|
||||
(id [_] (name id))
|
||||
(scope [_] scope)
|
||||
(description [_] description)
|
||||
(parameters [_] parameters)
|
||||
(validate [_ _ _])
|
||||
(on-send [_ _ _])
|
||||
(on-receive [_ _ _])
|
||||
(short-preview [_ props] (short-preview props))
|
||||
(preview [_ props] (preview props)))]
|
||||
(load-commands [new-command] cofx)))
|
||||
(unhook [_ id {:keys [scope]} {:keys [db] :as cofx}]
|
||||
(remove-command (get-in db [:id->command [(name id) scope] :type]) cofx))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:load-commands
|
||||
[re-frame/trim-v]
|
||||
(fn [cofx [commands]]
|
||||
(load-commands commands cofx)))
|
||||
|
||||
(defn chat-commands
|
||||
"Takes `id->command`, `access-scope->command-id` and `chat` parameters and returns
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
[status-im.ui.components.chat-preview :as chat-preview]
|
||||
[status-im.ui.components.list.views :as list]
|
||||
[status-im.ui.components.animation :as animation]
|
||||
[status-im.ui.components.svgimage :as svgimage]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
|
@ -40,21 +41,40 @@
|
|||
#_[react/text {:style transactions-styles/asset-balance}
|
||||
(str (money/internal->formatted amount symbol decimals))]]]))
|
||||
|
||||
(defn- render-nft-asset [selected-event-creator]
|
||||
(fn [{:keys [name symbol amount] :as asset}]
|
||||
[react/touchable-highlight
|
||||
{:on-press #(re-frame/dispatch (selected-event-creator (clojure.core/name symbol)))}
|
||||
[react/view transactions-styles/asset-container
|
||||
[react/view transactions-styles/asset-main
|
||||
[react/image {:source (-> asset :icon :source)
|
||||
:style transactions-styles/asset-icon}]
|
||||
[react/text {:style transactions-styles/asset-symbol} name]]
|
||||
[react/text {:style transactions-styles/nft-asset-amount} (money/to-fixed amount)]]]))
|
||||
|
||||
(def assets-separator [react/view transactions-styles/asset-separator])
|
||||
|
||||
(defview choose-asset [selected-event-creator]
|
||||
(defview choose-asset [nft? selected-event-creator]
|
||||
(letsubs [assets [:wallet/visible-assets-with-amount]]
|
||||
[react/view
|
||||
[list/flat-list {:data (filter #(not (:nft? %)) assets)
|
||||
[list/flat-list {:data (filter #(if nft?
|
||||
(:nft? %)
|
||||
(not (:nft? %)))
|
||||
assets)
|
||||
:key-fn (comp name :symbol)
|
||||
:render-fn (render-asset selected-event-creator)
|
||||
:render-fn (if nft?
|
||||
(render-nft-asset selected-event-creator)
|
||||
(render-asset selected-event-creator))
|
||||
:enableEmptySections true
|
||||
:separator assets-separator
|
||||
:keyboardShouldPersistTaps :always
|
||||
:bounces false}]]))
|
||||
|
||||
(defn choose-asset-suggestion [selected-event-creator]
|
||||
[choose-asset selected-event-creator])
|
||||
[choose-asset false selected-event-creator])
|
||||
|
||||
(defn choose-nft-asset-suggestion [selected-event-creator]
|
||||
[choose-asset true selected-event-creator])
|
||||
|
||||
(defn personal-send-request-short-preview
|
||||
[label-key {:keys [content]}]
|
||||
|
@ -77,6 +97,32 @@
|
|||
:type :number
|
||||
:placeholder "Amount"}])
|
||||
|
||||
(defview choose-nft-token [selected-event-creator]
|
||||
(letsubs [{:keys [input-params]} [:selected-chat-command]
|
||||
collectibles [:collectibles]]
|
||||
(let [collectible-tokens (get collectibles (keyword (:symbol input-params)))]
|
||||
[react/view {:flex-direction :row
|
||||
:align-items :center
|
||||
:padding-vertical 11}
|
||||
(map
|
||||
(fn [[id {:keys [name image_url]}]]
|
||||
[react/touchable-highlight
|
||||
{:key id
|
||||
:on-press #(re-frame/dispatch (selected-event-creator (str id)))}
|
||||
[react/view {:flex-direction :column
|
||||
:align-items :center
|
||||
:margin-left 10
|
||||
:border-radius 2
|
||||
:border-width 1
|
||||
:border-color colors/gray}
|
||||
[svgimage/svgimage {:style transactions-styles/nft-token-icon
|
||||
:source {:uri image_url}}]
|
||||
[react/text {} name]]])
|
||||
collectible-tokens)])))
|
||||
|
||||
(defn choose-nft-token-suggestion [selected-event-creator]
|
||||
[choose-nft-token selected-event-creator])
|
||||
|
||||
;;TODO(goranjovic): currently we only allow tokens which are enabled in Manage assets here
|
||||
;; because balances are only fetched for them. Revisit this decision with regard to battery/network consequences
|
||||
;; if we were to update all balances.
|
||||
|
|
|
@ -23,6 +23,17 @@
|
|||
(def asset-symbol
|
||||
{:color colors/black})
|
||||
|
||||
(def nft-asset-amount
|
||||
{:font-size 16
|
||||
:color colors/gray
|
||||
:padding-right 14})
|
||||
|
||||
(def nft-token-icon
|
||||
{:width 100
|
||||
:height 100
|
||||
:margin-left 20
|
||||
:margin-right 20})
|
||||
|
||||
(def asset-name
|
||||
{:color colors/gray
|
||||
:padding-left 4})
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
:prefill [(get parameter-map :asset)
|
||||
(get parameter-map :amount)]}
|
||||
:content-type constants/content-type-command-request}}
|
||||
path))
|
||||
path
|
||||
{:content-type constants/content-type-command}))
|
||||
|
||||
(defn- create-command-message
|
||||
"Create message map from chat-id, command & input parameters"
|
||||
|
|
|
@ -157,56 +157,10 @@
|
|||
{:padding-top 12
|
||||
:padding-bottom 10})))
|
||||
|
||||
(def author
|
||||
{:color styles/color-gray4
|
||||
:margin-bottom 4
|
||||
:font-size 12})
|
||||
|
||||
(def audio-container
|
||||
{:flex-direction :row
|
||||
:align-items :center})
|
||||
|
||||
(def play-view
|
||||
{:width 33
|
||||
:height 33
|
||||
:border-radius 16
|
||||
:elevation 1})
|
||||
|
||||
(def play-image
|
||||
{:width 33
|
||||
:height 33})
|
||||
|
||||
(def track-container
|
||||
{:margin-top 10
|
||||
:margin-left 10
|
||||
:width 120
|
||||
:height 26
|
||||
:elevation 1})
|
||||
|
||||
(def track
|
||||
{:position :absolute
|
||||
:top 4
|
||||
:width 120
|
||||
:height 2
|
||||
:background-color :#EC7262})
|
||||
|
||||
(def track-mark
|
||||
{:position :absolute
|
||||
:left 0
|
||||
:top 0
|
||||
:width 2
|
||||
:height 10
|
||||
:background-color :#4A5258})
|
||||
|
||||
(def track-duration-text
|
||||
{:position :absolute
|
||||
:left 1
|
||||
:top 11
|
||||
:font-size 11
|
||||
:color :#4A5258
|
||||
:letter-spacing 1
|
||||
:line-height 15})
|
||||
|
||||
(def status-container
|
||||
{:flex 1
|
||||
:align-self :center
|
||||
|
|
|
@ -280,10 +280,12 @@
|
|||
:<- [:chat-parameter-box]
|
||||
:<- [:show-suggestions?]
|
||||
:<- [:validation-messages]
|
||||
(fn [[chat-parameter-box show-suggestions? validation-messages]]
|
||||
:<- [:selected-chat-command]
|
||||
(fn [[chat-parameter-box show-suggestions? validation-messages {:keys [command-completion]}]]
|
||||
(and chat-parameter-box
|
||||
(not validation-messages)
|
||||
(not show-suggestions?))))
|
||||
(not show-suggestions?)
|
||||
(not (= :complete command-completion)))))
|
||||
|
||||
(reg-sub
|
||||
:show-suggestions-view?
|
||||
|
|
|
@ -42,22 +42,12 @@
|
|||
:font :default}
|
||||
status])])))
|
||||
|
||||
(defn message-content-audio [_]
|
||||
[react/view style/audio-container
|
||||
[react/view style/play-view
|
||||
[react/image {:style style/play-image}]]
|
||||
[react/view style/track-container
|
||||
[react/view style/track]
|
||||
[react/view style/track-mark]
|
||||
[react/text {:style style/track-duration-text
|
||||
:font :default}
|
||||
"03:39"]]])
|
||||
|
||||
(defview message-content-command
|
||||
[command-message]
|
||||
(letsubs [id->command [:get-id->command]]
|
||||
(when-let [command (commands-receiving/lookup-command-by-ref command-message id->command)]
|
||||
(commands/generate-preview command command-message))))
|
||||
(if-let [command (commands-receiving/lookup-command-by-ref command-message id->command)]
|
||||
(commands/generate-preview command command-message)
|
||||
[react/text (str "Unhandled command: " (-> command-message :content :command-path first))])))
|
||||
|
||||
(def rtl-characters-regex #"[^\u0591-\u06EF\u06FA-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]*?[\u0591-\u06EF\u06FA-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]")
|
||||
|
||||
|
@ -235,11 +225,10 @@
|
|||
[wrapper message [emoji-message message]])
|
||||
|
||||
(defmethod message-content :default
|
||||
[wrapper {:keys [content-type content] :as message}]
|
||||
[wrapper {:keys [content-type] :as message}]
|
||||
[wrapper message
|
||||
[message-view message
|
||||
[message-content-audio {:content content
|
||||
:content-type content-type}]]])
|
||||
[react/text {} (str "Unhandled content-type " content-type)]]])
|
||||
|
||||
(defn- text-status [status]
|
||||
[react/view style/delivery-view
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
(ns status-im.extensions.core
|
||||
(:require [clojure.string :as string]))
|
||||
|
||||
(defn url->storage-details [s]
|
||||
(when s
|
||||
(let [[_ type id] (string/split s #".*[:/]([a-z]*)@(.*)")]
|
||||
[(keyword type) id])))
|
|
@ -0,0 +1,52 @@
|
|||
(ns status-im.extensions.registry
|
||||
(:require [pluto.reader :as reader]
|
||||
[pluto.registry :as registry]
|
||||
[pluto.host :as host]
|
||||
[pluto.storage :as storage]
|
||||
[pluto.storage.gist :as gist]
|
||||
[status-im.extensions.core :as extension]
|
||||
[status-im.chat.commands.core :as commands]
|
||||
[status-im.chat.commands.impl.transactions :as transactions]
|
||||
[status-im.ui.components.react :as react]))
|
||||
|
||||
(def components
|
||||
{'view react/view
|
||||
'text react/text
|
||||
'asset-selector transactions/choose-nft-asset-suggestion
|
||||
'token-selector transactions/choose-nft-token-suggestion})
|
||||
|
||||
(def app-hooks #{commands/command-hook})
|
||||
|
||||
(def capacities
|
||||
(reduce (fn [capacities hook]
|
||||
(assoc-in capacities [:hooks (host/id hook)] hook))
|
||||
{:components components
|
||||
:queries #{:get-in}
|
||||
:events #{:set-in}
|
||||
:permissions {:read {:include-paths #{[:network]
|
||||
[:current-chat-id]
|
||||
[:chats #".*"]}}
|
||||
:write {:include-paths #{}}}}
|
||||
app-hooks))
|
||||
|
||||
(defn parse [{:keys [data]}]
|
||||
(try
|
||||
(let [{:keys [errors] :as extension-data} (reader/parse {:capacities capacities} data)]
|
||||
(when errors
|
||||
(println "Failed to parse status extensions" errors))
|
||||
extension-data)
|
||||
(catch :default e (println "EXC" e))))
|
||||
|
||||
(def storages
|
||||
{:gist (gist/GistStorage.)})
|
||||
|
||||
(defn read-extension [o]
|
||||
(-> o :value first :content reader/read))
|
||||
|
||||
(defn load-from [url f]
|
||||
(let [[type id] (extension/url->storage-details url)
|
||||
storage (get storages type)]
|
||||
(when (and storage id)
|
||||
(storage/fetch storage
|
||||
{:value id}
|
||||
#(f %)))))
|
|
@ -72,7 +72,7 @@
|
|||
:contacts/dapps default-dapps)}
|
||||
(group-chat-messages)
|
||||
(add-default-contacts)
|
||||
(commands/index-commands commands/register))))
|
||||
(commands/load-commands commands/register))))
|
||||
|
||||
(defn process-pending-messages
|
||||
"Change status of own messages which are still in `sending` status to `not-sent`
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
:mailserver-reconnect "Could not connect to mailserver. Tap to reconnect"
|
||||
:fetching-messages "Fetching messages..."
|
||||
:search-for "Search for..."
|
||||
:find "Find"
|
||||
:install "Install"
|
||||
:success "Success"
|
||||
:cancel "Cancel"
|
||||
:next "Next"
|
||||
:open "Open"
|
||||
|
@ -710,6 +713,18 @@
|
|||
:network-id "Network ID"
|
||||
:specify-network-id "Specify network id"
|
||||
|
||||
:extension "Extension"
|
||||
:extensions "Extensions"
|
||||
:extension-installed "You installed an extension"
|
||||
:extension-find "Find extension"
|
||||
:extension-address "Extension address"
|
||||
:extension-url "Enter an extension URL"
|
||||
:no-extension "No extension installed"
|
||||
:identifier "Identifier"
|
||||
:errors "Errors"
|
||||
:hooks "Hooks"
|
||||
:permissions "Permissions"
|
||||
|
||||
;; invalid-key
|
||||
|
||||
:invalid-key-title "We detected a problem with the encryption key"
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
[status-im.constants :as constants]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.utils.dimensions :as dimensions]
|
||||
pluto.registry
|
||||
status-im.transport.db
|
||||
status-im.ui.screens.accounts.db
|
||||
status-im.ui.screens.contacts.db
|
||||
|
@ -55,7 +56,8 @@
|
|||
:tooltips {}
|
||||
:desktop/desktop {:tab-view-id :home}
|
||||
:dimensions/window (dimensions/window)
|
||||
:push-notifications/stored {}})
|
||||
:push-notifications/stored {}
|
||||
:registry {}})
|
||||
|
||||
;;;;GLOBAL
|
||||
|
||||
|
@ -120,6 +122,8 @@
|
|||
|
||||
(spec/def :navigation.screen-params/collectibles-list map?)
|
||||
|
||||
(spec/def :navigation.screen-params/show-extension map?)
|
||||
|
||||
(spec/def :navigation/screen-params (spec/nilable (allowed-keys :opt-un [:navigation.screen-params/network-details
|
||||
:navigation.screen-params/browser
|
||||
:navigation.screen-params/profile-qr-viewer
|
||||
|
@ -127,7 +131,8 @@
|
|||
:navigation.screen-params/group-contacts
|
||||
:navigation.screen-params/edit-contact-group
|
||||
:navigation.screen-params/dapp-description
|
||||
:navigation.screen-params/collectibles-list])))
|
||||
:navigation.screen-params/collectibles-list
|
||||
:navigation.screen-params/show-extension])))
|
||||
|
||||
(spec/def :desktop/desktop (spec/nilable any?))
|
||||
(spec/def ::tooltips (spec/nilable any?))
|
||||
|
@ -144,6 +149,9 @@
|
|||
(spec/def ::collectible (spec/nilable map?))
|
||||
(spec/def ::collectibles (spec/nilable map?))
|
||||
|
||||
(spec/def ::extension-url (spec/nilable string?))
|
||||
(spec/def ::staged-extension (spec/nilable any?))
|
||||
|
||||
;;;;NODE
|
||||
|
||||
(spec/def ::message-envelopes (spec/nilable map?))
|
||||
|
@ -284,4 +292,7 @@
|
|||
:notifications/notifications
|
||||
::device-UUID
|
||||
::collectible
|
||||
::collectibles]))
|
||||
::collectibles
|
||||
::extension-url
|
||||
::staged-extension
|
||||
:registry/registry]))
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
status-im.ui.screens.network-settings.events
|
||||
status-im.ui.screens.profile.events
|
||||
status-im.ui.screens.qr-scanner.events
|
||||
status-im.ui.screens.extensions.events
|
||||
status-im.ui.screens.wallet.events
|
||||
[status-im.models.wallet :as models.wallet]
|
||||
status-im.ui.screens.wallet.collectibles.events
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
(ns status-im.ui.screens.extensions.add.events
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[pluto.registry :as registry]
|
||||
[status-im.extensions.registry :as extensions]
|
||||
[status-im.ui.screens.navigation :as navigation]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.handlers-macro :as handlers-macro]))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:extension/load
|
||||
(fn [[url follow-up-event]]
|
||||
(extensions/load-from url #(re-frame/dispatch [follow-up-event (-> % extensions/read-extension extensions/parse)]))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:extension/install
|
||||
[re-frame/trim-v]
|
||||
(fn [cofx [extension-data]]
|
||||
(let [extension-key (get-in extension-data ['meta :name])]
|
||||
(handlers-macro/merge-fx cofx
|
||||
{:show-confirmation {:title (i18n/label :t/success)
|
||||
:content (i18n/label :t/extension-installed)
|
||||
:on-accept #(re-frame/dispatch [:navigate-to-clean :home])
|
||||
:on-cancel nil}}
|
||||
(registry/add extension-data)
|
||||
(registry/activate extension-key)))))
|
||||
|
||||
(handlers/register-handler-db
|
||||
:extension/edit-address
|
||||
[re-frame/trim-v]
|
||||
(fn [db [address]]
|
||||
(assoc db :extension-url address)))
|
||||
|
||||
(handlers/register-handler-db
|
||||
:extension/stage
|
||||
[re-frame/trim-v]
|
||||
(fn [db [extension-data]]
|
||||
(-> db
|
||||
(assoc :staged-extension extension-data)
|
||||
(navigation/navigate-to :show-extension))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:extension/show
|
||||
[re-frame/trim-v]
|
||||
(fn [cofx [uri]]
|
||||
{:extension/load [uri :extension/stage]}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:extension/toggle-activation
|
||||
[re-frame/trim-v]
|
||||
(fn [cofx [id state]]
|
||||
(when-let [toggle-fn (get {true registry/activate
|
||||
false registry/deactivate}
|
||||
state)]
|
||||
(toggle-fn id cofx))))
|
|
@ -0,0 +1,51 @@
|
|||
(ns status-im.ui.screens.extensions.add.styles
|
||||
(:require-macros [status-im.utils.styles :refer [defstyle]])
|
||||
(:require [status-im.ui.components.styles :as styles]
|
||||
[status-im.ui.components.colors :as colors]))
|
||||
|
||||
(def wrapper
|
||||
{:flex 1
|
||||
:margin 16})
|
||||
|
||||
(def input-container
|
||||
{:flex-direction :row
|
||||
:align-items :center
|
||||
:justify-content :space-between
|
||||
:border-radius styles/border-radius
|
||||
:height 52
|
||||
:margin-top 15})
|
||||
|
||||
(defstyle input
|
||||
{:flex 1
|
||||
:font-size 15
|
||||
:letter-spacing -0.2
|
||||
:android {:padding 0}})
|
||||
|
||||
(def bottom-container
|
||||
{:flex-direction :row
|
||||
:margin-horizontal 12
|
||||
:margin-vertical 15})
|
||||
|
||||
(def hooks
|
||||
{:margin-top 20
|
||||
:margin-left 10})
|
||||
|
||||
(def text
|
||||
{:color colors/black})
|
||||
|
||||
(def cartouche-container
|
||||
{:flex 1
|
||||
:margin-top 16
|
||||
:margin-horizontal 16})
|
||||
|
||||
(def cartouche-header
|
||||
{:color colors/gray})
|
||||
|
||||
(def cartouche-content-wrapper
|
||||
{:flex-direction :row
|
||||
:margin-top 8
|
||||
:border-color colors/gray-lighter
|
||||
:border-width 1
|
||||
:border-radius styles/border-radius
|
||||
:padding 16
|
||||
:background-color colors/white-transparent})
|
|
@ -0,0 +1,12 @@
|
|||
(ns status-im.ui.screens.extensions.add.subs
|
||||
(:require [re-frame.core :as re-frame]))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:get-extension-url
|
||||
(fn [db]
|
||||
(:extension-url db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:get-staged-extension
|
||||
(fn [db]
|
||||
(:staged-extension db)))
|
|
@ -0,0 +1,78 @@
|
|||
(ns status-im.ui.screens.extensions.add.views
|
||||
(:require-macros [status-im.utils.views :as views])
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[clojure.string :as string]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.ui.components.styles :as components.styles]
|
||||
[status-im.ui.components.common.common :as components.common]
|
||||
[status-im.ui.components.status-bar.view :as status-bar]
|
||||
[status-im.ui.components.toolbar.view :as toolbar]
|
||||
[status-im.ui.components.text-input.view :as text-input]
|
||||
[status-im.ui.screens.extensions.add.styles :as styles]))
|
||||
|
||||
(defn cartouche [{:keys [header]} content]
|
||||
[react/view {:style styles/cartouche-container}
|
||||
[react/text {:style styles/cartouche-header}
|
||||
header]
|
||||
[react/view {:style styles/cartouche-content-wrapper}
|
||||
[react/view {:flex 1}
|
||||
[react/text {:style styles/text}
|
||||
content]]]])
|
||||
|
||||
(defn hooks [{:keys [hooks]}]
|
||||
(mapcat (fn [[hook-id values]]
|
||||
(map (fn [[id]]
|
||||
(symbol "hook" (str (name hook-id) "." (name id))))
|
||||
values))
|
||||
hooks))
|
||||
|
||||
(views/defview show-extension []
|
||||
(views/letsubs [{:keys [data errors]} [:get-staged-extension]]
|
||||
[react/view components.styles/flex
|
||||
[status-bar/status-bar]
|
||||
[react/keyboard-avoiding-view components.styles/flex
|
||||
[toolbar/simple-toolbar (i18n/label :t/extension)]
|
||||
[react/scroll-view {:keyboard-should-persist-taps :handled}
|
||||
[react/view styles/wrapper
|
||||
[cartouche {:header (i18n/label :t/identifier)}
|
||||
(str (get-in data ['meta :name]))]
|
||||
[cartouche {:header (i18n/label :t/name)}
|
||||
(str (get-in data ['meta :name]))]
|
||||
[cartouche {:header (i18n/label :t/description)}
|
||||
(str (get-in data ['meta :description]))]
|
||||
[cartouche {:header (i18n/label :t/hooks)}
|
||||
(string/join " " (hooks data))]
|
||||
[cartouche {:header (i18n/label :t/permissions)}
|
||||
(i18n/label :t/none)]
|
||||
[cartouche {:header (i18n/label :t/errors)}
|
||||
(i18n/label :t/none)]]]
|
||||
[react/view styles/bottom-container
|
||||
[react/view components.styles/flex]
|
||||
[components.common/bottom-button
|
||||
{:forward? true
|
||||
:label (i18n/label :t/install)
|
||||
:disabled? (seq errors)
|
||||
:on-press #(re-frame/dispatch [:extension/install data])}]]]]))
|
||||
|
||||
(views/defview add-extension []
|
||||
(views/letsubs [extension-url [:get-extension-url]]
|
||||
[react/view components.styles/flex
|
||||
[status-bar/status-bar]
|
||||
[react/keyboard-avoiding-view components.styles/flex
|
||||
[toolbar/simple-toolbar (i18n/label :t/extension-find)]
|
||||
[react/scroll-view {:keyboard-should-persist-taps :handled}
|
||||
[react/view styles/wrapper
|
||||
[text-input/text-input-with-label
|
||||
{:label (i18n/label :t/extension-address)
|
||||
:style styles/input
|
||||
:container styles/input-container
|
||||
:placeholder (i18n/label :t/extension-url)
|
||||
:on-change-text #(re-frame/dispatch [:extension/edit-address %])}]]]
|
||||
[react/view styles/bottom-container
|
||||
[react/view components.styles/flex]
|
||||
[components.common/bottom-button
|
||||
{:forward? true
|
||||
:label (i18n/label :t/find)
|
||||
:disabled? (string/blank? extension-url)
|
||||
:on-press #(re-frame/dispatch [:extension/show extension-url])}]]]]))
|
|
@ -0,0 +1,12 @@
|
|||
(ns status-im.ui.screens.extensions.events
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[pluto.registry :as registry]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
status-im.ui.screens.extensions.add.events))
|
||||
|
||||
(handlers/register-handler-db
|
||||
:extensions/toggle-activation
|
||||
[re-frame/trim-v]
|
||||
(fn [db [id m]]
|
||||
nil))
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
(ns status-im.ui.screens.extensions.styles
|
||||
(:require [status-im.ui.components.colors :as colors]))
|
||||
|
||||
(def wrapper
|
||||
{:flex 1
|
||||
:background-color colors/white})
|
||||
|
||||
(defn wnode-icon [connected?]
|
||||
{:width 40
|
||||
:height 40
|
||||
:border-radius 20
|
||||
:background-color (if connected?
|
||||
colors/blue
|
||||
colors/gray-light)
|
||||
:align-items :center
|
||||
:justify-content :center})
|
||||
|
||||
(def empty-list
|
||||
{:color colors/black
|
||||
:text-align :center})
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
(ns status-im.ui.screens.extensions.subs
|
||||
(:require [re-frame.core :as re-frame]
|
||||
status-im.ui.screens.extensions.add.subs))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:get-extensions
|
||||
(fn [db]
|
||||
(seq (:registry db))))
|
|
@ -0,0 +1,47 @@
|
|||
(ns status-im.ui.screens.extensions.views
|
||||
(:require-macros [status-im.utils.views :as views])
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.extensions.registry :as registry]
|
||||
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
||||
[status-im.ui.components.list.views :as list]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.status-bar.view :as status-bar]
|
||||
[status-im.ui.components.toolbar.view :as toolbar]
|
||||
[status-im.ui.components.toolbar.actions :as toolbar.actions]
|
||||
[status-im.ui.screens.extensions.styles :as styles]))
|
||||
|
||||
(def wnode-icon
|
||||
[react/view (styles/wnode-icon true)
|
||||
[vector-icons/icon :icons/wnode {:color :white}]])
|
||||
|
||||
(defn navigate-to-add-extension [wnode-id]
|
||||
(re-frame/dispatch [:navigate-to :add-extension wnode-id]))
|
||||
|
||||
(defn- render-extension [[id {:keys [state]}]]
|
||||
[list/list-item-with-checkbox
|
||||
{:checked? (= :active state)
|
||||
:on-value-change #(re-frame/dispatch [:extension/toggle-activation id %])}
|
||||
[list/item
|
||||
wnode-icon
|
||||
[list/item-content
|
||||
[list/item-primary id]
|
||||
[list/item-secondary id]]]])
|
||||
|
||||
(views/defview extensions-settings []
|
||||
(views/letsubs [extensions [:get-extensions]]
|
||||
[react/view {:flex 1}
|
||||
[status-bar/status-bar]
|
||||
[toolbar/toolbar {}
|
||||
toolbar/default-nav-back
|
||||
[toolbar/content-title (i18n/label :t/extensions)]
|
||||
[toolbar/actions
|
||||
[(toolbar.actions/add false (partial navigate-to-add-extension nil))]]]
|
||||
[react/view styles/wrapper
|
||||
[list/flat-list {:data extensions
|
||||
:default-separator? false
|
||||
:key-fn first
|
||||
:render-fn render-extension
|
||||
:content-container-style (merge (when (zero? (count extensions)) {:flex-grow 1}) {:justify-content :center})
|
||||
:empty-component [react/text {:style styles/empty-list}
|
||||
(i18n/label :t/no-extension)]}]]]))
|
|
@ -145,6 +145,11 @@
|
|||
(letsubs [{:keys [sharing-usage-data?]} [:get-current-account]]
|
||||
{:component-did-mount on-show}
|
||||
[react/view
|
||||
(when (and config/extensions-enabled? dev-mode?)
|
||||
[profile.components/settings-item
|
||||
{:label-kw :t/extensions
|
||||
:action-fn #(re-frame/dispatch [:navigate-to :extensions-settings])
|
||||
:accessibility-label :extensions-button}])
|
||||
(when dev-mode?
|
||||
[profile.components/settings-item
|
||||
{:label-kw :t/network
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
[status-im.utils.ethereum.core :as ethereum]
|
||||
status-im.chat.subs
|
||||
status-im.ui.screens.accounts.subs
|
||||
status-im.ui.screens.extensions.subs
|
||||
status-im.ui.screens.home.subs
|
||||
status-im.ui.screens.contacts.subs
|
||||
status-im.ui.screens.group.subs
|
||||
|
|
|
@ -40,8 +40,10 @@
|
|||
[status-im.ui.screens.network-settings.views :refer [network-settings]]
|
||||
[status-im.ui.screens.network-settings.network-details.views :refer [network-details]]
|
||||
[status-im.ui.screens.network-settings.edit-network.views :refer [edit-network]]
|
||||
[status-im.ui.screens.extensions.views :refer [extensions-settings]]
|
||||
[status-im.ui.screens.offline-messaging-settings.views :refer [offline-messaging-settings]]
|
||||
[status-im.ui.screens.offline-messaging-settings.edit-mailserver.views :refer [edit-mailserver]]
|
||||
[status-im.ui.screens.extensions.add.views :refer [add-extension show-extension]]
|
||||
[status-im.ui.screens.bootnodes-settings.views :refer [bootnodes-settings]]
|
||||
[status-im.ui.screens.bootnodes-settings.edit-bootnode.views :refer [edit-bootnode]]
|
||||
[status-im.ui.screens.currency-settings.views :refer [currency-settings]]
|
||||
|
@ -87,10 +89,13 @@
|
|||
:login login
|
||||
:recover recover
|
||||
:network-settings network-settings
|
||||
:extensions-settings extensions-settings
|
||||
:network-details network-details
|
||||
:edit-network edit-network
|
||||
:offline-messaging-settings offline-messaging-settings
|
||||
:edit-mailserver edit-mailserver
|
||||
:add-extension add-extension
|
||||
:show-extension show-extension
|
||||
:bootnodes-settings bootnodes-settings
|
||||
:edit-bootnode edit-bootnode
|
||||
:currency-settings currency-settings
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
(defn load-token [web3 i items-number contract address symbol]
|
||||
(when (< i items-number)
|
||||
(erc721/token-of-owner-by-index web3 contract address i
|
||||
(fn [v1 v2]
|
||||
(fn [_ v2]
|
||||
(load-token web3 (inc i) items-number contract address symbol)
|
||||
(re-frame/dispatch [:load-collectible symbol (.toNumber v2)])))))
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
(def in-app-notifications-enabled? (enabled? (get-config :IN_APP_NOTIFICATIONS_ENABLED 0)))
|
||||
(def cached-webviews-enabled? (enabled? (get-config :CACHED_WEBVIEWS_ENABLED 0)))
|
||||
(def rn-bridge-threshold-warnings-enabled? (enabled? (get-config :RN_BRIDGE_THRESHOLD_WARNINGS 0)))
|
||||
(def extensions-enabled? (enabled? (get-config :EXTENSIONS 0)))
|
||||
|
||||
;; CONFIG VALUES
|
||||
(def log-level
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
(def public-chat-regex #".*/chat/public/(.*)$")
|
||||
(def profile-regex #".*/user/(.*)$")
|
||||
(def browse-regex #".*/browse/(.*)$")
|
||||
(def extension-regex #".*/extension/(.*)$")
|
||||
|
||||
(defn match-url [url regex]
|
||||
(some->> url
|
||||
|
@ -50,6 +51,10 @@
|
|||
(navigation/navigate-to-cofx :my-profile nil cofx)
|
||||
(chat.events/show-profile profile-id true cofx)))
|
||||
|
||||
(defn handle-extension [url cofx]
|
||||
(log/info "universal-links: handling url profile" url)
|
||||
{:extension/load [url :extensions/stage]})
|
||||
|
||||
(defn handle-not-found [full-url]
|
||||
(log/info "universal-links: no handler for " full-url))
|
||||
|
||||
|
@ -73,6 +78,9 @@
|
|||
(match-url url browse-regex)
|
||||
(handle-browse url cofx)
|
||||
|
||||
(match-url url extension-regex)
|
||||
(handle-extension url cofx)
|
||||
|
||||
:else (handle-not-found url)))
|
||||
|
||||
(defn store-url-for-later
|
||||
|
|
|
@ -64,8 +64,8 @@
|
|||
(def TestCommandInstance (TestCommand.))
|
||||
(def AnotherTestCommandInstance (AnotherTestCommand.))
|
||||
|
||||
(deftest index-commands-test
|
||||
(let [fx (core/index-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}})]
|
||||
(deftest load-commands-test
|
||||
(let [fx (core/load-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}})]
|
||||
(testing "Primary composite key index for command is correctly created"
|
||||
(is (= TestCommandInstance
|
||||
(get-in fx [:db :id->command
|
||||
|
@ -94,7 +94,7 @@
|
|||
(core/command-id AnotherTestCommandInstance))))))
|
||||
|
||||
(deftest chat-commands-test
|
||||
(let [fx (core/index-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}})]
|
||||
(let [fx (core/load-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}})]
|
||||
(testing "That relevant commands are looked up for chat"
|
||||
(is (= #{TestCommandInstance AnotherTestCommandInstance}
|
||||
(into #{}
|
||||
|
@ -119,7 +119,7 @@
|
|||
{:chat-id "contact"})))))))
|
||||
|
||||
(deftest selected-chat-command-test
|
||||
(let [fx (core/index-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}})
|
||||
(let [fx (core/load-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}})
|
||||
commands (core/chat-commands (get-in fx [:db :id->command])
|
||||
(get-in fx [:db :access-scope->command-id])
|
||||
{:chat-id "contact"})]
|
||||
|
|
Loading…
Reference in New Issue