Added extensions loading panel

Signed-off-by: Julien Eluard <julien.eluard@gmail.com>
This commit is contained in:
Julien Eluard 2018-07-13 17:50:35 +02:00
parent f82285b1f4
commit ae976e205f
No known key found for this signature in database
GPG Key ID: 6FD7DB5437FCBEF6
36 changed files with 548 additions and 87 deletions

1
.env
View File

@ -14,3 +14,4 @@ DEBUG_WEBVIEW=1
INSTABUG_SURVEYS=1 INSTABUG_SURVEYS=1
GROUP_CHATS_ENABLED=0 GROUP_CHATS_ENABLED=0
CACHED_WEBVIEWS_ENABLED=1 CACHED_WEBVIEWS_ENABLED=1
EXTENSIONS=1

View File

@ -9,4 +9,5 @@ POW_TIME=1
DEFAULT_NETWORK=testnet_rpc DEFAULT_NETWORK=testnet_rpc
INSTABUG_TOKEN=758630ed52864cbad9c5eeeac596c60c INSTABUG_TOKEN=758630ed52864cbad9c5eeeac596c60c
DEBUG_WEBVIEW=1 DEBUG_WEBVIEW=1
GROUP_CHATS_ENABLED=1 GROUP_CHATS_ENABLED=1
EXTENSIONS=0

View File

@ -13,3 +13,4 @@ DEBUG_WEBVIEW=1
GROUP_CHATS_ENABLED=0 GROUP_CHATS_ENABLED=0
MAINNET_WARNING_ENABLED=1 MAINNET_WARNING_ENABLED=1
CACHED_WEBVIEWS_ENABLED=1 CACHED_WEBVIEWS_ENABLED=1
EXTENSIONS=0

View File

@ -12,3 +12,4 @@ INSTABUG_TOKEN=758630ed52864cbad9c5eeeac596c60c
DEBUG_WEBVIEW=1 DEBUG_WEBVIEW=1
GROUP_CHATS_ENABLED=0 GROUP_CHATS_ENABLED=0
MAINNET_WARNING_ENABLED=1 MAINNET_WARNING_ENABLED=1
EXTENSIONS=1

View File

@ -13,3 +13,4 @@ DEBUG_WEBVIEW=1
INSTABUG_SURVEYS=1 INSTABUG_SURVEYS=1
GROUP_CHATS_ENABLED=0 GROUP_CHATS_ENABLED=0
MAINNET_WARNING_ENABLED=1 MAINNET_WARNING_ENABLED=1
EXTENSIONS=0

View File

@ -13,4 +13,5 @@ TESTFAIRY_TOKEN=969f6c921cb435cea1d41d1ea3f5b247d6026d55
INSTABUG_TOKEN=758630ed52864cbad9c5eeeac596c60c INSTABUG_TOKEN=758630ed52864cbad9c5eeeac596c60c
DEBUG_WEBVIEW=0 DEBUG_WEBVIEW=0
GROUP_CHATS_ENABLED=0 GROUP_CHATS_ENABLED=0
MAINNET_WARNING_ENABLED=1 MAINNET_WARNING_ENABLED=1
EXTENSIONS=0

View File

@ -10,7 +10,8 @@
com.andrewmcveigh/cljs-time {:mvn/version "0.5.2"} com.andrewmcveigh/cljs-time {:mvn/version "0.5.2"}
com.taoensso/timbre {:mvn/version "4.10.0"} com.taoensso/timbre {:mvn/version "4.10.0"}
hickory {:mvn/version "0.7.1"} 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 :aliases
{:dev {:extra-deps {:dev {:extra-deps

View File

@ -10,7 +10,8 @@
[com.andrewmcveigh/cljs-time "0.5.2"] [com.andrewmcveigh/cljs-time "0.5.2"]
[com.taoensso/timbre "4.10.0"] [com.taoensso/timbre "4.10.0"]
[hickory "0.7.1"] [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"] :plugins [[lein-cljsbuild "1.1.7"]
[lein-re-frisk "0.5.8"] [lein-re-frisk "0.5.8"]
[lein-cljfmt "0.5.7"] [lein-cljfmt "0.5.7"]

View File

@ -1,6 +1,8 @@
(ns status-im.chat.commands.core (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] [clojure.string :as string]
[pluto.host :as host]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.chat.constants :as chat-constants] [status-im.chat.constants :as chat-constants]
[status-im.chat.commands.protocol :as protocol] [status-im.chat.commands.protocol :as protocol]
@ -8,6 +10,7 @@
[status-im.chat.models :as chat-model] [status-im.chat.models :as chat-model]
[status-im.chat.models.input :as input-model] [status-im.chat.models.input :as input-model]
[status-im.chat.models.message :as message-model] [status-im.chat.models.message :as message-model]
[status-im.utils.handlers :as handlers]
[status-im.utils.handlers-macro :as handlers-macro])) [status-im.utils.handlers-macro :as handlers-macro]))
(def register (def register
@ -80,7 +83,7 @@
(defn index-commands (defn index-commands
"Takes collecton of things implementing the command protocol, and "Takes collecton of things implementing the command protocol, and
correctly indexes them by their composite ids and access scopes." correctly indexes them by their composite ids and access scopes."
[commands {:keys [db]}] [commands]
(let [id->command (reduce (fn [acc command] (let [id->command (reduce (fn [acc command]
(assoc acc (command-id command) (assoc acc (command-id command)
{:type command {:type command
@ -100,9 +103,66 @@
access-scopes))) access-scopes)))
{} {}
id->command)] id->command)]
{:db (assoc db {:id->command id->command
:id->command id->command :access-scope->command-id access-scope->command-id}))
: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 (defn chat-commands
"Takes `id->command`, `access-scope->command-id` and `chat` parameters and returns "Takes `id->command`, `access-scope->command-id` and `chat` parameters and returns

View File

@ -12,6 +12,7 @@
[status-im.ui.components.chat-preview :as chat-preview] [status-im.ui.components.chat-preview :as chat-preview]
[status-im.ui.components.list.views :as list] [status-im.ui.components.list.views :as list]
[status-im.ui.components.animation :as animation] [status-im.ui.components.animation :as animation]
[status-im.ui.components.svgimage :as svgimage]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.core :as ethereum]
@ -40,21 +41,40 @@
#_[react/text {:style transactions-styles/asset-balance} #_[react/text {:style transactions-styles/asset-balance}
(str (money/internal->formatted amount symbol decimals))]]])) (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]) (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]] (letsubs [assets [:wallet/visible-assets-with-amount]]
[react/view [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) :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 :enableEmptySections true
:separator assets-separator :separator assets-separator
:keyboardShouldPersistTaps :always :keyboardShouldPersistTaps :always
:bounces false}]])) :bounces false}]]))
(defn choose-asset-suggestion [selected-event-creator] (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 (defn personal-send-request-short-preview
[label-key {:keys [content]}] [label-key {:keys [content]}]
@ -77,6 +97,32 @@
:type :number :type :number
:placeholder "Amount"}]) :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 ;;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 ;; because balances are only fetched for them. Revisit this decision with regard to battery/network consequences
;; if we were to update all balances. ;; if we were to update all balances.

View File

@ -23,6 +23,17 @@
(def asset-symbol (def asset-symbol
{:color colors/black}) {: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 (def asset-name
{:color colors/gray {:color colors/gray
:padding-left 4}) :padding-left 4})

View File

@ -25,7 +25,8 @@
:prefill [(get parameter-map :asset) :prefill [(get parameter-map :asset)
(get parameter-map :amount)]} (get parameter-map :amount)]}
:content-type constants/content-type-command-request}} :content-type constants/content-type-command-request}}
path)) path
{:content-type constants/content-type-command}))
(defn- create-command-message (defn- create-command-message
"Create message map from chat-id, command & input parameters" "Create message map from chat-id, command & input parameters"

View File

@ -157,56 +157,10 @@
{:padding-top 12 {:padding-top 12
:padding-bottom 10}))) :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 (def play-image
{:width 33 {:width 33
:height 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 (def status-container
{:flex 1 {:flex 1
:align-self :center :align-self :center

View File

@ -280,10 +280,12 @@
:<- [:chat-parameter-box] :<- [:chat-parameter-box]
:<- [:show-suggestions?] :<- [:show-suggestions?]
:<- [:validation-messages] :<- [: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 (and chat-parameter-box
(not validation-messages) (not validation-messages)
(not show-suggestions?)))) (not show-suggestions?)
(not (= :complete command-completion)))))
(reg-sub (reg-sub
:show-suggestions-view? :show-suggestions-view?

View File

@ -42,22 +42,12 @@
:font :default} :font :default}
status])]))) 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 (defview message-content-command
[command-message] [command-message]
(letsubs [id->command [:get-id->command]] (letsubs [id->command [:get-id->command]]
(when-let [command (commands-receiving/lookup-command-by-ref command-message id->command)] (if-let [command (commands-receiving/lookup-command-by-ref command-message id->command)]
(commands/generate-preview command command-message)))) (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]") (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]]) [wrapper message [emoji-message message]])
(defmethod message-content :default (defmethod message-content :default
[wrapper {:keys [content-type content] :as message}] [wrapper {:keys [content-type] :as message}]
[wrapper message [wrapper message
[message-view message [message-view message
[message-content-audio {:content content [react/text {} (str "Unhandled content-type " content-type)]]])
:content-type content-type}]]])
(defn- text-status [status] (defn- text-status [status]
[react/view style/delivery-view [react/view style/delivery-view

View File

@ -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])))

View File

@ -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 %)))))

View File

@ -72,7 +72,7 @@
:contacts/dapps default-dapps)} :contacts/dapps default-dapps)}
(group-chat-messages) (group-chat-messages)
(add-default-contacts) (add-default-contacts)
(commands/index-commands commands/register)))) (commands/load-commands commands/register))))
(defn process-pending-messages (defn process-pending-messages
"Change status of own messages which are still in `sending` status to `not-sent` "Change status of own messages which are still in `sending` status to `not-sent`

View File

@ -12,6 +12,9 @@
:mailserver-reconnect "Could not connect to mailserver. Tap to reconnect" :mailserver-reconnect "Could not connect to mailserver. Tap to reconnect"
:fetching-messages "Fetching messages..." :fetching-messages "Fetching messages..."
:search-for "Search for..." :search-for "Search for..."
:find "Find"
:install "Install"
:success "Success"
:cancel "Cancel" :cancel "Cancel"
:next "Next" :next "Next"
:open "Open" :open "Open"
@ -710,6 +713,18 @@
:network-id "Network ID" :network-id "Network ID"
:specify-network-id "Specify 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
:invalid-key-title "We detected a problem with the encryption key" :invalid-key-title "We detected a problem with the encryption key"

View File

@ -4,6 +4,7 @@
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.utils.dimensions :as dimensions] [status-im.utils.dimensions :as dimensions]
pluto.registry
status-im.transport.db status-im.transport.db
status-im.ui.screens.accounts.db status-im.ui.screens.accounts.db
status-im.ui.screens.contacts.db status-im.ui.screens.contacts.db
@ -55,7 +56,8 @@
:tooltips {} :tooltips {}
:desktop/desktop {:tab-view-id :home} :desktop/desktop {:tab-view-id :home}
:dimensions/window (dimensions/window) :dimensions/window (dimensions/window)
:push-notifications/stored {}}) :push-notifications/stored {}
:registry {}})
;;;;GLOBAL ;;;;GLOBAL
@ -120,6 +122,8 @@
(spec/def :navigation.screen-params/collectibles-list map?) (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 (spec/def :navigation/screen-params (spec/nilable (allowed-keys :opt-un [:navigation.screen-params/network-details
:navigation.screen-params/browser :navigation.screen-params/browser
:navigation.screen-params/profile-qr-viewer :navigation.screen-params/profile-qr-viewer
@ -127,7 +131,8 @@
:navigation.screen-params/group-contacts :navigation.screen-params/group-contacts
:navigation.screen-params/edit-contact-group :navigation.screen-params/edit-contact-group
:navigation.screen-params/dapp-description :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 :desktop/desktop (spec/nilable any?))
(spec/def ::tooltips (spec/nilable any?)) (spec/def ::tooltips (spec/nilable any?))
@ -144,6 +149,9 @@
(spec/def ::collectible (spec/nilable map?)) (spec/def ::collectible (spec/nilable map?))
(spec/def ::collectibles (spec/nilable map?)) (spec/def ::collectibles (spec/nilable map?))
(spec/def ::extension-url (spec/nilable string?))
(spec/def ::staged-extension (spec/nilable any?))
;;;;NODE ;;;;NODE
(spec/def ::message-envelopes (spec/nilable map?)) (spec/def ::message-envelopes (spec/nilable map?))
@ -284,4 +292,7 @@
:notifications/notifications :notifications/notifications
::device-UUID ::device-UUID
::collectible ::collectible
::collectibles])) ::collectibles
::extension-url
::staged-extension
:registry/registry]))

View File

@ -26,6 +26,7 @@
status-im.ui.screens.network-settings.events status-im.ui.screens.network-settings.events
status-im.ui.screens.profile.events status-im.ui.screens.profile.events
status-im.ui.screens.qr-scanner.events status-im.ui.screens.qr-scanner.events
status-im.ui.screens.extensions.events
status-im.ui.screens.wallet.events status-im.ui.screens.wallet.events
[status-im.models.wallet :as models.wallet] [status-im.models.wallet :as models.wallet]
status-im.ui.screens.wallet.collectibles.events status-im.ui.screens.wallet.collectibles.events

View File

@ -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))))

View File

@ -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})

View File

@ -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)))

View File

@ -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])}]]]]))

View File

@ -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))

View File

@ -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})

View File

@ -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))))

View File

@ -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)]}]]]))

View File

@ -145,6 +145,11 @@
(letsubs [{:keys [sharing-usage-data?]} [:get-current-account]] (letsubs [{:keys [sharing-usage-data?]} [:get-current-account]]
{:component-did-mount on-show} {:component-did-mount on-show}
[react/view [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? (when dev-mode?
[profile.components/settings-item [profile.components/settings-item
{:label-kw :t/network {:label-kw :t/network

View File

@ -3,6 +3,7 @@
[status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.core :as ethereum]
status-im.chat.subs status-im.chat.subs
status-im.ui.screens.accounts.subs status-im.ui.screens.accounts.subs
status-im.ui.screens.extensions.subs
status-im.ui.screens.home.subs status-im.ui.screens.home.subs
status-im.ui.screens.contacts.subs status-im.ui.screens.contacts.subs
status-im.ui.screens.group.subs status-im.ui.screens.group.subs

View File

@ -40,8 +40,10 @@
[status-im.ui.screens.network-settings.views :refer [network-settings]] [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.network-details.views :refer [network-details]]
[status-im.ui.screens.network-settings.edit-network.views :refer [edit-network]] [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.views :refer [offline-messaging-settings]]
[status-im.ui.screens.offline-messaging-settings.edit-mailserver.views :refer [edit-mailserver]] [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.views :refer [bootnodes-settings]]
[status-im.ui.screens.bootnodes-settings.edit-bootnode.views :refer [edit-bootnode]] [status-im.ui.screens.bootnodes-settings.edit-bootnode.views :refer [edit-bootnode]]
[status-im.ui.screens.currency-settings.views :refer [currency-settings]] [status-im.ui.screens.currency-settings.views :refer [currency-settings]]
@ -87,10 +89,13 @@
:login login :login login
:recover recover :recover recover
:network-settings network-settings :network-settings network-settings
:extensions-settings extensions-settings
:network-details network-details :network-details network-details
:edit-network edit-network :edit-network edit-network
:offline-messaging-settings offline-messaging-settings :offline-messaging-settings offline-messaging-settings
:edit-mailserver edit-mailserver :edit-mailserver edit-mailserver
:add-extension add-extension
:show-extension show-extension
:bootnodes-settings bootnodes-settings :bootnodes-settings bootnodes-settings
:edit-bootnode edit-bootnode :edit-bootnode edit-bootnode
:currency-settings currency-settings :currency-settings currency-settings

View File

@ -26,7 +26,7 @@
(defn load-token [web3 i items-number contract address symbol] (defn load-token [web3 i items-number contract address symbol]
(when (< i items-number) (when (< i items-number)
(erc721/token-of-owner-by-index web3 contract address i (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) (load-token web3 (inc i) items-number contract address symbol)
(re-frame/dispatch [:load-collectible symbol (.toNumber v2)]))))) (re-frame/dispatch [:load-collectible symbol (.toNumber v2)])))))

View File

@ -26,6 +26,7 @@
(def in-app-notifications-enabled? (enabled? (get-config :IN_APP_NOTIFICATIONS_ENABLED 0))) (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 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 rn-bridge-threshold-warnings-enabled? (enabled? (get-config :RN_BRIDGE_THRESHOLD_WARNINGS 0)))
(def extensions-enabled? (enabled? (get-config :EXTENSIONS 0)))
;; CONFIG VALUES ;; CONFIG VALUES
(def log-level (def log-level

View File

@ -18,6 +18,7 @@
(def public-chat-regex #".*/chat/public/(.*)$") (def public-chat-regex #".*/chat/public/(.*)$")
(def profile-regex #".*/user/(.*)$") (def profile-regex #".*/user/(.*)$")
(def browse-regex #".*/browse/(.*)$") (def browse-regex #".*/browse/(.*)$")
(def extension-regex #".*/extension/(.*)$")
(defn match-url [url regex] (defn match-url [url regex]
(some->> url (some->> url
@ -50,6 +51,10 @@
(navigation/navigate-to-cofx :my-profile nil cofx) (navigation/navigate-to-cofx :my-profile nil cofx)
(chat.events/show-profile profile-id true 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] (defn handle-not-found [full-url]
(log/info "universal-links: no handler for " full-url)) (log/info "universal-links: no handler for " full-url))
@ -73,6 +78,9 @@
(match-url url browse-regex) (match-url url browse-regex)
(handle-browse url cofx) (handle-browse url cofx)
(match-url url extension-regex)
(handle-extension url cofx)
:else (handle-not-found url))) :else (handle-not-found url)))
(defn store-url-for-later (defn store-url-for-later

View File

@ -64,8 +64,8 @@
(def TestCommandInstance (TestCommand.)) (def TestCommandInstance (TestCommand.))
(def AnotherTestCommandInstance (AnotherTestCommand.)) (def AnotherTestCommandInstance (AnotherTestCommand.))
(deftest index-commands-test (deftest load-commands-test
(let [fx (core/index-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}})] (let [fx (core/load-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}})]
(testing "Primary composite key index for command is correctly created" (testing "Primary composite key index for command is correctly created"
(is (= TestCommandInstance (is (= TestCommandInstance
(get-in fx [:db :id->command (get-in fx [:db :id->command
@ -94,7 +94,7 @@
(core/command-id AnotherTestCommandInstance)))))) (core/command-id AnotherTestCommandInstance))))))
(deftest chat-commands-test (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" (testing "That relevant commands are looked up for chat"
(is (= #{TestCommandInstance AnotherTestCommandInstance} (is (= #{TestCommandInstance AnotherTestCommandInstance}
(into #{} (into #{}
@ -119,7 +119,7 @@
{:chat-id "contact"}))))))) {:chat-id "contact"})))))))
(deftest selected-chat-command-test (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]) commands (core/chat-commands (get-in fx [:db :id->command])
(get-in fx [:db :access-scope->command-id]) (get-in fx [:db :access-scope->command-id])
{:chat-id "contact"})] {:chat-id "contact"})]