Merge pull request #180 from status-im/feature/webview-api

inject status api into webview
This commit is contained in:
Roman Volosovskyi 2016-08-24 18:09:40 +03:00 committed by GitHub
commit 8483d444af
23 changed files with 284 additions and 94 deletions

View File

@ -25,7 +25,8 @@
"react-native-fs",
"react-native-dialogs",
"react-native-image-resizer",
"react-native-image-crop-picker"
"react-native-image-crop-picker",
"react-native-webview-bridge"
],
"imageDirs": [
"images"
@ -35,4 +36,4 @@
"dev": "env/dev",
"prod": "env/prod"
}
}
}

View File

@ -137,6 +137,7 @@ dependencies {
compile project(':react-native-orientation')
compile project(':react-native-fs')
compile project(':react-native-image-crop-picker')
compile project(':react-native-webview-bridge')
//compile(name:'statusgo-android-16', ext:'aar')
compile(group: 'status-im', name: 'status-go', version: 'unlock', ext: 'aar')

View File

@ -22,6 +22,7 @@ import com.statusim.geth.module.GethPackage;
import com.aakashns.reactnativedialogs.ReactNativeDialogsPackage;
import fr.bamlab.rnimageresizer.ImageResizerPackage;
import com.reactnative.picker.PickerPackage;
import com.github.alinz.reactnativewebviewbridge.WebViewBridgePackage;
import java.util.Arrays;
import java.util.List;
@ -52,7 +53,9 @@ public class MainApplication extends Application implements ReactApplication {
new GethPackage(),
new ReactNativeDialogsPackage(),
new ImageResizerPackage(),
new PickerPackage()
new PickerPackage(),
new WebViewBridgePackage()
);
}
};

View File

@ -27,5 +27,9 @@ include ':react-native-orientation', ':app'
project(':react-native-orientation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-orientation/android')
include ':react-native-fs'
project(':react-native-fs').projectDir = new File(settingsDir, '../node_modules/react-native-fs/android')
include ':react-native-image-crop-picker'
project(':react-native-image-crop-picker').projectDir = new File(settingsDir, '../node_modules/react-native-image-crop-picker/android')
include ':react-native-webview-bridge'
project(':react-native-webview-bridge').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview-bridge/android')

View File

@ -50,6 +50,7 @@
"react-native-tcp": "^1.0.1",
"react-native-udp": "^1.2.5",
"react-native-vector-icons": "^2.0.3",
"react-native-webview-bridge": "github:rasom/react-native-webview-bridge#master",
"readable-stream": "^1.0.33",
"realm": "^0.14.0",
"stream-browserify": "^1.0.0",

View File

@ -274,6 +274,7 @@ function validateBalance(params) {
]
};
}
var balance = web3.eth.getBalance(params.command.address);
if (bn(val).greaterThan(bn(balance))) {
return {

View File

@ -131,6 +131,9 @@ var status = {
var response = new Response();
return response.create(h);
},
autorun: function (commandName) {
_status_catalog.autorun = commandName;
},
types: {
TEXT: 'text',
NUMBER: 'number',

20
resources/wallet.js Normal file
View File

@ -0,0 +1,20 @@
function wallet() {
var url = 'http://127.0.0.1:3450';
return {webViewUrl: url};
}
status.command({
name: "wallet",
description: "wallet",
color: "#ffa500",
fullscreen: true,
suggestionsTrigger: 'on-send',
params: [{
name: "webpage",
suggestions: wallet,
type: status.types.TEXT
}]
});
status.autorun("wallet");

26
resources/webview.js Normal file
View File

@ -0,0 +1,26 @@
(function () {
window.statusAPI = {
dispatch: function (event, options) {
console.log("statusAPI.dispatch: " + JSON.stringify(options));
if (options.callback) {
console.log(options.callback);
statusAPI.callbacks[event] = options.callback;
}
var json = JSON.stringify({
event: event,
options: options
});
console.log("sending from webview: " + json);
WebViewBridge.send(json);
},
callbacks: {}
};
WebViewBridge.onMessage = function (messageString) {
console.log("received from react-native: " + messageString);
var message = JSON.parse(messageString);
if (statusAPI.callbacks[message.event]) {
statusAPI.callbacks[message.event](message.params);
}
};
}());

View File

@ -45,11 +45,13 @@
(dispatch-sync [:add-account account])
(dispatch [:login-account address password])))))
(register-handler
:create-account
(fn [db [_ password]]
(geth/create-account password (fn [result] (account-created result password)))
db))
(register-handler :create-account
(after #(dispatch [:init-wallet-chat]))
(u/side-effect!
(fn [_ [_ password]]
(geth/create-account
password
#(account-created % password)))))
(defn save-account-to-realm!
[{:keys [current-account-id accounts]} _]

View File

@ -1,6 +1,7 @@
(ns status-im.chat.handlers
(:require-macros [cljs.core.async.macros :as am])
(:require [re-frame.core :refer [enrich after debug dispatch path]]
(:require [re-frame.core :refer [enrich after debug dispatch]]
[status-im.models.commands :as commands]
[clojure.string :as str]
[status-im.components.styles :refer [default-chat-color]]
@ -30,7 +31,9 @@
status-im.chat.handlers.unviewed-messages
status-im.chat.handlers.send-message
status-im.chat.handlers.receive-message
[cljs.core.async :as a]))
[cljs.core.async :as a]
status-im.chat.handlers.webview-bridge
status-im.chat.handlers.wallet-chat))
(register-handler :set-show-actions
(fn [db [_ show-actions]]
@ -250,17 +253,18 @@
init-chat))))
(defn prepare-chat
[{:keys [contacts] :as db} [_ contact-id]]
(let [name (get-in contacts [contact-id :name])
chat {:chat-id contact-id
:name (or name contact-id)
:color default-chat-color
:group-chat false
:is-active true
:timestamp (.getTime (js/Date.))
:contacts [{:identity contact-id}]
:dapp-url nil
:dapp-hash nil}]
[{:keys [contacts] :as db} [_ contcat-id options]]
(let [name (get-in contacts [contcat-id :name])
chat (merge {:chat-id contcat-id
:name (or name contcat-id)
:color default-chat-color
:group-chat false
:is-active true
:timestamp (.getTime (js/Date.))
:contacts [{:identity contcat-id}]
:dapp-url nil
:dapp-hash nil}
options)]
(assoc db :new-chat chat)))
(defn add-chat [{:keys [new-chat] :as db} [_ chat-id]]
@ -273,15 +277,22 @@
(chats/create-chat new-chat))
(defn open-chat!
[_ [_ chat-id]]
(dispatch [:navigate-to :chat chat-id]))
[_ [_ chat-id _ navigation-type]]
(dispatch [(or navigation-type :navigate-to) :chat chat-id]))
(register-handler :start-chat
(register-handler ::start-chat!
(-> prepare-chat
((enrich add-chat))
((after save-chat!))
((after open-chat!))))
(register-handler :start-chat
(u/side-effect!
(fn [{:keys [chats]} [_ contcat-id options navigation-type]]
(if (chats contcat-id)
(dispatch [(or navigation-type :navigate-to) :chat contcat-id])
(dispatch [::start-chat! contcat-id options navigation-type])))))
(register-handler :add-chat
(-> prepare-chat
((enrich add-chat))
@ -392,3 +403,14 @@
(if (get-in db [:chats chat-id])
(update-in db [:chats chat-id] merge new-chat-data)
db)))
(register-handler :check-autorun
(u/side-effect!
(fn [{:keys [current-chat-id] :as db}]
(let [autorun (get-in db [:chats current-chat-id :autorun])]
(when autorun
(am/go
;;todo: find another way to make it work...
(a/<! (a/timeout 100))
(dispatch [:set-chat-command (keyword autorun)])
(dispatch [:animate-command-suggestions])))))))

View File

@ -0,0 +1,10 @@
(ns status-im.chat.handlers.wallet-chat
(:require [re-frame.core :refer [after enrich path dispatch]]
[status-im.utils.handlers :refer [register-handler] :as u]))
(register-handler :init-wallet-chat
(u/side-effect!
(fn []
(dispatch [:add-chat "wallet" {:name "Wallet"
:dapp-url "http://127.0.0.1:3450"}]))))

View File

@ -0,0 +1,39 @@
(ns status-im.chat.handlers.webview-bridge
(:require [re-frame.core :refer [after dispatch enrich]]
[status-im.utils.handlers :refer [register-handler]]
[status-im.utils.handlers :as u]
[status-im.utils.types :as t]
[status-im.utils.logging :as log]))
(register-handler :set-webview-bridge
(fn [db [_ bridge]]
(assoc db :webview-bridge bridge)))
(defn contacts-click-handler [whisper-identity]
#(dispatch [:chat-with-command whisper-identity :send]))
(defn chat-with-command
[_ [_ whisper-identity command]]
(dispatch [:start-chat whisper-identity {} :navigate-back])
(dispatch [:remove-contacts-click-handler])
(let [callback #(dispatch [:set-chat-command command])]
(dispatch [:add-commands-loading-callback whisper-identity callback])))
(register-handler :chat-with-command
(u/side-effect! chat-with-command))
(register-handler :webview-bridge-message
(u/side-effect!
(fn [_ [_ message-string]]
(let [message (t/json->clj message-string)
event (keyword (:event message))]
(log/debug (str "message from webview: " message))
(case event
:webview-send-transaction (dispatch [:show-contacts contacts-click-handler])
(log/error (str "Unknown event: " event)))))))
(register-handler :send-to-webview-bridge
(u/side-effect!
(fn [{:keys [webview-bridge]} [_ data]]
(when webview-bridge
(.sendToBridge webview-bridge (t/clj->json data))))))

View File

@ -288,24 +288,25 @@
[animated-view {:style (st/messages-container messages-offset)}
messages])})))
(defview chat [{platform-specific :platform-specific}]
[group-chat [:chat :group-chat]
show-actions-atom [:show-actions]
command [:get-chat-command]
command? [:command?]
suggestions [:get-suggestions]
to-msg-id [:get-chat-command-to-msg-id]
layout-height [:get :layout-height]]
[view {:style st/chat-view
:onLayout (fn [event]
(let [height (.. event -nativeEvent -layout -height)]
(when (not= height layout-height)
(dispatch [:set-layout-height height]))))}
[chat-toolbar platform-specific]
[messages-container
[messages-view platform-specific group-chat]]
(when group-chat [typing-all platform-specific])
[response-view]
(when-not command? [suggestion-container])
[chat-message-new platform-specific]
(when show-actions-atom [actions-view platform-specific])])
(defn chat [{platform-specific :platform-specific}]
(let [group-chat (subscribe [:chat :group-chat])
show-actions (subscribe [:show-actions])
command? (subscribe [:command?])
layout-height (subscribe [:get :layout-height])]
(r/create-class
{:component-did-mount #(dispatch [:check-autorun])
:reagent-render
(fn [{platform-specific :platform-specific}]
[view {:style st/chat-view
:onLayout (fn [event]
(let [height (.. event -nativeEvent -layout -height)]
(when (not= height @layout-height)
(dispatch [:set-layout-height height]))))}
[chat-toolbar platform-specific]
[messages-container
[messages-view platform-specific @group-chat]]
(when @group-chat [typing-all platform-specific])
[response-view]
(when-not @command? [suggestion-container])
[chat-message-new platform-specific]
(when @show-actions [actions-view platform-specific])])})))

View File

@ -39,7 +39,7 @@
:onChangeText (when-not disable? command/set-input-message)
:onSubmitEditing (on-press-commands-handler command)})
(defview message-input [input-options {:keys [suggestions-trigger] :as command}]
(defview message-input [input-options command]
[command? [:command?]
input-message [:get-chat-input-text]
input-command [:get-chat-command-content]
@ -66,7 +66,7 @@
[view st/input-view
[plain-message/commands-button]
[message-input-container
[message-input input-options]]
[message-input input-options command]]
;; TODO emoticons: not implemented
[plain-message/smile-button]
(when (or command? valid-plain-message?)

View File

@ -1,6 +1,7 @@
(ns status-im.chat.views.response
(:require-macros [reagent.ratom :refer [reaction]]
[status-im.utils.views :refer [defview]])
[status-im.utils.views :refer [defview]]
[status-im.utils.slurp :refer [slurp]])
(:require [re-frame.core :refer [subscribe dispatch]]
[reagent.core :as r]
[status-im.components.react :refer [view
@ -18,7 +19,8 @@
[status-im.components.animation :as anim]
[status-im.chat.suggestions-responder :as resp]
[status-im.chat.constants :as c]
[status-im.chat.views.command-validation :as cv]))
[status-im.chat.views.command-validation :as cv]
[status-im.components.webview-bridge :refer [webview-bridge]]))
(defn drag-icon []
[view st/drag-container
@ -97,10 +99,14 @@
(defview suggestions-web-view []
[url [:web-view-url]]
(when url
[web-view {:source {:uri url}
:java-script-enabled true
:style {:height 300}
:on-navigation-state-change on-navigation-change}]))
[webview-bridge
{:ref #(dispatch [:set-webview-bridge %])
:on-bridge-message #(dispatch [:webview-bridge-message %])
:source {:uri url}
:java-script-enabled true
:injected-java-script (slurp "resources/webview.js")
:style {:height 300}
:on-navigation-state-change on-navigation-change}]))
(defview placeholder []
[suggestions [:get-content-suggestions]]

View File

@ -23,12 +23,18 @@
(defn fetch-commands!
[db [identity]]
(when true
;;when-let [url (:dapp-url (get-in db [:chats identity]))]
;; todo fix this after demo
(if true
;(= "console" identity)
;-let [url (get-in db [:chats identity :dapp-url])]
(cond
(= "console" identity)
(dispatch [::validate-hash identity (slurp "resources/commands.js")])
(= "wallet" identity)
(dispatch [::validate-hash identity (slurp "resources/wallet.js")])
:else
(dispatch [::validate-hash identity (slurp "resources/commands.js")])
#_(http-get (s/join "/" [url commands-js])
#(dispatch [::validate-hash identity %])
#(dispatch [::loading-failed! identity ::file-was-not-found])))))
@ -69,11 +75,12 @@
(into {})))
(defn add-commands
[db [id _ {:keys [commands responses]}]]
[db [id _ {:keys [commands responses autorun] :as data}]]
(-> db
(update-in [id :commands] merge (mark-as :command commands))
(update-in [id :responses] merge (mark-as :response responses))
(assoc-in [id :commands-loaded] true)))
(assoc-in [id :commands-loaded] true)
(assoc-in [id :autorun] autorun)))
(defn save-commands-js!
[_ [id file]]
@ -101,7 +108,26 @@
(reg-handler ::add-commands
[(path :chats)
(after save-commands-js!)]
(after save-commands-js!)
(after #(dispatch [:check-autorun]))
(after (fn [_ [id]]
(dispatch [:invoke-commands-loading-callbacks id])))]
add-commands)
(reg-handler ::loading-failed! (u/side-effect! loading-failed!))
(reg-handler :add-commands-loading-callback
(fn [db [chat-id callback]]
(update-in db [::commands-callbacks chat-id] conj callback)))
(reg-handler :invoke-commands-loading-callbacks
(u/side-effect!
(fn [db [chat-id]]
(let [callbacks (get-in db [::commands-callbacks chat-id])]
(doseq [callback callbacks]
(callback))
(dispatch [::clear-commands-callbacks chat-id])))))
(reg-handler ::clear-commands-callbacks
(fn [db [chat-id]]
(assoc-in db [::commands-callbacks chat-id] nil)))

View File

@ -7,7 +7,6 @@
view
text
image
navigator
drawer-layout-android
touchable-opacity]]
[status-im.resources :as res]
@ -64,7 +63,7 @@
:handler #(dispatch [:navigate-to :discovery])
:platform-specific platform-specific}]
[menu-item {:name (label :t/contacts)
:handler #(dispatch [:show-contacts navigator])
:handler #(dispatch [:show-contacts])
:platform-specific platform-specific}]
[menu-item {:name (label :t/invite-friends)
:handler (fn []

View File

@ -0,0 +1,9 @@
(ns status-im.components.webview-bridge
(:require [status-im.utils.utils :as u]
[reagent.core :as r]))
(def webview-bridge-class
(r/adapt-react-class (u/require "react-native-webview-bridge")))
(defn webview-bridge [opts]
[webview-bridge-class opts])

View File

@ -11,7 +11,7 @@
list-item] :as react]
[status-im.components.action-button :refer [action-button
action-button-item]]
[status-im.contacts.views.contact :refer [contact-extended-view]]
[status-im.contacts.views.contact :refer [contact-extended-view on-press]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar :refer [toolbar]]
[status-im.components.drawer.view :refer [open-drawer]]
@ -41,7 +41,7 @@
(def contacts-limit 10)
(defn contact-group [contacts contacts-count title group top?]
(defn contact-group [contacts contacts-count title group top? click-handler]
[view st/contact-group
[view st/contact-group-header
(when-not top?
@ -55,9 +55,14 @@
;; todo what if there is no contacts, should we show some information
;; about this?
[view {:flexDirection :column}
(for [contact contacts]
(doall
;; TODO not imlemented: contact more button handler
^{:key contact} [contact-extended-view contact nil nil])]
(map (fn [contact]
(let [whisper-identity (:whisper-identity contact)
click-handler (or click-handler on-press)]
^{:key contact}
[contact-extended-view contact nil (click-handler whisper-identity) nil]))
contacts))]
(when (= contacts-limit (count contacts))
[view st/show-all
[touchable-highlight {:on-press #(dispatch [:show-group-contacts group])}
@ -66,6 +71,7 @@
(defn contact-list [{platform-specific :platform-specific}]
(let [contacts (subscribe [:get-contacts-with-limit contacts-limit])
contcats-count (subscribe [:contacts-count])
click-handler (subscribe [:get :contacts-click-handler])
show-toolbar-shadow? (r/atom false)]
(fn []
[view st/contacts-list-container
@ -79,17 +85,18 @@
:onScroll (fn [e]
(let [offset (.. e -nativeEvent -contentOffset -y)]
(reset! show-toolbar-shadow? (<= st/contact-group-header-height offset))))}
;; TODO not implemented: dapps and persons separation
[contact-group
@contacts
@contcats-count
(label :t/contacs-group-dapps)
:dapps true]
:dapps true
@click-handler]
[contact-group
@contacts
@contcats-count
(label :t/contacs-group-people)
:people false]]
:people false
@click-handler]]
[view st/empty-contact-groups
[react/icon :group_big st/empty-contacts-icon]
[text {:style st/empty-contacts-text} (label :t/no-contacts)]])

View File

@ -1,7 +1,7 @@
(ns status-im.contacts.views.contact
(:require-macros [status-im.utils.views :refer [defview]])
(:require [status-im.components.react :refer [view text icon touchable-highlight]]
[re-frame.core :refer [dispatch subscribe]]
[re-frame.core :refer [dispatch]]
[status-im.contacts.styles :as st]
[status-im.contacts.views.contact-inner :refer [contact-inner-view]]))
@ -10,23 +10,21 @@
(when letter
[text {:style st/letter-text} letter])])
(defn on-press [chat whisper-identity]
(if chat
#(dispatch [:navigate-to :chat whisper-identity])
#(dispatch [:start-chat whisper-identity])))
(defn on-press [whisper-identity]
#(dispatch [:start-chat whisper-identity]))
(defview contact-with-letter-view [{:keys [whisper-identity letter] :as contact}]
[chat [:get-chat whisper-identity]]
[touchable-highlight
{:onPress (on-press chat whisper-identity)}
{:onPress (on-press whisper-identity)}
[view st/contact-container
[letter-view letter]
[contact-inner-view contact]]])
(defview contact-extended-view [{:keys [whisper-identity] :as contact} info more-click-handler]
(defview contact-extended-view [{:keys [whisper-identity] :as contact} info click-handler more-click-handler]
[chat [:get-chat whisper-identity]]
[touchable-highlight
{:onPress (on-press chat whisper-identity)}
{:onPress click-handler}
[view st/contact-container
[contact-inner-view contact info]
[touchable-highlight

View File

@ -6,7 +6,7 @@
touchable-highlight
list-view
list-item]]
[status-im.contacts.views.contact :refer [contact-with-letter-view]]
[status-im.contacts.views.contact :refer [contact-with-letter-view on-press]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar :refer [toolbar]]
[status-im.components.drawer.view :refer [drawer-view open-drawer]]
@ -20,9 +20,12 @@
[status-im.i18n :refer [label]]
[status-im.components.styles :as cst]))
(defn render-row [row _ _]
(list-item [contact-with-letter-view row]))
(defn render-row [click-handler]
(fn [row _ _]
(list-item [contact-with-letter-view row
(or click-handler
(let [whisper-identity (:whisper-identity row)]
(on-press whisper-identity)))])))
(defview contact-list-toolbar [platform-specific]
[group [:get :contacts-group]]
@ -37,14 +40,16 @@
:handler (fn [])}}]])
(defview contact-list [{platform-specific :platform-specific}]
[contacts [:contacts-with-letters]]
[drawer-view {:platform-specific platform-specific}
[view st/contacts-list-container
[contact-list-toolbar platform-specific]
;; todo what if there is no contacts, should we show some information
;; about this?
(when contacts
[list-view {:dataSource (lw/to-datasource contacts)
:enableEmptySections true
:renderRow render-row
:style st/contacts-list}])]])
[contacts [:contacts-with-letters]
click-handler [:get :contacts-click-handler]]
(let [click-handler click-handler]
[drawer-view {:platform-specific platform-specific}
[view st/contacts-list-container
[contact-list-toolbar platform-specific]
;; todo what if there is no contacts, should we show some information
;; about this?
(when contacts
[list-view {:dataSource (lw/to-datasource contacts)
:enableEmptySections true
:renderRow (render-row click-handler)
:style st/contacts-list}])]]))

View File

@ -66,8 +66,14 @@
(assoc :new-chat-name nil)))))
(register-handler :show-contacts
(fn [db _]
(push-view db :contact-list)))
(fn [db [_ click-handler]]
(-> db
(assoc :contacts-click-handler click-handler)
(push-view :contact-list))))
(register-handler :remove-contacts-click-handler
(fn [db]
(dissoc db :contacts-click-handler)))
(register-handler :show-group-contacts
(fn [db [_ group]]