Merge pull request #153 from status-im/feature/send-transaction

Send transaction
This commit is contained in:
Roman Volosovskyi 2016-08-19 11:53:42 +03:00 committed by GitHub
commit 8148b7e01f
64 changed files with 1132 additions and 493 deletions

View File

@ -138,7 +138,7 @@ dependencies {
compile project(':react-native-fs')
compile project(':react-native-image-crop-picker')
//compile(name:'statusgo-android-16', ext:'aar')
compile(group: 'status-im', name: 'status-go', version: '0.1.0-201607011545-da53ec', ext: 'aar')
compile(group: 'status-im', name: 'status-go', version: 'unlock', ext: 'aar')
compile fileTree(dir: "node_modules/realm/android/libs", include: ["*.jar"])
}

View File

@ -36,7 +36,7 @@ public class MainApplication extends Application implements ReactApplication {
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
return Arrays.asList(
new MainReactPackage(),
new JailPackage(),
new RealmReactPackage(),

View File

@ -30,8 +30,7 @@ public class RootUtil {
try {
process = Runtime.getRuntime().exec(new String[] { "/system/xbin/which", "su" });
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
if (in.readLine() != null) return true;
return false;
return in.readLine() != null;
} catch (Throwable t) {
return false;
} finally {

View File

@ -3,6 +3,7 @@ package com.statusim.geth.module;
import android.app.Activity;
import android.os.Bundle;
import android.os.Message;
import android.os.RemoteException;
import com.facebook.react.bridge.*;
import com.statusim.geth.service.ConnectorHandler;
import com.statusim.geth.service.GethConnector;
@ -13,20 +14,19 @@ import android.util.Log;
import java.util.HashMap;
import java.util.UUID;
public class GethModule extends ReactContextBaseJavaModule implements LifecycleEventListener, ConnectorHandler {
class GethModule extends ReactContextBaseJavaModule implements LifecycleEventListener, ConnectorHandler {
private static final String TAG = "GethModule";
protected GethConnector geth = null;
protected String handlerIdentifier = createIdentifier();
private GethConnector geth = null;
protected HashMap<String, Callback> startNodeCallbacks = new HashMap<>();
protected HashMap<String, Callback> createAccountCallbacks = new HashMap<>();
protected HashMap<String, Callback> addAccountCallbacks = new HashMap<>();
protected HashMap<String, Callback> unlockAccountCallbacks = new HashMap<>();
private HashMap<String, Callback> startNodeCallbacks = new HashMap<>();
private HashMap<String, Callback> createAccountCallbacks = new HashMap<>();
private HashMap<String, Callback> unlockAccountCallbacks = new HashMap<>();
private HashMap<String, Callback> transactionCallbacks = new HashMap<>();
public GethModule(ReactApplicationContext reactContext) {
GethModule(ReactApplicationContext reactContext) {
super(reactContext);
reactContext.addLifecycleEventListener(this);
}
@ -67,12 +67,6 @@ public class GethModule extends ReactContextBaseJavaModule implements LifecycleE
}
}
@Override
public String getID() {
return handlerIdentifier;
}
@Override
public void onConnectorConnected() {
}
@ -89,7 +83,7 @@ public class GethModule extends ReactContextBaseJavaModule implements LifecycleE
Bundle data = message.getData();
String callbackIdentifier = data.getString(GethConnector.CALLBACK_IDENTIFIER);
Log.d(TAG, "callback identifier: " + callbackIdentifier);
Callback callback = null;
Callback callback;
switch (message.what) {
case GethMessages.MSG_NODE_STARTED:
Log.d(TAG, "handle startNodeCallbacks size: " + startNodeCallbacks.size());
@ -108,18 +102,20 @@ public class GethModule extends ReactContextBaseJavaModule implements LifecycleE
callback.invoke(data.getString("data"));
}
break;
case GethMessages.MSG_ACCOUNT_ADDED:
callback = addAccountCallbacks.remove(callbackIdentifier);
if (callback != null) {
callback.invoke(null, "{ \"address\": \"" + data.getString("address") + "\"}");
}
break;
case GethMessages.MSG_LOGGED_IN:
callback = unlockAccountCallbacks.remove(callbackIdentifier);
if (callback != null) {
callback.invoke(data.getString("result"));
}
break;
case GethMessages.MSG_TRANSACTION_COMPLETED:
callback = transactionCallbacks.remove(callbackIdentifier);
String result = data.getString("result");
Log.d(TAG, "Send result: " + result + (callback == null));
if (callback != null) {
callback.invoke(result);
}
break;
default:
isClaimed = false;
}
@ -130,7 +126,7 @@ public class GethModule extends ReactContextBaseJavaModule implements LifecycleE
@ReactMethod
public void startNode(Callback callback, Callback onAlreadyRunning) {
if(GethService.isRunning()){
if (GethService.isRunning()) {
onAlreadyRunning.invoke();
return;
}
@ -197,9 +193,12 @@ public class GethModule extends ReactContextBaseJavaModule implements LifecycleE
geth.createAccount(callbackIdentifier, password);
}
@ReactMethod
public void addAccount(String privateKey, Callback callback) {
private String createIdentifier() {
return UUID.randomUUID().toString();
}
@ReactMethod
public void completeTransaction(String hash, Callback callback) {
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
@ -212,13 +211,27 @@ public class GethModule extends ReactContextBaseJavaModule implements LifecycleE
return;
}
Log.d(TAG, "Complete transaction: " + hash);
String callbackIdentifier = createIdentifier();
addAccountCallbacks.put(callbackIdentifier, callback);
geth.addAccount(callbackIdentifier, privateKey);
transactionCallbacks.put(callbackIdentifier, callback);
geth.completeTransaction(callbackIdentifier, hash);
}
protected String createIdentifier() {
return UUID.randomUUID().toString();
private static Callback signalEventCallback;
//todo: move this method to GethService
public static void signalEvent(String jsonEvent) {
Log.d(TAG, "Signal event: " + jsonEvent);
if(signalEventCallback != null) {
signalEventCallback.invoke(jsonEvent);
}
}
@ReactMethod
public void registerSignalEventCallback(Callback callback) {
Log.d(TAG, "registerSignalEventCallback");
signalEventCallback = callback;
}
}

View File

@ -4,9 +4,7 @@ package com.statusim.geth.service;
import android.os.Message;
public interface ConnectorHandler {
boolean handleMessage(Message message);
void onConnectorConnected();
void onConnectorDisconnected();
String getID();
}
}

View File

@ -71,22 +71,21 @@ public class GethConnector extends ServiceConnector {
}
}
public void addAccount(String callbackIdentifier, String privateKey) {
public void completeTransaction(String callbackIdentifier, String hash){
if (checkBound()) {
Bundle data = new Bundle();
data.putString("privateKey", privateKey);
Message msg = createMessage(callbackIdentifier, GethMessages.MSG_ADD_ACCOUNT, data);
data.putString("hash", hash);
Message msg = createMessage(callbackIdentifier, GethMessages.MSG_COMPLETE_TRANSACTION, data);
try {
serviceMessenger.send(msg);
} catch (RemoteException e) {
Log.e(TAG, "Exception sending message(addAccount) to service: ", e);
Log.e(TAG, "Exception sending message(completeTransaction) to service: ", e);
}
}
}
protected boolean checkBound() {
private boolean checkBound() {
if (!isBound) {
Log.d(TAG, "GethConnector not bound!");
@ -95,7 +94,7 @@ public class GethConnector extends ServiceConnector {
return true;
}
protected Message createMessage(String callbackIdentifier, int idMessage, Bundle data) {
private Message createMessage(String callbackIdentifier, int idMessage, Bundle data) {
Log.d(TAG, "Client messenger: " + clientMessenger.toString());
Message msg = Message.obtain(null, idMessage, 0, 0);

View File

@ -6,7 +6,7 @@ public class GethMessages {
/**
* Start the node
*/
public static final int MSG_START_NODE = 1;
static final int MSG_START_NODE = 1;
/**
* Node started event
@ -16,7 +16,7 @@ public class GethMessages {
/**
* Stop the node
*/
public static final int MSG_STOP_NODE = 3;
static final int MSG_STOP_NODE = 3;
/**
* Node stopped event
@ -26,7 +26,7 @@ public class GethMessages {
/**
* Unlock an account
*/
public static final int MSG_LOGIN = 5;
static final int MSG_LOGIN = 5;
/**
* Account unlocked event
@ -36,7 +36,7 @@ public class GethMessages {
/**
* Create an account
*/
public static final int MSG_CREATE_ACCOUNT = 7;
static final int MSG_CREATE_ACCOUNT = 7;
/**
* Account created event
@ -44,13 +44,13 @@ public class GethMessages {
public static final int MSG_ACCOUNT_CREATED = 8;
/**
* Add an account
* Account complete transaction event
*/
public static final int MSG_ADD_ACCOUNT = 9;
static final int MSG_COMPLETE_TRANSACTION = 11;
/**
* Account added event
* Account complete transaction event
*/
public static final int MSG_ACCOUNT_ADDED = 10;
public static final int MSG_TRANSACTION_COMPLETED = 11;
}

View File

@ -16,19 +16,18 @@ public class GethService extends Service {
private static final String TAG = "GethService";
private static boolean isGethStarted = false;
private static boolean isGethInitialized = false;
private final Handler handler = new Handler();
private static String dataFolder;
static class IncomingHandler extends Handler {
private static class IncomingHandler extends Handler {
private final WeakReference<GethService> service;
IncomingHandler(GethService service) {
this.service = new WeakReference<GethService>(service);
this.service = new WeakReference<>(service);
}
@Override
@ -43,11 +42,14 @@ public class GethService extends Service {
}
}
final Messenger serviceMessenger = new Messenger(new IncomingHandler(this));
private final Messenger serviceMessenger = new Messenger(new IncomingHandler(this));
public static void signalEvent(String jsonEvent) {
System.out.println("\n\n\nIT WOOOOOORKS1111!!!!!!\n\n\n");
Log.d(TAG, "Signal event: " + jsonEvent);
Bundle replyData = new Bundle();
replyData.putString("event", jsonEvent);
//createAndSendReply(message, GethMessages.MSG_SIGNAL_EVENT, replyData);
}
@Nullable
@ -68,7 +70,6 @@ public class GethService extends Service {
super.onDestroy();
//TODO: stop geth
stopNode(null);
isGethStarted = false;
isGethInitialized = false;
Log.d(TAG, "Geth Service stopped !");
}
@ -78,7 +79,7 @@ public class GethService extends Service {
return Service.START_STICKY;
}
protected boolean handleMessage(Message message) {
private boolean handleMessage(Message message) {
switch (message.what) {
case GethMessages.MSG_START_NODE:
@ -94,14 +95,14 @@ public class GethService extends Service {
createAccount(message);
break;
case GethMessages.MSG_ADD_ACCOUNT:
addAccount(message);
break;
case GethMessages.MSG_LOGIN:
login(message);
break;
case GethMessages.MSG_COMPLETE_TRANSACTION:
completeTransaction(message);
break;
default:
return false;
}
@ -109,7 +110,7 @@ public class GethService extends Service {
return true;
}
protected void startNode(Message message) {
private void startNode(Message message) {
if (!isGethInitialized) {
isGethInitialized = true;
Log.d(TAG, "Client messenger1: " + message.replyTo.toString());
@ -120,12 +121,12 @@ public class GethService extends Service {
}
}
protected class StartTask extends AsyncTask<Void, Void, Void> {
private class StartTask extends AsyncTask<Void, Void, Void> {
protected String callbackIdentifier;
protected Messenger messenger;
String callbackIdentifier;
Messenger messenger;
public StartTask(Messenger messenger, String callbackIdentifier) {
StartTask(Messenger messenger, String callbackIdentifier) {
this.messenger = messenger;
this.callbackIdentifier = callbackIdentifier;
}
@ -140,9 +141,8 @@ public class GethService extends Service {
}
}
protected void onGethStarted(Messenger messenger, String callbackIdentifier) {
private void onGethStarted(Messenger messenger, String callbackIdentifier) {
Log.d(TAG, "Geth Service started");
isGethStarted = true;
Message replyMessage = Message.obtain(null, GethMessages.MSG_NODE_STARTED, 0, 0, null);
Bundle replyData = new Bundle();
Log.d(TAG, "Callback identifier: " + callbackIdentifier);
@ -151,7 +151,7 @@ public class GethService extends Service {
sendReply(messenger, replyMessage);
}
protected void startGeth() {
private void startGeth() {
File extStore = Environment.getExternalStorageDirectory();
@ -160,28 +160,38 @@ public class GethService extends Service {
extStore.getAbsolutePath() + "/ethereum" :
getApplicationInfo().dataDir + "/ethereum";
Log.d(TAG, "Starting background Geth Service in folder: " + dataFolder);
try {
final File newFile = new File(dataFolder);
// todo handle error?
newFile.mkdir();
} catch (Exception e) {
Log.e(TAG, "error making folder: " + dataFolder, e);
}
final Runnable addPeer = new Runnable() {
public void run() {
Log.w("Geth", "adding peer");
Statusgo.addPeer("enode://409772c7dea96fa59a912186ad5bcdb5e51b80556b3fe447d940f99d9eaadb51d4f0ffedb68efad232b52475dd7bd59b51cee99968b3cc79e2d5684b33c4090c@139.162.166.59:30303");
}
};
new Thread(new Runnable() {
public void run() {
Statusgo.StartNode(dataFolder);
}
}).start();
handler.postDelayed(addPeer, 5000);
}
protected void stopNode(Message message) {
private void stopNode(Message message) {
// TODO: stop node
createAndSendReply(message, GethMessages.MSG_NODE_STOPPED, null);
}
protected void createAccount(Message message) {
private void createAccount(Message message) {
Bundle data = message.getData();
String password = data.getString("password");
// TODO: remove second argument
@ -194,21 +204,7 @@ public class GethService extends Service {
createAndSendReply(message, GethMessages.MSG_ACCOUNT_CREATED, replyData);
}
protected void addAccount(Message message) {
Bundle data = message.getData();
String privateKey = data.getString("privateKey");
String password = data.getString("password");
// TODO: add account
//String address = Statusgo.doAddAccount(privateKey, password);
String address = "added account address";
Log.d(TAG, "Added account: " + address);
Bundle replyData = new Bundle();
replyData.putString("address", address);
createAndSendReply(message, GethMessages.MSG_ACCOUNT_ADDED, replyData);
}
protected void login(Message message) {
private void login(Message message) {
Bundle data = message.getData();
String address = data.getString("address");
String password = data.getString("password");
@ -221,11 +217,24 @@ public class GethService extends Service {
createAndSendReply(message, GethMessages.MSG_LOGGED_IN, replyData);
}
private void completeTransaction(Message message){
Bundle data = message.getData();
String hash = data.getString("hash");
Log.d(TAG, "Before CompleteTransaction: " + hash);
String result = Statusgo.CompleteTransaction(hash);
Log.d(TAG, "After CompleteTransaction: " + result);
Bundle replyData = new Bundle();
replyData.putString("result", result);
createAndSendReply(message, GethMessages.MSG_TRANSACTION_COMPLETED, replyData);
}
public static boolean isRunning() {
return isGethInitialized;
}
protected void createAndSendReply(Message message, int replyIdMessage, Bundle replyData) {
private static void createAndSendReply(Message message, int replyIdMessage, Bundle replyData) {
if (message == null) {
return;
@ -243,7 +252,7 @@ public class GethService extends Service {
sendReply(message.replyTo, replyMessage);
}
protected void sendReply(Messenger messenger, Message message) {
private static void sendReply(Messenger messenger, Message message) {
try {
messenger.send(message);

View File

@ -11,39 +11,27 @@ import java.util.ArrayList;
public class ServiceConnector {
private static final String TAG = "ServiceConnector";
/**
* Incoming message handler. Calls to its binder are sequential!
*/
protected final IncomingHandler handler;
/**
* Handler thread to avoid running on the main UI thread
*/
protected final HandlerThread handlerThread;
/** Context of the activity from which this connector was launched */
protected Context context;
private Context context;
/** The class of the service we want to connect to */
protected Class serviceClass;
private Class serviceClass;
/** Flag indicating if the service is bound. */
protected boolean isBound;
boolean isBound;
/** Sends messages to the service. */
protected Messenger serviceMessenger = null;
Messenger serviceMessenger = null;
/** Receives messages from the service. */
protected Messenger clientMessenger = null;
Messenger clientMessenger = null;
protected ArrayList<ConnectorHandler> handlers = new ArrayList<>();
private ArrayList<ConnectorHandler> handlers = new ArrayList<>();
/** Handles incoming messages from service. */
class IncomingHandler extends Handler {
private class IncomingHandler extends Handler {
public IncomingHandler(HandlerThread thread) {
IncomingHandler(HandlerThread thread) {
super(thread.getLooper());
}
@ -72,7 +60,7 @@ public class ServiceConnector {
/**
* Class for interacting with the main interface of the service.
*/
protected ServiceConnection serviceConnection = new ServiceConnection() {
private ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
@ -100,13 +88,14 @@ public class ServiceConnector {
}
};
public ServiceConnector(Context context, Class serviceClass) {
ServiceConnector(Context context, Class serviceClass) {
this.context = context;
this.serviceClass = serviceClass;
handlerThread = new HandlerThread("HandlerThread");
// Handler thread to avoid running on the main UI thread
HandlerThread handlerThread = new HandlerThread("HandlerThread");
handlerThread.start();
handler = new IncomingHandler(handlerThread);
// Incoming message handler. Calls to its binder are sequential!
IncomingHandler handler = new IncomingHandler(handlerThread);
clientMessenger = new Messenger(handler);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -5,6 +5,8 @@
(enable-console-print!)
(set! js/console.disableYellowBox true)
(def cnt (r/atom 0))
(defn reloader [] @cnt [core/app-root])
(def root-el (r/as-element [reloader]))
@ -14,4 +16,4 @@
:heads-up-display false
:jsload-callback #(swap! cnt inc))
(core/init)
(core/init)

View File

@ -9,12 +9,15 @@
[re-frame "0.7.0"]
[prismatic/schema "1.0.4"]
^{:voom {:repo "git@github.com:status-im/status-lib.git"
:branch "discover-rework"}}
[status-im/protocol "0.1.3-20160818_085900-gda79e8e"]
:branch "master"}}
[status-im/protocol "0.1.3-20160818_172519-g2f734a6"]
[natal-shell "0.3.0"]
[com.andrewmcveigh/cljs-time "0.4.0"]]
[com.andrewmcveigh/cljs-time "0.4.0"]
[tailrecursion/cljs-priority-map "1.2.0"]
[cljsjs/web3 "0.16.0-0"]]
:plugins [[lein-cljsbuild "1.1.1"]
[lein-figwheel "0.5.0-2"]]
[lein-figwheel "0.5.0-2"]
[lein-voom "0.1.0-20160311_203101-g259fbfc"]]
:clean-targets ["target/" "index.ios.js" "index.android.js"]
:aliases {"prod-build" ^{:doc "Recompile code with prod profile."}
["do" "clean"

View File

@ -260,3 +260,60 @@ status.command({
type: status.types.TEXT
}]
});
function validateBalance(params) {
try {
var val = web3.toWei(params.value, "ether");
} catch (err) {
return {
errors: [
status.components.validationMessage(
"Amount",
"Amount is not valid number"//err.message
)
]
};
}
var balance = web3.eth.getBalance(params.command.address);
if (bn(val).greaterThan(bn(balance))) {
return {
errors: [
status.components.validationMessage(
"Amount",
"Not enough ETH on balance ("
+ web3.fromWei(balance, "ether")
+ " ETH)"
)
]
};
}
}
function sendTransaction(params) {
var data = {
from: params.command.from,
to: params.command.to,
value: web3.toWei(params.value, "ether")
};
var hash = web3.eth.sendTransaction(data);
return {"transaction-hash": hash};
}
status.command({
name: "send",
color: "#5fc48d",
description: "Send transaction",
params: [{
name: "amount",
type: status.types.NUMBER
}],
preview: function (params) {
return status.components.text(
{},
params.value + " ETH"
);
},
handler: sendTransaction,
validator: validateBalance
});

View File

@ -22,6 +22,7 @@ Command.prototype.create = function (com) {
this.name = com.name;
this.description = com.description;
this.handler = com.handler;
this["has-handler"] = com.handler != null;
this.validator = com.validator;
this.color = com.color;
this.icon = com.icon;
@ -110,7 +111,7 @@ function validationMessage(titleText, descriptionText) {
};
var description = status.components.text(descriptionStyle, descriptionText);
var message = status.components.view(
return status.components.view(
{
backgroundColor: "red",
height: 61,
@ -119,8 +120,6 @@ function validationMessage(titleText, descriptionText) {
},
[title, description]
);
return message;
}
var status = {

View File

@ -39,7 +39,7 @@
:address address
:name address
:photo-path (identicon public-key)}]
(log/debug "account-created: " account)
(log/debug "account-created")
(when (not (str/blank? public-key))
(do
(dispatch-sync [:add-account account])
@ -90,7 +90,7 @@
(defn logged-in [db address]
(let [is-login-screen? (= (:view-id db) :login)
new-account? (not is-login-screen?)]
(log/debug "Logged in: " address)
(log/debug "Logged in: ")
(realm/change-account-realm address new-account?
#(if (nil? %)
(initialize-account db address)
@ -98,16 +98,17 @@
(register-handler
:login-account
(-> (fn [db [_ address password]]
(geth/login address password (fn [result]
(let [data (json->clj result)
error (:error data)
success (zero? (count error))]
(log/debug "Logged in account: " address result)
(if success
(logged-in db address)
(dispatch [:set-in [:login :error] error])))))
db)))
(u/side-effect!
(fn [db [_ address password]]
(geth/login address password
(fn [result]
(let [data (json->clj result)
error (:error data)
success (zero? (count error))]
(log/debug "Logged in account: ")
(if success
(logged-in db address)
(dispatch [:set-in [:login :error] error]))))))))
(defn load-accounts! [db _]
(let [accounts (->> (accounts/get-accounts)
@ -120,7 +121,7 @@
(defn console-create-account [db _]
(let [msg-id (random/id)]
(dispatch [:received-msg
(dispatch [:received-message
{:msg-id msg-id
:content {:command (name :keypair)
:content (label :t/keypair-generated)}
@ -130,4 +131,4 @@
:to "me"}])
db))
(register-handler :console-create-account console-create-account)
(register-handler :console-create-account console-create-account)

View File

@ -18,6 +18,7 @@
[status-im.chat.screen :refer [chat]]
[status-im.accounts.login.screen :refer [login]]
[status-im.accounts.screen :refer [accounts]]
[status-im.transactions.screen :refer [confirm]]
[status-im.chats-list.screen :refer [chats-list]]
[status-im.new-group.screen :refer [new-group]]
[status-im.participants.views.add :refer [new-participants]]
@ -98,6 +99,7 @@
:profile-photo-capture profile-photo-capture
:accounts accounts
:login login
:confirm confirm
:my-profile my-profile)]
[component {:platform-specific {:styles styles
:list-selection-fn show-dialog}}])))})))

View File

@ -1,5 +1,6 @@
(ns status-im.chat.handlers
(:require [re-frame.core :refer [enrich after debug dispatch]]
(:require-macros [cljs.core.async.macros :as am])
(:require [re-frame.core :refer [enrich after debug dispatch path]]
[status-im.models.commands :as commands]
[clojure.string :as str]
[status-im.components.styles :refer [default-chat-color]]
@ -20,31 +21,39 @@
[status-im.handlers.content-suggestions :refer [get-content-suggestions]]
[status-im.utils.phone-number :refer [format-phone-number
valid-mobile-number?]]
[status-im.utils.datetime :as time]
[status-im.components.jail :as j]
[status-im.utils.types :refer [json->clj]]
[status-im.commands.utils :refer [generate-hiccup]]
[status-im.chat.handlers.commands :refer [command-prefix]]
[status-im.chat.utils :refer [console? not-console?]]
status-im.chat.handlers.animation
status-im.chat.handlers.requests
status-im.chat.handlers.unviewed-messages))
status-im.chat.handlers.unviewed-messages
status-im.chat.handlers.send-message
status-im.chat.handlers.receive-message
[cljs.core.async :as a]))
(register-handler :set-show-actions
(fn [db [_ show-actions]]
(assoc db :show-actions show-actions)))
(register-handler :load-more-messages
(fn [{:keys [current-chat-id] :as db} _]
(fn [{:keys [current-chat-id loading-allowed] :as db} _]
(let [all-loaded? (get-in db [:chats current-chat-id :all-loaded?])]
(if all-loaded?
db
(let [messages-path [:chats current-chat-id :messages]
messages (get-in db messages-path)
new-messages (messages/get-messages current-chat-id (count messages))
all-loaded? (> default-number-of-messages (count new-messages))]
(-> db
(update-in messages-path concat new-messages)
(assoc-in [:chats current-chat-id :all-loaded?] all-loaded?)))))))
(if loading-allowed
(do (am/go
(<! (a/timeout 400))
(dispatch [:set :loading-allowed true]))
(if all-loaded?
db
(let [messages-path [:chats current-chat-id :messages]
messages (get-in db messages-path)
new-messages (messages/get-messages current-chat-id (count messages))
all-loaded? (> default-number-of-messages (count new-messages))]
(-> db
(assoc :loading-allowed false)
(update-in messages-path concat new-messages)
(assoc-in [:chats current-chat-id :all-loaded?] all-loaded?)))))
db))))
(defn safe-trim [s]
(when (string? s)
@ -99,12 +108,6 @@
(dispatch [:set-chat-command (ffirst suggestions)])
(dispatch [::set-text chat-id text]))))
(defn console? [s]
(= "console" s))
(def not-console?
(complement console?))
(register-handler :set-chat-input-text
(u/side-effect!
(fn [{:keys [current-chat-id]} [_ text]]
@ -142,23 +145,6 @@
(register-handler ::set-text update-text)
(defn check-author-direction
[db chat-id {:keys [from outgoing] :as message}]
(let [previous-message (first (get-in db [:chats chat-id :messages]))]
(merge message
{:same-author (if previous-message
(= (:from previous-message) from)
true)
:same-direction (if previous-message
(= (:outgoing previous-message) outgoing)
true)})))
(defn add-message-to-db
[db chat-id message]
(let [messages [:chats chat-id :messages]]
(update-in db messages conj (assoc message :chat-id chat-id
:new? true))))
(defn set-message-shown
[db chat-id msg-id]
(update-in db [:chats chat-id :messages] (fn [messages]
@ -172,141 +158,14 @@
(fn [db [_ {:keys [chat-id msg-id]}]]
(set-message-shown db chat-id msg-id)))
(defn default-delivery-status [chat-id]
(if (console? chat-id)
:seen
:pending))
(defn prepare-message
[{:keys [identity current-chat-id] :as db} _]
(let [text (get-in db [:chats current-chat-id :input-text])
[command] (suggestions/check-suggestion db (str text " "))
message (check-author-direction
db current-chat-id
{:msg-id (random/id)
:chat-id current-chat-id
:content text
:to current-chat-id
:from identity
:content-type text-content-type
:delivery-status (default-delivery-status current-chat-id)
:outgoing true
:timestamp (time/now-ms)})]
_ (.log js/console "WOW3")
(if command
(commands/set-command-input db :commands command)
(assoc db :new-message (when-not (str/blank? text) message)))))
(defn prepare-command
[identity chat-id {:keys [preview preview-string content command to-message]}]
(let [content {:command (command :name)
:content content}]
{:msg-id (random/id)
:from identity
:to chat-id
:content content
:content-type content-type-command
:delivery-status (default-delivery-status chat-id)
:outgoing true
:preview preview-string
:rendered-preview preview
:to-message to-message}))
(defn prepare-staged-commans
[{:keys [current-chat-id identity] :as db} _]
(let [staged-commands (get-in db [:chats current-chat-id :staged-commands])]
(->> staged-commands
(map #(prepare-command identity current-chat-id %))
;todo this is wrong :(
(map #(check-author-direction db current-chat-id %))
(assoc db :new-commands))))
(defn add-message
[{:keys [new-message current-chat-id] :as db}]
(if new-message
(add-message-to-db db current-chat-id new-message)
db))
(defn add-commands
[{:keys [new-commands current-chat-id] :as db}]
(reduce
#(add-message-to-db %1 current-chat-id %2)
db
new-commands))
(defn clear-input
[{:keys [current-chat-id new-message] :as db} _]
(if new-message
(assoc-in db [:chats current-chat-id :input-text] nil)
db))
(defn clear-staged-commands
[{:keys [current-chat-id] :as db} _]
(assoc-in db [:chats current-chat-id :staged-commands] []))
(defn send-message!
[{:keys [new-message current-chat-id] :as db} _]
(when (and new-message (not-console? current-chat-id))
(let [{:keys [group-chat]} (get-in db [:chats current-chat-id])
message (select-keys new-message [:content :msg-id])]
(if group-chat
(api/send-group-user-msg (assoc message :group-id current-chat-id))
(api/send-user-msg (assoc message :to current-chat-id))))))
(defn save-message-to-realm!
[{:keys [new-message current-chat-id]} _]
(when new-message
(messages/save-message current-chat-id new-message)))
(defn save-commands-to-realm!
[{:keys [new-commands current-chat-id]} _]
(doseq [new-command new-commands]
(messages/save-message
current-chat-id
(dissoc new-command :rendered-preview :to-message))))
(defn dispatch-responded-requests!
[{:keys [new-commands current-chat-id]} _]
(doseq [{:keys [to-message]} new-commands]
(when to-message
(dispatch [:request-answered! current-chat-id to-message]))))
(defn invoke-commands-handlers!
[{:keys [new-commands current-chat-id]}]
(doseq [{:keys [content] :as com} new-commands]
(let [{:keys [command content]} content
type (:type command)
path [(if (= :command type) :commands :responses)
command
:handler]
params {:value content}]
(j/call current-chat-id
path
params
#(dispatch [:command-handler! com %])))))
(register-handler :send-chat-msg
(-> prepare-message
((enrich prepare-staged-commans))
((enrich add-message))
((enrich add-commands))
((enrich clear-input))
((enrich clear-staged-commands))
((after send-message!))
((after save-message-to-realm!))
((after save-commands-to-realm!))
((after dispatch-responded-requests!))
;; todo maybe it is better to track if it was handled or not
((after invoke-commands-handlers!))))
(register-handler :init-console-chat
(fn [db [_]]
(sign-up-service/init db)))
(register-handler :save-password
(fn [db [_ password]]
(dispatch [:create-account password])
(sign-up-service/save-password password)
(dispatch [:create-account password])
(assoc db :password-saved true)))
(register-handler :sign-up
@ -372,35 +231,6 @@
(after #(dispatch [:load-unviewed-messages!]))
((enrich initialize-chats) load-chats!))
(defn store-message!
[{:keys [new-message]} [_ {chat-id :from}]]
(messages/save-message chat-id new-message))
(defn dispatch-request!
[{:keys [new-message]} [_ {chat-id :from}]]
(when (= (:content-type new-message) content-type-command-request)
(dispatch [:add-request chat-id new-message])))
(defn receive-message
[db [_ {chat-id :from :as message}]]
(let [message' (-> db
(check-author-direction chat-id message)
(assoc :delivery-status :pending))]
(-> db
(add-message-to-db chat-id message')
(assoc :new-message message'))))
(defn dispatch-unviewed-message!
[{:keys [new-message]} [_ {chat-id :from}]]
(let [{:keys [msg-id]} new-message]
(dispatch [:add-unviewed-message chat-id msg-id])))
(register-handler :received-msg
[(after store-message!)
(after dispatch-request!)
(after dispatch-unviewed-message!)]
receive-message)
(register-handler :group-received-msg
(u/side-effect!
(fn [_ [_ {chat-id :group-id :as msg}]]
@ -412,6 +242,7 @@
messages (get-in db [:chats chat-id :messages])
db' (assoc db :current-chat-id chat-id)]
(dispatch [:load-requests! chat-id])
(dispatch [:load-commands! chat-id])
(if (seq messages)
db'
(-> db'
@ -419,15 +250,15 @@
init-chat))))
(defn prepare-chat
[{:keys [contacts] :as db} [_ contcat-id]]
(let [name (get-in contacts [contcat-id :name])
chat {:chat-id contcat-id
:name name
[{: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 contcat-id}]
:contacts [{:identity contact-id}]
:dapp-url nil
:dapp-hash nil}]
(assoc db :new-chat chat)))
@ -451,6 +282,11 @@
((after save-chat!))
((after open-chat!))))
(register-handler :add-chat
(-> prepare-chat
((enrich add-chat))
((after save-chat!))))
(register-handler :switch-command-suggestions!
(u/side-effect!
(fn [db]

View File

@ -48,7 +48,7 @@
suggestion? (get-in db [:has-suggestions? current-chat-id])
custom-errors (get-in db [:custom-validation-errors current-chat-id])
validation-height (if (or (seq errors) (seq custom-errors))
request-info-height
(+ suggestions-header-height request-info-height)
0)]
(+ validation-height
(if (= :response type)

View File

@ -7,7 +7,9 @@
[clojure.string :as str]
[status-im.commands.utils :as cu]
[status-im.utils.phone-number :as pn]
[status-im.i18n :as i18n]))
[status-im.i18n :as i18n]
[status-im.utils.datetime :as time]
[status-im.utils.random :as random]))
(def command-prefix "c ")
@ -61,7 +63,7 @@
(defn invoke-command-preview!
[{:keys [staged-command]} [_ chat-id]]
(let [{:keys [command content]} staged-command
(let [{:keys [command content id]} staged-command
{:keys [name type]} command
path [(if (= :command type) :commands :responses)
name
@ -70,7 +72,7 @@
(j/call chat-id
path
params
#(dispatch [:command-preview chat-id %]))))
#(dispatch [:command-preview chat-id id %]))))
(defn command-input
([{:keys [current-chat-id] :as db}]
@ -102,7 +104,8 @@
path [(if (= :command type) :commands :responses)
name
:validator]
params {:value content}]
params {:value content
:command data}]
(j/call chat-id
path
params
@ -110,13 +113,14 @@
(register-handler :stage-command
(after start-validate!)
(fn [{:keys [current-chat-id] :as db}]
(fn [{:keys [current-chat-id current-account-id] :as db}]
(let [{:keys [command content]} (command-input db)
content' (content-by-command command content)]
(-> db
(assoc ::command {:content content'
:command command
:chat-id current-chat-id})
:chat-id current-chat-id
:address current-account-id})
(assoc-in [:disable-staging current-chat-id] true)))))
(register-handler ::finish-command-staging
@ -128,7 +132,9 @@
content' (content-by-command command content)
command-info {:command command
:content content'
:to-message to-msg-id}]
:to-message to-msg-id
:created-at (time/now-ms)
:id (random/id)}]
(-> db
(commands/stage-command command-info)
(assoc :staged-command command-info)
@ -172,14 +178,19 @@
(fn [db]
(dissoc db :validation-errors :custom-validation-errors)))
(defn dispatch-error!
[chat-id title description]
(letfn [(wrap-params [p] (if (seqable? p) p [p]))]
(dispatch [::set-validation-error
chat-id
{:title (apply i18n/label (wrap-params title))
:description (apply i18n/label (wrap-params description))}])))
(def validation-handlers
{:phone (fn [chat-id [number]]
(if (pn/valid-mobile-number? number)
(dispatch [::finish-command-staging chat-id])
(dispatch [::set-validation-error
chat-id
{:title (i18n/label :t/phone-number)
:description (i18n/label :t/invalid-phone)}])))})
(dispatch-error! chat-id :t/phone-number :t/invalid-phone)))})
(defn validator [name]
(validation-handlers (keyword name)))
@ -190,7 +201,6 @@
(when-let [handler (validator name)]
(handler chat-id params)))))
(register-handler ::set-validation-error
(after #(dispatch [:fix-response-height]))
(fn [db [_ chat-id error]]

View File

@ -0,0 +1,41 @@
(ns status-im.chat.handlers.receive-message
(:require [status-im.utils.handlers :refer [register-handler] :as u]
[re-frame.core :refer [enrich after debug dispatch path]]
[status-im.models.messages :as messages]
[status-im.chat.utils :as cu]
[status-im.commands.utils :refer [generate-hiccup]]
[status-im.constants :refer [content-type-command-request]]
[cljs.reader :refer [read-string]]
[status-im.models.chats :as c]))
(defn check-previev [{:keys [content] :as message}]
(if-let [preview (:preview content)]
(let [rendered-preview (generate-hiccup (read-string preview))]
(assoc message
:preview preview
:rendered-preview rendered-preview))
message))
(defn store-message [{chat-id :from :as message}]
(messages/save-message chat-id (dissoc message :rendered-preview :new?)))
(register-handler :received-message
(u/side-effect!
(fn [{:keys [chats] :as db} [_ {chat-id :from :keys [msg-id] :as message}]]
(let [same-message (messages/get-message msg-id)]
(when-not same-message
(let [message' (assoc (->> message
(cu/check-author-direction db chat-id)
(check-previev))
:delivery-status :pending)]
(store-message message')
(when-not (c/chat-exists? chat-id)
(dispatch [:add-chat chat-id]))
(dispatch [::add-message message'])
(when (= (:content-type message') content-type-command-request)
(dispatch [:add-request chat-id message']))
(dispatch [:add-unviewed-message chat-id msg-id])))))))
(register-handler ::add-message
(fn [db [_ {chat-id :from :keys [new?] :as message}]]
(cu/add-message-to-db db chat-id message new?)))

View File

@ -1,9 +1,9 @@
(ns status-im.chat.handlers.requests
(:require [re-frame.core :refer [after dispatch enrich]]
[status-im.utils.handlers :refer [register-handler]]
[status-im.persistence.realm.core :as realm]
[status-im.models.requests :as requests]
[status-im.utils.handlers :as u]))
[status-im.utils.handlers :refer [register-handler] :as u]
[status-im.persistence.realm.core :as realm]))
(defn store-request!
[{:keys [new-request] :as db}]

View File

@ -0,0 +1,201 @@
(ns status-im.chat.handlers.send-message
(:require [status-im.utils.handlers :refer [register-handler] :as u]
[clojure.string :as s]
[status-im.models.messages :as messages]
[status-im.components.jail :as j]
[status-im.utils.random :as random]
[status-im.utils.datetime :as time]
[re-frame.core :refer [enrich after debug dispatch path]]
[status-im.chat.suggestions :as suggestions]
[status-im.models.commands :as commands]
[status-im.chat.utils :as cu]
[status-im.constants :refer [text-content-type
content-type-command
content-type-command-request
default-number-of-messages]]
[status-im.protocol.api :as api]))
(defn default-delivery-status [chat-id]
(if (cu/console? chat-id)
:seen
:pending))
(defn prepare-message
[{:keys [identity current-chat-id] :as db} _]
(let [text (get-in db [:chats current-chat-id :input-text])
[command] (suggestions/check-suggestion db (str text " "))
message (cu/check-author-direction
db current-chat-id
{:msg-id (random/id)
:chat-id current-chat-id
:content text
:to current-chat-id
:from identity
:content-type text-content-type
:delivery-status (default-delivery-status current-chat-id)
:outgoing true
:timestamp (time/now-ms)})]
(if command
(commands/set-command-input db :commands command)
(assoc db :new-message (when-not (s/blank? text) message)))))
(defn prepare-command
[identity chat-id {:keys [preview preview-string content command to-message]}]
(let [content {:command (command :name)
:content content}]
{:msg-id (random/id)
:from identity
:to chat-id
:content (assoc content :preview preview-string)
:content-type content-type-command
:delivery-status (default-delivery-status chat-id)
:outgoing true
:preview preview-string
:rendered-preview preview
:to-message to-message
:type (:type command)
:has-handler (:has-handler command)}))
(register-handler :send-chat-msg
(u/side-effect!
(fn [{:keys [current-chat-id identity current-account-id] :as db}]
(let [staged-commands (vals (get-in db [:chats current-chat-id :staged-commands]))
text (get-in db [:chats current-chat-id :input-text])
data {:commands staged-commands
:message text
:chat-id current-chat-id
:identity identity
:address current-account-id}]
(dispatch [:clear-input current-chat-id])
(cond
(seq staged-commands)
(dispatch [::check-commands-handlers! data])
(not (s/blank? text))
(dispatch [::prepare-message data]))))))
(register-handler ::check-commands-handlers!
(u/side-effect!
(fn [_ [_ {:keys [commands message] :as params}]]
(doseq [{:keys [command] :as message} commands]
(let [params' (assoc params :command message)]
(if (:pending message)
(dispatch [:navigate-to :confirm])
(if (:has-handler command)
(dispatch [::invoke-command-handlers! params'])
(dispatch [:prepare-command! params'])))))
(when-not (s/blank? message)
(dispatch [::prepare-message params])))))
(register-handler :clear-input
(path :chats)
(fn [db [_ chat-id]]
(assoc-in db [chat-id :input-text] nil)))
(register-handler :prepare-command!
(u/side-effect!
(fn [db [_ {:keys [chat-id command identity] :as params}]]
(let [command' (->> command
(prepare-command identity chat-id)
(cu/check-author-direction db chat-id))]
(dispatch [::clear-command chat-id (:id command)])
(dispatch [::send-command! (assoc params :command command')])))))
(register-handler ::clear-command
(fn [db [_ chat-id id]]
(update-in db [:chats chat-id :staged-commands] dissoc id)))
(register-handler ::send-command!
(u/side-effect!
(fn [_ [_ params]]
(dispatch [::add-command params])
(dispatch [::save-command! params])
(dispatch [::dispatch-responded-requests! params])
(dispatch [::send-command-protocol! params]))))
(register-handler ::add-command
(after (fn [_ [_ {:keys [handler]}]]
(when handler (handler))))
(fn [db [_ {:keys [chat-id command]}]]
(cu/add-message-to-db db chat-id command)))
(register-handler ::save-command!
(u/side-effect!
(fn [_ [_ {:keys [command chat-id]}]]
(messages/save-message
chat-id
(dissoc command :rendered-preview :to-message :has-handler)))))
(register-handler ::dispatch-responded-requests!
(u/side-effect!
(fn [_ [_ {:keys [command chat-id]}]]
(let [{:keys [to-message]} command]
(when to-message
(dispatch [:request-answered! chat-id to-message]))))))
(register-handler ::invoke-command-handlers!
(u/side-effect!
(fn [db [_ {:keys [chat-id address] :as parameters}]]
(let [{:keys [command content]} (:command parameters)
{:keys [type name]} command
path [(if (= :command type) :commands :responses)
name
:handler]
to (get-in db [:contacts chat-id :address])
params {:value content
:command {:from address
:to to}}]
(j/call chat-id
path
params
#(dispatch [:command-handler! chat-id parameters %]))))))
(register-handler ::prepare-message
(u/side-effect!
(fn [db [_ {:keys [chat-id identity message] :as params}]]
(let [message' (cu/check-author-direction
db chat-id
{:msg-id (random/id)
:chat-id chat-id
:content message
:to chat-id
:from identity
:content-type text-content-type
:delivery-status (default-delivery-status chat-id)
:outgoing true
:timestamp (time/now-ms)})
params' (assoc params :message message')]
(dispatch [::add-message params'])
(dispatch [::save-message! params'])
(dispatch [::send-message! params'])))))
(register-handler ::add-message
(fn [db [_ {:keys [chat-id message]}]]
(cu/add-message-to-db db chat-id message)))
(register-handler ::save-message!
(u/side-effect!
(fn [_ [_ {:keys [chat-id message]}]]
(messages/save-message chat-id message))))
(register-handler ::send-message!
(u/side-effect!
(fn [db [_ {:keys [message chat-id]}]]
(when (and message (cu/not-console? chat-id))
(let [{:keys [group-chat]} (get-in db [:chats chat-id])
message' (select-keys message [:content :msg-id])]
(if group-chat
(api/send-group-user-msg (assoc message' :group-id chat-id))
(api/send-user-msg (assoc message' :to chat-id))))))))
(register-handler ::send-command-protocol!
(u/side-effect!
(fn [db [_ {:keys [chat-id command]}]]
(let [{:keys [content]} command]
(when (cu/not-console? chat-id)
(let [{:keys [group-chat]} (get-in db [:chats chat-id])
message {:content content
:content-type content-type-command}]
(if group-chat
(api/send-group-user-msg (assoc message :group-id chat-id))
(api/send-user-msg (assoc message :to chat-id)))))))))

View File

@ -123,7 +123,7 @@
(defn actions-list-view [{styles :styles :as platform-specific}]
(let [{:keys [group-chat chat-id]} (subscribe [:chat-properties [:group-chat :chat-id]])
members (subscribe [:current-chat-contacts])
members (subscribe [:current-chat-contacts])
status-bar-height (get-in styles [:components :status-bar :default :height])]
(when-let [actions (if @group-chat
[{:title (label :t/members-title)
@ -203,9 +203,10 @@
(label :t/active-unknown))))
(defn toolbar-content [platform-specific]
(let [{:keys [group-chat chat-id name contacts]} (subscribe [:chat-properties [:group-chat :chat-id :name :contacts]])
contact (subscribe [:get-in [:contacts @chat-id]])
show-actions (subscribe [:show-actions])]
(let [{:keys [group-chat name contacts chat-id]}
(subscribe [:chat-properties [:group-chat :name :contacts :chat-id]])
show-actions (subscribe [:show-actions])
contact (subscribe [:get-in [:contacts @chat-id]])]
(fn []
[view (st/chat-name-view @show-actions)
[text {:style st/chat-name-text
@ -242,7 +243,7 @@
(defn chat-toolbar [platform-specific]
(let [{:keys [group-chat name contacts]} (subscribe [:chat-properties [:group-chat :name :contacts]])
show-actions (subscribe [:show-actions])]
show-actions (subscribe [:show-actions])]
[view
[status-bar {:platform-specific platform-specific}]
[toolbar {:hide-nav? @show-actions
@ -252,14 +253,15 @@
(defview messages-view [platform-specific group-chat]
[messages [:chat :messages]
contacts [:chat :contacts]]
contacts [:chat :contacts]
loaded? [:all-messages-loaded?]]
(let [contacts' (contacts-by-identity contacts)]
[list-view {:renderRow (message-row {:contact-by-identity contacts'
:platform-specific platform-specific
:group-chat group-chat
:messages-count (count messages)})
:renderScrollComponent #(invertible-scroll-view (js->clj %))
:onEndReached #(dispatch [:load-more-messages])
:onEndReached (when-not loaded? #(dispatch [:load-more-messages]))
:enableEmptySections true
:keyboardShouldPersistTaps true
:dataSource (to-datasource-inverted messages)}]))

View File

@ -37,7 +37,7 @@
;; -- Send phone number ----------------------------------------
(defn on-sign-up-response [& [message]]
(let [msg-id (random/id)]
(dispatch [:received-msg
(dispatch [:received-message
{:msg-id msg-id
:content (command-content
:confirmation-code
@ -62,7 +62,7 @@
;; -- Send confirmation code and synchronize contacts---------------------------
(defn on-sync-contacts []
(dispatch [:received-msg
(dispatch [:received-message
{:msg-id (random/id)
:content (label :t/contacts-syncronized)
:content-type text-content-type
@ -76,7 +76,7 @@
(dispatch [:sync-contacts on-sync-contacts]))
(defn on-send-code-response [body]
(dispatch [:received-msg
(dispatch [:received-message
{:msg-id (random/id)
:content (:message body)
:content-type text-content-type
@ -96,47 +96,52 @@
;; -- Saving password ----------------------------------------
(defn save-password [password]
;; TODO validate and save password
(dispatch [:received-msg
(dispatch [:received-message
{:msg-id (random/id)
:content (label :t/password-saved)
:content-type text-content-type
:outgoing false
:from "console"
:to "me"}])
(dispatch [:received-msg
:to "me"
:new? false}])
(dispatch [:received-message
{:msg-id (random/id)
:content (label :t/generate-passphrase)
:content-type text-content-type
:outgoing false
:from "console"
:to "me"}])
(dispatch [:received-msg
:to "me"
:new? false}])
(dispatch [:received-message
{:msg-id (random/id)
:content (label :t/passphrase)
:content-type text-content-type
:outgoing false
:from "console"
:to "me"}])
:to "me"
:new? false}])
;; TODO generate passphrase
(let [passphrase (str "The brash businessman's braggadocio and public squabbing with "
"candidates in the US presidential election")]
(dispatch [:received-msg
(dispatch [:received-message
{:msg-id (random/id)
:content passphrase
:content-type text-content-type
:outgoing false
:from "console"
:to "me"}]))
(dispatch [:received-msg
:to "me"
:new? false}]))
(dispatch [:received-message
{:msg-id "8"
:content (label :t/written-down)
:content-type text-content-type
:outgoing false
:from "console"
:to "me"}])
:to "me"
:new? false}])
;; TODO highlight '!phone'
(let [msg-id (random/id)]
(dispatch [:received-msg
(dispatch [:received-message
{:msg-id msg-id
:content (command-content
:phone
@ -157,15 +162,15 @@
:to "me"})
(defn intro [db]
(dispatch [:received-msg intro-status])
(dispatch [:received-msg
(dispatch [:received-message intro-status])
(dispatch [:received-message
{:msg-id "intro-message1"
:content (label :t/intro-message1)
:content-type text-content-type
:outgoing false
:from "console"
:to "me"}])
(dispatch [:received-msg
(dispatch [:received-message
{:msg-id "intro-message2"
:content (label :t/intro-message2)
:content-type text-content-type
@ -173,7 +178,7 @@
:from "console"
:to "me"}])
(let [msg-id "into-message3"]
(dispatch [:received-msg
(dispatch [:received-message
{:msg-id msg-id
:content (command-content
:keypair

View File

@ -3,15 +3,16 @@
[status-im.chat.constants :as constants]))
(def messages-container
{:background-color :red
{:background-color :#d50000
:height constants/request-info-height
:padding-left 16
:padding-top 14})
:padding-top 12})
(def title
{:color :white
:font-size 12
:font-size 14
:font-family st/font})
(def description
(assoc title :opacity 0.69))
(assoc title :opacity 0.9
:font-size 12))

View File

@ -70,6 +70,7 @@
(fn [db _]
(->> [:chats (:current-chat-id @db) :staged-commands]
(get-in @db)
vals
(reaction))))
(register-sub :valid-plain-message?
@ -232,3 +233,8 @@
20
:else 0)))))
(register-sub :all-messages-loaded?
(fn [db]
(let [chat-id (subscribe [:get-current-chat-id])]
(reaction (get-in @db [:chats @chat-id :all-loaded?])))))

View File

@ -3,8 +3,7 @@
[status-im.db :as db]
[status-im.models.commands :refer [get-commands
get-chat-command-request
get-chat-command-to-msg-id
clear-staged-commands]]
get-chat-command-to-msg-id]]
[status-im.utils.utils :refer [log on-error http-get]]
[clojure.string :as s]))
@ -39,15 +38,6 @@
(fn []
(command-handler to-msg-id command-key content)))))
(defn apply-staged-commands [db]
(let [staged-commands (get-in db (db/chat-staged-commands-path
(:current-chat-id db)))]
(dorun (map (fn [staged-command]
(when-let [handler (:handler staged-command)]
(handler)))
staged-commands))
(clear-staged-commands db)))
(defn check-suggestion [db message]
(when-let [suggestion-text (when (string? message)
(re-matches #"^![^\s]+\s" message))]
@ -60,7 +50,3 @@
(-> db
(get-in [:chats (:current-chat-id db) :input-text])
suggestion?))
(defn switch-command-suggestions [db]
(let [text (if (typing-command? db) nil "!")]
(assoc-in db [:chats (:current-chat-id db) :input-text] text)))

View File

@ -0,0 +1,27 @@
(ns status-im.chat.utils)
(defn console? [s]
(= "console" s))
(def not-console?
(complement console?))
(defn add-message-to-db
([db chat-id message] (add-message-to-db db chat-id message true))
([db chat-id message new?]
(let [messages [:chats chat-id :messages]]
(update-in db messages conj (assoc message :chat-id chat-id
:new? (if (nil? new?)
true
new?))))))
(defn check-author-direction
[db chat-id {:keys [from outgoing] :as message}]
(let [previous-message (first (get-in db [:chats chat-id :messages]))]
(merge message
{:same-author (if previous-message
(= (:from previous-message) from)
true)
:same-direction (if previous-message
(= (:outgoing previous-message) outgoing)
true)})))

View File

@ -88,7 +88,7 @@
[text {:style st/command-text
:platform-specific platform-specific
:font :default}
content])]))
(str content)])]))
(defn set-chat-command [msg-id command]
(dispatch [:set-response-chat-command msg-id (keyword (:name command))]))
@ -114,7 +114,7 @@
[text {:style (st/text-message message)
:platform-specific platform-specific
:font :default}
content]])
(str content)]])
(defmethod message-content text-content-type
[wrapper message platform-specific]
@ -213,9 +213,7 @@
:callback anim-callback}
on-update (message-container-animation-logic context)]
(r/create-class
{:component-did-mount
on-update
:component-did-update
{:component-did-update
on-update
:reagent-render
(fn [message & children]
@ -241,7 +239,8 @@
(dispatch [:send-seen! chat-id msg-id])))
:reagent-render
(fn [{:keys [outgoing delivery-status timestamp new-day group-chat]
:as message}]
:as message}
platform-specific]
[message-container message
;; TODO there is no new-day info in message
(when new-day

View File

@ -72,12 +72,14 @@
[suggestions [:get-suggestions]
requests [:get-requests]]
[scroll-view {:keyboardShouldPersistTaps true}
;; todo translations
(when (seq requests) [title "Requests"])
(when (seq requests)
[view
[list-view {:dataSource (to-datasource requests)
:keyboardShouldPersistTaps true
:renderRow render-request-row}]])
;; todo translations
[title "Commands"]
[view
[list-view {:dataSource (to-datasource suggestions)

View File

@ -12,7 +12,8 @@
[{:keys [chat-id name color new-messages-count
online group-chat contacts] :as chat}]
[unviewed-messages [:unviewed-messages-count chat-id]]
(let [last-message (first (:messages chat))]
(let [last-message (first (:messages chat))
name (or name chat-id)]
[view st/chat-container
[view st/chat-icon-container
[chat-icon-view-chat-list chat-id group-chat name color online]]

View File

@ -19,24 +19,37 @@
(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 %])})
{:save-password (fn [[parameter]]
(dispatch [:save-password parameter]))
:sign-up (fn [[parameter]]
(dispatch [:sign-up parameter]))
:confirm-sign-up (fn [[parameter]]
(dispatch [:sign-up-confirm parameter]))})
(def regular-events {})
(defn command-hadler!
[_ [{:keys [to]} {:keys [result]} ]]
(when result
(let [{:keys [event params]} result
events (if (= "console" to)
(merge regular-events console-events)
regular-events)]
(when-let [handler (events (keyword event))]
(apply handler params)))))
[_ [chat-id {:keys [command] :as parameters} {:keys [result error]}]]
(cond
result
(let [{:keys [event params transaction-hash]} result
command' (assoc command :handler-data result)
parameters' (assoc parameters :command command')]
(if transaction-hash
(dispatch [:wait-for-transaction transaction-hash parameters'])
(let [events (if (= "console" chat-id)
(merge regular-events console-events)
regular-events)
parameters'' (if-let [handler (events (keyword event))]
(assoc parameters' :handler #(handler params command'))
parameters')]
(dispatch [:prepare-command! parameters'']))))
(not error)
(dispatch [:prepare-command! parameters])
:else nil))
(defn suggestions-handler!
[db [{:keys [chat-id]} {:keys [result]} ]]
[db [{:keys [chat-id]} {:keys [result]}]]
(let [{:keys [markup webViewUrl]} result
hiccup (generate-hiccup markup)]
(-> db
@ -52,13 +65,10 @@
nil))
(defn command-preview
[db [chat-id {:keys [result]}]]
[db [chat-id command-id {:keys [result]}]]
(if result
(let [path [:chats chat-id :staged-commands]
commands-cnt (count (get-in db path))]
;; todo (dec commands-cnt) looks like hack have to find better way to
;; do this
(update-in db (conj path (dec commands-cnt)) assoc
(let [path [:chats chat-id :staged-commands command-id]]
(update-in db path assoc
:preview (generate-hiccup result)
:preview-string (str result)))
db))
@ -73,18 +83,20 @@
(reg-handler ::render-command render-command)
(reg-handler :command-handler!
(after (print-error-message! "Error on command handling"))
(u/side-effect! command-hadler!))
(after (print-error-message! "Error on command handling"))
(u/side-effect! command-hadler!))
(reg-handler :suggestions-handler
[(after #(dispatch [:animate-show-response]))
(after (print-error-message! "Error on param suggestions"))
(after (fn [_ [{:keys [command]} {:keys [result]}]]
(when (= :on-send (keyword (:suggestions-trigger command)))
(when (:webViewUrl result)
(dispatch [:set-soft-input-mode :pan]))
(r/dismiss-keyboard!))))]
suggestions-handler!)
[(after #(dispatch [:animate-show-response]))
(after (print-error-message! "Error on param suggestions"))
(after (fn [_ [{:keys [command]} {:keys [result]}]]
(when (= :on-send (keyword (:suggestions-trigger command)))
(when (:webViewUrl result)
(dispatch [:set-soft-input-mode :pan]))
(r/dismiss-keyboard!))))]
suggestions-handler!)
(reg-handler :suggestions-event! (u/side-effect! suggestions-events-handler!))
(reg-handler :command-preview
(after (print-error-message! "Error on command preview"))
command-preview)
(after (print-error-message! "Error on command preview"))
command-preview)

View File

@ -1,6 +1,6 @@
(ns status-im.commands.handlers.loading
(:require-macros [status-im.utils.slurp :refer [slurp]])
(:require [re-frame.core :refer [after dispatch subscribe trim-v debug]]
(:require [re-frame.core :refer [path after dispatch subscribe trim-v debug]]
[status-im.utils.handlers :as u]
[status-im.utils.utils :refer [http-get toast]]
[clojure.string :as s]
@ -22,10 +22,13 @@
(defn fetch-commands!
[db [identity]]
(when-let [url (:dapp-url (get-in db [:chats identity]))]
(if (= "console" identity)
(when true
;;when-let [url (:dapp-url (get-in db [:chats identity]))]
;; todo fix this after demo
(if true
;(= "console" identity)
(dispatch [::validate-hash identity (slurp "resources/commands.js")])
(http-get (s/join "/" [url commands-js])
#_(http-get (s/join "/" [url commands-js])
#(dispatch [::validate-hash identity %])
#(dispatch [::loading-failed! identity ::file-was-not-found])))))
@ -68,8 +71,9 @@
(defn add-commands
[db [id _ {:keys [commands responses]}]]
(-> db
(update-in [:chats id :commands] merge (mark-as :command commands))
(update-in [:chats id :responses] merge (mark-as :response responses))))
(update-in [id :commands] merge (mark-as :command commands))
(update-in [id :responses] merge (mark-as :response responses))
(assoc-in [id :commands-loaded] true)))
(defn save-commands-js!
[_ [id file]]
@ -96,7 +100,8 @@
(reg-handler ::parse-commands! (u/side-effect! parse-commands!))
(reg-handler ::add-commands
(after save-commands-js!)
[(path :chats)
(after save-commands-js!)]
add-commands)
(reg-handler ::loading-failed! (u/side-effect! loading-failed!))

View File

@ -20,8 +20,10 @@
(defn get-active-page [data]
(get data :activePage 0))
(defn get-sneak [data]
(get data :sneak (:sneak defaults)))
(defn get-sneak [{:keys [sneak count] }]
(if (> (or count 2) 1)
(or sneak (:sneak defaults))
0))
(defn get-gap [data]
(get data :gap (:gap defaults)))
@ -64,7 +66,7 @@
state (reagent.core/state component)
page-width (get-page-width state)
gap (get-gap state)
page-position (* page (+ page-width gap))]
page-position (+ (* page page-width) (* (- page 1) gap))]
(log/debug "go-to-page: props-page-width=" page-width "; gap=" gap
"; page-position=" page-position)
(scroll-to component page-position 0)

View File

@ -14,7 +14,7 @@
{:flex 1})
(defn content-container [sneak gap]
{:paddingLeft (+ sneak (quot gap 2))
{:paddingLeft (+ 0 (quot gap 2))
:paddingRight (+ sneak (quot gap 2))})
(defn page [page-width margin]

View File

@ -31,9 +31,9 @@
:style st/photo-pencil}]])]))
(defview chat-icon-view [chat-id group-chat name online styles]
[photo-path [:chat-photo chat-id]]
[view (:container styles)
(if-not (s/blank? photo-path)
[photo-path [:chat-photo chat-id]]
[view (:container styles)
(if-not (or (s/blank? photo-path) (= chat-id "console"))
[chat-icon photo-path styles]
[default-chat-icon name styles])
(when-not group-chat

View File

@ -1,10 +1,20 @@
(ns status-im.components.geth
(:require [status-im.components.react :as r]))
(:require [status-im.components.react :as r]
[re-frame.core :refer [dispatch]]))
(def geth
(when (exists? (.-NativeModules r/react-native))
(.-Geth (.-NativeModules r/react-native))))
(defn register-signal-callback []
(when geth
(.registerSignalEventCallback
geth
#(do (dispatch [:signal-event %])
(register-signal-callback)))))
(register-signal-callback)
(defn start-node [on-result on-already-running]
(when geth
(.startNode geth on-result on-already-running)))
@ -15,4 +25,9 @@
(defn login [address password on-result]
(when geth
(.login geth address password on-result)))
(.login geth address password on-result)))
(defn complete-transaction
[hash callback]
(when geth
(.completeTransaction geth hash callback)))

View File

@ -79,6 +79,10 @@
{:width 18
:height 18})
(def icon-close
{:width 12
:height 12})
(def form-text-input
{:marginLeft -4
:fontSize 14

View File

@ -29,6 +29,7 @@
:lineColor "#0000001f"
:focusLineColor "#0000001f"
:errorColor "#d50000"
:secureTextEntry false
:onFocus #()
:onBlur #()
:onChangeText #()
@ -144,19 +145,19 @@
label-font-size
line-width
max-line-width] :as state} (r/state component)
{:keys [wrapperStyle inputStyle lineColor focusLineColor
{:keys [wrapperStyle inputStyle lineColor focusLineColor secureTextEntry
labelColor errorColor error label value onFocus onBlur
onChangeText onChange editable] :as props} (merge default-props (r/props component))
lineColor (if error errorColor lineColor)
focusLineColor (if error errorColor focusLineColor)
labelColor (if (and error (not float-label?)) errorColor labelColor)
label (if error (str label " *") label)]
;(log/debug "reagent-render: " data)
[view (merge st/text-field-container wrapperStyle)
[animated-text {:style (st/label label-top label-font-size labelColor)} label]
[text-input {:style (merge st/text-input inputStyle)
:placeholder ""
:editable editable
:secureTextEntry secureTextEntry
:onFocus #(on-focus {:component component
:animation {:top label-top
:to-top (:label-top config)
@ -192,4 +193,4 @@
:display-name "text-field"
:reagent-render reagent-render}]
;(log/debug "Creating text-field component: " data)
(r/create-class component-data)))
(r/create-class component-data)))

View File

@ -78,9 +78,10 @@
(into {})))
(defn add-identity [contacts-by-hash contacts]
(map (fn [{:keys [phone-number-hash whisper-identity]}]
(map (fn [{:keys [phone-number-hash whisper-identity address]}]
(let [contact (contacts-by-hash phone-number-hash)]
(assoc contact :whisper-identity whisper-identity)))
(assoc contact :whisper-identity whisper-identity
:address address)))
(js->clj contacts)))
(defn request-stored-contacts [contacts]

View File

@ -1,6 +1,7 @@
(ns status-im.contacts.subs
(:require-macros [reagent.ratom :refer [reaction]])
(:require [re-frame.core :refer [register-sub subscribe]]))
(:require [re-frame.core :refer [register-sub subscribe]]
[status-im.utils.identicon :refer [identicon]]))
(register-sub :get-contacts
(fn [db _]
@ -87,4 +88,6 @@
(if (:group-chat @chat)
;; TODO return group chat icon
nil
(:photo-path (first @contacts))))))))
(if (pos? (count @contacts))
(:photo-path (first @contacts))
(identicon chat-id))))))))

View File

@ -41,11 +41,10 @@
:keyboard-height 0
:disable-group-creation false
:animations {;; todo clear this
:tabs-bar-value (anim/create-value 0)}})
:tabs-bar-value (anim/create-value 0)}
:loading-allowed true})
(def protocol-initialized-path [:protocol-initialized])
(defn chat-input-text-path [chat-id]
[:chats chat-id :input-text])
(defn chat-staged-commands-path [chat-id]
[:chats chat-id :staged-commands])
(defn chat-command-path [chat-id]

View File

@ -23,7 +23,8 @@
status-im.qr-scanner.handlers
status-im.accounts.handlers
status-im.protocol.handlers
[status-im.utils.datetime :as time]))
[status-im.utils.datetime :as time]
status-im.transactions.handlers))
;; -- Middleware ------------------------------------------------------------
;;
@ -103,7 +104,7 @@
(dispatch [:crypt-initialized]))))))))
(defn node-started [db result]
(log/debug "Started Node: " result))
(log/debug "Started Node: "))
(register-handler :initialize-geth
(u/side-effect!

View File

@ -101,11 +101,6 @@
(r/single-cljs)
(r/list-to-array :contacts)))
(defn chat-by-id2 [chat-id]
(-> (r/get-by-field :account :chat :chat-id chat-id)
r/collection->map
first))
(defn chat-add-participants [chat-id identities]
(r/write :account
(fn []

View File

@ -1,5 +1,6 @@
(ns status-im.models.commands
(:require [status-im.db :as db]))
(:require [status-im.db :as db]
[tailrecursion.priority-map :refer [priority-map-by]]))
(defn get-commands [{:keys [current-chat-id] :as db}]
(or (get-in db [:chats current-chat-id :commands]) {}))
@ -37,21 +38,22 @@
[{:keys [current-chat-id] :as db}]
(get-in db (db/chat-command-to-msg-id-path current-chat-id)))
(defn compare-commands
[{created-at-1 :created-at} {created-at-2 :created-at}]
(compare created-at-1 created-at-2))
(defn stage-command
[{:keys [current-chat-id] :as db} command-info]
(update-in db (db/chat-staged-commands-path current-chat-id)
#(if %
(conj % command-info)
[command-info])))
[{:keys [current-chat-id] :as db} {:keys [id] :as command-info}]
(let [path (db/chat-staged-commands-path current-chat-id)
staged-commands (get-in db path)
staged-coomands' (if (seq staged-commands)
staged-commands
(priority-map-by compare-commands))]
(assoc-in db path (assoc staged-coomands' id command-info))))
(defn unstage-command [db staged-command]
(defn unstage-command [db {:keys [id]}]
(update-in db (db/chat-staged-commands-path (:current-chat-id db))
(fn [staged-commands]
(filterv #(not= % staged-command) staged-commands))))
(defn clear-staged-commands
[{:keys [current-chat-id] :as db}]
(assoc-in db (db/chat-staged-commands-path current-chat-id) []))
dissoc id))
(defn get-chat-command-request
[{:keys [current-chat-id] :as db}]

View File

@ -57,9 +57,13 @@
(r/collection->map))
(into '())
reverse
(keep (fn [{:keys [content-type] :as message}]
(keep (fn [{:keys [content-type preview] :as message}]
(if (command-type? content-type)
(update message :content str-to-map)
(-> message
(update :content str-to-map)
(assoc :rendered-preview
(when preview
(generate-hiccup (read-string preview)))))
message))))))
(defn update-message! [{:keys [msg-id] :as msg}]
@ -68,3 +72,7 @@
(fn []
(when (r/exists? :account :message :msg-id msg-id)
(r/create :account :message msg true)))))
(defn get-message [id]
(r/get-one-by-field :account :message :msg-id id))

View File

@ -25,8 +25,10 @@
(register-handler :navigate-to
(enrich preload-data!)
(fn [db [_ view-id]]
(push-view db view-id)))
(fn [{:keys [view-id] :as db} [_ new-view-id]]
(if (= view-id new-view-id)
db
(push-view db new-view-id))))
(register-handler :navigation-replace
(enrich preload-data!)

View File

@ -128,3 +128,8 @@
(after (fn [_ [_ chat-id]]
(dispatch [:remove-unviewed-messages chat-id])))]
(update-message-status :seen))
(register-handler :send-transaction!
(u/side-effect!
(fn [_ [_ amount message]]
(println :send-transacion! amount message))))

View File

@ -14,12 +14,11 @@
:active-group-ids (active-group-chats)
:storage kv/kv-store
:handler (fn [{:keys [event-type] :as event}]
(log/info "Event:" (clj->js event))
(case event-type
:initialized (let [{:keys [identity]} event]
(dispatch [:protocol-initialized identity]))
:new-msg (let [{:keys [from to payload]} event]
(dispatch [:received-msg (assoc payload :from from :to to)]))
(dispatch [:received-message (assoc payload :from from :to to)]))
:msg-acked (let [{:keys [msg-id from]} event]
(dispatch [:acked-msg from msg-id]))
:msg-seen (let [{:keys [msg-id from]} event]

View File

@ -6,7 +6,8 @@
status-im.discovery.subs
status-im.contacts.subs
status-im.new-group.subs
status-im.participants.subs))
status-im.participants.subs
status-im.transactions.subs))
(register-sub :get
(fn [db [_ k]]

View File

@ -0,0 +1,119 @@
(ns status-im.transactions.handlers
(:require [re-frame.core :refer [after dispatch debug enrich]]
[status-im.utils.handlers :refer [register-handler]]
[status-im.navigation.handlers :as nav]
[status-im.utils.handlers :as u]
[status-im.utils.types :as t]
[status-im.components.geth :as g]
cljsjs.web3
[clojure.string :as s]))
(defmethod nav/preload-data! :confirm
[{:keys [transactions-queue] :as db} _]
(assoc db :transactions transactions-queue))
(defn on-unlock [hashes]
(fn [result-str]
(let [{:keys [error]} (t/json->clj result-str)]
;; todo: add message about wrong password
(if (s/blank? error)
(do
(dispatch [:set :wrong-password? false])
(doseq [hash hashes]
(g/complete-transaction
hash
#(dispatch [:transaction-completed hash %])))
(dispatch [:navigate-back]))
(dispatch [:set :wrong-password? true])))))
(register-handler :accept-transactions
(u/side-effect!
(fn [{:keys [transactions current-account-id]} [_ password]]
(let [hashes (keys transactions)]
(g/login current-account-id password (on-unlock hashes))))))
(register-handler :deny-transactions
(u/side-effect!
(fn [{:keys [transactions]}]
(let [hashes (keys transactions)]
(dispatch [::remove-pending-messages hashes])
(dispatch [::remove-trqqansactions hashes])
(dispatch [:navigate-back])))))
(register-handler :deny-transaction
(u/side-effect!
(fn [_ [_ hash]]
(dispatch [::remove-pending-message hash])
(dispatch [::remove-transaction hash]))))
(register-handler ::remove-transactions
(fn [db [_ hashes]]
(-> db
(dissoc :transactions)
(update :transactions-queue #(apply dissoc % hashes)))))
(register-handler ::remove-transaction
(fn [db [_ hash]]
(-> db
(update :transactions dissoc hash)
(update :transactions-queue dissoc hash))))
(register-handler :wait-for-transaction
(fn [db [_ hash {:keys [chat-id command] :as params}]]
(let [id (:id command)]
(-> db
(update-in [:chats chat-id :staged-commands id] assoc :pending true)
(assoc-in [:transaction-subscribers hash] params)))))
(defn remove-pending-message [db hash]
(let [{:keys [chat-id command]} (get-in db [:transaction-subscribers hash])
path [:chats chat-id :staged-commands]]
(-> db
(update :transaction-subscribers dissoc hash)
(update-in path dissoc (:id command)))))
(register-handler ::remove-pending-messages
(fn [db [_ hashes]]
(reduce remove-pending-message db hashes)))
(register-handler ::remove-pending-message
(fn [db [_ hash]]
(remove-pending-message db hash)))
(register-handler :signal-event
(u/side-effect!
(fn [_ [_ event-str]]
(let [{:keys [type event]} (t/json->clj event-str)]
(case type
"sendTransactionQueued" (dispatch [:transaction-queued event]))))))
(register-handler :transaction-queued
(after #(dispatch [:navigate-to :confirm]))
(fn [db [_ {:keys [hash args]}]]
(let [{:keys [from to value]} args
transaction {:hash hash
:from from
:to to
:value (.toDecimal js/Web3.prototype value)}]
(assoc-in db [:transactions-queue hash] transaction))))
(register-handler :transaction-completed
(u/side-effect!
(fn [db [_ old-hash result-str]]
(let [{:keys [hash error]} (t/json->clj result-str)]
;; todo: handle error
(when hash
(dispatch [::send-pending-message old-hash hash])
(dispatch [::remove-transaction old-hash]))))))
(register-handler ::send-pending-message
(u/side-effect!
(fn [{:keys [transaction-subscribers] :as db} [_ old-hash new-hash]]
(when-let [params (transaction-subscribers old-hash)]
(let [params' (assoc-in params [:handler-data :transaction-hash] new-hash)]
(dispatch [:prepare-command! params']))
(dispatch [::remove-transaction-subscriber old-hash])))))
(register-handler ::remove-transaction-subscriber
(fn [db [_ old-hash]]
(update db :transaction-subscribers dissoc old-hash)))

View File

@ -0,0 +1,62 @@
(ns status-im.transactions.screen
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [subscribe dispatch]]
[status-im.components.react :refer [view
text
image
icon
scroll-view
touchable-highlight
touchable-opacity]]
[status-im.components.styles :refer [icon-ok
icon-close
toolbar-title-container]]
[status-im.components.carousel.carousel :refer [carousel]]
[status-im.components.toolbar :refer [toolbar]]
[status-im.components.text-field.view :refer [text-field]]
[status-im.transactions.views.transaction-page :refer [transaction-page]]
[status-im.transactions.styles :as st]
[status-im.i18n :refer [label label-pluralize]]
[clojure.string :as s]))
(defview confirm []
[transactions [:transactions]
{:keys [password]} [:get :confirm-transactions]
wrong-password? [:wrong-password?]]
[view st/transactions-screen
[toolbar
{:style st/transactions-toolbar
:nav-action {:image {:source {:uri :icon_close_white}
:style icon-close}
:handler #(dispatch [:deny-transactions])}
:custom-content [view {:style toolbar-title-container}
[text {:style st/toolbar-title-text}
(label-pluralize (count transactions) :t/confirm-transactions)]]
:action {:image {:source {:uri (if-not (s/blank? password)
:icon_ok
:icon_ok_disabled_inversed)}
:style icon-ok}
:handler #(dispatch [:accept-transactions password])}}]
[view st/carousel-container
[carousel {:pageStyle st/carousel-page-style
:gap 16
:count (count transactions)
:sneak 20}
(when transactions
(for [transaction transactions]
[transaction-page transaction]))]]
[view st/form-container
[text-field
{:inputStyle st/password-style
:secureTextEntry true
:error (when wrong-password? (label :t/wrong-password))
:errorColor :#ffffff80 #_:#7099e6
:lineColor :white
:labelColor :#ffffff80
:value password
:label (label :t/password)
:onChangeText #(dispatch [:set-in [:confirm-transactions :password] %])}]]])
;(re-frame.core/dispatch [:set :view-id :confirm])

View File

@ -0,0 +1,107 @@
(ns status-im.transactions.styles
(:require [status-im.components.styles :refer [toolbar-height
color-white]]))
(def transactions-screen
{:flex 1
:backgroundColor "#828b92"})
(def transactions-toolbar
{:backgroundColor "#828b92"
:elevation 0})
(def toolbar-title-text
{:color :white
:fontSize 16})
(def carousel-page-style
{})
(def form-container
{:flex 1
:paddingLeft 16})
(def password-style
{:color :white})
;transaction-page
(def transaction-page
{:flex 1
:backgroundColor "#f3f4f4"})
(def title-bar
{:backgroundColor :white
:height 39
:justifyContent :center})
(def title-bar-text
{:fontFamily "sans-serif-medium"
:color "#838c93"
:fontSize 13
:marginLeft 12})
(def icon-close-container
{:position :absolute
:right 12
:top 13})
(def icon-close
{:width 12
:height 12})
(def transaction-info-container
{:flex 1
:paddingTop 6})
(def scroll-view-container
{:flex 1})
(def scroll-view
{:flex 1
:height 175})
(def scroll-view-content
{:paddingVertical 6})
(def transaction-info-row
{:flex 1
:flexDirection :row
:height 20
})
(def transaction-info-column-title
{:flex 0.4
:flexDirection :column
:paddingHorizontal 6})
(def transaction-info-column-value
{:flex 0.6
:flexDirection :column
:paddingHorizontal 6})
(def transaction-info-item
{:flex 1
:padding 6})
(def transaction-info-title
{:textAlign :right
:color "#838c93de"
:fontSize 14
:lineHeight 20})
(def transaction-info-value
{:color "#000000de"
:fontSize 14
:lineHeight 20
})
(def scroll-view-item
{:flex 1
:height 20
:padding 6 })
(def carousel-container
{:min-height 215
:flex 1})

View File

@ -0,0 +1,28 @@
(ns status-im.transactions.subs
(:require-macros [reagent.ratom :refer [reaction]])
(:require [re-frame.core :refer [register-sub subscribe]]
[clojure.string :as s]))
(register-sub :transactions
(fn [db]
(reaction (vals (:transactions @db)))))
(register-sub :contacts-by-address
(fn [db]
(reaction (into {} (map (fn [[_ {:keys [address] :as contact}]]
[address contact])
(:contacts @db)
)))))
(register-sub :contact-by-address
(fn [_ [_ address]]
(let [contacts (subscribe [:contacts-by-address])
address' (when address
(if (s/starts-with? address "0x")
(subs address 2)
address))]
(reaction (@contacts address')))))
(register-sub :wrong-password?
(fn [db] (reaction (:wrong-password? @db))))

View File

@ -0,0 +1,49 @@
(ns status-im.transactions.views.transaction-page
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [subscribe dispatch]]
[status-im.components.react :refer [view
text
image
icon
scroll-view
touchable-highlight
touchable-opacity]]
[status-im.components.styles :refer [icon-ok
icon-close]]
[status-im.transactions.styles :as st]
[status-im.i18n :refer [label label-pluralize]]
cljsjs.web3))
(defn title-bar [title hash]
[view st/title-bar
[text {:style st/title-bar-text} title]
[touchable-highlight {:style st/icon-close-container
:on-press #(dispatch [:deny-transaction hash])}
[view [image {:source {:uri :icon_close_gray}
:style st/icon-close}]]]])
(defn transaction-info [index [name value]]
[view {:style st/transaction-info-item
:key index}
[view {:style st/transaction-info-row}
[view st/transaction-info-column-title
[text {:style st/transaction-info-title} name]]
[view st/transaction-info-column-value
[text {:style st/transaction-info-value} value]]]])
(defview transaction-page [{:keys [hash from to value] :as transaction}]
[{:keys [name] :as contact} [:contact-by-address to]]
(let [eth-value (.fromWei js/Web3.prototype value "ether")
title (str eth-value " ETH to " name)
transactions-info [[(label :t/status) (label :t/pending-confirmation)]
[(label :t/recipient) name]
[(label :t/value) (str eth-value " ETH")]]]
[view {:style st/transaction-page
:key hash}
[title-bar title hash]
[view st/scroll-view-container
[scroll-view {:style st/scroll-view
:contentContainerStyle st/scroll-view-content
:showsVerticalScrollIndicator true
:scrollEnabled true}
(map-indexed transaction-info transactions-info)]]]))

View File

@ -160,9 +160,22 @@
:login "Login"
:wrong-password "Wrong password"
;users
;accounts
:add-account "Add account"
;validation
:invalid-phone "Invalid phone number"
:amount "Amount"
:not-enough-eth (str "Not enough ETH on balance "
"({{balance}} ETH)")
;transactions
:confirm-transactions {:one "Confirm transaction"
:other "Confirm {{count}} transactions"
:zero "No transactions"}
:status "Status"
:pending-confirmation "Pending confirmation"
:recipient "Recipient"
:one-more-item "One more item"
:fee "Fee"
:value "Value"
})

View File

@ -4,15 +4,17 @@
[cljs-time.format :refer [formatters
formatter
unparse]]
[status-im.i18n :refer [label label-pluralize]]))
[status-im.i18n :refer [label label-pluralize]]
[goog.string :as gstring]
goog.string.format))
(def hour (* 1000 60 60))
(def day (* hour 24))
(def week (* 7 day))
(def units [{:name (label :t/datetime-second) :limit 60 :in-second 1}
{:name (label :t/datetime-minute) :limit 3600 :in-second 60}
{:name (label :t/datetime-hour) :limit 86400 :in-second 3600}
{:name (label :t/datetime-day) :limit nil :in-second 86400}])
(def units [{:name :t/datetime-second :limit 60 :in-second 1}
{:name :t/datetime-minute :limit 3600 :in-second 60}
{:name :t/datetime-hour :limit 86400 :in-second 3600}
{:name :t/datetime-day :limit nil :in-second 86400}])
(def time-zone-offset (hours (- (/ (.getTimezoneOffset (js/Date.)) 60))))
@ -29,6 +31,10 @@
(before? local today) (label :t/datetime-yesterday)
:else (unparse (formatters :hour-minute) local))))
(defn format-time-ago [diff unit]
(let [name (label-pluralize diff (:name unit))]
(gstring/format "%s %s %s" diff name (label :t/datetime-ago))))
(defn time-ago [time]
(let [diff (t/in-seconds (t/interval time (t/now)))]
(if (< diff 60)
@ -39,7 +45,7 @@
(-> (/ diff (:in-second unit))
Math/floor
int
(#(str % " " (label-pluralize % (:name unit)) " " (label :t/datetime-ago))))))))
(format-time-ago unit))))))
(defn to-date [ms]
(from-long ms))

View File

@ -1,5 +1,6 @@
(ns status-im.utils.handlers
(:require [re-frame.core :refer [after dispatch debug] :as re-core]))
(:require [re-frame.core :refer [after dispatch debug] :as re-core]
[re-frame.utils :refer [log]]))
(defn side-effect!
"Middleware for handlers that will not affect db."
@ -8,7 +9,18 @@
(handler db params)
db))
(defn debug-handlers-names
"Middleware which logs debug information to js/console for each event.
Includes a clojure.data/diff of the db, before vs after, showing the changes
caused by the event."
[handler]
(fn debug-handler
[db v]
(log "Handling re-frame event: " (first v))
(let [new-db (handler db v)]
new-db)))
(defn register-handler
([name handler] (register-handler name nil handler))
([name middleware handler]
(re-core/register-handler name [#_debug middleware] handler)))
(re-core/register-handler name [debug-handlers-names middleware] handler)))