diff --git a/.re-natal b/.re-natal index f1c7bbd66b..657ba14566 100644 --- a/.re-natal +++ b/.re-natal @@ -1,7 +1,7 @@ { "name": "StatusIm", "interface": "reagent", - "androidHost": "10.0.3.2", + "androidHost": "localhost", "modules": [ "react-native-contacts", "react-native-invertible-scroll-view", @@ -17,7 +17,9 @@ "dismissKeyboard", "react-native-linear-gradient", "react-native-android-sms-listener", - "react-native-status" + "react-native-status", + "react-native-camera", + "react-native-qrcode" ], "imageDirs": [ "images" diff --git a/android/app/build.gradle b/android/app/build.gradle index edc54bc340..4182c69686 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -130,6 +130,7 @@ dependencies { compile project(':react-native-i18n') compile project(':react-native-linear-gradient') compile project(':ReactNativeAndroidSmsListener') + compile project(':react-native-camera') compile project(':react-native-status') // todo replace this when jail will be integrated into geth compile (name: "geth-android-16", ext:"aar") diff --git a/android/app/src/main/java/com/statusim/MainActivity.java b/android/app/src/main/java/com/statusim/MainActivity.java index dc1a4cd324..a074f3f653 100644 --- a/android/app/src/main/java/com/statusim/MainActivity.java +++ b/android/app/src/main/java/com/statusim/MainActivity.java @@ -8,31 +8,35 @@ import com.facebook.react.shell.MainReactPackage; import com.rt2zz.reactnativecontacts.ReactNativeContacts; import android.os.Bundle; import android.os.Environment; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface.OnCancelListener; import com.github.ethereum.go_ethereum.cmd.Geth; import com.bitgo.randombytes.RandomBytesPackage; import com.BV.LinearGradient.LinearGradientPackage; import com.centaurwarchief.smslistener.SmsListener; +import android.os.Handler; +import android.util.Log; + import java.util.Arrays; import java.util.List; import java.util.Properties; import java.io.File; import com.statusim.Jail.JailPackage; +import com.lwansbrough.RCTCamera.*; import com.i18n.reactnativei18n.ReactNativeI18n; import io.realm.react.RealmReactPackage; + public class MainActivity extends ReactActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + final Handler handler = new Handler(); - // Required for android-16 (???) - System.loadLibrary("gethraw"); - System.loadLibrary("geth"); - - // Required because of crazy APN settings redirecting localhost + protected void startStatus() { + // Required because of crazy APN settings redirecting localhost (found in GB) Properties properties = System.getProperties(); properties.setProperty("http.nonProxyHosts", "localhost|127.0.0.1"); properties.setProperty("https.nonProxyHosts", "localhost|127.0.0.1"); @@ -44,11 +48,56 @@ public class MainActivity extends ReactActivity { getApplicationInfo().dataDir; // Launch! + final Runnable addPeer = new Runnable() { + public void run() { + Log.w("Geth", "adding peer"); + Geth.run("--exec admin.addPeer(\"enode://e2f28126720452aa82f7d3083e49e6b3945502cb94d9750a15e27ee310eed6991618199f878e5fbc7dfa0e20f0af9554b41f491dc8f1dbae8f0f2d37a3a613aa@139.162.13.89:55555\") attach http://localhost:8545"); + } + }; new Thread(new Runnable() { public void run() { - Geth.run("--bootnodes enode://e2f28126720452aa82f7d3083e49e6b3945502cb94d9750a15e27ee310eed6991618199f878e5fbc7dfa0e20f0af9554b41f491dc8f1dbae8f0f2d37a3a613aa@139.162.13.89:30303 --shh --ipcdisable --nodiscover --rpc --rpcapi db,eth,net,web3,shh,admin --fast --datadir=" + dataFolder); + Geth.run("--shh --ipcdisable --nodiscover --rpc --rpcapi db,eth,net,web3,shh,admin --fast --datadir=" + dataFolder); + } }).start(); + handler.postDelayed(addPeer, 5000); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Required for android-16 (???) + // Crash if put in startStatus() ? + System.loadLibrary("gethraw"); + System.loadLibrary("geth"); + + if(!RootUtil.isDeviceRooted()) { + startStatus(); + } else { + AlertDialog dialog = new AlertDialog.Builder(MainActivity.this).setMessage(getResources().getString(R.string.root_warning)) + .setPositiveButton(getResources().getString(R.string.root_okay), new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + startStatus(); + } + }).setNegativeButton(getResources().getString(R.string.root_cancel), new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + MainActivity.this.finishAffinity(); + } + }).setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + dialog.dismiss(); + MainActivity.this.finishAffinity(); + } + }).create(); + dialog.show(); + } + } /** @@ -84,6 +133,7 @@ public class MainActivity extends ReactActivity { new ReactNativeI18n(), new RandomBytesPackage(), new LinearGradientPackage(), + new RCTCameraPackage(), new SmsListener(this) ); } diff --git a/android/app/src/main/java/com/statusim/RootUtil.java b/android/app/src/main/java/com/statusim/RootUtil.java new file mode 100644 index 0000000000..c5b9e88df0 --- /dev/null +++ b/android/app/src/main/java/com/statusim/RootUtil.java @@ -0,0 +1,41 @@ +package com.statusim; + +import java.io.File; +import java.io.BufferedReader; +import java.io.InputStreamReader; + +/** @author Kevin Kowalewski */ +public class RootUtil { + + public static boolean isDeviceRooted() { + return checkRootMethod1() || checkRootMethod2() || checkRootMethod3(); + } + + private static boolean checkRootMethod1() { + String buildTags = android.os.Build.TAGS; + return buildTags != null && buildTags.contains("test-keys"); + } + + private static boolean checkRootMethod2() { + String[] paths = { "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", + "/system/bin/failsafe/su", "/data/local/su" }; + for (String path : paths) { + if (new File(path).exists()) return true; + } + return false; + } + + private static boolean checkRootMethod3() { + Process process = null; + try { + process = Runtime.getRuntime().exec(new String[] { "/system/xbin/which", "su" }); + BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); + if (in.readLine() != null) return true; + return false; + } catch (Throwable t) { + return false; + } finally { + if (process != null) process.destroy(); + } + } +} diff --git a/android/app/src/main/res/drawable-hdpi/corner_left_bottom.png b/android/app/src/main/res/drawable-hdpi/corner_left_bottom.png new file mode 100644 index 0000000000..1c9c9b446d Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/corner_left_bottom.png differ diff --git a/android/app/src/main/res/drawable-hdpi/corner_left_top.png b/android/app/src/main/res/drawable-hdpi/corner_left_top.png new file mode 100644 index 0000000000..1a0a4e57f8 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/corner_left_top.png differ diff --git a/android/app/src/main/res/drawable-hdpi/corner_right_bottom.png b/android/app/src/main/res/drawable-hdpi/corner_right_bottom.png new file mode 100644 index 0000000000..e6071d1ae1 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/corner_right_bottom.png differ diff --git a/android/app/src/main/res/drawable-hdpi/corner_right_top.png b/android/app/src/main/res/drawable-hdpi/corner_right_top.png new file mode 100644 index 0000000000..6eaca3cd9a Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/corner_right_top.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_back_white.png b/android/app/src/main/res/drawable-hdpi/icon_back_white.png new file mode 100644 index 0000000000..6bd5aec7c4 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_back_white.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_close_white.png b/android/app/src/main/res/drawable-hdpi/icon_close_white.png new file mode 100644 index 0000000000..79f55e700a Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_close_white.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_dollar_green.png b/android/app/src/main/res/drawable-hdpi/icon_dollar_green.png new file mode 100644 index 0000000000..32cf51e421 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_dollar_green.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_drag_white.png b/android/app/src/main/res/drawable-hdpi/icon_drag_white.png new file mode 100644 index 0000000000..6a079b6f6f Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_drag_white.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_qr.png b/android/app/src/main/res/drawable-hdpi/icon_qr.png new file mode 100644 index 0000000000..6b2f825374 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_qr.png differ diff --git a/android/app/src/main/res/drawable-mdpi/corner_left_bottom.png b/android/app/src/main/res/drawable-mdpi/corner_left_bottom.png new file mode 100644 index 0000000000..da09ea7a24 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/corner_left_bottom.png differ diff --git a/android/app/src/main/res/drawable-mdpi/corner_left_top.png b/android/app/src/main/res/drawable-mdpi/corner_left_top.png new file mode 100644 index 0000000000..988d93651c Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/corner_left_top.png differ diff --git a/android/app/src/main/res/drawable-mdpi/corner_right_bottom.png b/android/app/src/main/res/drawable-mdpi/corner_right_bottom.png new file mode 100644 index 0000000000..e74544e83b Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/corner_right_bottom.png differ diff --git a/android/app/src/main/res/drawable-mdpi/corner_right_top.png b/android/app/src/main/res/drawable-mdpi/corner_right_top.png new file mode 100644 index 0000000000..33726dfa01 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/corner_right_top.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_back_white.png b/android/app/src/main/res/drawable-mdpi/icon_back_white.png new file mode 100644 index 0000000000..ac448ee920 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_back_white.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_close_white.png b/android/app/src/main/res/drawable-mdpi/icon_close_white.png new file mode 100644 index 0000000000..956a2c04fb Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_close_white.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_dollar_green.png b/android/app/src/main/res/drawable-mdpi/icon_dollar_green.png new file mode 100644 index 0000000000..fc6f77f756 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_dollar_green.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_drag_white.png b/android/app/src/main/res/drawable-mdpi/icon_drag_white.png new file mode 100644 index 0000000000..7845600020 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_drag_white.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_qr.png b/android/app/src/main/res/drawable-mdpi/icon_qr.png new file mode 100644 index 0000000000..c8b6ef10fd Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_qr.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/corner_left_bottom.png b/android/app/src/main/res/drawable-xhdpi/corner_left_bottom.png new file mode 100644 index 0000000000..b43f25a58d Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/corner_left_bottom.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/corner_left_top.png b/android/app/src/main/res/drawable-xhdpi/corner_left_top.png new file mode 100644 index 0000000000..f8180a9c1c Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/corner_left_top.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/corner_right_bottom.png b/android/app/src/main/res/drawable-xhdpi/corner_right_bottom.png new file mode 100644 index 0000000000..ce0a7ab9c0 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/corner_right_bottom.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/corner_right_top.png b/android/app/src/main/res/drawable-xhdpi/corner_right_top.png new file mode 100644 index 0000000000..2ae2e61860 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/corner_right_top.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_back_white.png b/android/app/src/main/res/drawable-xhdpi/icon_back_white.png new file mode 100644 index 0000000000..bcc735db8e Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_back_white.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_close_white.png b/android/app/src/main/res/drawable-xhdpi/icon_close_white.png new file mode 100644 index 0000000000..5c8005f904 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_close_white.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_dollar_green.png b/android/app/src/main/res/drawable-xhdpi/icon_dollar_green.png new file mode 100644 index 0000000000..cdea0d4af4 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_dollar_green.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_drag_white.png b/android/app/src/main/res/drawable-xhdpi/icon_drag_white.png new file mode 100644 index 0000000000..2b200673bb Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_drag_white.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_qr.png b/android/app/src/main/res/drawable-xhdpi/icon_qr.png new file mode 100644 index 0000000000..c55551f687 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_qr.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/corner_left_bottom.png b/android/app/src/main/res/drawable-xxhdpi/corner_left_bottom.png new file mode 100644 index 0000000000..0fff341220 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/corner_left_bottom.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/corner_left_top.png b/android/app/src/main/res/drawable-xxhdpi/corner_left_top.png new file mode 100644 index 0000000000..e25037ece3 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/corner_left_top.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/corner_right_bottom.png b/android/app/src/main/res/drawable-xxhdpi/corner_right_bottom.png new file mode 100644 index 0000000000..e542ec0d64 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/corner_right_bottom.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/corner_right_top.png b/android/app/src/main/res/drawable-xxhdpi/corner_right_top.png new file mode 100644 index 0000000000..a6a65aedc9 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/corner_right_top.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_back_white.png b/android/app/src/main/res/drawable-xxhdpi/icon_back_white.png new file mode 100644 index 0000000000..166bc865a4 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_back_white.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_close_white.png b/android/app/src/main/res/drawable-xxhdpi/icon_close_white.png new file mode 100644 index 0000000000..32e84becb6 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_close_white.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_dollar_green.png b/android/app/src/main/res/drawable-xxhdpi/icon_dollar_green.png new file mode 100644 index 0000000000..dac30955fe Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_dollar_green.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_drag_white.png b/android/app/src/main/res/drawable-xxhdpi/icon_drag_white.png new file mode 100644 index 0000000000..23fa971c5e Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_drag_white.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_qr.png b/android/app/src/main/res/drawable-xxhdpi/icon_qr.png new file mode 100644 index 0000000000..f7b3c5ad15 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_qr.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/corner_left_bottom.png b/android/app/src/main/res/drawable-xxxhdpi/corner_left_bottom.png new file mode 100644 index 0000000000..79d8ff98d3 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/corner_left_bottom.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/corner_left_top.png b/android/app/src/main/res/drawable-xxxhdpi/corner_left_top.png new file mode 100644 index 0000000000..8ccdd18d77 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/corner_left_top.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/corner_right_bottom.png b/android/app/src/main/res/drawable-xxxhdpi/corner_right_bottom.png new file mode 100644 index 0000000000..1fa347dffe Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/corner_right_bottom.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/corner_right_top.png b/android/app/src/main/res/drawable-xxxhdpi/corner_right_top.png new file mode 100644 index 0000000000..a26ebe0e61 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/corner_right_top.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_back_white.png b/android/app/src/main/res/drawable-xxxhdpi/icon_back_white.png new file mode 100644 index 0000000000..8992428891 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_back_white.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_close_white.png b/android/app/src/main/res/drawable-xxxhdpi/icon_close_white.png new file mode 100644 index 0000000000..68dbc7fb36 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_close_white.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_dollar_green.png b/android/app/src/main/res/drawable-xxxhdpi/icon_dollar_green.png new file mode 100644 index 0000000000..1f3c1c57d1 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_dollar_green.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_drag_white.png b/android/app/src/main/res/drawable-xxxhdpi/icon_drag_white.png new file mode 100644 index 0000000000..fd3f08e9ae Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_drag_white.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_qr.png b/android/app/src/main/res/drawable-xxxhdpi/icon_qr.png new file mode 100644 index 0000000000..863a0a5eb1 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_qr.png differ diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index fb87cafc71..428623ba43 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,3 +1,6 @@ - StatusIm - + Status + Your phone appears to be ROOTED, by pressing CONTINUE you understand and accept the risks in using this software. + Continue + Exit + \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle index a42e8ee4c2..fa0064d1f4 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -21,3 +21,6 @@ project(':ReactNativeAndroidSmsListener').projectDir = new File(rootProject.proj include ':react-native-status' project(':react-native-status').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-status/android') +include ':react-native-camera' +project(':react-native-camera').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-camera/android') + diff --git a/env/dev/env/android/main.cljs b/env/dev/env/android/main.cljs index 1bf474e505..f0c3d96fca 100644 --- a/env/dev/env/android/main.cljs +++ b/env/dev/env/android/main.cljs @@ -10,7 +10,7 @@ (def root-el (r/as-element [reloader])) (figwheel/watch-and-reload - :websocket-url "ws://localhost:3449/figwheel-ws" + :websocket-url "ws://10.0.3.2:3449/figwheel-ws" :heads-up-display false :jsload-callback #(swap! cnt inc)) diff --git a/package.json b/package.json index e6ea438243..59e41e8753 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,14 @@ "react-native": "^0.24.1", "react-native-action-button": "^1.1.4", "react-native-android-sms-listener": "^0.1.3", + "react-native-camera": "github:codyhazelwood/react-native-camera", "react-native-circle-checkbox": "^0.1.3", "react-native-contacts": "^0.2.4", "react-native-i18n": "0.0.8", "react-native-invertible-scroll-view": "^1.0.0", "react-native-linear-gradient": "^1.5.7", "react-native-loading-spinner-overlay": "0.0.8", + "react-native-qrcode": "^0.2.2", "react-native-randombytes": "^2.0.0", "react-native-vector-icons": "^1.3.4", "realm": "^0.11.1", diff --git a/resources/commands.js b/resources/commands.js index b4820adf51..47bd9909d4 100644 --- a/resources/commands.js +++ b/resources/commands.js @@ -62,6 +62,7 @@ var descriptionStyle = { }; function startsWith(str1, str2) { + // String.startsWith(...) doesn't work in otto return str1.lastIndexOf(str2, 0) == 0 && str1 != str2; } @@ -134,7 +135,7 @@ status.response({ name: "confirmation-code", color: "#7099e6", description: "Confirmation code", - parameters: [{ + params: [{ name: "code", type: status.types.NUMBER }], @@ -151,7 +152,7 @@ status.response({ color: "#7099e6", description: "Keypair password", icon: "icon_lock_white", - parameters: [{ + params: [{ name: "password", type: status.types.PASSWORD }], diff --git a/src/status_im/android/core.cljs b/src/status_im/android/core.cljs index c864b06ec6..951c7d1b32 100644 --- a/src/status_im/android/core.cljs +++ b/src/status_im/android/core.cljs @@ -8,6 +8,8 @@ [status-im.components.react :refer [navigator app-registry]] [status-im.components.main-tabs :refer [main-tabs]] [status-im.contacts.screen :refer [contact-list]] + [status-im.contacts.views.new-contact :refer [new-contact]] + [status-im.qr-scanner.screen :refer [qr-scanner]] [status-im.discovery.screen :refer [discovery]] [status-im.discovery.tag :refer [discovery-tag]] [status-im.chat.screen :refer [chat]] @@ -44,6 +46,8 @@ :new-group [new-group] :group-settings [group-settings] :contact-list [main-tabs] + :new-contact [new-contact] + :qr-scanner [qr-scanner] :chat [chat] :profile [profile] :my-profile [my-profile])))) diff --git a/src/status_im/chat/handlers.cljs b/src/status_im/chat/handlers.cljs index d7d6b4cc60..d66ab6440b 100644 --- a/src/status_im/chat/handlers.cljs +++ b/src/status_im/chat/handlers.cljs @@ -3,6 +3,7 @@ [status-im.models.commands :as commands] [clojure.string :as str] [status-im.components.styles :refer [default-chat-color]] + [status-im.chat.styles.response :refer [request-info-height response-height-normal]] [status-im.chat.suggestions :as suggestions] [status-im.protocol.api :as api] [status-im.models.messages :as messages] @@ -15,10 +16,13 @@ [status-im.utils.handlers :as u] [status-im.persistence.realm :as r] [status-im.handlers.server :as server] + [status-im.handlers.content-suggestions :refer [get-content-suggestions]] [status-im.utils.phone-number :refer [format-phone-number]] [status-im.utils.datetime :as time] [status-im.components.jail :as j] - [status-im.commands.utils :refer [generate-hiccup]])) + [status-im.commands.utils :refer [generate-hiccup]] + [status-im.chat.handlers.animation :refer [update-response-height + get-response-height]])) (register-handler :set-show-actions (fn [db [_ show-actions]] @@ -59,10 +63,27 @@ #(dispatch [:suggestions-handler {:command command :content content} %])))) +(register-handler :start-cancel-command + (u/side-effect! + (fn [db _] + (if (commands/get-chat-command-to-msg-id db) + (dispatch [:animate-cancel-command]) + (dispatch [:cancel-command]))))) + +(defn animate-set-chat-command-content [db _] + (when (commands/get-chat-command-to-msg-id db) + (dispatch [:animate-response-resize]))) + (register-handler :set-chat-command-content - (after invoke-suggestions-handler!) - (fn [db [_ content]] - (commands/set-chat-command-content db content))) + [(after invoke-suggestions-handler!) + (after animate-set-chat-command-content)] + (fn [{:keys [current-chat-id] :as db} [_ content]] + (as-> db db + (commands/set-chat-command-content db content) + (assoc-in db [:chats current-chat-id :input-text] nil) + (if (commands/get-chat-command-to-msg-id db) + (update-response-height db) + db)))) (defn update-input-text [{:keys [current-chat-id] :as db} text] @@ -85,7 +106,7 @@ (register-handler :stage-command (after invoke-command-preview!) (fn [{:keys [current-chat-id] :as db} _] - (let [db (update-input-text db nil) + (let [db (update-input-text db nil) {:keys [command content]} (get-in db [:chats current-chat-id :command-input]) command-info {:command command @@ -94,8 +115,19 @@ (commands/stage-command command-info) (assoc :staged-command command-info))))) +(register-handler :set-message-input [] + (fn [db [_ input]] + (assoc db :message-input input))) + +(register-handler :blur-message-input + (u/side-effect! + (fn [db _] + (when-let [message-input (:message-input db)] + (.blur message-input))))) + (register-handler :set-response-chat-command - (after invoke-suggestions-handler!) + [(after invoke-suggestions-handler!) + (after #(dispatch [:animate-show-response]))] (fn [db [_ to-msg-id command-key]] (commands/set-response-chat-command db to-msg-id command-key))) @@ -104,8 +136,12 @@ (update-input-text db text)) (defn update-command [db [_ text]] - (let [[command] (suggestions/check-suggestion db text)] - (commands/set-chat-command db command))) + (if-not (commands/get-chat-command db) + (let [{:keys [command]} (suggestions/check-suggestion db text)] + (if command + (commands/set-chat-command db command) + db)) + db)) (register-handler :set-chat-input-text ((enrich update-command) update-text)) @@ -130,7 +166,21 @@ (defn add-message-to-db [db chat-id message] (let [messages [:chats chat-id :messages]] - (update-in db messages conj message))) + (update-in db messages conj (assoc message :chat-id chat-id + :new? true)))) + +(defn set-message-shown + [db chat-id msg-id] + (update-in db [:chats chat-id :messages] (fn [messages] + (map (fn [msg] + (if (= msg-id (:msg-id msg)) + (assoc msg :new? false) + msg)) + messages)))) + +(register-handler :set-message-shown + (fn [db [_ {:keys [chat-id msg-id]}]] + (set-message-shown db chat-id msg-id))) (defn prepare-message [{:keys [identity current-chat-id] :as db} _] diff --git a/src/status_im/chat/handlers/animation.cljs b/src/status_im/chat/handlers/animation.cljs new file mode 100644 index 0000000000..2859261ab3 --- /dev/null +++ b/src/status_im/chat/handlers/animation.cljs @@ -0,0 +1,111 @@ +(ns status-im.chat.handlers.animation + (:require [re-frame.core :refer [register-handler after dispatch]] + [re-frame.middleware :refer [path]] + [status-im.models.commands :as commands] + [status-im.handlers.content-suggestions :refer [get-content-suggestions]] + [status-im.chat.styles.message-input :refer [input-height]] + [status-im.chat.styles.response :refer [request-info-height response-height-normal]] + [status-im.chat.styles.response-suggestions :as response-suggestions-styles] + [status-im.constants :refer [response-input-hiding-duration]])) + +(def zero-height input-height) + +(defn animation-handler + ([name handler] (animation-handler name nil handler)) + ([name middleware handler] + (register-handler name [(path :animations) middleware] handler))) + +(animation-handler :finish-animate-cancel-command + (fn [db _] + (assoc db :commands-input-is-switching? false))) + +(animation-handler :animate-cancel-command + (fn [db _] + (if-not (:commands-input-is-switching? db) + (assoc db + :commands-input-is-switching? true + :message-input-buttons-scale 1 + :message-input-offset 0 + :to-response-height zero-height + :messages-offset 0) + db))) + +(animation-handler :finish-animate-response-resize + (fn [db _] + (let [fixed (:to-response-height db)] + (assoc db :response-height-current fixed + :response-resize? false)))) + +(animation-handler :set-response-height + (fn [db [_ value]] + (assoc db :response-height-current value))) + +(animation-handler :animate-response-resize + (fn [db _] + (assoc db :response-resize? true))) + +(defn get-response-height [db] + (let [command (commands/get-chat-command db) + text (commands/get-chat-command-content db) + suggestions (get-content-suggestions command text) + suggestions-height (reduce + 0 (map #(if (:header %) + response-suggestions-styles/header-height + response-suggestions-styles/suggestion-height) + suggestions))] + (+ zero-height + (min response-height-normal (+ suggestions-height request-info-height))))) + +(defn update-response-height [db] + (assoc-in db [:animations :to-response-height] (get-response-height db))) + +(animation-handler :finish-show-response + (fn [db _] + (assoc db :commands-input-is-switching? false))) + +(register-handler :animate-show-response + (after #(dispatch [:animate-response-resize])) + (fn [db _] + (-> db + (assoc-in [:animations :commands-input-is-switching?] true) + (assoc-in [:animations :response-height-current] zero-height) + (assoc-in [:animations :message-input-buttons-scale] 0.1) + (assoc-in [:animations :message-input-offset] -40) + (assoc-in [:animations :messages-offset] request-info-height) + (update-response-height)))) + +(animation-handler :set-response-max-height + (fn [db [_ height]] + (let [prev-height (:response-height-max db)] + (if (not= height prev-height) + (let [db (assoc db :response-height-max height)] + (if (= prev-height (:to-response-height db)) + (assoc db :to-response-height height + :response-height-current height) + db)) + db)))) + +(animation-handler :on-drag-response + (fn [db [_ dy]] + (let [fixed (:to-response-height db)] + (assoc db :response-height-current (- fixed dy) + :response-resize? false)))) + +(register-handler :fix-response-height + (fn [db _] + (if (and (commands/get-chat-command-to-msg-id db) + (not (get-in db [:animations :commands-input-is-switching?]))) + (let [current (get-in db [:animations :response-height-current]) + normal-height response-height-normal + command (commands/get-chat-command db) + text (commands/get-chat-command-content db) + suggestions (get-content-suggestions command text) + max-height (get-in db [:animations :response-height-max]) + delta (/ normal-height 2) + new-fixed (cond + (or (<= current (+ zero-height delta)) + (empty? suggestions)) (+ zero-height request-info-height) + (<= current (+ zero-height normal-height delta)) (get-response-height db) + :else max-height)] + (dispatch [:animate-response-resize]) + (assoc-in db [:animations :to-response-height] new-fixed)) + db))) diff --git a/src/status_im/chat/screen.cljs b/src/status_im/chat/screen.cljs index 3ff250e238..feb88de74e 100644 --- a/src/status_im/chat/screen.cljs +++ b/src/status_im/chat/screen.cljs @@ -3,6 +3,7 @@ (:require [re-frame.core :refer [subscribe dispatch]] [clojure.string :as s] [status-im.components.react :refer [view + animated-view text image icon @@ -12,13 +13,18 @@ [status-im.components.chat-icon.screen :refer [chat-icon-view-action chat-icon-view-menu-item]] [status-im.chat.styles.screen :as st] - [status-im.utils.listview :refer [to-datasource]] + [status-im.utils.listview :refer [to-datasource-inverted]] [status-im.utils.utils :refer [truncate-str]] [status-im.components.invertible-scroll-view :refer [invertible-scroll-view]] [status-im.components.toolbar :refer [toolbar]] [status-im.chat.views.message :refer [chat-message]] + [status-im.chat.views.content-suggestions :refer [content-suggestions-view]] + [status-im.chat.views.suggestions :refer [suggestions-view]] + [status-im.chat.views.response :refer [response-view]] [status-im.chat.views.new-message :refer [chat-message-new]] - [status-im.i18n :refer [label label-pluralize]])) + [status-im.i18n :refer [label label-pluralize]] + [status-im.components.animation :as anim] + [reagent.core :as r])) (defn contacts-by-identity [contacts] @@ -55,12 +61,12 @@ (for [member ["Geoff" "Justas"]] ^{:key member} [typing member])]) -(defn message-row [contact-by-identity group-chat] +(defn message-row [contact-by-identity group-chat messages-count] (fn [row _ idx] (let [msg (-> row (add-msg-color contact-by-identity) (assoc :group-chat group-chat) - (assoc :last-msg (zero? (js/parseInt idx))))] + (assoc :last-msg (= (js/parseInt idx) (dec messages-count))))] (list-item [chat-message msg])))) (defn on-action-selected [position] @@ -213,21 +219,58 @@ :custom-action [toolbar-action]}]))) (defview messages-view [group-chat] - [messages [:chat :messages] - contacts [:chat :contacts]] - (let [contacts' (contacts-by-identity contacts)] - [list-view {:renderRow (message-row contacts' group-chat) - :renderScrollComponent #(invertible-scroll-view (js->clj %)) - :onEndReached #(dispatch [:load-more-messages]) - :enableEmptySections true - :dataSource (to-datasource messages)}])) + [messages [:chat :messages] + contacts [:chat :contacts]] + (let [contacts' (contacts-by-identity contacts)] + [list-view {:renderRow (message-row contacts' group-chat (count messages)) + :renderScrollComponent #(invertible-scroll-view (js->clj %)) + :onEndReached #(dispatch [:load-more-messages]) + :enableEmptySections true + :keyboardShouldPersistTaps true + :dataSource (to-datasource-inverted messages)}])) + +(defn messages-container-animation-logic [{:keys [to-value val]}] + (fn [_] + (let [to-value @to-value] + (anim/start (anim/spring val {:toValue to-value}) + (fn [arg] + (when (.-finished arg) + (dispatch [:set-animation ::messages-offset-current to-value]))))))) + +(defn messages-container [messages] + (let [to-messages-offset (subscribe [:animations :messages-offset]) + cur-messages-offset (subscribe [:animations ::messages-offset-current]) + messages-offset (anim/create-value (or @cur-messages-offset 0)) + context {:to-value to-messages-offset + :val messages-offset} + on-update (messages-container-animation-logic context)] + (r/create-class + {:component-did-mount + on-update + :component-did-update + on-update + :reagent-render + (fn [messages] + @to-messages-offset + [animated-view {:style (st/messages-container messages-offset)} + messages])}))) (defview chat [] [group-chat [:chat :group-chat] - show-actions-atom [:show-actions]] - [view st/chat-view + show-actions-atom [:show-actions] + command [:get-chat-command] + to-msg-id [:get-chat-command-to-msg-id]] + [view {:style st/chat-view + :onLayout (fn [event] + (let [height (.. event -nativeEvent -layout -height)] + (dispatch [:set-response-max-height height])))} [chat-toolbar] - [messages-view group-chat] + [messages-container + [messages-view group-chat]] (when group-chat [typing-all]) + (cond + (and command to-msg-id) [response-view] + command [content-suggestions-view] + :else [suggestions-view]) [chat-message-new] (when show-actions-atom [actions-view])]) diff --git a/src/status_im/chat/styles/content_suggestions.cljs b/src/status_im/chat/styles/content_suggestions.cljs index 01b328fc78..99c64afc20 100644 --- a/src/status_im/chat/styles/content_suggestions.cljs +++ b/src/status_im/chat/styles/content_suggestions.cljs @@ -43,6 +43,9 @@ :backgroundColor color-white :borderRadius 5}) +(def container + {:backgroundColor color-white}) + (def drag-down-touchable {:height 22 :alignItems :center diff --git a/src/status_im/chat/styles/input.cljs b/src/status_im/chat/styles/input.cljs index 474cd2a115..53e02cb16e 100644 --- a/src/status_im/chat/styles/input.cljs +++ b/src/status_im/chat/styles/input.cljs @@ -37,7 +37,8 @@ (def command-input {:flex 1 :marginLeft 8 - :marginTop 7 + :marginTop -2 + :padding 0 :fontSize 14 :fontFamily font :color text1-color}) diff --git a/src/status_im/chat/styles/message.cljs b/src/status_im/chat/styles/message.cljs index 2357e7e0f2..5bb60573d4 100644 --- a/src/status_im/chat/styles/message.cljs +++ b/src/status_im/chat/styles/message.cljs @@ -131,20 +131,27 @@ (def command-request-message-view {:borderRadius 14 :padding 12 + :paddingRight 28 :backgroundColor color-white}) (def command-request-from-text (merge style-sub-text {:marginBottom 2})) -(defn command-request-image-view - [command] - {:position :absolute - :top 12 - :right 0 - :width 32 +(def command-request-image-touchable + {:position :absolute + :top 4 + :right -8 + :alignItems :center + :justifyContent :center + :width 48 + :height 48}) + +(defn command-request-image-view [command scale] + {:width 32 :height 32 :borderRadius 50 - :backgroundColor (:color command)}) + :backgroundColor (:color command) + :transform [{:scale scale}]}) (defn command-image-view [command] @@ -317,6 +324,9 @@ (def message-date-text (assoc style-sub-text :textAlign :center)) +(defn message-container [height] + {:height height}) + (def new-message-container {:backgroundColor color-white :elevation 4}) diff --git a/src/status_im/chat/styles/message_input.cljs b/src/status_im/chat/styles/message_input.cljs new file mode 100644 index 0000000000..9cbcc4e27f --- /dev/null +++ b/src/status_im/chat/styles/message_input.cljs @@ -0,0 +1,32 @@ +(ns status-im.chat.styles.message-input + (:require [status-im.components.styles :refer [color-white + color-blue]])) + +(def input-height 56) + +(defn message-input-container [offset] + {:flex 1 + :transform [{:translateX offset}] + :marginRight offset}) + +(def input-container + {:flexDirection :column}) + +(def input-view + {:flexDirection :row + :height input-height + :backgroundColor color-white}) + +(def send-icon + {:marginTop 10.5 + :marginLeft 12 + :width 15 + :height 15}) + +(def send-container + {:marginTop 10 + :marginRight 10 + :width 36 + :height 36 + :borderRadius 50 + :backgroundColor color-blue}) diff --git a/src/status_im/chat/styles/plain_input.cljs b/src/status_im/chat/styles/plain_input.cljs deleted file mode 100644 index b8806518dd..0000000000 --- a/src/status_im/chat/styles/plain_input.cljs +++ /dev/null @@ -1,55 +0,0 @@ -(ns status-im.chat.styles.plain-input - (:require [status-im.components.styles :refer [font - text2-color - color-white - color-blue]])) - -(def input-container - {:flexDirection :column}) - -(def input-view - {:flexDirection :row - :height 56 - :backgroundColor color-white}) - -(def switch-commands-touchable - {:width 56 - :height 56 - :alignItems :center - :justifyContent :center}) - -(def list-icon - {:width 13 - :height 12}) - -(def close-icon - {:width 12 - :height 12}) - -(def message-input - {:flex 1 - :marginTop -2 - :padding 0 - :fontSize 14 - :fontFamily font - :color text2-color}) - -(def smile-icon - {:marginTop 18 - :marginRight 18 - :width 20 - :height 20}) - -(def send-icon - {:marginTop 10.5 - :marginLeft 12 - :width 15 - :height 15}) - -(def send-container - {:marginTop 10 - :marginRight 10 - :width 36 - :height 36 - :borderRadius 50 - :backgroundColor color-blue}) diff --git a/src/status_im/chat/styles/plain_message.cljs b/src/status_im/chat/styles/plain_message.cljs new file mode 100644 index 0000000000..d52ae4d257 --- /dev/null +++ b/src/status_im/chat/styles/plain_message.cljs @@ -0,0 +1,32 @@ +(ns status-im.chat.styles.plain-message + (:require [status-im.components.styles :refer [font + text2-color]])) + +(def message-input-button-touchable + {:width 56 + :height 56 + :alignItems :center + :justifyContent :center}) + +(defn message-input-button [scale] + {:transform [{:scale scale}]}) + +(def list-icon + {:width 13 + :height 12}) + +(def close-icon + {:width 12 + :height 12}) + +(def message-input + {:flex 1 + :marginTop -2 + :padding 0 + :fontSize 14 + :fontFamily font + :color text2-color}) + +(def smile-icon + {:width 20 + :height 20}) diff --git a/src/status_im/chat/styles/response.cljs b/src/status_im/chat/styles/response.cljs new file mode 100644 index 0000000000..029ca2ed86 --- /dev/null +++ b/src/status_im/chat/styles/response.cljs @@ -0,0 +1,96 @@ +(ns status-im.chat.styles.response + (:require [status-im.components.styles :refer [font + color-white + color-blue + text1-color + text2-color + chat-background + color-black]] + [status-im.chat.styles.message-input :refer [input-height]])) + +(def response-height-normal 211) +(def request-info-height 61) + +(def drag-container + {:height 16 + :alignItems :center + :justifyContent :center}) + +(def drag-icon + {:width 14 + :height 3}) + +(def command-icon-container + {:marginTop 1 + :marginLeft 12 + :width 32 + :height 32 + :alignItems :center + :justifyContent :center + :borderRadius 50 + :backgroundColor color-white}) + +(def command-icon + {:width 9 + :height 15}) + +(def info-container + {:flex 1 + :marginLeft 12}) + +(def command-name + {:marginTop 0 + :fontSize 12 + :fontFamily font + :color color-white}) + +(def message-info + {:marginTop 1 + :fontSize 12 + :fontFamily font + :opacity 0.69 + :color color-white}) + +(defn response-view [height] + {:flexDirection :column + :position :absolute + :left 0 + :right 0 + :bottom 0 + :height height + :backgroundColor color-white + :elevation 2}) + +(def input-placeholder + {:height input-height}) + +(defn request-info [color] + {:flexDirection :column + :height request-info-height + :backgroundColor color}) + +(def inner-container + {:flexDirection :row + :height 45}) + +(def cancel-container + {:marginTop 2.5 + :marginRight 16 + :width 24 + :height 24}) + +(def cancel-icon + {:marginTop 6 + :marginLeft 6 + :width 12 + :height 12}) + +(def command-input + {:flex 1 + :marginLeft 56 + :marginRight 16 + :marginTop -2 + :padding 0 + :fontSize 14 + :fontFamily font + :color text1-color}) diff --git a/src/status_im/chat/styles/response_suggestions.cljs b/src/status_im/chat/styles/response_suggestions.cljs new file mode 100644 index 0000000000..2561f2ae53 --- /dev/null +++ b/src/status_im/chat/styles/response_suggestions.cljs @@ -0,0 +1,59 @@ +(ns status-im.chat.styles.response-suggestions + (:require [status-im.components.styles :refer [font + font-medium + color-light-blue-transparent + color-white + color-black + color-blue + color-blue-transparent + selected-message-color + online-color + separator-color + text1-color + text2-color + text3-color]])) + +(def header-height 50) +(def suggestion-height 56) + +(def header-container + {:paddingLeft 16 + :height header-height + :backgroundColor color-white}) + +(def header-text + {:marginTop 18 + :fontSize 13 + :fontFamily font-medium + :color text2-color}) + +(def suggestion-container + {:flexDirection :column + :paddingLeft 16 + :height suggestion-height + :backgroundColor color-white}) + +(def suggestion-sub-container + {:height suggestion-height + :borderBottomWidth 1 + :borderBottomColor separator-color}) + +(def value-text + {:marginTop 10 + :fontSize 12 + :fontFamily font + :color text1-color}) + +(def description-text + {:marginTop 2 + :fontSize 12 + :fontFamily font + :color text2-color}) + +(def suggestions-container + {:flexDirection :row + :flex 1 + :marginVertical 1 + :marginHorizontal 0 + :backgroundColor color-white + :borderRadius 5}) diff --git a/src/status_im/chat/styles/screen.cljs b/src/status_im/chat/styles/screen.cljs index 35e4d0b3d7..49133f8d32 100644 --- a/src/status_im/chat/styles/screen.cljs +++ b/src/status_im/chat/styles/screen.cljs @@ -14,6 +14,10 @@ {:flex 1 :backgroundColor chat-background}) +(defn messages-container [bottom] + {:flex 1 + :bottom bottom}) + (def toolbar-view {:flexDirection :row :height 56 diff --git a/src/status_im/chat/styles/suggestions.cljs b/src/status_im/chat/styles/suggestions.cljs index 2d5eaaab8e..0c35347f79 100644 --- a/src/status_im/chat/styles/suggestions.cljs +++ b/src/status_im/chat/styles/suggestions.cljs @@ -59,6 +59,9 @@ :backgroundColor color-white :borderRadius 5}) +(def container + {:backgroundColor color-white}) + (def drag-down-touchable {:height 22 :alignItems :center diff --git a/src/status_im/chat/subs.cljs b/src/status_im/chat/subs.cljs index c9de7660e5..fc805b3183 100644 --- a/src/status_im/chat/subs.cljs +++ b/src/status_im/chat/subs.cljs @@ -1,11 +1,12 @@ (ns status-im.chat.subs (:require-macros [reagent.ratom :refer [reaction]]) - (:require [re-frame.core :refer [register-sub]] + (:require [re-frame.core :refer [register-sub dispatch]] [status-im.db :as db] ;todo handlers in subs?... [status-im.chat.suggestions :refer - [get-suggestions typing-command? get-content-suggestions]] + [get-suggestions typing-command?]] [status-im.models.commands :as commands] + [status-im.constants :refer [response-suggesstion-resize-duration]] [status-im.handlers.content-suggestions :refer [get-content-suggestions]])) (register-sub :chat-properties @@ -82,6 +83,10 @@ (fn [db _] (reaction (commands/get-chat-command-content @db)))) +(register-sub :get-chat-command-to-msg-id + (fn [db _] + (reaction (commands/get-chat-command-to-msg-id @db)))) + (register-sub :chat-command-request (fn [db _] (reaction (commands/get-chat-command-request @db)))) @@ -102,5 +107,5 @@ (register-sub :get-content-suggestions (fn [db _] (let [command (reaction (commands/get-chat-command @db)) - text (reaction (commands/get-chat-command-content @db))] - (reaction (get-content-suggestions @db @command @text))))) + text (reaction (commands/get-chat-command-content @db))] + (reaction (get-content-suggestions @command @text))))) diff --git a/src/status_im/chat/views/command.cljs b/src/status_im/chat/views/command.cljs index 039f949c9b..4f5d889c9e 100644 --- a/src/status_im/chat/views/command.cljs +++ b/src/status_im/chat/views/command.cljs @@ -1,16 +1,15 @@ (ns status-im.chat.views.command + (:require-macros [status-im.utils.views :refer [defview]]) (:require [re-frame.core :refer [subscribe dispatch]] [status-im.components.react :refer [view - icon - text - text-input - touchable-highlight]] - [status-im.chat.views.content-suggestions :refer - [content-suggestions-view]] + icon + text + text-input + touchable-highlight]] [status-im.chat.styles.input :as st])) (defn cancel-command-input [] - (dispatch [:cancel-command])) + (dispatch [:start-cancel-command])) (defn set-input-message [message] (dispatch [:set-chat-command-content message])) @@ -24,7 +23,8 @@ (validator message) (pos? (count message)))) -(defn simple-command-input-view [command input-options & {:keys [validator]}] + +#_(defn simple-command-input-view [command input-options & {:keys [validator]}] (let [message-atom (subscribe [:get-chat-command-content])] (fn [command input-options & {:keys [validator]}] (let [message @message-atom] @@ -49,3 +49,19 @@ [touchable-highlight {:on-press cancel-command-input} [view st/cancel-container [icon :close-gray st/cancel-icon]]])]])))) + +(defn try-send [message validator] + (when (valid? message validator) + (send-command))) + +(defn command-icon [command] + [view (st/command-text-container command) + [text {:style st/command-text} (:text command)]]) + +(defview cancel-button [] + [commands-input-is-switching? [:animations :commands-input-is-switching?]] + [touchable-highlight {:disabled commands-input-is-switching? + :on-press cancel-command-input} + [view st/cancel-container + [icon :close-gray st/cancel-icon]]]) + diff --git a/src/status_im/chat/views/confirmation_code.cljs b/src/status_im/chat/views/confirmation_code.cljs deleted file mode 100644 index b1d5f4c94e..0000000000 --- a/src/status_im/chat/views/confirmation_code.cljs +++ /dev/null @@ -1,6 +0,0 @@ -(ns status-im.chat.views.confirmation-code - (:require - [status-im.chat.views.command :refer [simple-command-input-view]])) - -(defn confirmation-code-input-view [command] - [simple-command-input-view command {:keyboardType :numeric}]) diff --git a/src/status_im/chat/views/content_suggestions.cljs b/src/status_im/chat/views/content_suggestions.cljs index 1578f9484e..bcf5103606 100644 --- a/src/status_im/chat/views/content_suggestions.cljs +++ b/src/status_im/chat/views/content_suggestions.cljs @@ -2,11 +2,11 @@ (:require-macros [status-im.utils.views :refer [defview]]) (:require [re-frame.core :refer [subscribe dispatch]] [status-im.components.react :refer [view - icon - text - touchable-highlight - list-view - list-item]] + icon + text + touchable-highlight + list-view + list-item]] [status-im.chat.styles.content-suggestions :as st] [status-im.utils.listview :refer [to-datasource]])) @@ -26,7 +26,7 @@ (defview content-suggestions-view [] [suggestions [:get :current-suggestion]] (when (seq suggestions) - [view + [view st/container [touchable-highlight {:style st/drag-down-touchable ;; TODO hide suggestions? :onPress (fn [])} diff --git a/src/status_im/chat/views/message.cljs b/src/status_im/chat/views/message.cljs index 7bf6ee480e..656cb72631 100644 --- a/src/status_im/chat/views/message.cljs +++ b/src/status_im/chat/views/message.cljs @@ -2,23 +2,28 @@ (:require-macros [status-im.utils.views :refer [defview]]) (:require [clojure.string :as s] [re-frame.core :refer [subscribe dispatch]] + [reagent.core :as r] [status-im.components.react :refer [view text image + animated-view touchable-highlight]] + [status-im.components.animation :as anim] + [status-im.chat.views.request-message :refer [message-content-command-request]] [status-im.chat.styles.message :as st] [status-im.models.commands :refer [parse-command-msg-content parse-command-request]] [status-im.resources :as res] + [status-im.utils.datetime :as time] [status-im.constants :refer [text-content-type content-type-status content-type-command content-type-command-request]])) -(defn message-date [{:keys [date]}] +(defn message-date [timestamp] [view {} [view st/message-date-container - [text {:style st/message-date-text} date]]]) + [text {:style st/message-date-text} (time/to-short-str timestamp)]]]) (defn contact-photo [{:keys [photo-path]}] [view st/contact-photo-container @@ -79,7 +84,8 @@ (->> (when command (name command)) (str "request-"))) -(defview message-content-command-request +;; todo remove (merging leftover) +#_(defview message-content-command-request [{:keys [msg-id content from incoming-group]}] [commands [:get-responses]] (let [{:keys [command content]} (parse-command-request commands content)] @@ -183,12 +189,50 @@ (when (and outgoing delivery-status) [message-delivery-status {:delivery-status delivery-status}])])) +(defn message-container-animation-logic [{:keys [to-value val callback]}] + (fn [_] + (let [to-value @to-value] + (when (< 0 to-value) + (anim/start + (anim/spring val {:toValue to-value + :friction 4 + :tension 10}) + (fn [arg] + (when (.-finished arg) + (callback)))))))) + +(defn message-container [message & children] + (if (:new? message) + (let [layout-height (r/atom 0) + anim-value (anim/create-value 1) + anim-callback #(dispatch [:set-message-shown message]) + context {:to-value layout-height + :val anim-value + :callback anim-callback} + on-update (message-container-animation-logic context)] + (r/create-class + {:component-did-mount + on-update + :component-did-update + on-update + :reagent-render + (fn [message & children] + @layout-height + [animated-view {:style (st/message-container anim-value)} + (into [view {:onLayout (fn [event] + (let [height (.. event -nativeEvent -layout -height)] + (reset! layout-height height)))}] + children)])})) + (into [view] children))) + (defn chat-message - [{:keys [outgoing delivery-status date new-day group-chat] + [{:keys [outgoing delivery-status timestamp new-day group-chat] :as message}] - [view {} - (when new-day [message-date {:date date}]) - [view {} + [message-container message + ;; TODO there is no new-day info in message + (when new-day + [message-date timestamp]) + [view (let [incoming-group (and group-chat (not outgoing))] [message-content (if incoming-group diff --git a/src/status_im/chat/views/message_input.cljs b/src/status_im/chat/views/message_input.cljs new file mode 100644 index 0000000000..4c26e15eb8 --- /dev/null +++ b/src/status_im/chat/views/message_input.cljs @@ -0,0 +1,127 @@ +(ns status-im.chat.views.message-input + (:require-macros [status-im.utils.views :refer [defview]]) + (:require [re-frame.core :refer [subscribe dispatch]] + [reagent.core :as r] + [status-im.components.react :refer [view + animated-view + icon + touchable-highlight + text-input + dismiss-keyboard!]] + [status-im.components.animation :as anim] + [status-im.chat.views.plain-message :as plain-message] + [status-im.chat.views.command :as command] + [status-im.chat.views.response :as response] + [status-im.chat.styles.message-input :as st] + [status-im.chat.styles.plain-message :as st-message] + [status-im.chat.styles.input :as st-command] + [status-im.chat.styles.response :as st-response] + [status-im.constants :refer [response-input-hiding-duration]])) + +(defview send-button [{:keys [on-press accessibility-label]}] + [commands-input-is-switching? [:animations :commands-input-is-switching?]] + [touchable-highlight {:disabled commands-input-is-switching? + :on-press on-press + :accessibility-label accessibility-label} + [view st/send-container + [icon :send st/send-icon]]]) + +(defn animation-logic [{:keys [to-value val]}] + (fn [_] + (let [to-value @to-value] + (anim/start (anim/timing val {:toValue to-value + :duration response-input-hiding-duration}) + (fn [arg] + (when (.-finished arg) + (dispatch [:set-animation ::message-input-offset-current to-value]))))))) + +(defn message-input-container [input] + (let [to-message-input-offset (subscribe [:animations :message-input-offset]) + cur-message-input-offset (subscribe [:animations ::message-input-offset-current]) + message-input-offset (anim/create-value (or @cur-message-input-offset 0)) + context {:to-value to-message-input-offset + :val message-input-offset} + on-update (animation-logic context)] + (r/create-class + {:component-did-mount + on-update + :component-did-update + on-update + :reagent-render + (fn [input] + @to-message-input-offset + [animated-view {:style (st/message-input-container message-input-offset)} + input])}))) + +(defview message-input [input-options validator] + [input-message [:get-chat-input-text] + command [:get-chat-command] + to-msg-id [:get-chat-command-to-msg-id] + input-command [:get-chat-command-content] + staged-commands [:get-chat-staged-commands] + typing-command? [:typing-command?] + commands-input-is-switching? [:animations :commands-input-is-switching?]] + (let [dismiss-keyboard (not (or command typing-command?)) + response? (and command to-msg-id) + message-input? (or (not command) commands-input-is-switching?) + animation? commands-input-is-switching?] + [text-input (merge {:style (cond + message-input? st-message/message-input + response? st-response/command-input + command st-command/command-input) + :ref (fn [input] + (dispatch [:set-message-input input])) + :autoFocus false + :blurOnSubmit dismiss-keyboard + :onChangeText (fn [text] + (when-not animation? + ((if message-input? + plain-message/set-input-message + command/set-input-message) + text))) + :onSubmitEditing #(when-not animation? + (if message-input? + (plain-message/try-send staged-commands + input-message + dismiss-keyboard) + (command/try-send input-command validator)))} + (when command + {:accessibility-label :command-input}) + input-options) + (if message-input? + input-message + input-command)])) + +(defview plain-message-input-view [{:keys [input-options validator]}] + [input-message [:get-chat-input-text] + command [:get-chat-command] + to-msg-id [:get-chat-command-to-msg-id] + input-command [:get-chat-command-content] + staged-commands [:get-chat-staged-commands] + typing-command? [:typing-command?] + commands-input-is-switching? [:animations :commands-input-is-switching?]] + (let [dismiss-keyboard (not (or command typing-command?)) + response? (and command to-msg-id) + message-input? (or (not command) commands-input-is-switching?)] + [view st/input-container + [view st/input-view + (if message-input? + [plain-message/commands-button] + (when (and command (not response?)) + [command/command-icon command response?])) + [message-input-container + [message-input input-options validator]] + ;; TODO emoticons: not implemented + (when message-input? + [plain-message/smile-button]) + (if message-input? + (when (plain-message/message-valid? staged-commands input-message) + [send-button {:on-press #(plain-message/try-send staged-commands + input-message + dismiss-keyboard) + :accessibility-label :send-message}]) + (if (command/valid? input-command validator) + [send-button {:on-press command/send-command + :accessibility-label :stage-command}] + (when-not response? + [command/cancel-button])))]])) diff --git a/src/status_im/chat/views/money.cljs b/src/status_im/chat/views/money.cljs deleted file mode 100644 index 0051b597f6..0000000000 --- a/src/status_im/chat/views/money.cljs +++ /dev/null @@ -1,7 +0,0 @@ -(ns status-im.chat.views.money - (:require - [status-im.chat.views.command :refer [simple-command-input-view]])) - -(defn money-input-view [command] - [simple-command-input-view command - {:keyboardType :numeric}]) diff --git a/src/status_im/chat/views/new_message.cljs b/src/status_im/chat/views/new_message.cljs index 29b203a067..d6cdf010dd 100644 --- a/src/status_im/chat/views/new_message.cljs +++ b/src/status_im/chat/views/new_message.cljs @@ -1,14 +1,11 @@ (ns status-im.chat.views.new-message + (:require-macros [status-im.utils.views :refer [defview]]) (:require [re-frame.core :refer [subscribe]] [status-im.components.react :refer [view]] - [status-im.chat.views.plain-input :refer [plain-message-input-view]] - [status-im.chat.views.command :refer [simple-command-input-view]] - [status-im.chat.views.phone :refer [phone-input-view]] - [status-im.chat.views.password :refer [password-input-view]] - [status-im.chat.views.confirmation-code :refer [confirmation-code-input-view]] - [status-im.chat.views.money :refer [money-input-view]] + [status-im.chat.views.message-input :refer [plain-message-input-view]] [status-im.chat.views.staged-command :refer [simple-command-staged-view]] + [status-im.utils.phone-number :refer [valid-mobile-number?]] [status-im.chat.styles.message :as st])) (defn staged-command-view [stage-command] @@ -19,27 +16,23 @@ (for [command staged-commands] ^{:key command} [staged-command-view command])]) -(defn default-command-input-view [command] - [simple-command-input-view command {}]) +(defn show-input [command] + [plain-message-input-view + (when command + (case (:command command) + :phone {:input-options {:keyboardType :phone-pad} + :validator valid-mobile-number?} + :keypair {:input-options {:secureTextEntry true}} + :confirmation-code {:input-options {:keyboardType :numeric}} + :money {:input-options {:keyboardType :numeric}} + :request {:input-options {:keyboardType :numeric}} + ;; todo maybe nil is finr for now :) + nil #_(throw (js/Error. "Uknown command type"))))]) -(defn special-input-view [command] - (case (:command command) - :phone [phone-input-view command] - :keypair-password [password-input-view command] - :confirmation-code [confirmation-code-input-view command] - :money [money-input-view command] - :request [money-input-view command] - [default-command-input-view command])) - -(defn chat-message-new [] - (let [command-atom (subscribe [:get-chat-command]) - staged-commands-atom (subscribe [:get-chat-staged-commands])] - (fn [] - (let [command @command-atom - staged-commands @staged-commands-atom] - [view st/new-message-container - (when (and staged-commands (pos? (count staged-commands))) - [staged-commands-view staged-commands]) - (if command - [special-input-view command] - [plain-message-input-view])])))) +(defview chat-message-new [] + [command [:get-chat-command] + staged-commands [:get-chat-staged-commands]] + [view st/new-message-container + (when (and staged-commands (pos? (count staged-commands))) + [staged-commands-view staged-commands]) + [show-input command]]) diff --git a/src/status_im/chat/views/password.cljs b/src/status_im/chat/views/password.cljs deleted file mode 100644 index cc87e05fff..0000000000 --- a/src/status_im/chat/views/password.cljs +++ /dev/null @@ -1,7 +0,0 @@ -(ns status-im.chat.views.password - (:require - [status-im.chat.views.command - :refer [simple-command-input-view]])) - -(defn password-input-view [command] - [simple-command-input-view command {:secureTextEntry true}]) diff --git a/src/status_im/chat/views/phone.cljs b/src/status_im/chat/views/phone.cljs deleted file mode 100644 index f74ad65568..0000000000 --- a/src/status_im/chat/views/phone.cljs +++ /dev/null @@ -1,9 +0,0 @@ -(ns status-im.chat.views.phone - (:require - [status-im.chat.views.command - :refer [simple-command-input-view]] - [status-im.utils.phone-number :refer [valid-mobile-number?]])) - -(defn phone-input-view [command] - [simple-command-input-view command {:keyboardType :phone-pad} - :validator valid-mobile-number?]) diff --git a/src/status_im/chat/views/plain_input.cljs b/src/status_im/chat/views/plain_input.cljs deleted file mode 100644 index b932d53032..0000000000 --- a/src/status_im/chat/views/plain_input.cljs +++ /dev/null @@ -1,50 +0,0 @@ -(ns status-im.chat.views.plain-input - (:require [re-frame.core :refer [subscribe dispatch]] - [status-im.components.react :refer [view - icon - touchable-highlight - text-input]] - [status-im.chat.views.suggestions :refer [suggestions-view]] - [status-im.chat.styles.plain-input :as st])) - -(defn set-input-message [message] - (dispatch [:set-chat-input-text message])) - -(defn send [] (dispatch [:send-chat-msg])) - -(defn message-valid? [staged-commands message] - (or (and (pos? (count message)) - (not= "!" message)) - (pos? (count staged-commands)))) - -(defn try-send [staged-commands message] - (when (message-valid? staged-commands message) (send))) - -(defn plain-message-input-view [] - (let [input-message-atom (subscribe [:get-chat-input-text]) - staged-commands-atom (subscribe [:get-chat-staged-commands]) - typing-command? (subscribe [:typing-command?])] - (fn [] - (let [input-message @input-message-atom] - [view st/input-container - [suggestions-view] - [view st/input-view - [touchable-highlight {:on-press #(dispatch [:switch-command-suggestions]) - :style st/switch-commands-touchable} - [view nil - (if @typing-command? - [icon :close-gray st/close-icon] - [icon :list st/list-icon])]] - [text-input {:style st/message-input - :autoFocus (pos? (count @staged-commands-atom)) - :onChangeText set-input-message - :onSubmitEditing #(try-send @staged-commands-atom - input-message)} - input-message] - ;; TODO emoticons: not implemented - [icon :smile st/smile-icon] - (when (message-valid? @staged-commands-atom input-message) - [touchable-highlight {:on-press #(send) - :accessibility-label :send-message} - [view st/send-container - [icon :send st/send-icon]]])]])))) diff --git a/src/status_im/chat/views/plain_message.cljs b/src/status_im/chat/views/plain_message.cljs new file mode 100644 index 0000000000..7c637ebbd0 --- /dev/null +++ b/src/status_im/chat/views/plain_message.cljs @@ -0,0 +1,108 @@ +(ns status-im.chat.views.plain-message + (:require-macros [status-im.utils.views :refer [defview]]) + (:require [re-frame.core :refer [subscribe dispatch]] + [reagent.core :as r] + [status-im.components.react :refer [view + animated-view + icon + touchable-highlight + dismiss-keyboard!]] + [status-im.components.animation :as anim] + [status-im.chat.styles.plain-message :as st] + [status-im.constants :refer [response-input-hiding-duration]])) + +(defn set-input-message [message] + (dispatch [:set-chat-input-text message])) + +(defn send [dismiss-keyboard] + (when dismiss-keyboard + (dismiss-keyboard!)) + (dispatch [:send-chat-msg])) + +(defn message-valid? [staged-commands message] + (or (and (pos? (count message)) + (not= "!" message)) + (pos? (count staged-commands)))) + +(defn try-send [staged-commands message dismiss-keyboard] + (when (message-valid? staged-commands message) + (send dismiss-keyboard))) + +(defn prepare-message-input [message-input] + (when message-input + (.clear message-input) + (.focus message-input))) + +(defn commands-button-animation-callback [message-input] + (fn [arg to-value] + (when (.-finished arg) + (dispatch [:set-animation ::message-input-buttons-scale-current to-value]) + (when (<= to-value 0.1) + (dispatch [:finish-show-response]) + (prepare-message-input @message-input))))) + +(defn button-animation-logic [{:keys [to-value val callback]}] + (fn [_] + (let [to-scale @to-value + minimum 0.1 + scale (cond (< 1 to-scale) 1 + (< to-scale minimum) minimum + :else to-scale)] + (anim/start (anim/timing val {:toValue scale + :duration response-input-hiding-duration}) + (when callback + (fn [arg] + (callback arg to-scale))))))) + +(defn commands-button [] + (let [typing-command? (subscribe [:typing-command?]) + message-input (subscribe [:get :message-input]) + animation? (subscribe [:animations :commands-input-is-switching?]) + to-scale (subscribe [:animations :message-input-buttons-scale]) + cur-scale (subscribe [:animations ::message-input-buttons-scale-current]) + buttons-scale (anim/create-value (or @cur-scale 1)) + anim-callback (commands-button-animation-callback message-input) + context {:to-value to-scale + :val buttons-scale + :callback anim-callback} + on-update (button-animation-logic context)] + (r/create-class + {:component-did-mount + on-update + :component-did-update + on-update + :reagent-render + (fn [] + (let [typing-command? @typing-command?] + @to-scale + [touchable-highlight {:disabled @animation? + :on-press #(dispatch [:switch-command-suggestions]) + :style st/message-input-button-touchable} + [animated-view {:style (st/message-input-button buttons-scale)} + (if typing-command? + [icon :close-gray st/close-icon] + [icon :list st/list-icon])]]))}))) + +(defn smile-button [] + (let [animation? (subscribe [:animations :commands-input-is-switching?]) + to-scale (subscribe [:animations :message-input-buttons-scale]) + cur-scale (subscribe [:animations ::message-input-buttons-scale-current]) + buttons-scale (anim/create-value (or @cur-scale 1)) + context {:to-value to-scale + :val buttons-scale} + on-update (button-animation-logic context)] + (r/create-class + {:component-did-mount + on-update + :component-did-update + on-update + :reagent-render + (fn [] + @to-scale + [touchable-highlight {:disabled @animation? + :on-press (fn [] + ;; TODO emoticons: not implemented + ) + :style st/message-input-button-touchable} + [animated-view {:style (st/message-input-button buttons-scale)} + [icon :smile st/smile-icon]]])}))) diff --git a/src/status_im/chat/views/request_message.cljs b/src/status_im/chat/views/request_message.cljs new file mode 100644 index 0000000000..907fc52311 --- /dev/null +++ b/src/status_im/chat/views/request_message.cljs @@ -0,0 +1,86 @@ +(ns status-im.chat.views.request-message + (:require [re-frame.core :refer [subscribe dispatch]] + [reagent.core :as r] + [status-im.components.react :refer [view + animated-view + text + image + touchable-highlight]] + [status-im.chat.styles.message :as st] + [status-im.models.commands :refer [parse-command-request]] + [status-im.components.animation :as anim])) + +(def request-message-icon-scale-delay 600) + +(defn set-chat-command [msg-id command] + (dispatch [:set-response-chat-command msg-id (keyword (:name command))])) + +(defn label [{:keys [command]}] + (->> (name command) + (str "request-"))) + +(defn request-button-animation-logic [{:keys [to-value val loop?]}] + (fn [_] + (let [loop? @loop? + minimum 1 + maximum 1.3 + to-scale (if loop? + (or @to-value maximum) + minimum)] + (anim/start + (anim/anim-sequence + [(anim/anim-delay (if loop? request-message-icon-scale-delay 0)) + (anim/spring val {:toValue to-scale})]) + (fn [arg] + (when (.-finished arg) + (dispatch [:set-animation ::request-button-scale-current to-scale]) + (when loop? + (dispatch [:set-animation ::request-button-scale (if (= to-scale minimum) + maximum + minimum)])))))))) + +(defn request-button [msg-id command] + (let [to-scale (subscribe [:animations ::request-button-scale]) + cur-scale (subscribe [:animations ::request-button-scale-current]) + scale-anim-val (anim/create-value (or @cur-scale 1)) + loop? (r/atom true) + context {:to-value to-scale + :val scale-anim-val + :loop? loop?} + on-update (request-button-animation-logic context)] + (r/create-class + {:component-did-mount + on-update + :component-did-update + on-update + :reagent-render + (fn [msg-id command] + @to-scale + [touchable-highlight {:on-press (fn [] + (reset! loop? false) + (set-chat-command msg-id command)) + :style st/command-request-image-touchable + ;:accessibility-label (label command) + } + [animated-view {:style (st/command-request-image-view command scale-anim-val)} + [image {:source {:uri (:icon command)} + :style st/command-request-image}]]])}))) + +(defn message-content-command-request + [{:keys [msg-id content from incoming-group]}] + (let [commands-atom (subscribe [:get-responses])] + (fn [{:keys [msg-id content from incoming-group]}] + (let [commands @commands-atom + {:keys [command content]} (parse-command-request commands content)] + [view st/comand-request-view + [view st/command-request-message-view + (when incoming-group + [text {:style st/command-request-from-text} + from]) + [text {:style st/style-message-text} + content]] + [request-button msg-id command] + (when (:request-text command) + [view st/command-request-text-view + [text {:style st/style-sub-text} + (:request-text command)]])])))) diff --git a/src/status_im/chat/views/response.cljs b/src/status_im/chat/views/response.cljs new file mode 100644 index 0000000000..6f745b5844 --- /dev/null +++ b/src/status_im/chat/views/response.cljs @@ -0,0 +1,99 @@ +(ns status-im.chat.views.response + (:require-macros [reagent.ratom :refer [reaction]] + [status-im.utils.views :refer [defview]]) + (:require [re-frame.core :refer [subscribe dispatch]] + [reagent.core :as r] + [status-im.components.react :refer [view + animated-view + icon + image + text + text-input + touchable-highlight]] + [status-im.components.drag-drop :as drag] + [status-im.chat.views.response-suggestions :refer [response-suggestions-view]] + [status-im.chat.styles.response :as st] + [status-im.chat.styles.message-input :refer [input-height]] + [status-im.components.animation :as anim])) + +(defn drag-icon [] + [view st/drag-container + [icon :drag-white st/drag-icon]]) + +(defn command-icon [] + [view st/command-icon-container + ;; TODO stub data: command icon + [icon :dollar-green st/command-icon]]) + +(defn info-container [command] + [view st/info-container + [text {:style st/command-name} + (:description command)] + [text {:style st/message-info} + ;; TODO stub data: request message info + "By ???, MMM 1st at HH:mm"]]) + +(defn create-response-pan-responder [] + (drag/create-pan-responder + {:on-move (fn [e gesture] + (dispatch [:on-drag-response (.-dy gesture)])) + :on-release (fn [e gesture] + (dispatch [:fix-response-height]))})) + +(defn request-info [] + (let [pan-responder (create-response-pan-responder) + command (subscribe [:get-chat-command])] + (fn [] + [view (merge (drag/pan-handlers pan-responder) + {:style (st/request-info (:color @command))}) + [drag-icon] + [view st/inner-container + [command-icon nil] + [info-container @command] + [touchable-highlight {:on-press #(dispatch [:start-cancel-command])} + [view st/cancel-container + [icon :close-white st/cancel-icon]]]]]))) + +(defn container-animation-logic [{:keys [animation? to-value current-value val]}] + (fn [_] + (if @animation? + (let [to-value @to-value] + (anim/start (anim/spring val {:toValue to-value}) + (fn [arg] + (when (.-finished arg) + (dispatch [:set-animation :response-height-current to-value]) + (dispatch [:finish-animate-response-resize]) + (when (= to-value input-height) + (dispatch [:finish-animate-cancel-command]) + (dispatch [:cancel-command])))))) + (anim/set-value val @current-value)))) + +(defn container [& children] + (let [commands-input-is-switching? (subscribe [:animations :commands-input-is-switching?]) + response-resize? (subscribe [:animations :response-resize?]) + to-response-height (subscribe [:animations :to-response-height]) + cur-response-height (subscribe [:animations :response-height-current]) + response-height (anim/create-value (or @cur-response-height 0)) + context {:animation? (reaction (or @commands-input-is-switching? @response-resize?)) + :to-value to-response-height + :current-value cur-response-height + :val response-height} + on-update (container-animation-logic context)] + (r/create-class + {:component-did-mount + on-update + :component-did-update + on-update + :reagent-render + (fn [& children] + @to-response-height + (into [animated-view {:style (st/response-view (if (or @commands-input-is-switching? @response-resize?) + response-height + (or @cur-response-height 0)))}] + children))}))) + +(defn response-view [] + [container + [request-info] + [response-suggestions-view] + [view st/input-placeholder]]) diff --git a/src/status_im/chat/views/response_suggestions.cljs b/src/status_im/chat/views/response_suggestions.cljs new file mode 100644 index 0000000000..54eed3bb7d --- /dev/null +++ b/src/status_im/chat/views/response_suggestions.cljs @@ -0,0 +1,38 @@ +(ns status-im.chat.views.response-suggestions + (:require-macros [status-im.utils.views :refer [defview]]) + (:require [re-frame.core :refer [subscribe dispatch]] + [status-im.components.react :refer [view + icon + text + touchable-highlight + list-view + list-item]] + [status-im.chat.styles.response-suggestions :as st] + [status-im.utils.listview :refer [to-datasource]])) + +(defn set-command-content [content] + (dispatch [:set-chat-command-content content])) + +(defn header-list-item [{:keys [header]}] + [view st/header-container + [text {:style st/header-text} header]]) + +(defn suggestion-list-item [{:keys [value description]}] + [touchable-highlight {:onPress #(set-command-content value)} + [view st/suggestion-container + [view st/suggestion-sub-container + [text {:style st/value-text} value] + [text {:style st/description-text} description]]]]) + +(defn render-row [row _ _] + (list-item (if (:header row) + [header-list-item row] + [suggestion-list-item row]))) + +(defview response-suggestions-view [] + [suggestions [:get-content-suggestions]] + (when (seq suggestions) + [view st/suggestions-container + [list-view {:dataSource (to-datasource suggestions) + :keyboardShouldPersistTaps true + :renderRow render-row}]])) diff --git a/src/status_im/chat/views/suggestions.cljs b/src/status_im/chat/views/suggestions.cljs index 67c55ff148..202150423f 100644 --- a/src/status_im/chat/views/suggestions.cljs +++ b/src/status_im/chat/views/suggestions.cljs @@ -15,8 +15,8 @@ (defn suggestion-list-item [[command {:keys [description] - name :name - :as suggestion}]] + name :name + :as suggestion}]] (let [label (str "!" name)] [touchable-highlight {:onPress #(set-command-input command)} @@ -30,10 +30,11 @@ (defn render-row [row _ _] (list-item [suggestion-list-item row])) + (defview suggestions-view [] [suggestions [:get-suggestions]] (when (seq suggestions) - [view + [view st/container [touchable-highlight {:style st/drag-down-touchable :onPress (fn [] ;; TODO hide suggestions? @@ -41,6 +42,7 @@ [view [icon :drag_down st/drag-down-icon]]] [view (st/suggestions-container (count suggestions)) - [list-view {:dataSource (to-datasource suggestions) - :enableEmptySections true - :renderRow render-row}]]])) + [list-view {:dataSource (to-datasource suggestions) + :enableEmptySections true + :keyboardShouldPersistTaps true + :renderRow render-row}]]])) diff --git a/src/status_im/chats_list/screen.cljs b/src/status_im/chats_list/screen.cljs index a82849d7e6..572f0ac5a9 100644 --- a/src/status_im/chats_list/screen.cljs +++ b/src/status_im/chats_list/screen.cljs @@ -36,7 +36,7 @@ [drawer-view [view st/chats-container [chats-list-toolbar] - [list-view {:dataSource (to-datasource (vals @chats)) + [list-view {:dataSource (to-datasource @chats) :renderRow (fn [row _ _] (list-item [chat-list-item row])) :style st/list-container}] diff --git a/src/status_im/chats_list/views/chat_list_item.cljs b/src/status_im/chats_list/views/chat_list_item.cljs index 0c343f9b59..a7877e5e82 100644 --- a/src/status_im/chats_list/views/chat_list_item.cljs +++ b/src/status_im/chats_list/views/chat_list_item.cljs @@ -1,17 +1,18 @@ (ns status-im.chats-list.views.chat-list-item (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] [status-im.components.react :refer [view - text - image - touchable-highlight]] + text + image + touchable-highlight]] [status-im.components.styles :refer [font]] [status-im.chats-list.views.inner-item :refer [chat-list-item-inner-view]])) -(defn chat-list-item [{:keys [chat-id] :as chat}] +(defn chat-list-item [[chat-id chat]] [touchable-highlight {:on-press #(dispatch [:navigate-to :chat chat-id])} [view [chat-list-item-inner-view (merge chat ;; TODO stub data - {:new-messages-count 3 + {:chat-id chat-id + :new-messages-count 3 :online true})]]]) diff --git a/src/status_im/chats_list/views/inner_item.cljs b/src/status_im/chats_list/views/inner_item.cljs index 783578c3a1..2803b5d6e4 100644 --- a/src/status_im/chats_list/views/inner_item.cljs +++ b/src/status_im/chats_list/views/inner_item.cljs @@ -7,7 +7,7 @@ [status-im.utils.datetime :as time])) (defn chat-list-item-inner-view - [{:keys [chat-id name color photo-path new-messages-count + [{:keys [chat-id name color new-messages-count online group-chat contacts] :as chat}] (let [last-message (first (:messages chat))] [view st/chat-container diff --git a/src/status_im/components/animation.cljs b/src/status_im/components/animation.cljs new file mode 100644 index 0000000000..a945d44f4c --- /dev/null +++ b/src/status_im/components/animation.cljs @@ -0,0 +1,51 @@ +(ns status-im.components.animation + (:require [status-im.components.react :refer [animated]])) + +(defn start + ([anim] (.start anim)) + ([anim callback] (.start anim callback))) + +(defn timing [anim-value config] + (.timing animated anim-value (clj->js config))) + +(defn spring [anim-value config] + (.spring animated anim-value (clj->js config))) + +(defn anim-sequence [animations] + (.sequence animated (clj->js animations))) + +(defn anim-delay [duration] + (.delay animated duration)) + +(defn event [config] + (.event animated (clj->js [nil, config]))) + +(defn add-listener [anim-value listener] + (.addListener anim-value listener)) + +(defn remove-all-listeners [anim-value] + (.removeAllListeners anim-value)) + +(defn stop-animation [anim-value] + (.stopAnimation anim-value)) + +(defn value [anim-value] + (.-value anim-value)) + +(defn set-value [anim-value value] + (.setValue anim-value value)) + +(defn create-value [value] + (js/React.Animated.Value. value)) + +(defn x [value-xy] + (.-x value-xy)) + +(defn y [value-xy] + (.-y value-xy)) + +(defn get-layout [value-xy] + (js->clj (.getLayout value-xy))) + +(defn create-value-xy [x y] + (js/React.Animated.ValueXY. (clj->js {:x x, :y y}))) diff --git a/src/status_im/components/camera.cljs b/src/status_im/components/camera.cljs new file mode 100644 index 0000000000..f53b70ffae --- /dev/null +++ b/src/status_im/components/camera.cljs @@ -0,0 +1,7 @@ +(ns status-im.components.camera + (:require [reagent.core :as r])) + +(def class (.-default (js/require "react-native-camera"))) + +(defn camera [props] + (r/create-element class (clj->js (merge {:inverted true} props)))) \ No newline at end of file diff --git a/src/status_im/components/drag_drop.cljs b/src/status_im/components/drag_drop.cljs new file mode 100644 index 0000000000..708efdfb12 --- /dev/null +++ b/src/status_im/components/drag_drop.cljs @@ -0,0 +1,12 @@ +(ns status-im.components.drag-drop + (:require [status-im.components.react :refer [animated pan-responder]] + [status-im.components.animation :as anim])) + +(defn pan-handlers [pan-responder] + (js->clj (.-panHandlers pan-responder))) + +(defn create-pan-responder [{:keys [on-move on-release]}] + (.create pan-responder + (clj->js {:onStartShouldSetPanResponder (fn [] true) + :onPanResponderMove on-move + :onPanResponderRelease on-release}))) diff --git a/src/status_im/components/main_tabs.cljs b/src/status_im/components/main_tabs.cljs index 16fb5b074d..01551e75da 100644 --- a/src/status_im/components/main_tabs.cljs +++ b/src/status_im/components/main_tabs.cljs @@ -1,11 +1,16 @@ (ns status-im.components.main-tabs - (:require-macros [status-im.utils.views :refer [defview]]) + (:require-macros [reagent.ratom :refer [reaction]] + [status-im.utils.views :refer [defview]]) (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [reagent.core :as r] [status-im.components.react :refer [view + animated-view text-input text image - touchable-highlight]] + touchable-highlight + get-dimensions]] + [status-im.components.animation :as anim] [status-im.chats-list.screen :refer [chats-list]] [status-im.discovery.screen :refer [discovery]] [status-im.contacts.screen :refer [contact-list]] @@ -14,33 +19,91 @@ [status-im.components.styles :as common-st] [status-im.i18n :refer [label]])) +(def window-width (:width (get-dimensions "window"))) + (def tab-list [{:view-id :chat-list :title (label :t/chats) :screen chats-list - :icon :icon_tab_chats} + :icon :icon_tab_chats + :index 0} {:view-id :discovery :title (label :t/discovery) :screen discovery - :icon :icon_tab_discovery} + :icon :icon_tab_discovery + :index 1} {:view-id :contact-list :title (label :t/contacts) :screen contact-list - :icon :icon_tab_contacts}]) + :icon :icon_tab_contacts + :index 2}]) -(defn show-view? [current-view view-id] - (let [key-map {:key view-id}] - (if (= current-view view-id) - (merge st/show-tab key-map) - (merge st/hide-tab key-map)))) +(defn animation-logic [{:keys [offsets val tab-id to-tab-id]}] + (fn [_] + (when-let [offsets @offsets] + (let [from-value (:from offsets) + to-value (:to offsets) + to-tab-id @to-tab-id] + (anim/set-value val from-value) + (when to-value + (anim/start + (anim/timing val {:toValue to-value + :duration 300}) + (when (= tab-id to-tab-id) + (fn [arg] + (when (.-finished arg) + (dispatch [:on-navigated-to-tab])))))))))) -(defn tab-view [current-view {:keys [view-id screen]}] - [view (show-view? current-view view-id) +(defn get-tab-index-by-id [id] + (:index (first (filter #(= id (:view-id %)) tab-list)))) + +(defn get-offsets [tab-id from-id to-id] + (let [tab (get-tab-index-by-id tab-id) + from (get-tab-index-by-id from-id) + to (get-tab-index-by-id to-id)] + (if (or (= tab from) (= tab to)) + (cond + (or (nil? from) (= from to)) {:from 0} + (< from to) (if (= tab to) + {:from window-width, :to 0} + {:from 0, :to (- window-width)}) + (< to from) (if (= tab to) + {:from (- window-width), :to 0} + {:from 0, :to window-width})) + {:from (- window-width)}))) + +(defn tab-view-container [tab-id content] + (let [cur-tab-id (subscribe [:get :view-id]) + prev-tab-id (subscribe [:get :prev-tab-view-id]) + offsets (reaction (get-offsets tab-id @prev-tab-id @cur-tab-id)) + anim-value (anim/create-value (- window-width)) + context {:offsets offsets + :val anim-value + :tab-id tab-id + :to-tab-id cur-tab-id} + on-update (animation-logic context)] + (r/create-class + {:component-did-mount + on-update + :component-did-update + on-update + :reagent-render + (fn [tab-id content] + @offsets + [animated-view {:style (st/tab-view-container anim-value)} + content])}))) + +(defn tab-view [{:keys [view-id screen]}] + ^{:key view-id} + [tab-view-container view-id [screen]]) (defview main-tabs [] - [view-id [:get :view-id]] - [view common-st/flex - (doall (map #(tab-view view-id %1) tab-list)) - [tabs {:selected-view-id view-id - :tab-list tab-list}]]) + [view-id [:get :view-id] + tab-animation? [:get :prev-tab-view-id]] + [view {:style common-st/flex} + [view {:style common-st/flex + :pointerEvents (if tab-animation? :none :auto)} + (doall (map #(tab-view %) tab-list))] + [tabs {:selected-view-id view-id + :tab-list tab-list}]]) diff --git a/src/status_im/components/qr-code.cljs b/src/status_im/components/qr-code.cljs new file mode 100644 index 0000000000..6c1d7d066d --- /dev/null +++ b/src/status_im/components/qr-code.cljs @@ -0,0 +1,7 @@ +(ns status-im.components.qr-code + (:require [reagent.core :as r])) + +(def class (js/require "react-native-qrcode")) + +(defn qr-code [props] + (r/create-element class (clj->js (merge {:inverted true} props)))) \ No newline at end of file diff --git a/src/status_im/components/react.cljs b/src/status_im/components/react.cljs index 0ab2d2b755..1347f22ea2 100644 --- a/src/status_im/components/react.cljs +++ b/src/status_im/components/react.cljs @@ -46,6 +46,14 @@ (when-let [picker (get-react-property "Picker")] (adapt-class (.-Item picker)))) +(def pan-responder (.-PanResponder js/React)) +(def animated (.-Animated js/React)) +(def animated-view (r/adapt-react-class (.-View animated))) +(def animated-text (r/adapt-react-class (.-Text animated))) + +(def dimensions (.-Dimensions js/React)) +(defn get-dimensions [name] + (js->clj (.get dimensions name) :keywordize-keys true)) (defn icon ([n] (icon n {})) diff --git a/src/status_im/components/styles.cljs b/src/status_im/components/styles.cljs index 5aa0bf675d..a1176d46bf 100644 --- a/src/status_im/components/styles.cljs +++ b/src/status_im/components/styles.cljs @@ -19,6 +19,7 @@ (def text1-color color-black) (def text2-color color-gray) (def text3-color color-blue) +(def text4-color color-white) (def online-color color-blue) (def new-messages-count-color color-blue-transparent) (def chat-background color-light-gray) @@ -28,5 +29,73 @@ (def toolbar-background2 color-light-gray) (def default-chat-color color-purple) +(def toolbar-height 56) + (def flex - {:style {:flex 1}}) + {:flex 1}) + +(def hamburger-icon + {:width 16 + :height 12}) + +(def icon-search + {:width 17 + :height 17}) + +(def create-icon + {:fontSize 20 + :height 22 + :color :white}) + +(def icon-back + {:width 8 + :height 14}) + +(def icon-add + {:width 14 + :height 14}) + +(def icon-ok + {:width 18 + :height 14}) + +(def icon-qr + {:width 23 + :height 22}) + +(def icon-plus + {:width 18 + :height 18}) + +(def form-text-input + {:marginLeft -4 + :fontSize 14 + :fontFamily font + :color text1-color}) + +(def white-form-text-input + {:marginLeft -4 + :fontSize 14 + :fontFamily font + :color color-white}) + +(def toolbar-title-container + {:flex 1 + :alignItems :center + :justifyContent :center}) + +(def toolbar-title-text + {:marginTop -2.5 + :color text1-color + :fontSize 16 + :fontFamily font}) + +(def button-input-container + {:flex 1 + :flexDirection :row + :height 50}) + +(def button-input + {:flex 1 + :flexDirection :column + :height 50}) \ No newline at end of file diff --git a/src/status_im/components/tabs/styles.cljs b/src/status_im/components/tabs/styles.cljs index f3571cf3c8..45af60e439 100644 --- a/src/status_im/components/tabs/styles.cljs +++ b/src/status_im/components/tabs/styles.cljs @@ -54,17 +54,11 @@ :justifyContent :center :alignItems :center}) -(def show-tab - {:flex 1 - :pointerEvents :auto - :position :absolute - :top 0 - :left 0 - :right 0 - :bottom tab-height}) - -(def hide-tab - {:opacity 0 - :pointerEvents :none - :overflow :hidden}) - +(defn tab-view-container [offset-x] + {:flex 1 + :position :absolute + :top 0 + :left 0 + :right 0 + :bottom tab-height + :transform [{:translateX offset-x}]}) diff --git a/src/status_im/components/tabs/tab.cljs b/src/status_im/components/tabs/tab.cljs index e2f56af6b5..9ee44dcc44 100644 --- a/src/status_im/components/tabs/tab.cljs +++ b/src/status_im/components/tabs/tab.cljs @@ -10,9 +10,9 @@ [status-im.components.tabs.styles :as st])) (defview tab [{:keys [view-id title icon selected-view-id]}] - [touchable-highlight {:style st/tab - :onPress #(dispatch [:navigate-to - view-id])} + [touchable-highlight {:style st/tab + :disabled (= view-id selected-view-id) + :onPress #(dispatch [:navigate-to-tab view-id])} [view {:style st/tab-container} [image {:source {:uri icon} :style st/tab-icon}] diff --git a/src/status_im/components/toolbar.cljs b/src/status_im/components/toolbar.cljs index 0591c1aba1..820b338b9c 100644 --- a/src/status_im/components/toolbar.cljs +++ b/src/status_im/components/toolbar.cljs @@ -1,24 +1,28 @@ (ns status-im.components.toolbar (:require [re-frame.core :refer [subscribe dispatch]] [status-im.components.react :refer [view - text-input - icon - text - image - touchable-highlight]] + text-input + icon + text + image + touchable-highlight]] [status-im.components.styles :refer [font - title-font - color-white - color-purple - text1-color - text2-color - toolbar-background1]])) + title-font + color-white + color-purple + text1-color + text2-color + toolbar-background1 + toolbar-title-container + toolbar-title-text + icon-back + toolbar-height]])) (defn toolbar [{:keys [title nav-action hide-nav? action custom-action background-color custom-content style]}] (let [style (merge {:flexDirection :row :backgroundColor (or background-color toolbar-background1) - :height 56 + :height toolbar-height :elevation 2} style)] [view {:style style} (when (not hide-nav?) @@ -31,20 +35,14 @@ [image (:image nav-action)]]] [touchable-highlight {:on-press #(dispatch [:navigate-back])} [view {:width 56 - :height 56} + :height 56 + :alignItems :center + :justifyContent :center} [image {:source {:uri :icon_back} - :style {:marginTop 21 - :marginLeft 23 - :width 8 - :height 14}}]]])) + :style icon-back}]]])) (or custom-content - [view {:style {:flex 1 - :alignItems :center - :justifyContent :center}} - [text {:style {:marginTop -2.5 - :color text1-color - :fontSize 16 - :fontFamily font}} + [view {:style toolbar-title-container} + [text {:style toolbar-title-text} title]]) custom-action (when action diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index a684182899..556f9b7898 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -10,3 +10,8 @@ (def content-type-command "command") (def content-type-command-request "command-request") (def content-type-status "status") + +(def max-chat-name-length 20) + +(def response-input-hiding-duration 300) +(def response-suggesstion-resize-duration 100) diff --git a/src/status_im/contacts/handlers.cljs b/src/status_im/contacts/handlers.cljs index 2fd7e586c4..2e0cdc73a6 100644 --- a/src/status_im/contacts/handlers.cljs +++ b/src/status_im/contacts/handlers.cljs @@ -103,3 +103,21 @@ (register-handler :add-contacts (after save-contacts!) add-new-contacts) + +(defn add-new-contact [db [_ {:keys [whisper-identity] :as contact}]] + (-> db + (update :contacts assoc whisper-identity contact) + (assoc :new-contact {:name "" + :address "" + :whisper-identity "" + :phone-number ""}))) + +(register-handler :add-new-contact + (after save-contact) + add-new-contact) + +(defn set-new-contact-from-qr + [{:keys [new-contact] :as db} [_ _ qr-contact]] + (assoc db :new-contact (merge new-contact qr-contact))) + +(register-handler :set-new-contact-from-qr set-new-contact-from-qr) diff --git a/src/status_im/contacts/screen.cljs b/src/status_im/contacts/screen.cljs index b8672807da..a763c2ea50 100644 --- a/src/status_im/contacts/screen.cljs +++ b/src/status_im/contacts/screen.cljs @@ -6,9 +6,18 @@ touchable-highlight list-view list-item]] + [status-im.components.action-button :refer [action-button + action-button-item]] [status-im.contacts.views.contact :refer [contact-view]] [status-im.components.styles :refer [toolbar-background2]] [status-im.components.toolbar :refer [toolbar]] + [status-im.components.drawer.view :refer [drawer-view open-drawer]] + [status-im.components.icons.ionicons :refer [icon]] + [status-im.components.styles :refer [color-blue + hamburger-icon + icon-search + create-icon + toolbar-background2]] [status-im.contacts.styles :as st] [status-im.utils.listview :as lw] [status-im.i18n :refer [label]])) @@ -17,20 +26,34 @@ (list-item [contact-view row])) (defn contact-list-toolbar [] - [toolbar {:title (label :t/contacts) + [toolbar {:nav-action {:image {:source {:uri :icon_hamburger} + :style hamburger-icon} + :handler open-drawer} + :title (label :t/contacts) :background-color toolbar-background2 :action {:image {:source {:uri :icon_search} - :style st/search-icon} + :style icon-search} :handler (fn [])}}]) (defview contact-list [] [contacts [:get-contacts]] - [view st/contacts-list-container - [contact-list-toolbar] - ;; todo what if there is no contacts, should we show some information - ;; about this? - (when contacts - [list-view {:dataSource (lw/to-datasource contacts) - :enableEmptySections true - :renderRow render-row - :style st/contacts-list}])]) + [drawer-view + [view st/contacts-list-container + [contact-list-toolbar] + ;; todo what if there is no contacts, should we show some information + ;; about this? + (when contacts + [list-view {:dataSource (lw/to-datasource contacts) + :enableEmptySections true + :renderRow render-row + :style st/contacts-list}]) + [action-button {:buttonColor color-blue + :offsetY 16 + :offsetX 16} + [action-button-item + {:title (label :t/new-contact) + :buttonColor :#9b59b6 + :onPress #(dispatch [:navigate-to :new-contact])} + [icon {:name :android-create + :style create-icon}]] + ]]]) diff --git a/src/status_im/contacts/styles.cljs b/src/status_im/contacts/styles.cljs index 4cbec2cdff..4b64148fc9 100644 --- a/src/status_im/contacts/styles.cljs +++ b/src/status_im/contacts/styles.cljs @@ -1,13 +1,12 @@ (ns status-im.contacts.styles (:require [status-im.components.styles :refer [font - title-font - text1-color - color-white - online-color]])) + title-font + text1-color + color-white + toolbar-background2 + online-color]])) + -(def search-icon - {:width 17 - :height 17}) (def contacts-list-container {:flex 1 @@ -67,3 +66,20 @@ :fontSize 16 :fontFamily font :color text1-color}) + +; new contact + +(def contact-form-container + {:flex 1 + :color :white}) + +(def gradient-background + {:position :absolute + :top 0 + :right 0 + :bottom 0 + :left 0}) + +(def form-container + {:marginLeft 16 + :margin-top 50}) \ No newline at end of file diff --git a/src/status_im/contacts/views/new_contact.cljs b/src/status_im/contacts/views/new_contact.cljs new file mode 100644 index 0000000000..469ed6b914 --- /dev/null +++ b/src/status_im/contacts/views/new_contact.cljs @@ -0,0 +1,78 @@ +(ns status-im.contacts.views.new-contact + (:require-macros [status-im.utils.views :refer [defview]]) + (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [status-im.components.react :refer [view + text + text-input + image + linear-gradient + touchable-highlight]] + [status-im.components.toolbar :refer [toolbar]] + [status-im.components.drawer.view :refer [drawer-view open-drawer]] + [status-im.components.styles :refer [color-purple + color-white + icon-search + icon-back + icon-qr + toolbar-background1 + toolbar-title-container + toolbar-title-text + button-input-container + button-input + white-form-text-input]] + [status-im.qr-scanner.views.import-button :refer [import-button]] + [status-im.i18n :refer [label]] + [status-im.contacts.styles :as st])) + + + +(def toolbar-title + [view toolbar-title-container + [text {:style (merge toolbar-title-text {:color color-white})} + (label :t/new-contact)]]) + +(defview contact-name-input [name] + [] + [text-input + {:underlineColorAndroid color-white + :placeholderTextColor color-white + :style white-form-text-input + :autoFocus true + :placeholder (label :t/contact-name) + :onChangeText #(dispatch [:set-in [:new-contact :name] %])} + name]) + +(defview contact-whisper-id-input [whisper-identity] + [view button-input-container + [text-input + {:underlineColorAndroid color-white + :placeholderTextColor color-white + :style (merge white-form-text-input button-input) + :autoFocus true + :placeholder (label :t/whisper-identity) + :onChangeText #(dispatch [:set-in [:new-contact :whisper-identity] %])} + whisper-identity] + [import-button #(dispatch [:scan-qr-code {:toolbar-title (label :t/new-contact)} :set-new-contact-from-qr])]]) + +(defview new-contact [] + [{:keys [name whisper-identity phone-number] :as new-contact} [:get :new-contact]] + [drawer-view + [view st/contact-form-container + [linear-gradient {:colors ["rgba(182, 116, 241, 1)" "rgba(107, 147, 231, 1)" "rgba(43, 171, 238, 1)"] + :start [0, 0] + :end [0.5, 1] + :locations [0, 0.8 ,1] + :style st/gradient-background}] + + [toolbar {:background-color :transparent + :nav-action {:image {:source {:uri :icon_back_white} + :style icon-back} + :handler #(dispatch [:navigate-back])} + :custom-content toolbar-title + :action {:image {:source {:uri :icon_add} + :style icon-search} + :handler #(dispatch [:add-new-contact new-contact])}}] + [view st/form-container + [contact-whisper-id-input whisper-identity] + [contact-name-input name] + ]]]) diff --git a/src/status_im/db.cljs b/src/status_im/db.cljs index a46c61f087..2b3ad6f88f 100644 --- a/src/status_im/db.cljs +++ b/src/status_im/db.cljs @@ -1,5 +1,7 @@ (ns status-im.db - (:require [schema.core :as s :include-macros true])) + (:require [schema.core :as s :include-macros true] + [status-im.components.react :refer [animated]] + [status-im.components.animation :as anim])) ;; schema of app-db (def schema {:greeting s/Str}) @@ -29,7 +31,20 @@ :email "myemail@gmail.com" :status "Hi, this is my status" :current-tag nil - :disable-group-creation false}) + :qr-codes {} + :new-contact {:name "" + :address "" + :whisper-identity "" + :phone-number ""} + :disable-group-creation false + :animations {;; mutable data + :to-response-height nil + :response-height-current nil + :message-input-offset 0 + :message-input-buttons-scale 1 + :messages-offset 0 + :commands-input-is-switching? false + :response-resize? false}}) (def protocol-initialized-path [:protocol-initialized]) (defn chat-input-text-path [chat-id] diff --git a/src/status_im/group_settings/screen.cljs b/src/status_im/group_settings/screen.cljs index 346b783912..a35bdbcf53 100644 --- a/src/status_im/group_settings/screen.cljs +++ b/src/status_im/group_settings/screen.cljs @@ -146,7 +146,9 @@ (defview chat-name [] [name [:chat :name] new-name [:get :new-chat-name] - focused? [:get ::name-input-focused]] + validation-messages [:new-chat-name-validation-messages] + focused? [:get ::name-input-focused] + valid? [:new-chat-name-valid?]] [view [text {:style st/chat-name-text} (label :t/chat-name)] [view (st/chat-name-value-container focused?) @@ -157,12 +159,14 @@ :on-blur blur} name] (if (or focused? (not= name new-name)) - [touchable-highlight {:style st/chat-name-btn-edit-container + [touchable-highlight {:style (st/chat-name-btn-edit-container valid?) :on-press save} [view [icon :ok-purple st/add-members-icon]]] - [touchable-highlight {:style st/chat-name-btn-edit-container + [touchable-highlight {:style (st/chat-name-btn-edit-container true) :on-press focus} - [text {:style st/chat-name-btn-edit-text} (label :t/edit)]])]]) + [text {:style st/chat-name-btn-edit-text} (label :t/edit)]])] + (when (pos? (count validation-messages)) + [text {:style st/chat-name-validation-message} (first validation-messages)])]) (defview group-settings [] [show-color-picker [:group-settings :show-color-picker]] diff --git a/src/status_im/group_settings/styles/group_settings.cljs b/src/status_im/group_settings/styles/group_settings.cljs index 91d407c604..9950bb2f9c 100644 --- a/src/status_im/group_settings/styles/group_settings.cljs +++ b/src/status_im/group_settings/styles/group_settings.cljs @@ -91,9 +91,15 @@ :fontFamily font :color text1-color}) -(def chat-name-btn-edit-container +(def chat-name-validation-message + {:marginTop 8 + :marginLeft 16 + :color :red}) + +(defn chat-name-btn-edit-container [enabled?] {:padding 16 - :justifyContent :center}) + :justifyContent :center + :opacity (if enabled? 1 0.3)}) (def chat-name-btn-edit-text {:marginTop -1 diff --git a/src/status_im/group_settings/subs.cljs b/src/status_im/group_settings/subs.cljs index f2b6ac1db3..c1bb17da4d 100644 --- a/src/status_im/group_settings/subs.cljs +++ b/src/status_im/group_settings/subs.cljs @@ -1,6 +1,7 @@ (ns status-im.group-settings.subs (:require-macros [reagent.ratom :refer [reaction]]) - (:require [re-frame.core :refer [register-sub]])) + (:require [re-frame.core :refer [register-sub]] + [status-im.constants :refer [max-chat-name-length]])) (register-sub :selected-participant (fn [db _] @@ -11,3 +12,20 @@ (register-sub :group-settings (fn [db [_ k]] (reaction (get-in @db [:group-settings k])))) + +(defn get-chat-name-validation-messages [chat-name] + (filter some? + (list (when (zero? (count chat-name)) + "Chat name can't be empty") + (when (< max-chat-name-length (count chat-name)) + "Chat name is too long")))) + +(register-sub :new-chat-name-validation-messages + (fn [db [_]] + (let [chat-name (reaction (:new-chat-name @db))] + (reaction (get-chat-name-validation-messages @chat-name))))) + +(register-sub :new-chat-name-valid? + (fn [db [_]] + (let [chat-name (reaction (:new-chat-name @db))] + (reaction (zero? (count (get-chat-name-validation-messages @chat-name))))))) diff --git a/src/status_im/handlers.cljs b/src/status_im/handlers.cljs index bb48cec2d5..6540a30948 100644 --- a/src/status_im/handlers.cljs +++ b/src/status_im/handlers.cljs @@ -15,9 +15,10 @@ status-im.discovery.handlers status-im.new-group.handlers status-im.participants.handlers - status-im.protocol.handlers status-im.commands.handlers.loading - status-im.commands.handlers.jail)) + status-im.commands.handlers.jail + status-im.qr-scanner.handlers + status-im.protocol.handlers)) ;; -- Middleware ------------------------------------------------------------ ;; @@ -49,6 +50,10 @@ debug set-in) +(register-handler :set-animation + (fn [db [_ k v]] + (assoc-in db [:animations k] v))) + (register-handler :initialize-db (fn [_ _] (assoc app-db diff --git a/src/status_im/handlers/content_suggestions.cljs b/src/status_im/handlers/content_suggestions.cljs index b8c193a8eb..c3928b3303 100644 --- a/src/status_im/handlers/content_suggestions.cljs +++ b/src/status_im/handlers/content_suggestions.cljs @@ -4,19 +4,22 @@ [status-im.utils.logging :as log] [clojure.string :as s])) +;; TODO stub data? (def suggestions - {:phone [{:value "89171111111" + {:phone [{:header "Phone number formats"} + {:value "89171111111" :description "Number format 1"} - {:value "+79171111111" + {:value "+79171111111" :description "Number format 2"} - {:value "9171111111" + {:value "9171111111" :description "Number format 3"}]}) -(defn get-content-suggestions [db command text] +(defn get-content-suggestions [command text] (or (when command (when-let [command-suggestions ((keyword (:name command)) suggestions)] (filterv (fn [s] - (and (.startsWith (:value s) (or text "")) - (not= (:value s) text))) + (or (:header s) + (and (.startsWith (:value s) (or text "")) + (not= (:value s) text)))) command-suggestions))) [])) diff --git a/src/status_im/i18n.cljs b/src/status_im/i18n.cljs index bdc113852c..f07f1d6d24 100644 --- a/src/status_im/i18n.cljs +++ b/src/status_im/i18n.cljs @@ -9,13 +9,15 @@ (set! (.-translations i18n) (clj->js {:en en/translations})) -(defn label [path & options] - (if (exists? i18n.t) - (.t i18n (name path) (clj->js options)) - (name path))) +(defn label + ([path] (label path {})) + ([path options] + (if (exists? i18n.t) + (.t i18n (name path) (clj->js options)) + (name path)))) + (defn label-pluralize [count path & options] (if (exists? i18n.t) (.p i18n count (name path) (clj->js options)) - (name path))) - + (name path))) \ No newline at end of file diff --git a/src/status_im/models/commands.cljs b/src/status_im/models/commands.cljs index 7da49a9b4b..aa426640eb 100644 --- a/src/status_im/models/commands.cljs +++ b/src/status_im/models/commands.cljs @@ -3,6 +3,7 @@ [clojure.walk :refer [stringify-keys keywordize-keys]] [re-frame.core :refer [subscribe dispatch]] [status-im.db :as db] + [status-im.components.animation :as anim] [status-im.components.styles :refer [color-blue color-dark-mint]] [status-im.i18n :refer [label]])) @@ -34,7 +35,7 @@ [{:keys [current-chat-id] :as db} msg-id command-key] (update-in db [:chats current-chat-id :command-input] merge {:content nil - :command (merge (get-command db command-key)) + :command (get-command db command-key) :to-msg-id msg-id})) (defn set-chat-command [db command-key] diff --git a/src/status_im/models/messages.cljs b/src/status_im/models/messages.cljs index 3d6611a887..22ec4a564c 100644 --- a/src/status_im/models/messages.cljs +++ b/src/status_im/models/messages.cljs @@ -49,9 +49,11 @@ (defn get-messages [chat-id] (->> (-> (r/get-by-field :msgs :chat-id chat-id) - (r/sorted :timestamp :asc) + (r/sorted :timestamp :desc) (r/collection->map)) (into '()) + ;; todo why reverse? + reverse (map (fn [{:keys [content-type preview] :as message}] (if (command-type? content-type) (-> message diff --git a/src/status_im/navigation/handlers.cljs b/src/status_im/navigation/handlers.cljs index 9e249c6186..e687ed45c6 100644 --- a/src/status_im/navigation/handlers.cljs +++ b/src/status_im/navigation/handlers.cljs @@ -42,12 +42,25 @@ (assoc :view-id view-id) (assoc :navigation-stack navigation-stack')))))) +(register-handler :navigate-to-tab + (enrich preload-data!) + (fn [db [_ view-id]] + (-> db + (assoc :prev-tab-view-id (:view-id db)) + (replace-view view-id)))) + +(register-handler :on-navigated-to-tab + (enrich preload-data!) + (fn [db [_]] + (assoc db :prev-tab-view-id nil))) + (register-handler :show-group-new (debug (fn [db _] (-> db (push-view :new-group) - (assoc :new-group #{}))))) + (assoc :new-group #{}) + (assoc :new-chat-name nil))))) (register-handler :show-contacts (fn [db _] diff --git a/src/status_im/new_group/screen.cljs b/src/status_im/new_group/screen.cljs index 4b266486c6..9e81f77b89 100644 --- a/src/status_im/new_group/screen.cljs +++ b/src/status_im/new_group/screen.cljs @@ -3,13 +3,13 @@ (:require [re-frame.core :refer [subscribe dispatch]] [status-im.resources :as res] [status-im.components.react :refer [view - text-input - text - image - icon - touchable-highlight - list-view - list-item]] + text-input + text + image + icon + touchable-highlight + list-view + list-item]] [status-im.components.styles :refer [color-purple]] [status-im.components.toolbar :refer [toolbar]] [status-im.utils.listview :refer [to-datasource]] @@ -19,24 +19,30 @@ (defview new-group-toolbar [] - [group-name [:get ::group-name] - creation-disabled? [:get :disable-group-creation]] - [toolbar - {:title (label :t/new-group-chat) - :action {:image {:source res/v ;; {:uri "icon_search"} - :style st/toolbar-icon} - :handler (when-not creation-disabled? - #(dispatch [:init-group-creation group-name]))}}]) + [group-name [:get :new-chat-name] + creation-disabled? [:get :disable-group-creation] + valid? [:new-chat-name-valid?]] + (let [create-btn-enabled? (and valid? (not creation-disabled?))] + [toolbar + {:title (label :t/new-group-chat) + :action {:image {:source res/v ;; {:uri "icon_search"} + :style (st/toolbar-icon create-btn-enabled?)} + :handler (when create-btn-enabled? + #(dispatch [:init-group-creation group-name]))}}])) (defview group-name-input [] - [group-name [:get ::group-name]] - [text-input - {:underlineColorAndroid color-purple - :style st/group-name-input - :autoFocus true - :placeholder (label :t/group-name) - :onChangeText #(dispatch [:set ::group-name %])} - group-name]) + [group-name [:get :new-chat-name] + validation-messages [:new-chat-name-validation-messages]] + [view + [text-input + {:underlineColorAndroid color-purple + :style st/group-name-input + :autoFocus true + :placeholder (label :t/group-name) + :onChangeText #(dispatch [:set :new-chat-name %])} + group-name] + (when (pos? (count validation-messages)) + [text {:style st/group-name-validation-message} (first validation-messages)])]) (defview new-group [] [contacts [:all-contacts]] diff --git a/src/status_im/new_group/styles.cljs b/src/status_im/new_group/styles.cljs index cd16b8dcc8..3d15673453 100644 --- a/src/status_im/new_group/styles.cljs +++ b/src/status_im/new_group/styles.cljs @@ -7,9 +7,10 @@ text2-color toolbar-background1]])) -(def toolbar-icon - {:width 20 - :height 18}) +(defn toolbar-icon [enabled?] + {:width 20 + :height 18 + :opacity (if enabled? 1 0.3)}) (def new-group-container {:flex 1 @@ -33,6 +34,9 @@ :fontFamily font :color text1-color}) +(def group-name-validation-message + {:color :red}) + (def members-text {:marginTop 24 :marginBottom 16 diff --git a/src/status_im/profile/screen.cljs b/src/status_im/profile/screen.cljs index bb0c52a1d8..ffff2d2e47 100644 --- a/src/status_im/profile/screen.cljs +++ b/src/status_im/profile/screen.cljs @@ -11,6 +11,8 @@ [status-im.components.chat-icon.screen :refer [profile-icon my-profile-icon]] [status-im.profile.styles :as st] + [status-im.components.qr-code :refer [qr-code]] + [status-im.utils.types :refer [clj->json]] [status-im.i18n :refer [label]])) (defn profile-property-view [{:keys [name value]}] @@ -64,7 +66,8 @@ photo-path [:get :photo-path] phone-number [:get :phone-number] email [:get :email] - status [:get :status]] + status [:get :status] + identity [:get-in [:user-identity :public]]] [scroll-view {:style st/profile} [touchable-highlight {:style st/back-btn-touchable :on-press #(dispatch [:navigate-back])} @@ -81,10 +84,14 @@ [my-profile-icon]] [text {:style st/user-name} username] [text {:style st/status} status]] - [view st/profile-properties-container + [scroll-view st/profile-properties-container [profile-property-view {:name (label :t/username) :value username}] [profile-property-view {:name (label :t/phone-number) :value phone-number}] [profile-property-view {:name (label :t/email) - :value email}]]]) + :value email}] + [view st/qr-code-container + [qr-code {:value (clj->json {:name username + :whisper-identity identity}) + :size 200}]]]]) diff --git a/src/status_im/profile/styles.cljs b/src/status_im/profile/styles.cljs index 95d230920e..fcaaad9b54 100644 --- a/src/status_im/profile/styles.cljs +++ b/src/status_im/profile/styles.cljs @@ -135,3 +135,8 @@ :color text2-color ;; IOS: :letterSpacing 0.5}) + +(def qr-code-container + {:flex 1 + :alignItems :center + :margin 15}) diff --git a/src/status_im/qr_scanner/handlers.cljs b/src/status_im/qr_scanner/handlers.cljs new file mode 100644 index 0000000000..be05d7fb65 --- /dev/null +++ b/src/status_im/qr_scanner/handlers.cljs @@ -0,0 +1,38 @@ +(ns status-im.qr-scanner.handlers + (:require [re-frame.core :refer [register-handler after dispatch debug enrich]] + [status-im.navigation.handlers :as nav] + [status-im.utils.handlers :as u])) + +(defmethod nav/preload-data! :qr-scanner + [db [_ _ identifier]] + (assoc db :current-qr-context identifier)) + +(defn set-current-identifier [db [_ identifier handler]] + (assoc-in db [:qr-codes identifier] handler)) + +(defn navigate-to-scanner + [_ [_ identifier]] + (dispatch [:navigate-to :qr-scanner identifier])) + +(register-handler :scan-qr-code + (after navigate-to-scanner) + set-current-identifier) + +(register-handler :clear-qr-code + (fn [db [_ identifier]] + (update db :qr-codes dissoc identifier))) + +(defn handle-qr-request + [db [_ context data]] + (let [handler (get-in db [:qr-codes context])] + (dispatch [handler context data]))) + +(defn clear-qr-request [db [_ context]] + (-> db + (update :qr-codes dissoc context) + (dissoc :current-qr-context))) + +(register-handler :set-qr-code + (-> (u/side-effect! handle-qr-request) + ((enrich clear-qr-request)) + ((after #(dispatch [:navigate-back]))))) diff --git a/src/status_im/qr_scanner/screen.cljs b/src/status_im/qr_scanner/screen.cljs new file mode 100644 index 0000000000..2f27d30b9f --- /dev/null +++ b/src/status_im/qr_scanner/screen.cljs @@ -0,0 +1,37 @@ +(ns status-im.qr-scanner.screen + (:require-macros [status-im.utils.views :refer [defview]]) + (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [status-im.components.react :refer [view + image]] + [status-im.components.camera :refer [camera]] + [status-im.components.styles :refer [toolbar-background1 + icon-search]] + [status-im.components.toolbar :refer [toolbar]] + [status-im.qr-scanner.styles :as st] + [status-im.utils.types :refer [json->clj]])) + +(defn qr-scanner-toolbar [title] + [toolbar {:title title + :background-color toolbar-background1 + :action {:image {:source {:uri :icon_lock_white} + :style icon-search} + :handler #()}}]) + +(defview qr-scanner [] + [identifier [:get :current-qr-context]] + [view st/barcode-scanner-container + [qr-scanner-toolbar (:toolbar-title identifier)] + [camera {;:on-bar-code-read #(js/alert "ok") + :onBarCodeRead #(let [data (json->clj (.-data %))] + (dispatch [:set-qr-code identifier data])) + :style st/barcode-scanner}] + [view st/rectangle-container + [view st/rectangle + [image {:source {:uri :corner_left_top} + :style st/corner-left-top}] + [image {:source {:uri :corner_right_top} + :style st/corner-right-top}] + [image {:source {:uri :corner_right_bottom} + :style st/corner-right-bottom}] + [image {:source {:uri :corner_left_bottom} + :style st/corner-left-bottom}]]]]) diff --git a/src/status_im/qr_scanner/styles.cljs b/src/status_im/qr_scanner/styles.cljs new file mode 100644 index 0000000000..05198031b1 --- /dev/null +++ b/src/status_im/qr_scanner/styles.cljs @@ -0,0 +1,76 @@ +(ns status-im.qr-scanner.styles + (:require [status-im.components.styles :refer [toolbar-height + color-white]])) + +(def barcode-scanner-container + {:flex 1 + :backgroundColor :white}) + +(def barcode-scanner + {:flex 1 + :justifyContent :flex-end + :alignItems :center}) + +(def rectangle-container + {:position :absolute + :left 0 + :top toolbar-height + :bottom 0 + :right 0 + :flex 1 + :alignItems :center + :justifyContent :center + :backgroundColor :transparent}) + +(def rectangle + {:height 250 + :width 250 + :backgroundColor :transparent}) + +(def corner-left-top + {:position :absolute + :left 0 + :top 0 + :width 56 + :height 56}) + +(def corner-right-top + {:position :absolute + :right 0 + :top 0 + :width 56 + :height 56}) + +(def corner-right-bottom + {:position :absolute + :right 0 + :bottom 0 + :width 56 + :height 56}) + +(def corner-left-bottom + {:position :absolute + :left 0 + :bottom 0 + :width 56 + :height 56}) + +(def import-button + {:position :absolute + :right 16 + :flex 1 + :height 50 + :alignItems :center}) + +(def import-button-content + {:flex 1 + :flexDirection :row + :height 50 + :alignItems :center + :alignSelf :center}) + +(def import-text + {:flex 1 + :flexDirection :column + :color color-white + :margin-left 8}) diff --git a/src/status_im/qr_scanner/views/import-qr-button.cljs b/src/status_im/qr_scanner/views/import-qr-button.cljs new file mode 100644 index 0000000000..99b7dbc7b7 --- /dev/null +++ b/src/status_im/qr_scanner/views/import-qr-button.cljs @@ -0,0 +1,23 @@ +(ns status-im.qr-scanner.views.import-button + (:require-macros [status-im.utils.views :refer [defview]]) + (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [status-im.components.react :refer [view + text + image + touchable-highlight]] + [status-im.components.toolbar :refer [toolbar]] + [status-im.components.drawer.view :refer [drawer-view open-drawer]] + [status-im.components.styles :refer [icon-qr]] + [status-im.i18n :refer [label]] + [status-im.qr-scanner.styles :as st])) + + +(defview import-button [handler] + [] + [view st/import-button + [touchable-highlight + {:on-press handler} + [view st/import-button-content + [image {:source {:uri :icon_qr} + :style icon-qr}] + [text {:style st/import-text} (label :t/import-qr)]]]]) \ No newline at end of file diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 972c57887c..f31dc4ebe2 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -15,3 +15,7 @@ (register-sub :get-in (fn [db [_ path]] (reaction (get-in @db path)))) + +(register-sub :animations + (fn [db [_ k]] + (reaction (get-in @db [:animations k])))) diff --git a/src/status_im/translations/en.cljs b/src/status_im/translations/en.cljs index cf3424d440..8ec3ba3fb7 100644 --- a/src/status_im/translations/en.cljs +++ b/src/status_im/translations/en.cljs @@ -92,7 +92,7 @@ :location-command-description "Send location" :phone-command-description "Send phone number" :phone-request-text "Phone number request" - :confirmation-code-coomand-description "Send confirmation code" + :confirmation-code-command-description "Send confirmation code" :confirmation-code-request-text "Confirmation code request" :send-command-description "Send location" :request-command-description "Send request" @@ -115,8 +115,18 @@ :You "You" ;new-contact - :import-qr "Import from QR" + :import-qr "Import" :contact-name "Contact Name" - :contact-address "Contact Address" + :whisper-identity "Whisper Identity" + + ;login + :recover-from-passphrase "Recover from passphrase" + :connect "Connect" + :address "Address" + :password "Password" + :login "Login" + + ;users + :add-account "Add account" }) \ No newline at end of file diff --git a/src/status_im/utils/listview.cljs b/src/status_im/utils/listview.cljs index dcd63da8bd..d9ab791710 100644 --- a/src/status_im/utils/listview.cljs +++ b/src/status_im/utils/listview.cljs @@ -7,3 +7,12 @@ (defn to-datasource [items] (clone-with-rows (data-source {:rowHasChanged not=}) items)) + +(defn clone-with-rows-inverted [ds rows] + (let [rows (reduce (fn [ac el] (.push ac el) ac) + (clj->js []) (reverse rows)) + row-ids (.reverse (.map rows (fn [_ index] index)))] + (.cloneWithRows ds rows row-ids))) + +(defn to-datasource-inverted [items] + (clone-with-rows-inverted (data-source {:rowHasChanged not=}) items)) diff --git a/src/status_im/utils/types.cljs b/src/status_im/utils/types.cljs index aa256f0056..93c942df77 100644 --- a/src/status_im/utils/types.cljs +++ b/src/status_im/utils/types.cljs @@ -6,4 +6,10 @@ s)) (defn to-edn-string [value] - (with-out-str (pr value))) \ No newline at end of file + (with-out-str (pr value))) + +(defn clj->json [data] + (.stringify js/JSON (clj->js data))) + +(defn json->clj [data] + (js->clj (.parse js/JSON data) :keywordize-keys true))