diff --git a/android/app/build.gradle b/android/app/build.gradle index 95beff7a12..edc54bc340 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -131,8 +131,9 @@ dependencies { compile project(':react-native-linear-gradient') compile project(':ReactNativeAndroidSmsListener') compile project(':react-native-status') -// compile(name:'geth', ext:'aar') - compile(group: 'status-im', name: 'android-geth', version: '1.4.0-201604110816-a97a114', ext: 'aar') + // todo replace this when jail will be integrated into geth + compile (name: "geth-android-16", ext:"aar") + //compile(group: 'status-im', name: 'android-geth', version: '1.4.0-201604110816-a97a114', ext: 'aar') compile fileTree(dir: "node_modules/realm/android/libs", include: ["*.jar"]) } @@ -142,3 +143,4 @@ task copyDownloadableDepsToLibs(type: Copy) { from configurations.compile into 'libs' } + diff --git a/android/app/libs/geth-android-16.aar b/android/app/libs/geth-android-16.aar new file mode 100644 index 0000000000..030bd1fbce Binary files /dev/null and b/android/app/libs/geth-android-16.aar differ diff --git a/resources/commands.js b/resources/commands.js new file mode 100644 index 0000000000..bc0a456671 --- /dev/null +++ b/resources/commands.js @@ -0,0 +1,176 @@ +status.command({ + name: "location", + description: "Send location", + color: "#9a5dcf" +}).param({ + name: "address", + type: status.types.STRING +}); + +function text(options, s) { + return ["text", options, s]; +} + +function view(options, elements) { + return ["view", options].concat(elements); +} + +function image(options) { + return ["image", options]; +} + +function touchable(options, element) { + return ["touchable", options, element]; +} + +function scrollView(options, elements) { + return ["scroll-view", options].concat(elements); +} + +var phones = [ + { + number: "89171111111", + description: "Number format 1" + }, + { + number: "89371111111", + description: "Number format 1" + }, + { + number: "+79171111111", + description: "Number format 2" + }, + { + number: "9171111111", + description: "Number format 3" + } +]; + +function suggestionsContainerStyle(suggestionsCount) { + return { + marginVertical: 1, + marginHorizontal: 0, + height: Math.min(150, (56 * suggestionsCount)), + backgroundColor: "white", + borderRadius: 5 + }; +} + +var suggestionContainerStyle = { + paddingLeft: 16, + backgroundColor: "white" +}; + +var suggestionSubContainerStyle = { + height: 56, + borderBottomWidth: 1, + borderBottomColor: "#0000001f" +}; + +var valueStyle = { + marginTop: 9, + fontSize: 14, + fontFamily: "font", + color: "#000000de" +}; + +var descriptionStyle = { + marginTop: 1.5, + fontSize: 14, + fontFamily: "font", + color: "#838c93de" +}; + +function startsWith(str1, str2) { + return str1.lastIndexOf(str2, 0) == 0 && str1 != str2; +} + +function phoneSuggestions(params) { + var ph, suggestions; + if (!params.value || params.value == "") { + ph = phones; + } else { + ph = phones.filter(function (phone) { + return startsWith(phone.number, params.value); + }); + } + + if (ph.length == 0) { + return; + } + + suggestions = ph.map(function (phone) { + return touchable( + {onPress: [status.events.SET_VALUE, phone.number]}, + view(suggestionContainerStyle, + [view(suggestionSubContainerStyle, + [ + text({style: valueStyle}, phone.number), + text({style: descriptionStyle}, phone.description) + ])]) + ); + }); + + return scrollView(suggestionsContainerStyle(ph.length), suggestions); +} + +status.response({ + name: "phone", + description: "Send phone number", + color: "#5fc48d", + params: [{ + name: "phone", + type: status.types.PHONE_NUMBER, + suggestions: phoneSuggestions + }], + handler: function (params) { + return { + event: "sign-up", + params: [params.value] + }; + } +}); + +status.command({ + name: "help", + description: "Help", + color: "#9a5dcf", + params: [{ + name: "query", + type: status.types.STRING + }] +}); + +status.response({ + name: "confirmation-code", + color: "#7099e6", + description: "Confirmation code", + parameters: [{ + name: "code", + type: status.types.NUMBER + }], + handler: function (params) { + return { + event: "confirm-sign-up", + params: [params.value] + }; + } +}); + +status.response({ + name: "keypair", + color: "#7099e6", + description: "Keypair password", + icon: "icon_lock_white", + parameters: [{ + name: "password", + type: status.types.PASSWORD + }], + handler: function (params) { + return { + event: "save-password", + params: [params.value] + }; + } +}); + diff --git a/resources/status.js b/resources/status.js new file mode 100644 index 0000000000..e53cb7e174 --- /dev/null +++ b/resources/status.js @@ -0,0 +1,77 @@ +var _status_catalog = { + commands: {}, + responses: {} +}; + +function Command() { +} +function Response() { +} + +Command.prototype.addToCatalog = function () { + _status_catalog.commands[this.name] = this; +}; + +Command.prototype.param = function (parameter) { + this.params.push(parameter); + + return this; +}; + +Command.prototype.create = function (com) { + this.name = com.name; + this.description = com.description; + this.handler = com.handler; + this.color = com.color; + this.icon = com.icon; + this.params = com.params || []; + this.addToCatalog(); + + return this; +}; + + +Response.prototype = Object.create(Command.prototype); +Response.prototype.addToCatalog = function () { + _status_catalog.responses[this.name] = this; +}; +Response.prototype.onReceiveResponse = function (handler) { + this.onReceive = handler; +}; + +function call(pathStr, paramsStr) { + var params = JSON.parse(paramsStr), + path = JSON.parse(pathStr), + fn, res; + + fn = path.reduce(function (catalog, name) { + if (catalog && catalog[name]) { + return catalog[name]; + } + }, + _status_catalog + ); + + res = fn(params); + + return JSON.stringify(res); +} + +var status = { + command: function (n, d, h) { + var command = new Command(); + return command.create(n, d, h); + }, + response: function (n, d, h) { + var response = new Response(); + return response.create(n, d, h); + }, + types: { + STRING: 'string', + PHONE_NUMBER: 'phone-number', + PASSWORD: 'password' + }, + events: { + SET_VALUE: 'set-value' + } +}; diff --git a/src/status_im/chat/handlers.cljs b/src/status_im/chat/handlers.cljs index 98d9830521..f6686ff3b9 100644 --- a/src/status_im/chat/handlers.cljs +++ b/src/status_im/chat/handlers.cljs @@ -16,7 +16,8 @@ [status-im.persistence.realm :as r] [status-im.handlers.server :as server] [status-im.utils.phone-number :refer [format-phone-number]] - [status-im.utils.datetime :as time])) + [status-im.utils.datetime :as time] + [status-im.components.jail :as j])) (register-handler :set-show-actions (fn [db [_ show-actions]] @@ -41,7 +42,24 @@ (assoc-in [:chats current-chat-id :command-input] {}) (update-in [:chats current-chat-id :input-text] safe-trim)))) +(defn invoke-suggestions-handler! + [{:keys [current-chat-id] :as db} _] + (let [commands (get-in db [:chats current-chat-id :commands]) + {:keys [command content]} (get-in db [:chats current-chat-id :command-input])] + (let [path [(if (commands command) :commands :responses) + (:name command) + :params + 0 + :suggestions] + params {:value content}] + (j/call current-chat-id + path + params + #(dispatch [:suggestions-handler {:command command + :content content} %]))))) + (register-handler :set-chat-command-content + (after invoke-suggestions-handler!) (fn [db [_ content]] (commands/set-chat-command-content db content))) @@ -55,11 +73,13 @@ {:keys [command content]} (get-in db [:chats current-chat-id :command-input]) command-info {:command command - :content content - :handler (:handler command)}] - (commands/stage-command db command-info)))) + :content content}] + (-> db + (commands/stage-command command-info) + (assoc :staged-command command-info))))) (register-handler :set-response-chat-command + (after invoke-suggestions-handler!) (fn [db [_ to-msg-id command-key]] (commands/set-response-chat-command db to-msg-id command-key))) @@ -179,6 +199,20 @@ (doseq [new-command new-commands] (messages/save-message current-chat-id (dissoc new-command :handler)))) +(defn invoke-commands-handlers! + [{:keys [new-commands current-chat-id] :as db}] + (let [commands (get-in db [:chats current-chat-id :commands])] + (doseq [{:keys [content] :as com} new-commands] + (let [{:keys [command content]} content + path [(if (commands command) :commands :responses) + command + :handler] + params {:value content}] + (j/call current-chat-id + path + params + #(dispatch [:command-handler! com %])))))) + (defn handle-commands [{:keys [new-commands]}] (doseq [{{content :content} :content @@ -196,6 +230,8 @@ ((after send-message!)) ((after save-message-to-realm!)) ((after save-commands-to-realm!)) + ;; todo maybe it is better to track if it was handled or not + ((after invoke-commands-handlers!)) ((after handle-commands)))) (register-handler :unstage-command diff --git a/src/status_im/chat/sign_up.cljs b/src/status_im/chat/sign_up.cljs index b182354e15..e4140eab16 100644 --- a/src/status_im/chat/sign_up.cljs +++ b/src/status_im/chat/sign_up.cljs @@ -173,7 +173,7 @@ (dispatch [:received-msg {:msg-id msg-id :content (command-content - :keypair-password + :keypair (label :t/keypair-generated)) :content-type content-type-command-request :outgoing false @@ -185,8 +185,8 @@ {:chat-id "console" :name "console" ; todo remove/change dapp config fot console - :dapp-url "http://localhost:8185" - :dapp-hash 834331894 + :dapp-url "http://localhost:8185/resources" + :dapp-hash 858845357 :color default-chat-color :group-chat false :is-active true diff --git a/src/status_im/chat/styles/content_suggestions.cljs b/src/status_im/chat/styles/content_suggestions.cljs index 198468a549..01b328fc78 100644 --- a/src/status_im/chat/styles/content_suggestions.cljs +++ b/src/status_im/chat/styles/content_suggestions.cljs @@ -1,22 +1,21 @@ (ns status-im.chat.styles.content-suggestions (:require [status-im.components.styles :refer [font - color-light-blue-transparent - color-white - color-black - color-blue - color-blue-transparent - selected-message-color - online-color - separator-color - text1-color - text2-color - text3-color]])) + color-light-blue-transparent + color-white + color-black + color-blue + color-blue-transparent + selected-message-color + online-color + separator-color + text1-color + text2-color + text3-color]])) (def suggestion-height 56) (def suggestion-container - {:flexDirection :column - :paddingLeft 16 + {:paddingLeft 16 :backgroundColor color-white}) (def suggestion-sub-container @@ -37,7 +36,7 @@ :color text2-color}) (defn suggestions-container [suggestions-count] - {:flexDirection :row + {:flex 1 :marginVertical 1 :marginHorizontal 0 :height (min 150 (* suggestion-height suggestions-count)) diff --git a/src/status_im/chat/suggestions.cljs b/src/status_im/chat/suggestions.cljs index 842a64ea32..73f90e884d 100644 --- a/src/status_im/chat/suggestions.cljs +++ b/src/status_im/chat/suggestions.cljs @@ -1,8 +1,7 @@ (ns status-im.chat.suggestions (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] [status-im.db :as db] - [status-im.models.commands :refer [commands - get-commands + [status-im.models.commands :refer [get-commands get-chat-command-request get-chat-command-to-msg-id clear-staged-commands]] diff --git a/src/status_im/chat/views/content_suggestions.cljs b/src/status_im/chat/views/content_suggestions.cljs index 20fb1df9e5..1578f9484e 100644 --- a/src/status_im/chat/views/content_suggestions.cljs +++ b/src/status_im/chat/views/content_suggestions.cljs @@ -24,13 +24,11 @@ (list-item [suggestion-list-item row])) (defview content-suggestions-view [] - [suggestions [:get-content-suggestions]] + [suggestions [:get :current-suggestion]] (when (seq suggestions) [view [touchable-highlight {:style st/drag-down-touchable ;; TODO hide suggestions? :onPress (fn [])} [view [icon :drag_down st/drag-down-icon]]] - [view (st/suggestions-container (count suggestions)) - [list-view {:dataSource (to-datasource suggestions) - :renderRow render-row}]]])) + suggestions])) diff --git a/src/status_im/commands/handlers.cljs b/src/status_im/commands/handlers.cljs index 9c43f0ab55..8d63368e00 100644 --- a/src/status_im/commands/handlers.cljs +++ b/src/status_im/commands/handlers.cljs @@ -6,7 +6,11 @@ [status-im.utils.utils :refer [http-get toast]] [clojure.string :as s] [status-im.persistence.realm :as realm] - [status-im.components.jail :as j])) + [status-im.components.jail :as j] + [clojure.walk :as w] + [status-im.components.react :refer [text scroll-view view + image touchable-highlight]] + [clojure.set :as set])) (defn reg-handler ([name handler] (reg-handler name nil handler)) @@ -17,9 +21,12 @@ (defn load-commands! [_ [identity]] - (if-let [{:keys [file]} (realm/get-one-by-field :commands :chat-id identity)] - (dispatch [::parse-commands! identity file]) - (dispatch [::fetch-commands! identity]))) + (dispatch [::fetch-commands! identity]) + ;; todo uncomment + #_(if-let [{:keys [file]} (realm/get-one-by-field :commands :chat-id + identity)] + (dispatch [::parse-commands! identity file]) + (dispatch [::fetch-commands! identity]))) (defn fetch-commands! [db [identity]] @@ -43,38 +50,28 @@ ;; todo tbd hashing algorithm (hash file)) -(defn json->clj [json] - (js->clj (.parse js/JSON json) :keywordize-keys true)) - -;; todo remove this -(def res {:commands {:location {:description "Send location" - :color "#9a5dcf" - :name "location"} - :phone {:description "Send phone number" - :color "#5fc48d" - :name "phone"} - :help {:description "Help" :color "#9a5dcf" :name "help"}} - :responses {:money {:description "Send money" :color "#5fc48d" :name "money"} - :confirmation-code {:description "Confirmation code" - :color "#7099e6" - :name "confirmationCode"} - :keypair-password {:description "Keypair password" - :color "#7099e6" - :name "keypair-password" - :icon "icon_lock_white"}}}) +(defn json->cljs [json] + (if (= json "undefined") + nil + (js->clj (.parse js/JSON json) :keywordize-keys true))) (defn parse-commands! [_ [identity file]] (j/parse identity file (fn [result] - (let [commands (json->clj result)] + (let [commands (json->cljs result)] ;; todo use commands from jail - (dispatch [::add-commands identity file res]))) - #(dispatch [::loading-failed! identity ::error-in-jail %]))) + (dispatch [::add-commands identity file commands]))) + #_(dispatch [::loading-failed! identity ::error-in-jail %]))) (defn validate-hash [db [identity file]] - (let [valid? (= (get-hash-by-identity db identity) - (get-hash-by-file file))] + (let [valid? true + ;; todo check + #_(= (get-hash-by-identity db identity) + (get-hash-by-file file))] + (println :hash + (get-hash-by-identity db identity) + (get-hash-by-file file)) (assoc db ::valid-hash valid?))) (defn add-commands @@ -96,6 +93,76 @@ (name reason) details])))) +(defn init-render-command! + [_ [chat-id command message-id data]] + (j/call chat-id [command :render] data + (fn [res] + (dispatch [::render-command chat-id message-id (json->cljs res)])))) + +(def elements + {:text text + :view view + :scroll-view scroll-view + :image image + :touchable touchable-highlight}) + +(defn get-element [n] + (elements (keyword (.toLowerCase n)))) + +(def events #{:onPress}) + +(defn wrap-event [event] + #(dispatch [:suggestions-event! event])) + +(defn check-events [m] + (let [ks (set (keys m)) + evs (set/intersection ks events)] + (reduce #(update %1 %2 wrap-event) m evs))) + +(defn generate-hiccup [markup] + ;; todo implement validation + (w/prewalk + (fn [el] + (if (and (vector? el) (string? (first el))) + (-> el + (update 0 get-element) + (update 1 check-events)) + el)) + markup)) + +(defn render-command + [db [chat-id message-id markup]] + (let [hiccup (generate-hiccup markup)] + (assoc-in db [:rendered-commands chat-id message-id] hiccup))) + +(def console-events + {:save-password #(dispatch [:save-password %]) + :sign-up #(dispatch [:sign-up %]) + :confirm-sign-up #(dispatch [:sign-up-confirm %])}) + +(def regular-events {}) + +(defn command-nadler! + [_ [{:keys [to]} response]] + (let [{:keys [event params]} (json->cljs response) + events (if (= "console" to) + (merge regular-events console-events) + regular-events)] + (when-let [handler (events (keyword event))] + (apply handler params)))) + +(defn suggestions-handler + [db [_ response-json]] + (let [response (json->cljs response-json)] + (println response) + (assoc db :current-suggestion (generate-hiccup response)))) + +(defn suggestions-events-handler! + [db [[n data]]] + (case (keyword n) + :set-value (dispatch [:set-chat-command-content data]) + db)) + (reg-handler :load-commands! (u/side-effect! load-commands!)) (reg-handler ::fetch-commands! (u/side-effect! fetch-commands!)) @@ -111,3 +178,9 @@ (reg-handler ::loading-failed! (u/side-effect! loading-failed!)) +(reg-handler :init-render-command! init-render-command!) +(reg-handler ::render-command render-command) + +(reg-handler :command-handler! (u/side-effect! command-nadler!)) +(reg-handler :suggestions-handler suggestions-handler) +(reg-handler :suggestions-event! (u/side-effect! suggestions-events-handler!)) diff --git a/src/status_im/components/jail.cljs b/src/status_im/components/jail.cljs index 23e904417b..66b596619d 100644 --- a/src/status_im/components/jail.cljs +++ b/src/status_im/components/jail.cljs @@ -1,15 +1,94 @@ (ns status-im.components.jail (:require [status-im.components.react :as r])) +(def status-js + "var _status_catalog = { + commands: {}, + responses: {} +}; + +function Command() { +} +function Response() { +} + +Command.prototype.addToCatalog = function () { + _status_catalog.commands[this.name] = this; +}; + +Command.prototype.param = function (parameter) { + this.params.push(parameter); + + return this; +}; + +Command.prototype.create = function (com) { + this.name = com.name; + this.description = com.description; + this.handler = com.handler; + this.color = com.color; + this.icon = com.icon; + this.params = com.params || []; + this.addToCatalog(); + + return this; +}; + + +Response.prototype = Object.create(Command.prototype); +Response.prototype.addToCatalog = function () { + _status_catalog.responses[this.name] = this; +}; +Response.prototype.onReceiveResponse = function (handler) { + this.onReceive = handler; +}; + +function call(pathStr, paramsStr) { + var params = JSON.parse(paramsStr), + path = JSON.parse(pathStr), + fn, res; + + fn = path.reduce(function (catalog, name) { + if (catalog && catalog[name]) { + return catalog[name]; + } + }, + _status_catalog + ); + + res = fn(params); + + return JSON.stringify(res); +} + +var status = { + command: function (n, d, h) { + var command = new Command(); + return command.create(n, d, h); + }, + response: function (n, d, h) { + var response = new Response(); + return response.create(n, d, h); + }, + types: { + STRING: 'string', + PHONE_NUMBER: 'phone-number', + PASSWORD: 'password' + }, + events: { + SET_VALUE: 'set-value' + } +};") + (def jail (.-Jail (.-NativeModules r/react))) +(.init jail status-js) -(defn parse [chat-id file success-callback fail-callback] - (.parse jail chat-id file success-callback fail-callback)) +(defn parse [chat-id file callback] + (.parse jail chat-id file callback)) -(defn call - [chat-id path params callback] - (.call jail chat-id (clj->js path) (clj->js params) callback)) +(defn cljs->json [data] + (.stringify js/JSON (clj->js data))) -(defn add-listener - [chat-id callback] - (.addListener jail chat-id callback)) +(defn call [chat-id path params callback] + (println :call chat-id (cljs->json path) (cljs->json params)) + (.call jail chat-id (cljs->json path) (cljs->json params) callback)) diff --git a/src/status_im/models/commands.cljs b/src/status_im/models/commands.cljs index 896d53d2c7..7da49a9b4b 100644 --- a/src/status_im/models/commands.cljs +++ b/src/status_im/models/commands.cljs @@ -6,49 +6,6 @@ [status-im.components.styles :refer [color-blue color-dark-mint]] [status-im.i18n :refer [label]])) -;; todo delete -(def commands [{:command :money - :text "!money" - :description (label :t/money-command-description) - :color color-dark-mint - :request-icon {:uri "icon_lock_white"} - :icon {:uri "icon_lock_gray"} - :suggestion true} - {:command :location - :text "!location" - :description (label :t/location-command-description) - :color "#9a5dcf" - :suggestion true} - {:command :phone - :text "!phone" - :description (label :t/phone-command-description) - :color color-dark-mint - :request-text (label :t/phone-request-text) - :suggestion true - :handler #(dispatch [:sign-up %])} - {:command :confirmation-code - :text "!confirmationCode" - :description (label :t/confirmation-code-command-description) - :request-text (label :t/confirmation-code-request-text) - :color color-blue - :request-icon {:uri "icon_lock_white"} - :icon {:uri "icon_lock_gray"} - :suggestion true - :handler #(dispatch [:sign-up-confirm %])} - {:command :keypair-password - :text "!keypair-password" - :description (label :t/keypair-password-command-description) - :color color-blue - :request-icon {:uri "icon_lock_white"} - :icon {:uri "icon_lock_gray"} - :suggestion false - :handler #(dispatch [:save-password %])} - {:command :help - :text "!help" - :description (label :t/help-command-description) - :color "#9a5dcf" - :suggestion true}]) - (defn get-commands [{:keys [current-chat-id] :as db}] (or (get-in db [:chats current-chat-id :commands]) {}))