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

Send transaction

Former-commit-id: 8148b7e01f
This commit is contained in:
Roman Volosovskyi 2016-08-19 11:53:42 +03:00 committed by GitHub
commit bf02104495
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-fs')
compile project(':react-native-image-crop-picker') compile project(':react-native-image-crop-picker')
//compile(name:'statusgo-android-16', ext:'aar') //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"]) compile fileTree(dir: "node_modules/realm/android/libs", include: ["*.jar"])
} }

View File

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

View File

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

View File

@ -3,6 +3,7 @@ package com.statusim.geth.module;
import android.app.Activity; import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.os.Message; import android.os.Message;
import android.os.RemoteException;
import com.facebook.react.bridge.*; import com.facebook.react.bridge.*;
import com.statusim.geth.service.ConnectorHandler; import com.statusim.geth.service.ConnectorHandler;
import com.statusim.geth.service.GethConnector; import com.statusim.geth.service.GethConnector;
@ -13,20 +14,19 @@ import android.util.Log;
import java.util.HashMap; import java.util.HashMap;
import java.util.UUID; 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"; private static final String TAG = "GethModule";
protected GethConnector geth = null; private GethConnector geth = null;
protected String handlerIdentifier = createIdentifier();
protected HashMap<String, Callback> startNodeCallbacks = new HashMap<>(); private HashMap<String, Callback> startNodeCallbacks = new HashMap<>();
protected HashMap<String, Callback> createAccountCallbacks = new HashMap<>(); private HashMap<String, Callback> createAccountCallbacks = new HashMap<>();
protected HashMap<String, Callback> addAccountCallbacks = new HashMap<>(); private HashMap<String, Callback> unlockAccountCallbacks = new HashMap<>();
protected HashMap<String, Callback> unlockAccountCallbacks = new HashMap<>(); private HashMap<String, Callback> transactionCallbacks = new HashMap<>();
public GethModule(ReactApplicationContext reactContext) { GethModule(ReactApplicationContext reactContext) {
super(reactContext); super(reactContext);
reactContext.addLifecycleEventListener(this); reactContext.addLifecycleEventListener(this);
} }
@ -67,12 +67,6 @@ public class GethModule extends ReactContextBaseJavaModule implements LifecycleE
} }
} }
@Override
public String getID() {
return handlerIdentifier;
}
@Override @Override
public void onConnectorConnected() { public void onConnectorConnected() {
} }
@ -89,7 +83,7 @@ public class GethModule extends ReactContextBaseJavaModule implements LifecycleE
Bundle data = message.getData(); Bundle data = message.getData();
String callbackIdentifier = data.getString(GethConnector.CALLBACK_IDENTIFIER); String callbackIdentifier = data.getString(GethConnector.CALLBACK_IDENTIFIER);
Log.d(TAG, "callback identifier: " + callbackIdentifier); Log.d(TAG, "callback identifier: " + callbackIdentifier);
Callback callback = null; Callback callback;
switch (message.what) { switch (message.what) {
case GethMessages.MSG_NODE_STARTED: case GethMessages.MSG_NODE_STARTED:
Log.d(TAG, "handle startNodeCallbacks size: " + startNodeCallbacks.size()); Log.d(TAG, "handle startNodeCallbacks size: " + startNodeCallbacks.size());
@ -108,18 +102,20 @@ public class GethModule extends ReactContextBaseJavaModule implements LifecycleE
callback.invoke(data.getString("data")); callback.invoke(data.getString("data"));
} }
break; 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: case GethMessages.MSG_LOGGED_IN:
callback = unlockAccountCallbacks.remove(callbackIdentifier); callback = unlockAccountCallbacks.remove(callbackIdentifier);
if (callback != null) { if (callback != null) {
callback.invoke(data.getString("result")); callback.invoke(data.getString("result"));
} }
break; 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: default:
isClaimed = false; isClaimed = false;
} }
@ -130,7 +126,7 @@ public class GethModule extends ReactContextBaseJavaModule implements LifecycleE
@ReactMethod @ReactMethod
public void startNode(Callback callback, Callback onAlreadyRunning) { public void startNode(Callback callback, Callback onAlreadyRunning) {
if(GethService.isRunning()){ if (GethService.isRunning()) {
onAlreadyRunning.invoke(); onAlreadyRunning.invoke();
return; return;
} }
@ -197,9 +193,12 @@ public class GethModule extends ReactContextBaseJavaModule implements LifecycleE
geth.createAccount(callbackIdentifier, password); geth.createAccount(callbackIdentifier, password);
} }
@ReactMethod private String createIdentifier() {
public void addAccount(String privateKey, Callback callback) { return UUID.randomUUID().toString();
}
@ReactMethod
public void completeTransaction(String hash, Callback callback) {
Activity currentActivity = getCurrentActivity(); Activity currentActivity = getCurrentActivity();
if (currentActivity == null) { if (currentActivity == null) {
@ -212,13 +211,27 @@ public class GethModule extends ReactContextBaseJavaModule implements LifecycleE
return; return;
} }
Log.d(TAG, "Complete transaction: " + hash);
String callbackIdentifier = createIdentifier(); String callbackIdentifier = createIdentifier();
addAccountCallbacks.put(callbackIdentifier, callback); transactionCallbacks.put(callbackIdentifier, callback);
geth.addAccount(callbackIdentifier, privateKey);
geth.completeTransaction(callbackIdentifier, hash);
} }
protected String createIdentifier() { private static Callback signalEventCallback;
return UUID.randomUUID().toString();
//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; import android.os.Message;
public interface ConnectorHandler { public interface ConnectorHandler {
boolean handleMessage(Message message); boolean handleMessage(Message message);
void onConnectorConnected(); void onConnectorConnected();
void onConnectorDisconnected(); 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()) { if (checkBound()) {
Bundle data = new Bundle(); Bundle data = new Bundle();
data.putString("privateKey", privateKey); data.putString("hash", hash);
Message msg = createMessage(callbackIdentifier, GethMessages.MSG_ADD_ACCOUNT, data); Message msg = createMessage(callbackIdentifier, GethMessages.MSG_COMPLETE_TRANSACTION, data);
try { try {
serviceMessenger.send(msg); serviceMessenger.send(msg);
} catch (RemoteException e) { } 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) { if (!isBound) {
Log.d(TAG, "GethConnector not bound!"); Log.d(TAG, "GethConnector not bound!");
@ -95,7 +94,7 @@ public class GethConnector extends ServiceConnector {
return true; 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()); Log.d(TAG, "Client messenger: " + clientMessenger.toString());
Message msg = Message.obtain(null, idMessage, 0, 0); Message msg = Message.obtain(null, idMessage, 0, 0);

View File

@ -6,7 +6,7 @@ public class GethMessages {
/** /**
* Start the node * Start the node
*/ */
public static final int MSG_START_NODE = 1; static final int MSG_START_NODE = 1;
/** /**
* Node started event * Node started event
@ -16,7 +16,7 @@ public class GethMessages {
/** /**
* Stop the node * Stop the node
*/ */
public static final int MSG_STOP_NODE = 3; static final int MSG_STOP_NODE = 3;
/** /**
* Node stopped event * Node stopped event
@ -26,7 +26,7 @@ public class GethMessages {
/** /**
* Unlock an account * Unlock an account
*/ */
public static final int MSG_LOGIN = 5; static final int MSG_LOGIN = 5;
/** /**
* Account unlocked event * Account unlocked event
@ -36,7 +36,7 @@ public class GethMessages {
/** /**
* Create an account * Create an account
*/ */
public static final int MSG_CREATE_ACCOUNT = 7; static final int MSG_CREATE_ACCOUNT = 7;
/** /**
* Account created event * Account created event
@ -44,13 +44,13 @@ public class GethMessages {
public static final int MSG_ACCOUNT_CREATED = 8; 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 final String TAG = "GethService";
private static boolean isGethStarted = false;
private static boolean isGethInitialized = false; private static boolean isGethInitialized = false;
private final Handler handler = new Handler(); private final Handler handler = new Handler();
private static String dataFolder; private static String dataFolder;
static class IncomingHandler extends Handler { private static class IncomingHandler extends Handler {
private final WeakReference<GethService> service; private final WeakReference<GethService> service;
IncomingHandler(GethService service) { IncomingHandler(GethService service) {
this.service = new WeakReference<GethService>(service); this.service = new WeakReference<>(service);
} }
@Override @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) { 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 @Nullable
@ -68,7 +70,6 @@ public class GethService extends Service {
super.onDestroy(); super.onDestroy();
//TODO: stop geth //TODO: stop geth
stopNode(null); stopNode(null);
isGethStarted = false;
isGethInitialized = false; isGethInitialized = false;
Log.d(TAG, "Geth Service stopped !"); Log.d(TAG, "Geth Service stopped !");
} }
@ -78,7 +79,7 @@ public class GethService extends Service {
return Service.START_STICKY; return Service.START_STICKY;
} }
protected boolean handleMessage(Message message) { private boolean handleMessage(Message message) {
switch (message.what) { switch (message.what) {
case GethMessages.MSG_START_NODE: case GethMessages.MSG_START_NODE:
@ -94,14 +95,14 @@ public class GethService extends Service {
createAccount(message); createAccount(message);
break; break;
case GethMessages.MSG_ADD_ACCOUNT:
addAccount(message);
break;
case GethMessages.MSG_LOGIN: case GethMessages.MSG_LOGIN:
login(message); login(message);
break; break;
case GethMessages.MSG_COMPLETE_TRANSACTION:
completeTransaction(message);
break;
default: default:
return false; return false;
} }
@ -109,7 +110,7 @@ public class GethService extends Service {
return true; return true;
} }
protected void startNode(Message message) { private void startNode(Message message) {
if (!isGethInitialized) { if (!isGethInitialized) {
isGethInitialized = true; isGethInitialized = true;
Log.d(TAG, "Client messenger1: " + message.replyTo.toString()); 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; String callbackIdentifier;
protected Messenger messenger; Messenger messenger;
public StartTask(Messenger messenger, String callbackIdentifier) { StartTask(Messenger messenger, String callbackIdentifier) {
this.messenger = messenger; this.messenger = messenger;
this.callbackIdentifier = callbackIdentifier; 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"); Log.d(TAG, "Geth Service started");
isGethStarted = true;
Message replyMessage = Message.obtain(null, GethMessages.MSG_NODE_STARTED, 0, 0, null); Message replyMessage = Message.obtain(null, GethMessages.MSG_NODE_STARTED, 0, 0, null);
Bundle replyData = new Bundle(); Bundle replyData = new Bundle();
Log.d(TAG, "Callback identifier: " + callbackIdentifier); Log.d(TAG, "Callback identifier: " + callbackIdentifier);
@ -151,7 +151,7 @@ public class GethService extends Service {
sendReply(messenger, replyMessage); sendReply(messenger, replyMessage);
} }
protected void startGeth() { private void startGeth() {
File extStore = Environment.getExternalStorageDirectory(); File extStore = Environment.getExternalStorageDirectory();
@ -160,28 +160,38 @@ public class GethService extends Service {
extStore.getAbsolutePath() + "/ethereum" : extStore.getAbsolutePath() + "/ethereum" :
getApplicationInfo().dataDir + "/ethereum"; getApplicationInfo().dataDir + "/ethereum";
Log.d(TAG, "Starting background Geth Service in folder: " + dataFolder); Log.d(TAG, "Starting background Geth Service in folder: " + dataFolder);
try { try {
final File newFile = new File(dataFolder); final File newFile = new File(dataFolder);
// todo handle error?
newFile.mkdir(); newFile.mkdir();
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "error making folder: " + dataFolder, 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() { new Thread(new Runnable() {
public void run() { public void run() {
Statusgo.StartNode(dataFolder); Statusgo.StartNode(dataFolder);
} }
}).start(); }).start();
handler.postDelayed(addPeer, 5000);
} }
protected void stopNode(Message message) { private void stopNode(Message message) {
// TODO: stop node // TODO: stop node
createAndSendReply(message, GethMessages.MSG_NODE_STOPPED, null); createAndSendReply(message, GethMessages.MSG_NODE_STOPPED, null);
} }
protected void createAccount(Message message) { private void createAccount(Message message) {
Bundle data = message.getData(); Bundle data = message.getData();
String password = data.getString("password"); String password = data.getString("password");
// TODO: remove second argument // TODO: remove second argument
@ -194,21 +204,7 @@ public class GethService extends Service {
createAndSendReply(message, GethMessages.MSG_ACCOUNT_CREATED, replyData); createAndSendReply(message, GethMessages.MSG_ACCOUNT_CREATED, replyData);
} }
protected void addAccount(Message message) { private void login(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) {
Bundle data = message.getData(); Bundle data = message.getData();
String address = data.getString("address"); String address = data.getString("address");
String password = data.getString("password"); String password = data.getString("password");
@ -221,11 +217,24 @@ public class GethService extends Service {
createAndSendReply(message, GethMessages.MSG_LOGGED_IN, replyData); 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() { public static boolean isRunning() {
return isGethInitialized; return isGethInitialized;
} }
protected void createAndSendReply(Message message, int replyIdMessage, Bundle replyData) { private static void createAndSendReply(Message message, int replyIdMessage, Bundle replyData) {
if (message == null) { if (message == null) {
return; return;
@ -243,7 +252,7 @@ public class GethService extends Service {
sendReply(message.replyTo, replyMessage); sendReply(message.replyTo, replyMessage);
} }
protected void sendReply(Messenger messenger, Message message) { private static void sendReply(Messenger messenger, Message message) {
try { try {
messenger.send(message); messenger.send(message);

View File

@ -11,39 +11,27 @@ import java.util.ArrayList;
public class ServiceConnector { 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 */ /** 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 */ /** The class of the service we want to connect to */
protected Class serviceClass; private Class serviceClass;
/** Flag indicating if the service is bound. */ /** Flag indicating if the service is bound. */
protected boolean isBound; boolean isBound;
/** Sends messages to the service. */ /** Sends messages to the service. */
protected Messenger serviceMessenger = null; Messenger serviceMessenger = null;
/** Receives messages from the service. */ /** 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. */ /** Handles incoming messages from service. */
class IncomingHandler extends Handler { private class IncomingHandler extends Handler {
public IncomingHandler(HandlerThread thread) { IncomingHandler(HandlerThread thread) {
super(thread.getLooper()); super(thread.getLooper());
} }
@ -72,7 +60,7 @@ public class ServiceConnector {
/** /**
* Class for interacting with the main interface of the service. * 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) { 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.context = context;
this.serviceClass = serviceClass; this.serviceClass = serviceClass;
handlerThread = new HandlerThread("HandlerThread"); // Handler thread to avoid running on the main UI thread
HandlerThread handlerThread = new HandlerThread("HandlerThread");
handlerThread.start(); handlerThread.start();
handler = new IncomingHandler(handlerThread); // Incoming message handler. Calls to its binder are sequential!
IncomingHandler handler = new IncomingHandler(handlerThread);
clientMessenger = new Messenger(handler); 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!) (enable-console-print!)
(set! js/console.disableYellowBox true)
(def cnt (r/atom 0)) (def cnt (r/atom 0))
(defn reloader [] @cnt [core/app-root]) (defn reloader [] @cnt [core/app-root])
(def root-el (r/as-element [reloader])) (def root-el (r/as-element [reloader]))
@ -14,4 +16,4 @@
:heads-up-display false :heads-up-display false
:jsload-callback #(swap! cnt inc)) :jsload-callback #(swap! cnt inc))
(core/init) (core/init)

View File

@ -9,12 +9,15 @@
[re-frame "0.7.0"] [re-frame "0.7.0"]
[prismatic/schema "1.0.4"] [prismatic/schema "1.0.4"]
^{:voom {:repo "git@github.com:status-im/status-lib.git" ^{:voom {:repo "git@github.com:status-im/status-lib.git"
:branch "discover-rework"}} :branch "master"}}
[status-im/protocol "0.1.3-20160818_085900-gda79e8e"] [status-im/protocol "0.1.3-20160818_172519-g2f734a6"]
[natal-shell "0.3.0"] [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"] :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"] :clean-targets ["target/" "index.ios.js" "index.android.js"]
:aliases {"prod-build" ^{:doc "Recompile code with prod profile."} :aliases {"prod-build" ^{:doc "Recompile code with prod profile."}
["do" "clean" ["do" "clean"

View File

@ -260,3 +260,60 @@ status.command({
type: status.types.TEXT 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.name = com.name;
this.description = com.description; this.description = com.description;
this.handler = com.handler; this.handler = com.handler;
this["has-handler"] = com.handler != null;
this.validator = com.validator; this.validator = com.validator;
this.color = com.color; this.color = com.color;
this.icon = com.icon; this.icon = com.icon;
@ -110,7 +111,7 @@ function validationMessage(titleText, descriptionText) {
}; };
var description = status.components.text(descriptionStyle, descriptionText); var description = status.components.text(descriptionStyle, descriptionText);
var message = status.components.view( return status.components.view(
{ {
backgroundColor: "red", backgroundColor: "red",
height: 61, height: 61,
@ -119,8 +120,6 @@ function validationMessage(titleText, descriptionText) {
}, },
[title, description] [title, description]
); );
return message;
} }
var status = { var status = {

View File

@ -39,7 +39,7 @@
:address address :address address
:name address :name address
:photo-path (identicon public-key)}] :photo-path (identicon public-key)}]
(log/debug "account-created: " account) (log/debug "account-created")
(when (not (str/blank? public-key)) (when (not (str/blank? public-key))
(do (do
(dispatch-sync [:add-account account]) (dispatch-sync [:add-account account])
@ -90,7 +90,7 @@
(defn logged-in [db address] (defn logged-in [db address]
(let [is-login-screen? (= (:view-id db) :login) (let [is-login-screen? (= (:view-id db) :login)
new-account? (not is-login-screen?)] new-account? (not is-login-screen?)]
(log/debug "Logged in: " address) (log/debug "Logged in: ")
(realm/change-account-realm address new-account? (realm/change-account-realm address new-account?
#(if (nil? %) #(if (nil? %)
(initialize-account db address) (initialize-account db address)
@ -98,16 +98,17 @@
(register-handler (register-handler
:login-account :login-account
(-> (fn [db [_ address password]] (u/side-effect!
(geth/login address password (fn [result] (fn [db [_ address password]]
(let [data (json->clj result) (geth/login address password
error (:error data) (fn [result]
success (zero? (count error))] (let [data (json->clj result)
(log/debug "Logged in account: " address result) error (:error data)
(if success success (zero? (count error))]
(logged-in db address) (log/debug "Logged in account: ")
(dispatch [:set-in [:login :error] error]))))) (if success
db))) (logged-in db address)
(dispatch [:set-in [:login :error] error]))))))))
(defn load-accounts! [db _] (defn load-accounts! [db _]
(let [accounts (->> (accounts/get-accounts) (let [accounts (->> (accounts/get-accounts)
@ -120,7 +121,7 @@
(defn console-create-account [db _] (defn console-create-account [db _]
(let [msg-id (random/id)] (let [msg-id (random/id)]
(dispatch [:received-msg (dispatch [:received-message
{:msg-id msg-id {:msg-id msg-id
:content {:command (name :keypair) :content {:command (name :keypair)
:content (label :t/keypair-generated)} :content (label :t/keypair-generated)}
@ -130,4 +131,4 @@
:to "me"}]) :to "me"}])
db)) 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.chat.screen :refer [chat]]
[status-im.accounts.login.screen :refer [login]] [status-im.accounts.login.screen :refer [login]]
[status-im.accounts.screen :refer [accounts]] [status-im.accounts.screen :refer [accounts]]
[status-im.transactions.screen :refer [confirm]]
[status-im.chats-list.screen :refer [chats-list]] [status-im.chats-list.screen :refer [chats-list]]
[status-im.new-group.screen :refer [new-group]] [status-im.new-group.screen :refer [new-group]]
[status-im.participants.views.add :refer [new-participants]] [status-im.participants.views.add :refer [new-participants]]
@ -98,6 +99,7 @@
:profile-photo-capture profile-photo-capture :profile-photo-capture profile-photo-capture
:accounts accounts :accounts accounts
:login login :login login
:confirm confirm
:my-profile my-profile)] :my-profile my-profile)]
[component {:platform-specific {:styles styles [component {:platform-specific {:styles styles
:list-selection-fn show-dialog}}])))}))) :list-selection-fn show-dialog}}])))})))

View File

@ -1,5 +1,6 @@
(ns status-im.chat.handlers (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] [status-im.models.commands :as commands]
[clojure.string :as str] [clojure.string :as str]
[status-im.components.styles :refer [default-chat-color]] [status-im.components.styles :refer [default-chat-color]]
@ -20,31 +21,39 @@
[status-im.handlers.content-suggestions :refer [get-content-suggestions]] [status-im.handlers.content-suggestions :refer [get-content-suggestions]]
[status-im.utils.phone-number :refer [format-phone-number [status-im.utils.phone-number :refer [format-phone-number
valid-mobile-number?]] valid-mobile-number?]]
[status-im.utils.datetime :as time]
[status-im.components.jail :as j] [status-im.components.jail :as j]
[status-im.utils.types :refer [json->clj]] [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.handlers.commands :refer [command-prefix]]
[status-im.chat.utils :refer [console? not-console?]]
status-im.chat.handlers.animation status-im.chat.handlers.animation
status-im.chat.handlers.requests 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 (register-handler :set-show-actions
(fn [db [_ show-actions]] (fn [db [_ show-actions]]
(assoc db :show-actions show-actions))) (assoc db :show-actions show-actions)))
(register-handler :load-more-messages (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?])] (let [all-loaded? (get-in db [:chats current-chat-id :all-loaded?])]
(if all-loaded? (if loading-allowed
db (do (am/go
(let [messages-path [:chats current-chat-id :messages] (<! (a/timeout 400))
messages (get-in db messages-path) (dispatch [:set :loading-allowed true]))
new-messages (messages/get-messages current-chat-id (count messages)) (if all-loaded?
all-loaded? (> default-number-of-messages (count new-messages))] db
(-> db (let [messages-path [:chats current-chat-id :messages]
(update-in messages-path concat new-messages) messages (get-in db messages-path)
(assoc-in [:chats current-chat-id :all-loaded?] all-loaded?))))))) 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] (defn safe-trim [s]
(when (string? s) (when (string? s)
@ -99,12 +108,6 @@
(dispatch [:set-chat-command (ffirst suggestions)]) (dispatch [:set-chat-command (ffirst suggestions)])
(dispatch [::set-text chat-id text])))) (dispatch [::set-text chat-id text]))))
(defn console? [s]
(= "console" s))
(def not-console?
(complement console?))
(register-handler :set-chat-input-text (register-handler :set-chat-input-text
(u/side-effect! (u/side-effect!
(fn [{:keys [current-chat-id]} [_ text]] (fn [{:keys [current-chat-id]} [_ text]]
@ -142,23 +145,6 @@
(register-handler ::set-text update-text) (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 (defn set-message-shown
[db chat-id msg-id] [db chat-id msg-id]
(update-in db [:chats chat-id :messages] (fn [messages] (update-in db [:chats chat-id :messages] (fn [messages]
@ -172,141 +158,14 @@
(fn [db [_ {:keys [chat-id msg-id]}]] (fn [db [_ {:keys [chat-id msg-id]}]]
(set-message-shown db 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 (register-handler :init-console-chat
(fn [db [_]] (fn [db [_]]
(sign-up-service/init db))) (sign-up-service/init db)))
(register-handler :save-password (register-handler :save-password
(fn [db [_ password]] (fn [db [_ password]]
(dispatch [:create-account password])
(sign-up-service/save-password password) (sign-up-service/save-password password)
(dispatch [:create-account password])
(assoc db :password-saved true))) (assoc db :password-saved true)))
(register-handler :sign-up (register-handler :sign-up
@ -372,35 +231,6 @@
(after #(dispatch [:load-unviewed-messages!])) (after #(dispatch [:load-unviewed-messages!]))
((enrich initialize-chats) load-chats!)) ((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 (register-handler :group-received-msg
(u/side-effect! (u/side-effect!
(fn [_ [_ {chat-id :group-id :as msg}]] (fn [_ [_ {chat-id :group-id :as msg}]]
@ -412,6 +242,7 @@
messages (get-in db [:chats chat-id :messages]) messages (get-in db [:chats chat-id :messages])
db' (assoc db :current-chat-id chat-id)] db' (assoc db :current-chat-id chat-id)]
(dispatch [:load-requests! chat-id]) (dispatch [:load-requests! chat-id])
(dispatch [:load-commands! chat-id])
(if (seq messages) (if (seq messages)
db' db'
(-> db' (-> db'
@ -419,15 +250,15 @@
init-chat)))) init-chat))))
(defn prepare-chat (defn prepare-chat
[{:keys [contacts] :as db} [_ contcat-id]] [{:keys [contacts] :as db} [_ contact-id]]
(let [name (get-in contacts [contcat-id :name]) (let [name (get-in contacts [contact-id :name])
chat {:chat-id contcat-id chat {:chat-id contact-id
:name name :name (or name contact-id)
:color default-chat-color :color default-chat-color
:group-chat false :group-chat false
:is-active true :is-active true
:timestamp (.getTime (js/Date.)) :timestamp (.getTime (js/Date.))
:contacts [{:identity contcat-id}] :contacts [{:identity contact-id}]
:dapp-url nil :dapp-url nil
:dapp-hash nil}] :dapp-hash nil}]
(assoc db :new-chat chat))) (assoc db :new-chat chat)))
@ -451,6 +282,11 @@
((after save-chat!)) ((after save-chat!))
((after open-chat!)))) ((after open-chat!))))
(register-handler :add-chat
(-> prepare-chat
((enrich add-chat))
((after save-chat!))))
(register-handler :switch-command-suggestions! (register-handler :switch-command-suggestions!
(u/side-effect! (u/side-effect!
(fn [db] (fn [db]

View File

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

View File

@ -7,7 +7,9 @@
[clojure.string :as str] [clojure.string :as str]
[status-im.commands.utils :as cu] [status-im.commands.utils :as cu]
[status-im.utils.phone-number :as pn] [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 ") (def command-prefix "c ")
@ -61,7 +63,7 @@
(defn invoke-command-preview! (defn invoke-command-preview!
[{:keys [staged-command]} [_ chat-id]] [{:keys [staged-command]} [_ chat-id]]
(let [{:keys [command content]} staged-command (let [{:keys [command content id]} staged-command
{:keys [name type]} command {:keys [name type]} command
path [(if (= :command type) :commands :responses) path [(if (= :command type) :commands :responses)
name name
@ -70,7 +72,7 @@
(j/call chat-id (j/call chat-id
path path
params params
#(dispatch [:command-preview chat-id %])))) #(dispatch [:command-preview chat-id id %]))))
(defn command-input (defn command-input
([{:keys [current-chat-id] :as db}] ([{:keys [current-chat-id] :as db}]
@ -102,7 +104,8 @@
path [(if (= :command type) :commands :responses) path [(if (= :command type) :commands :responses)
name name
:validator] :validator]
params {:value content}] params {:value content
:command data}]
(j/call chat-id (j/call chat-id
path path
params params
@ -110,13 +113,14 @@
(register-handler :stage-command (register-handler :stage-command
(after start-validate!) (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) (let [{:keys [command content]} (command-input db)
content' (content-by-command command content)] content' (content-by-command command content)]
(-> db (-> db
(assoc ::command {:content content' (assoc ::command {:content content'
:command command :command command
:chat-id current-chat-id}) :chat-id current-chat-id
:address current-account-id})
(assoc-in [:disable-staging current-chat-id] true))))) (assoc-in [:disable-staging current-chat-id] true)))))
(register-handler ::finish-command-staging (register-handler ::finish-command-staging
@ -128,7 +132,9 @@
content' (content-by-command command content) content' (content-by-command command content)
command-info {:command command command-info {:command command
:content content' :content content'
:to-message to-msg-id}] :to-message to-msg-id
:created-at (time/now-ms)
:id (random/id)}]
(-> db (-> db
(commands/stage-command command-info) (commands/stage-command command-info)
(assoc :staged-command command-info) (assoc :staged-command command-info)
@ -172,14 +178,19 @@
(fn [db] (fn [db]
(dissoc db :validation-errors :custom-validation-errors))) (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 (def validation-handlers
{:phone (fn [chat-id [number]] {:phone (fn [chat-id [number]]
(if (pn/valid-mobile-number? number) (if (pn/valid-mobile-number? number)
(dispatch [::finish-command-staging chat-id]) (dispatch [::finish-command-staging chat-id])
(dispatch [::set-validation-error (dispatch-error! chat-id :t/phone-number :t/invalid-phone)))})
chat-id
{:title (i18n/label :t/phone-number)
:description (i18n/label :t/invalid-phone)}])))})
(defn validator [name] (defn validator [name]
(validation-handlers (keyword name))) (validation-handlers (keyword name)))
@ -190,7 +201,6 @@
(when-let [handler (validator name)] (when-let [handler (validator name)]
(handler chat-id params))))) (handler chat-id params)))))
(register-handler ::set-validation-error (register-handler ::set-validation-error
(after #(dispatch [:fix-response-height])) (after #(dispatch [:fix-response-height]))
(fn [db [_ chat-id error]] (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 (ns status-im.chat.handlers.requests
(:require [re-frame.core :refer [after dispatch enrich]] (:require [re-frame.core :refer [after dispatch enrich]]
[status-im.utils.handlers :refer [register-handler]] [status-im.utils.handlers :refer [register-handler]]
[status-im.persistence.realm.core :as realm]
[status-im.models.requests :as requests] [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! (defn store-request!
[{:keys [new-request] :as db}] [{: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}] (defn actions-list-view [{styles :styles :as platform-specific}]
(let [{:keys [group-chat chat-id]} (subscribe [:chat-properties [:group-chat :chat-id]]) (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])] status-bar-height (get-in styles [:components :status-bar :default :height])]
(when-let [actions (if @group-chat (when-let [actions (if @group-chat
[{:title (label :t/members-title) [{:title (label :t/members-title)
@ -203,9 +203,10 @@
(label :t/active-unknown)))) (label :t/active-unknown))))
(defn toolbar-content [platform-specific] (defn toolbar-content [platform-specific]
(let [{:keys [group-chat chat-id name contacts]} (subscribe [:chat-properties [:group-chat :chat-id :name :contacts]]) (let [{:keys [group-chat name contacts chat-id]}
contact (subscribe [:get-in [:contacts @chat-id]]) (subscribe [:chat-properties [:group-chat :name :contacts :chat-id]])
show-actions (subscribe [:show-actions])] show-actions (subscribe [:show-actions])
contact (subscribe [:get-in [:contacts @chat-id]])]
(fn [] (fn []
[view (st/chat-name-view @show-actions) [view (st/chat-name-view @show-actions)
[text {:style st/chat-name-text [text {:style st/chat-name-text
@ -242,7 +243,7 @@
(defn chat-toolbar [platform-specific] (defn chat-toolbar [platform-specific]
(let [{:keys [group-chat name contacts]} (subscribe [:chat-properties [:group-chat :name :contacts]]) (let [{:keys [group-chat name contacts]} (subscribe [:chat-properties [:group-chat :name :contacts]])
show-actions (subscribe [:show-actions])] show-actions (subscribe [:show-actions])]
[view [view
[status-bar {:platform-specific platform-specific}] [status-bar {:platform-specific platform-specific}]
[toolbar {:hide-nav? @show-actions [toolbar {:hide-nav? @show-actions
@ -252,14 +253,15 @@
(defview messages-view [platform-specific group-chat] (defview messages-view [platform-specific group-chat]
[messages [:chat :messages] [messages [:chat :messages]
contacts [:chat :contacts]] contacts [:chat :contacts]
loaded? [:all-messages-loaded?]]
(let [contacts' (contacts-by-identity contacts)] (let [contacts' (contacts-by-identity contacts)]
[list-view {:renderRow (message-row {:contact-by-identity contacts' [list-view {:renderRow (message-row {:contact-by-identity contacts'
:platform-specific platform-specific :platform-specific platform-specific
:group-chat group-chat :group-chat group-chat
:messages-count (count messages)}) :messages-count (count messages)})
:renderScrollComponent #(invertible-scroll-view (js->clj %)) :renderScrollComponent #(invertible-scroll-view (js->clj %))
:onEndReached #(dispatch [:load-more-messages]) :onEndReached (when-not loaded? #(dispatch [:load-more-messages]))
:enableEmptySections true :enableEmptySections true
:keyboardShouldPersistTaps true :keyboardShouldPersistTaps true
:dataSource (to-datasource-inverted messages)}])) :dataSource (to-datasource-inverted messages)}]))

View File

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

View File

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

View File

@ -70,6 +70,7 @@
(fn [db _] (fn [db _]
(->> [:chats (:current-chat-id @db) :staged-commands] (->> [:chats (:current-chat-id @db) :staged-commands]
(get-in @db) (get-in @db)
vals
(reaction)))) (reaction))))
(register-sub :valid-plain-message? (register-sub :valid-plain-message?
@ -232,3 +233,8 @@
20 20
:else 0))))) :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.db :as db]
[status-im.models.commands :refer [get-commands [status-im.models.commands :refer [get-commands
get-chat-command-request get-chat-command-request
get-chat-command-to-msg-id get-chat-command-to-msg-id]]
clear-staged-commands]]
[status-im.utils.utils :refer [log on-error http-get]] [status-im.utils.utils :refer [log on-error http-get]]
[clojure.string :as s])) [clojure.string :as s]))
@ -39,15 +38,6 @@
(fn [] (fn []
(command-handler to-msg-id command-key content))))) (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] (defn check-suggestion [db message]
(when-let [suggestion-text (when (string? message) (when-let [suggestion-text (when (string? message)
(re-matches #"^![^\s]+\s" message))] (re-matches #"^![^\s]+\s" message))]
@ -60,7 +50,3 @@
(-> db (-> db
(get-in [:chats (:current-chat-id db) :input-text]) (get-in [:chats (:current-chat-id db) :input-text])
suggestion?)) 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 [text {:style st/command-text
:platform-specific platform-specific :platform-specific platform-specific
:font :default} :font :default}
content])])) (str content)])]))
(defn set-chat-command [msg-id command] (defn set-chat-command [msg-id command]
(dispatch [:set-response-chat-command msg-id (keyword (:name command))])) (dispatch [:set-response-chat-command msg-id (keyword (:name command))]))
@ -114,7 +114,7 @@
[text {:style (st/text-message message) [text {:style (st/text-message message)
:platform-specific platform-specific :platform-specific platform-specific
:font :default} :font :default}
content]]) (str content)]])
(defmethod message-content text-content-type (defmethod message-content text-content-type
[wrapper message platform-specific] [wrapper message platform-specific]
@ -213,9 +213,7 @@
:callback anim-callback} :callback anim-callback}
on-update (message-container-animation-logic context)] on-update (message-container-animation-logic context)]
(r/create-class (r/create-class
{:component-did-mount {:component-did-update
on-update
:component-did-update
on-update on-update
:reagent-render :reagent-render
(fn [message & children] (fn [message & children]
@ -241,7 +239,8 @@
(dispatch [:send-seen! chat-id msg-id]))) (dispatch [:send-seen! chat-id msg-id])))
:reagent-render :reagent-render
(fn [{:keys [outgoing delivery-status timestamp new-day group-chat] (fn [{:keys [outgoing delivery-status timestamp new-day group-chat]
:as message}] :as message}
platform-specific]
[message-container message [message-container message
;; TODO there is no new-day info in message ;; TODO there is no new-day info in message
(when new-day (when new-day

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,20 @@
(ns status-im.components.geth (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 (def geth
(when (exists? (.-NativeModules r/react-native)) (when (exists? (.-NativeModules r/react-native))
(.-Geth (.-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] (defn start-node [on-result on-already-running]
(when geth (when geth
(.startNode geth on-result on-already-running))) (.startNode geth on-result on-already-running)))
@ -15,4 +25,9 @@
(defn login [address password on-result] (defn login [address password on-result]
(when geth (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 {:width 18
:height 18}) :height 18})
(def icon-close
{:width 12
:height 12})
(def form-text-input (def form-text-input
{:marginLeft -4 {:marginLeft -4
:fontSize 14 :fontSize 14

View File

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

View File

@ -78,9 +78,10 @@
(into {}))) (into {})))
(defn add-identity [contacts-by-hash contacts] (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)] (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))) (js->clj contacts)))
(defn request-stored-contacts [contacts] (defn request-stored-contacts [contacts]

View File

@ -1,6 +1,7 @@
(ns status-im.contacts.subs (ns status-im.contacts.subs
(:require-macros [reagent.ratom :refer [reaction]]) (: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 (register-sub :get-contacts
(fn [db _] (fn [db _]
@ -87,4 +88,6 @@
(if (:group-chat @chat) (if (:group-chat @chat)
;; TODO return group chat icon ;; TODO return group chat icon
nil 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 :keyboard-height 0
:disable-group-creation false :disable-group-creation false
:animations {;; todo clear this :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]) (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] (defn chat-staged-commands-path [chat-id]
[:chats chat-id :staged-commands]) [:chats chat-id :staged-commands])
(defn chat-command-path [chat-id] (defn chat-command-path [chat-id]

View File

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

View File

@ -101,11 +101,6 @@
(r/single-cljs) (r/single-cljs)
(r/list-to-array :contacts))) (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] (defn chat-add-participants [chat-id identities]
(r/write :account (r/write :account
(fn [] (fn []

View File

@ -1,5 +1,6 @@
(ns status-im.models.commands (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}] (defn get-commands [{:keys [current-chat-id] :as db}]
(or (get-in db [:chats current-chat-id :commands]) {})) (or (get-in db [:chats current-chat-id :commands]) {}))
@ -37,21 +38,22 @@
[{:keys [current-chat-id] :as db}] [{:keys [current-chat-id] :as db}]
(get-in db (db/chat-command-to-msg-id-path current-chat-id))) (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 (defn stage-command
[{:keys [current-chat-id] :as db} command-info] [{:keys [current-chat-id] :as db} {:keys [id] :as command-info}]
(update-in db (db/chat-staged-commands-path current-chat-id) (let [path (db/chat-staged-commands-path current-chat-id)
#(if % staged-commands (get-in db path)
(conj % command-info) staged-coomands' (if (seq staged-commands)
[command-info]))) 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)) (update-in db (db/chat-staged-commands-path (:current-chat-id db))
(fn [staged-commands] dissoc id))
(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) []))
(defn get-chat-command-request (defn get-chat-command-request
[{:keys [current-chat-id] :as db}] [{:keys [current-chat-id] :as db}]

View File

@ -57,9 +57,13 @@
(r/collection->map)) (r/collection->map))
(into '()) (into '())
reverse reverse
(keep (fn [{:keys [content-type] :as message}] (keep (fn [{:keys [content-type preview] :as message}]
(if (command-type? content-type) (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)))))) message))))))
(defn update-message! [{:keys [msg-id] :as msg}] (defn update-message! [{:keys [msg-id] :as msg}]
@ -68,3 +72,7 @@
(fn [] (fn []
(when (r/exists? :account :message :msg-id msg-id) (when (r/exists? :account :message :msg-id msg-id)
(r/create :account :message msg true))))) (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 (register-handler :navigate-to
(enrich preload-data!) (enrich preload-data!)
(fn [db [_ view-id]] (fn [{:keys [view-id] :as db} [_ new-view-id]]
(push-view db view-id))) (if (= view-id new-view-id)
db
(push-view db new-view-id))))
(register-handler :navigation-replace (register-handler :navigation-replace
(enrich preload-data!) (enrich preload-data!)

View File

@ -128,3 +128,8 @@
(after (fn [_ [_ chat-id]] (after (fn [_ [_ chat-id]]
(dispatch [:remove-unviewed-messages chat-id])))] (dispatch [:remove-unviewed-messages chat-id])))]
(update-message-status :seen)) (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) :active-group-ids (active-group-chats)
:storage kv/kv-store :storage kv/kv-store
:handler (fn [{:keys [event-type] :as event}] :handler (fn [{:keys [event-type] :as event}]
(log/info "Event:" (clj->js event))
(case event-type (case event-type
:initialized (let [{:keys [identity]} event] :initialized (let [{:keys [identity]} event]
(dispatch [:protocol-initialized identity])) (dispatch [:protocol-initialized identity]))
:new-msg (let [{:keys [from to payload]} event] :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] :msg-acked (let [{:keys [msg-id from]} event]
(dispatch [:acked-msg from msg-id])) (dispatch [:acked-msg from msg-id]))
:msg-seen (let [{:keys [msg-id from]} event] :msg-seen (let [{:keys [msg-id from]} event]

View File

@ -6,7 +6,8 @@
status-im.discovery.subs status-im.discovery.subs
status-im.contacts.subs status-im.contacts.subs
status-im.new-group.subs status-im.new-group.subs
status-im.participants.subs)) status-im.participants.subs
status-im.transactions.subs))
(register-sub :get (register-sub :get
(fn [db [_ k]] (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" :login "Login"
:wrong-password "Wrong password" :wrong-password "Wrong password"
;users ;accounts
:add-account "Add account" :add-account "Add account"
;validation ;validation
:invalid-phone "Invalid phone number" :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 [cljs-time.format :refer [formatters
formatter formatter
unparse]] 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 hour (* 1000 60 60))
(def day (* hour 24)) (def day (* hour 24))
(def week (* 7 day)) (def week (* 7 day))
(def units [{:name (label :t/datetime-second) :limit 60 :in-second 1} (def units [{:name :t/datetime-second :limit 60 :in-second 1}
{:name (label :t/datetime-minute) :limit 3600 :in-second 60} {:name :t/datetime-minute :limit 3600 :in-second 60}
{:name (label :t/datetime-hour) :limit 86400 :in-second 3600} {:name :t/datetime-hour :limit 86400 :in-second 3600}
{:name (label :t/datetime-day) :limit nil :in-second 86400}]) {:name :t/datetime-day :limit nil :in-second 86400}])
(def time-zone-offset (hours (- (/ (.getTimezoneOffset (js/Date.)) 60)))) (def time-zone-offset (hours (- (/ (.getTimezoneOffset (js/Date.)) 60))))
@ -29,6 +31,10 @@
(before? local today) (label :t/datetime-yesterday) (before? local today) (label :t/datetime-yesterday)
:else (unparse (formatters :hour-minute) local)))) :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] (defn time-ago [time]
(let [diff (t/in-seconds (t/interval time (t/now)))] (let [diff (t/in-seconds (t/interval time (t/now)))]
(if (< diff 60) (if (< diff 60)
@ -39,7 +45,7 @@
(-> (/ diff (:in-second unit)) (-> (/ diff (:in-second unit))
Math/floor Math/floor
int int
(#(str % " " (label-pluralize % (:name unit)) " " (label :t/datetime-ago)))))))) (format-time-ago unit))))))
(defn to-date [ms] (defn to-date [ms]
(from-long ms)) (from-long ms))

View File

@ -1,5 +1,6 @@
(ns status-im.utils.handlers (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! (defn side-effect!
"Middleware for handlers that will not affect db." "Middleware for handlers that will not affect db."
@ -8,7 +9,18 @@
(handler db params) (handler db params)
db)) 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 (defn register-handler
([name handler] (register-handler name nil handler)) ([name handler] (register-handler name nil handler))
([name middleware handler] ([name middleware handler]
(re-core/register-handler name [#_debug middleware] handler))) (re-core/register-handler name [debug-handlers-names middleware] handler)))