support for local notification service on Android
- add option in profile on Android to enable local notifications - use foreground service to keep the app alive when running in the background - implement enable and disbable notification function in status module When enabling notifications, a foreground service is started that displays a sticky notification to make the user aware that the app is running in the background. Notifications are updated whenever a new.message signal is handled on java side. Currently only one to one chats are generating notifications but that can be easily extended to other types of messages, including mentions and keywords. The ens name of the user as well as keywords to follow should then be passed to the native side when calling the enable function. Signed-off-by: yenda <eric@status.im>
This commit is contained in:
parent
a6c2518de7
commit
36ad6fb762
|
@ -17,3 +17,4 @@ SNOOPY=0
|
||||||
RPC_NETWORKS_ONLY=1
|
RPC_NETWORKS_ONLY=1
|
||||||
PARTITIONED_TOPIC=0
|
PARTITIONED_TOPIC=0
|
||||||
MOBILE_UI_FOR_DESKTOP=1
|
MOBILE_UI_FOR_DESKTOP=1
|
||||||
|
LOCAL_NOTIFICATIONs=0
|
|
@ -4,6 +4,7 @@
|
||||||
package="im.status.ethereum">
|
package="im.status.ethereum">
|
||||||
|
|
||||||
<!-- non-dangerous permissions -->
|
<!-- non-dangerous permissions -->
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.NFC"/>
|
<uses-permission android:name="android.permission.NFC"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
@ -68,6 +69,7 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
|
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
|
||||||
|
<service android:name="im.status.ethereum.module.ForegroundService"></service>
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
|
|
|
@ -110,6 +110,7 @@ var TopLevel = {
|
||||||
"DeviceEventEmitter" : function () {},
|
"DeviceEventEmitter" : function () {},
|
||||||
"Dimensions" : function () {},
|
"Dimensions" : function () {},
|
||||||
"dispatch" : function () {},
|
"dispatch" : function () {},
|
||||||
|
"disableNotifications" : function () {},
|
||||||
"displayNotification" : function () {},
|
"displayNotification" : function () {},
|
||||||
"dividedBy" : function () {},
|
"dividedBy" : function () {},
|
||||||
"DocumentDirectoryPath" : function () {},
|
"DocumentDirectoryPath" : function () {},
|
||||||
|
@ -117,6 +118,7 @@ var TopLevel = {
|
||||||
"dy" : function () {},
|
"dy" : function () {},
|
||||||
"ease" : function () {},
|
"ease" : function () {},
|
||||||
"Easing" : function () {},
|
"Easing" : function () {},
|
||||||
|
"enableNotifications" : function () {},
|
||||||
"enableVibration" : function () {},
|
"enableVibration" : function () {},
|
||||||
"encode" : function () {},
|
"encode" : function () {},
|
||||||
"encodeURIComponent" : function () {},
|
"encodeURIComponent" : function () {},
|
||||||
|
|
|
@ -24,6 +24,6 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.facebook.react:react-native:+' // from node_modules
|
implementation 'com.facebook.react:react-native:+' // from node_modules
|
||||||
compile 'com.github.status-im:function:0.0.1'
|
compile 'com.github.status-im:function:0.0.1'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||||
implementation(group: 'status-im', name: 'status-go', version: getStatusGoSHA1(), ext: 'aar')
|
implementation(group: 'status-im', name: 'status-go', version: getStatusGoSHA1(), ext: 'aar')
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
package im.status.ethereum.module;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
public class ForegroundService extends Service {
|
||||||
|
private static final String CHANNEL_ID = "status-service";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
// NOTE: recent versions of Android require the service to display
|
||||||
|
// a sticky notification to inform the user that the service is running
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
// Create the NotificationChannel, but only on API 26+ because
|
||||||
|
// the NotificationChannel class is new and not in the support library
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationManager notificationManager =
|
||||||
|
context.getSystemService(NotificationManager.class);
|
||||||
|
notificationManager.createNotificationChannel(new NotificationChannel(CHANNEL_ID,
|
||||||
|
"Status Service",
|
||||||
|
NotificationManager.IMPORTANCE_HIGH));
|
||||||
|
}
|
||||||
|
String content = "Keep Status running to receive notifications";
|
||||||
|
Notification notification = new NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
|
.setSmallIcon(R.drawable.ic_stat_notify_status)
|
||||||
|
.setContentTitle("Background notification service opened")
|
||||||
|
.setContentText(content)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||||
|
.build();
|
||||||
|
// the id of the foreground notification MUST NOT be 0
|
||||||
|
startForeground(1, notification);
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,313 @@
|
||||||
|
package im.status.ethereum.module;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import androidx.core.app.Person;
|
||||||
|
import androidx.core.app.Person.Builder;
|
||||||
|
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import androidx.core.graphics.drawable.IconCompat;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
|
||||||
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.media.AudioAttributes;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
class NewMessageSignalHandler {
|
||||||
|
private static final String GROUP_STATUS_MESSAGES = "im.status.notifications.message";
|
||||||
|
private static final String CHANNEL_NAME = "Status";
|
||||||
|
private static final String CHANNEL_DESCRIPTION = "Get notifications on new messages and mentions";
|
||||||
|
private static final String CHANNEL_ID = "status-chat-notifications";
|
||||||
|
private static final String TAG = "StatusModule";
|
||||||
|
private NotificationManager notificationManager;
|
||||||
|
private HashMap<String, Person> persons;
|
||||||
|
private HashMap<String, StatusChat> chats;
|
||||||
|
private Context context;
|
||||||
|
private Intent serviceIntent;
|
||||||
|
private Boolean shouldRefreshNotifications;
|
||||||
|
|
||||||
|
public NewMessageSignalHandler(Context context) {
|
||||||
|
// NOTE: when instanciated the NewMessageSignalHandler class starts a foreground service
|
||||||
|
// to keep the app running in the background in order to receive notifications
|
||||||
|
// call the stop() method in order to stop the service
|
||||||
|
this.context = context;
|
||||||
|
this.persons = new HashMap<String, Person>();
|
||||||
|
this.chats = new HashMap<String, StatusChat>();
|
||||||
|
this.notificationManager = context.getSystemService(NotificationManager.class);
|
||||||
|
this.createNotificationChannel();
|
||||||
|
this.shouldRefreshNotifications = false;
|
||||||
|
Log.e(TAG, "Starting Foreground Service");
|
||||||
|
Intent serviceIntent = new Intent(context, ForegroundService.class);
|
||||||
|
context.startService(serviceIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
Log.e(TAG, "Stopping Foreground Service");
|
||||||
|
Intent serviceIntent = new Intent(context, ForegroundService.class);
|
||||||
|
context.stopService(serviceIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNotificationChannel() {
|
||||||
|
// Create the NotificationChannel, but only on API 26+ because
|
||||||
|
// the NotificationChannel class is new and not in the support library
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
Uri soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.getPackageName() + "/" + R.raw.notification_sound);
|
||||||
|
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
|
||||||
|
channel.setDescription(CHANNEL_DESCRIPTION);
|
||||||
|
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||||
|
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
|
||||||
|
.build();
|
||||||
|
channel.setSound(soundUri, audioAttributes);
|
||||||
|
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
|
||||||
|
notificationManager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notify(int notificationId, StatusChat chat) {
|
||||||
|
NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle("Me");
|
||||||
|
ArrayList<StatusMessage> messages = chat.getMessages();
|
||||||
|
for (int i = 0; i < messages.size(); i++) {
|
||||||
|
StatusMessage message = messages.get(i);
|
||||||
|
messagingStyle.addMessage(message.getText(),
|
||||||
|
message.getTimestamp(),
|
||||||
|
message.getAuthor());
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
|
.setSmallIcon(R.drawable.ic_stat_notify_status)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||||
|
.setStyle(messagingStyle)
|
||||||
|
.setGroup(GROUP_STATUS_MESSAGES);
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
builder.setVibrate(new long[0]);
|
||||||
|
}
|
||||||
|
notificationManager.notify(notificationId, builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshNotifications() {
|
||||||
|
NotificationCompat.InboxStyle summaryStyle = new NotificationCompat.InboxStyle();
|
||||||
|
String summary = "";
|
||||||
|
int notificationId = 2; // we start at 2 because the service is using 1 and can't use 0
|
||||||
|
int messageCounter = 0;
|
||||||
|
Iterator<StatusChat> chatIterator = chats.values().iterator();
|
||||||
|
while(chatIterator.hasNext()) {
|
||||||
|
StatusChat chat = (StatusChat)chatIterator.next();
|
||||||
|
notify(notificationId, chat);
|
||||||
|
notificationId++;
|
||||||
|
messageCounter += chat.getMessages().size();
|
||||||
|
summaryStyle.addLine(chat.getSummary());
|
||||||
|
summary += chat.getSummary() + "\n";
|
||||||
|
}
|
||||||
|
// NOTE: this is necessary for old versions of Android, newer versions are
|
||||||
|
// building this group themselves and I was not able to make any change to
|
||||||
|
// what this group displays
|
||||||
|
NotificationCompat.Builder groupBuilder =
|
||||||
|
new NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
|
.setContentText(summary)
|
||||||
|
.setSmallIcon(R.drawable.ic_stat_notify_status)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||||
|
.setContentTitle("You got " + messageCounter + " messages in " + chats.size() + " chats")
|
||||||
|
.setStyle(summaryStyle
|
||||||
|
.setBigContentTitle("You got " + messageCounter + " messages in " + chats.size() + " chats"))
|
||||||
|
.setGroup(GROUP_STATUS_MESSAGES)
|
||||||
|
.setGroupSummary(true);
|
||||||
|
notificationManager.notify(notificationId, groupBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleNewMessageSignal(JSONObject newMessageSignal) {
|
||||||
|
try {
|
||||||
|
JSONArray chatsNewMessagesData = newMessageSignal.getJSONObject("event").getJSONArray("messages");
|
||||||
|
for (int i = 0; i < chatsNewMessagesData.length(); i++) {
|
||||||
|
try {
|
||||||
|
upsertChat(chatsNewMessagesData.getJSONObject(i));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(TAG, "JSON conversion failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(shouldRefreshNotifications) {
|
||||||
|
refreshNotifications();
|
||||||
|
shouldRefreshNotifications = false;
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(TAG, "JSON conversion failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Person getPerson(String publicKey, String icon, String name) {
|
||||||
|
// TODO: invalidate cache if icon and name are not the same as
|
||||||
|
// the Person returned (in case the user set a different icon or username for instance)
|
||||||
|
// not critical it's just for notifications at the moment
|
||||||
|
// using a HashMap to cache Person because it's immutable
|
||||||
|
Person person = persons.get(publicKey);
|
||||||
|
if (person == null) {
|
||||||
|
String base64Image = icon.split(",")[1];
|
||||||
|
byte[] decodedString = Base64.decode(base64Image, Base64.DEFAULT);
|
||||||
|
Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
|
||||||
|
person = new Person.Builder().setIcon(IconCompat.createWithBitmap(decodedByte)).setName(name).build();
|
||||||
|
persons.put(publicKey, person);
|
||||||
|
}
|
||||||
|
return person;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void upsertChat(JSONObject chatNewMessagesData) {
|
||||||
|
try {
|
||||||
|
JSONObject chatData = chatNewMessagesData.getJSONObject("chat");
|
||||||
|
// NOTE: this is an exemple of chatData
|
||||||
|
// {"chatId":"contact-discovery-3622","filterId":"c0239d63f830e8b25f4bf7183c8d207f355a925b89514a17068cae4898e7f193",
|
||||||
|
// "symKeyId":"","oneToOne":true,"identity":"046599511623d7385b926ce709ac57d518dac10d451a81f75cd32c7fb4b1c...",
|
||||||
|
// "topic":"0xc446561b","discovery":false,"negotiated":false,"listen":true}
|
||||||
|
boolean oneToOne = chatData.getBoolean("oneToOne");
|
||||||
|
// NOTE: for now we only notify one to one chats
|
||||||
|
// TODO: also notifiy on mentions, keywords and group chats
|
||||||
|
// TODO: one would have to pass the ens name and keywords to notify on when instanciating the class as well
|
||||||
|
// as have a method to add new ones after the handler is instanciated
|
||||||
|
if (oneToOne) {
|
||||||
|
JSONArray messagesData = chatNewMessagesData.getJSONArray("messages");
|
||||||
|
|
||||||
|
// there is no proper id for oneToOne chat in chatData so we peek into first message sig
|
||||||
|
// TODO: won't work for sync becaus it could be our own message
|
||||||
|
String id = messagesData.getJSONObject(0).getJSONObject("message").getString("sig");
|
||||||
|
StatusChat chat = chats.get(id);
|
||||||
|
|
||||||
|
|
||||||
|
// if the chat was not already there, we create one
|
||||||
|
if (chat == null) {
|
||||||
|
chat = new StatusChat(id, oneToOne);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<StatusMessage> messages = chat.getMessages();
|
||||||
|
// parse the new messages
|
||||||
|
for (int j = 0; j < messagesData.length(); j++) {
|
||||||
|
StatusMessage message = createMessage(messagesData.getJSONObject(j));
|
||||||
|
if (message != null) {
|
||||||
|
messages.add(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!messages.isEmpty()) {
|
||||||
|
chat.setMessages(messages);
|
||||||
|
chats.put(id, chat);
|
||||||
|
shouldRefreshNotifications = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(TAG, "JSON conversion failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private StatusMessage createMessage(JSONObject messageData) {
|
||||||
|
try {
|
||||||
|
JSONObject metadata = messageData.getJSONObject("metadata");
|
||||||
|
JSONObject authorMetadata = metadata.getJSONObject("author");
|
||||||
|
JSONArray payload = new JSONArray(messageData.getString("payload"));
|
||||||
|
// NOTE: this is an exemple of payload we are currently working with
|
||||||
|
// it is in the transit format, which is basically JSON
|
||||||
|
// refer to `transport.message.transit.cljs` on react side for details
|
||||||
|
// ["~#c4",["7","text/plain","~:public-group-user-message",157201130275201,1572011302752,["^ ","~:chat-id","test","~:text","7"]]]
|
||||||
|
if (payload.getString(0).equals("~#c4")) {
|
||||||
|
Person author = getPerson(authorMetadata.getString("publicKey"), authorMetadata.getString("identicon"), authorMetadata.getString("alias"));
|
||||||
|
JSONArray payloadContent = payload.getJSONArray(1);
|
||||||
|
String text = payloadContent.getString(0);
|
||||||
|
Double timestamp = payloadContent.getDouble(4);
|
||||||
|
return new StatusMessage(author, timestamp.longValue(), text);
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(TAG, "JSON conversion failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatusChat {
|
||||||
|
private ArrayList<StatusMessage> messages;
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
private Boolean oneToOne;
|
||||||
|
|
||||||
|
StatusChat(String id, Boolean oneToOne) {
|
||||||
|
this.id = id;
|
||||||
|
this.oneToOne = oneToOne;
|
||||||
|
this.messages = new ArrayList<StatusMessage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
//TODO this should be improved as it would rename the chat
|
||||||
|
// after our own user if we were posting from another device
|
||||||
|
// in 1-1 chats it should be the name of the user whose
|
||||||
|
// key is different than ours
|
||||||
|
return getLastMessage().getAuthor().getName().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StatusMessage getLastMessage() {
|
||||||
|
return messages.get(messages.size()-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return getLastMessage().getTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<StatusMessage> getMessages() {
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessages(ArrayList<StatusMessage> messages) {
|
||||||
|
this.messages = messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSummary() {
|
||||||
|
return "<b>" + getLastMessage().getAuthor().getName() + "</b>: " + getLastMessage().getText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class StatusMessage {
|
||||||
|
public Person getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Person author;
|
||||||
|
private long timestamp;
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
StatusMessage(Person author, long timestamp, String text) {
|
||||||
|
this.author = author;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,9 @@ import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
@ -24,6 +26,8 @@ import com.facebook.react.bridge.LifecycleEventListener;
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
import com.facebook.react.bridge.ReactMethod;
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
import com.facebook.react.bridge.WritableMap;
|
import com.facebook.react.bridge.WritableMap;
|
||||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||||
|
|
||||||
|
@ -32,6 +36,8 @@ import statusgo.Statusgo;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
|
@ -51,16 +57,18 @@ import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
|
||||||
class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventListener, SignalHandler {
|
class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventListener, SignalHandler {
|
||||||
|
|
||||||
private static final String TAG = "StatusModule";
|
private static final String TAG = "StatusModule";
|
||||||
private static final String logsZipFileName = "Status-debug-logs.zip";
|
private static final String logsZipFileName = "Status-debug-logs.zip";
|
||||||
private static final String gethLogFileName = "geth.log";
|
private static final String gethLogFileName = "geth.log";
|
||||||
private static final String statusLogFileName = "Status.log";
|
private static final String statusLogFileName = "Status.log";
|
||||||
|
|
||||||
private static StatusModule module;
|
private static StatusModule module;
|
||||||
private ReactApplicationContext reactContext;
|
private ReactApplicationContext reactContext;
|
||||||
private boolean rootedDevice;
|
private boolean rootedDevice;
|
||||||
|
private NewMessageSignalHandler newMessageSignalHandler;
|
||||||
|
|
||||||
StatusModule(ReactApplicationContext reactContext, boolean rootedDevice) {
|
StatusModule(ReactApplicationContext reactContext, boolean rootedDevice) {
|
||||||
super(reactContext);
|
super(reactContext);
|
||||||
|
@ -82,46 +90,72 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHostPause() {
|
public void onHostPause() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHostDestroy() {
|
public void onHostDestroy() {
|
||||||
|
Intent intent = new Intent(getReactApplicationContext(), ForegroundService.class);
|
||||||
|
getReactApplicationContext().stopService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void enableNotifications() {
|
||||||
|
this.newMessageSignalHandler = new NewMessageSignalHandler(reactContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void disableNotifications() {
|
||||||
|
if (newMessageSignalHandler != null) {
|
||||||
|
newMessageSignalHandler.stop();
|
||||||
|
newMessageSignalHandler = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkAvailability() {
|
private boolean checkAvailability() {
|
||||||
// We wait at least 10s for getCurrentActivity to return a value,
|
// We wait at least 10s for getCurrentActivity to return a value,
|
||||||
// otherwise we give up
|
// otherwise we give up
|
||||||
for (int attempts = 0; attempts < 100; attempts++) {
|
for (int attempts = 0; attempts < 100; attempts++) {
|
||||||
if (getCurrentActivity() != null) {
|
if (getCurrentActivity() != null) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
if (getCurrentActivity() != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Activity doesn't exist");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
Thread.sleep(100);
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
if (getCurrentActivity() != null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
Log.d(TAG, "Activity doesn't exist");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "Activity doesn't exist");
|
Log.d(TAG, "Activity doesn't exist");
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNoBackupDirectory() {
|
public String getNoBackupDirectory() {
|
||||||
return this.getReactApplicationContext().getNoBackupFilesDir().getAbsolutePath();
|
return this.getReactApplicationContext().getNoBackupFilesDir().getAbsolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleSignal(String jsonEvent) {
|
public void handleSignal(final String jsonEventString) {
|
||||||
Log.d(TAG, "Signal event: " + jsonEvent);
|
try {
|
||||||
WritableMap params = Arguments.createMap();
|
final JSONObject jsonEvent = new JSONObject(jsonEventString);
|
||||||
params.putString("jsonEvent", jsonEvent);
|
String eventType = jsonEvent.getString("type");
|
||||||
this.getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("gethEvent", params);
|
Log.d(TAG, "Signal event: " + jsonEventString);
|
||||||
|
// NOTE: the newMessageSignalHandler is only instanciated if the user
|
||||||
|
// enabled notifications in the app
|
||||||
|
if (newMessageSignalHandler != null) {
|
||||||
|
if (eventType.equals("messages.new")) {
|
||||||
|
newMessageSignalHandler.handleNewMessageSignal(jsonEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WritableMap params = Arguments.createMap();
|
||||||
|
params.putString("jsonEvent", jsonEventString);
|
||||||
|
this.getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("gethEvent", params);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(TAG, "JSON conversion failed: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private File getLogsFile() {
|
private File getLogsFile() {
|
||||||
|
@ -278,29 +312,27 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void saveAccountAndLogin(final String accountData, final String password , final String config, final String subAccountsData) {
|
public void saveAccountAndLogin(final String accountData, final String password, final String config, final String subAccountsData) {
|
||||||
Log.d(TAG, "saveAccountAndLogin");
|
Log.d(TAG, "saveAccountAndLogin");
|
||||||
String finalConfig = prepareDirAndUpdateConfig(config);
|
String finalConfig = prepareDirAndUpdateConfig(config);
|
||||||
String result = Statusgo.saveAccountAndLogin(accountData, password, finalConfig, subAccountsData);
|
String result = Statusgo.saveAccountAndLogin(accountData, password, finalConfig, subAccountsData);
|
||||||
if (result.startsWith("{\"error\":\"\"")) {
|
if (result.startsWith("{\"error\":\"\"")) {
|
||||||
Log.d(TAG, "StartNode result: " + result);
|
Log.d(TAG, "StartNode result: " + result);
|
||||||
Log.d(TAG, "Geth node started");
|
Log.d(TAG, "Geth node started");
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Log.e(TAG, "StartNode failed: " + result);
|
Log.e(TAG, "StartNode failed: " + result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void saveAccountAndLoginWithKeycard(final String accountData, final String password , final String config, final String chatKey) {
|
public void saveAccountAndLoginWithKeycard(final String accountData, final String password, final String config, final String chatKey) {
|
||||||
Log.d(TAG, "saveAccountAndLoginWithKeycard");
|
Log.d(TAG, "saveAccountAndLoginWithKeycard");
|
||||||
String finalConfig = prepareDirAndUpdateConfig(config);
|
String finalConfig = prepareDirAndUpdateConfig(config);
|
||||||
String result = Statusgo.saveAccountAndLoginWithKeycard(accountData, password, finalConfig, chatKey);
|
String result = Statusgo.saveAccountAndLoginWithKeycard(accountData, password, finalConfig, chatKey);
|
||||||
if (result.startsWith("{\"error\":\"\"")) {
|
if (result.startsWith("{\"error\":\"\"")) {
|
||||||
Log.d(TAG, "StartNode result: " + result);
|
Log.d(TAG, "StartNode result: " + result);
|
||||||
Log.d(TAG, "Geth node started");
|
Log.d(TAG, "Geth node started");
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Log.e(TAG, "StartNode failed: " + result);
|
Log.e(TAG, "StartNode failed: " + result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -311,8 +343,7 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
String result = Statusgo.login(accountData, password);
|
String result = Statusgo.login(accountData, password);
|
||||||
if (result.startsWith("{\"error\":\"\"")) {
|
if (result.startsWith("{\"error\":\"\"")) {
|
||||||
Log.d(TAG, "Login result: " + result);
|
Log.d(TAG, "Login result: " + result);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Log.e(TAG, "Login failed: " + result);
|
Log.e(TAG, "Login failed: " + result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,11 +351,11 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void logout() {
|
public void logout() {
|
||||||
Log.d(TAG, "logout");
|
Log.d(TAG, "logout");
|
||||||
|
disableNotifications();
|
||||||
String result = Statusgo.logout();
|
String result = Statusgo.logout();
|
||||||
if (result.startsWith("{\"error\":\"\"")) {
|
if (result.startsWith("{\"error\":\"\"")) {
|
||||||
Log.d(TAG, "Logout result: " + result);
|
Log.d(TAG, "Logout result: " + result);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Log.e(TAG, "Logout failed: " + result);
|
Log.e(TAG, "Logout failed: " + result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -387,11 +418,11 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
}
|
}
|
||||||
|
|
||||||
Runnable r = new Runnable() {
|
Runnable r = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Statusgo.initKeystore(keydir);
|
Statusgo.initKeystore(keydir);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
StatusThreadPoolExecutor.getInstance().execute(r);
|
StatusThreadPoolExecutor.getInstance().execute(r);
|
||||||
}
|
}
|
||||||
|
@ -409,12 +440,12 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
}
|
}
|
||||||
|
|
||||||
Runnable r = new Runnable() {
|
Runnable r = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
String result =Statusgo.openAccounts(rootDir);
|
String result = Statusgo.openAccounts(rootDir);
|
||||||
callback.invoke(result);
|
callback.invoke(result);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
StatusThreadPoolExecutor.getInstance().execute(r);
|
StatusThreadPoolExecutor.getInstance().execute(r);
|
||||||
}
|
}
|
||||||
|
@ -450,8 +481,7 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
String result = Statusgo.loginWithKeycard(accountData, password, chatKey);
|
String result = Statusgo.loginWithKeycard(accountData, password, chatKey);
|
||||||
if (result.startsWith("{\"error\":\"\"")) {
|
if (result.startsWith("{\"error\":\"\"")) {
|
||||||
Log.d(TAG, "LoginWithKeycard result: " + result);
|
Log.d(TAG, "LoginWithKeycard result: " + result);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Log.e(TAG, "LoginWithKeycard failed: " + result);
|
Log.e(TAG, "LoginWithKeycard failed: " + result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -459,13 +489,13 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
private Boolean zip(File[] _files, File zipFile, Stack<String> errorList) {
|
private Boolean zip(File[] _files, File zipFile, Stack<String> errorList) {
|
||||||
final int BUFFER = 0x8000;
|
final int BUFFER = 0x8000;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
BufferedInputStream origin = null;
|
BufferedInputStream origin = null;
|
||||||
FileOutputStream dest = new FileOutputStream(zipFile);
|
FileOutputStream dest = new FileOutputStream(zipFile);
|
||||||
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest));
|
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest));
|
||||||
byte data[] = new byte[BUFFER];
|
byte data[] = new byte[BUFFER];
|
||||||
|
|
||||||
for (int i = 0; i < _files.length; i++) {
|
for (int i = 0; i < _files.length; i++) {
|
||||||
final File file = _files[i];
|
final File file = _files[i];
|
||||||
if (file == null || !file.exists()) {
|
if (file == null || !file.exists()) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -488,16 +518,16 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
Log.e(TAG, e.getMessage());
|
Log.e(TAG, e.getMessage());
|
||||||
errorList.push(e.getMessage());
|
errorList.push(e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out.close();
|
out.close();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, e.getMessage());
|
Log.e(TAG, e.getMessage());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dumpAdbLogsTo(final FileOutputStream statusLogStream) throws IOException {
|
private void dumpAdbLogsTo(final FileOutputStream statusLogStream) throws IOException {
|
||||||
|
@ -518,13 +548,13 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
final Activity activity = getCurrentActivity();
|
final Activity activity = getCurrentActivity();
|
||||||
|
|
||||||
new AlertDialog.Builder(activity)
|
new AlertDialog.Builder(activity)
|
||||||
.setTitle("Error")
|
.setTitle("Error")
|
||||||
.setMessage(message)
|
.setMessage(message)
|
||||||
.setNegativeButton("Exit", new DialogInterface.OnClickListener() {
|
.setNegativeButton("Exit", new DialogInterface.OnClickListener() {
|
||||||
public void onClick(final DialogInterface dialog, final int id) {
|
public void onClick(final DialogInterface dialog, final int id) {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
}
|
}
|
||||||
}).show();
|
}).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
|
@ -543,8 +573,7 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(dbFile));
|
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(dbFile));
|
||||||
outputStreamWriter.write(dbJson);
|
outputStreamWriter.write(dbJson);
|
||||||
outputStreamWriter.close();
|
outputStreamWriter.close();
|
||||||
}
|
} catch (IOException e) {
|
||||||
catch (IOException e) {
|
|
||||||
Log.e(TAG, "File write failed: " + e.toString());
|
Log.e(TAG, "File write failed: " + e.toString());
|
||||||
showErrorMessage(e.getLocalizedMessage());
|
showErrorMessage(e.getLocalizedMessage());
|
||||||
}
|
}
|
||||||
|
@ -567,7 +596,7 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
dumpAdbLogsTo(new FileOutputStream(statusLogFile));
|
dumpAdbLogsTo(new FileOutputStream(statusLogFile));
|
||||||
|
|
||||||
final Stack<String> errorList = new Stack<String>();
|
final Stack<String> errorList = new Stack<String>();
|
||||||
final Boolean zipped = zip(new File[] {dbFile, gethLogFile, statusLogFile}, zipFile, errorList);
|
final Boolean zipped = zip(new File[]{dbFile, gethLogFile, statusLogFile}, zipFile, errorList);
|
||||||
if (zipped && zipFile.exists()) {
|
if (zipped && zipFile.exists()) {
|
||||||
zipFile.setReadable(true, false);
|
zipFile.setReadable(true, false);
|
||||||
callback.invoke(zipFile.getAbsolutePath());
|
callback.invoke(zipFile.getAbsolutePath());
|
||||||
|
@ -579,8 +608,7 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
showErrorMessage(e.getLocalizedMessage());
|
showErrorMessage(e.getLocalizedMessage());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return;
|
return;
|
||||||
}
|
} finally {
|
||||||
finally {
|
|
||||||
dbFile.delete();
|
dbFile.delete();
|
||||||
statusLogFile.delete();
|
statusLogFile.delete();
|
||||||
zipFile.deleteOnExit();
|
zipFile.deleteOnExit();
|
||||||
|
@ -596,13 +624,13 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
}
|
}
|
||||||
|
|
||||||
Runnable r = new Runnable() {
|
Runnable r = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
String res = Statusgo.addPeer(enode);
|
String res = Statusgo.addPeer(enode);
|
||||||
|
|
||||||
callback.invoke(res);
|
callback.invoke(res);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
StatusThreadPoolExecutor.getInstance().execute(r);
|
StatusThreadPoolExecutor.getInstance().execute(r);
|
||||||
}
|
}
|
||||||
|
@ -1026,138 +1054,138 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Boolean is24Hour() {
|
private Boolean is24Hour() {
|
||||||
return android.text.format.DateFormat.is24HourFormat(this.reactContext.getApplicationContext());
|
return android.text.format.DateFormat.is24HourFormat(this.reactContext.getApplicationContext());
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void extractGroupMembershipSignatures(final String signaturePairs, final Callback callback) {
|
|
||||||
Log.d(TAG, "extractGroupMembershipSignatures");
|
|
||||||
if (!checkAvailability()) {
|
|
||||||
callback.invoke(false);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Runnable r = new Runnable() {
|
@ReactMethod
|
||||||
@Override
|
public void extractGroupMembershipSignatures(final String signaturePairs, final Callback callback) {
|
||||||
public void run() {
|
Log.d(TAG, "extractGroupMembershipSignatures");
|
||||||
String result = Statusgo.extractGroupMembershipSignatures(signaturePairs);
|
if (!checkAvailability()) {
|
||||||
|
callback.invoke(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
callback.invoke(result);
|
Runnable r = new Runnable() {
|
||||||
}
|
@Override
|
||||||
};
|
public void run() {
|
||||||
|
String result = Statusgo.extractGroupMembershipSignatures(signaturePairs);
|
||||||
|
|
||||||
StatusThreadPoolExecutor.getInstance().execute(r);
|
callback.invoke(result);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@ReactMethod
|
StatusThreadPoolExecutor.getInstance().execute(r);
|
||||||
public void signGroupMembership(final String content, final Callback callback) {
|
|
||||||
Log.d(TAG, "signGroupMembership");
|
|
||||||
if (!checkAvailability()) {
|
|
||||||
callback.invoke(false);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Runnable r = new Runnable() {
|
@ReactMethod
|
||||||
@Override
|
public void signGroupMembership(final String content, final Callback callback) {
|
||||||
public void run() {
|
Log.d(TAG, "signGroupMembership");
|
||||||
String result = Statusgo.signGroupMembership(content);
|
if (!checkAvailability()) {
|
||||||
|
callback.invoke(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
callback.invoke(result);
|
Runnable r = new Runnable() {
|
||||||
}
|
@Override
|
||||||
};
|
public void run() {
|
||||||
|
String result = Statusgo.signGroupMembership(content);
|
||||||
|
|
||||||
StatusThreadPoolExecutor.getInstance().execute(r);
|
callback.invoke(result);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
StatusThreadPoolExecutor.getInstance().execute(r);
|
||||||
@ReactMethod
|
|
||||||
public void updateMailservers(final String enodes, final Callback callback) {
|
|
||||||
Log.d(TAG, "updateMailservers");
|
|
||||||
if (!checkAvailability()) {
|
|
||||||
callback.invoke(false);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Runnable r = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
String res = Statusgo.updateMailservers(enodes);
|
|
||||||
|
|
||||||
callback.invoke(res);
|
@ReactMethod
|
||||||
}
|
public void updateMailservers(final String enodes, final Callback callback) {
|
||||||
};
|
Log.d(TAG, "updateMailservers");
|
||||||
|
if (!checkAvailability()) {
|
||||||
|
callback.invoke(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
StatusThreadPoolExecutor.getInstance().execute(r);
|
Runnable r = new Runnable() {
|
||||||
}
|
@Override
|
||||||
|
public void run() {
|
||||||
|
String res = Statusgo.updateMailservers(enodes);
|
||||||
|
|
||||||
@ReactMethod
|
callback.invoke(res);
|
||||||
public void chaosModeUpdate(final boolean on, final Callback callback) {
|
}
|
||||||
Log.d(TAG, "chaosModeUpdate");
|
};
|
||||||
if (!checkAvailability()) {
|
|
||||||
callback.invoke(false);
|
StatusThreadPoolExecutor.getInstance().execute(r);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Runnable r = new Runnable() {
|
@ReactMethod
|
||||||
@Override
|
public void chaosModeUpdate(final boolean on, final Callback callback) {
|
||||||
public void run() {
|
Log.d(TAG, "chaosModeUpdate");
|
||||||
String res = Statusgo.chaosModeUpdate(on);
|
if (!checkAvailability()) {
|
||||||
|
callback.invoke(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
callback.invoke(res);
|
Runnable r = new Runnable() {
|
||||||
}
|
@Override
|
||||||
};
|
public void run() {
|
||||||
|
String res = Statusgo.chaosModeUpdate(on);
|
||||||
|
|
||||||
StatusThreadPoolExecutor.getInstance().execute(r);
|
callback.invoke(res);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@ReactMethod(isBlockingSynchronousMethod = true)
|
StatusThreadPoolExecutor.getInstance().execute(r);
|
||||||
public String generateAlias(final String seed) {
|
|
||||||
return Statusgo.generateAlias(seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
||||||
public String identicon(final String seed) {
|
|
||||||
return Statusgo.identicon(seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void getNodesFromContract(final String rpcEndpoint, final String contractAddress, final Callback callback) {
|
|
||||||
Log.d(TAG, "getNodesFromContract");
|
|
||||||
if (!checkAvailability()) {
|
|
||||||
callback.invoke(false);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Runnable r = new Runnable() {
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||||
@Override
|
public String generateAlias(final String seed) {
|
||||||
public void run() {
|
return Statusgo.generateAlias(seed);
|
||||||
String res = Statusgo.getNodesFromContract(rpcEndpoint, contractAddress);
|
}
|
||||||
|
|
||||||
Log.d(TAG, res);
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||||
callback.invoke(res);
|
public String identicon(final String seed) {
|
||||||
}
|
return Statusgo.identicon(seed);
|
||||||
};
|
}
|
||||||
|
|
||||||
StatusThreadPoolExecutor.getInstance().execute(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@ReactMethod
|
||||||
public @Nullable
|
public void getNodesFromContract(final String rpcEndpoint, final String contractAddress, final Callback callback) {
|
||||||
Map<String, Object> getConstants() {
|
Log.d(TAG, "getNodesFromContract");
|
||||||
HashMap<String, Object> constants = new HashMap<String, Object>();
|
if (!checkAvailability()) {
|
||||||
|
callback.invoke(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
constants.put("is24Hour", this.is24Hour());
|
Runnable r = new Runnable() {
|
||||||
constants.put("model", Build.MODEL);
|
@Override
|
||||||
constants.put("brand", Build.BRAND);
|
public void run() {
|
||||||
constants.put("buildId", Build.ID);
|
String res = Statusgo.getNodesFromContract(rpcEndpoint, contractAddress);
|
||||||
constants.put("deviceId", Build.BOARD);
|
|
||||||
return constants;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
Log.d(TAG, res);
|
||||||
public void isDeviceRooted(final Callback callback) {
|
callback.invoke(res);
|
||||||
callback.invoke(rootedDevice);
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
StatusThreadPoolExecutor.getInstance().execute(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable
|
||||||
|
Map<String, Object> getConstants() {
|
||||||
|
HashMap<String, Object> constants = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
constants.put("is24Hour", this.is24Hour());
|
||||||
|
constants.put("model", Build.MODEL);
|
||||||
|
constants.put("brand", Build.BRAND);
|
||||||
|
constants.put("buildId", Build.ID);
|
||||||
|
constants.put("deviceId", Build.BOARD);
|
||||||
|
return constants;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void isDeviceRooted(final Callback callback) {
|
||||||
|
callback.invoke(rootedDevice);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 640 B |
Binary file not shown.
After Width: | Height: | Size: 429 B |
Binary file not shown.
After Width: | Height: | Size: 809 B |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 648 B |
Binary file not shown.
|
@ -61,6 +61,7 @@ https://dl.google.com/dl/android/maven2/com/android/databinding/baseLibrary/3.3.
|
||||||
https://dl.google.com/dl/android/maven2/com/android/databinding/baseLibrary/3.4.1/baseLibrary-3.4.1
|
https://dl.google.com/dl/android/maven2/com/android/databinding/baseLibrary/3.4.1/baseLibrary-3.4.1
|
||||||
https://dl.google.com/dl/android/maven2/com/android/databinding/compilerCommon/3.0.1/compilerCommon-3.0.1
|
https://dl.google.com/dl/android/maven2/com/android/databinding/compilerCommon/3.0.1/compilerCommon-3.0.1
|
||||||
https://dl.google.com/dl/android/maven2/com/android/support/appcompat-v7/28.0.0/appcompat-v7-28.0.0
|
https://dl.google.com/dl/android/maven2/com/android/support/appcompat-v7/28.0.0/appcompat-v7-28.0.0
|
||||||
|
https://dl.google.com/dl/android/maven2/com/android/support/support-compat/28.0.0/support-compat-28.0.0
|
||||||
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/crash/26.2.1/crash-26.2.1
|
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/crash/26.2.1/crash-26.2.1
|
||||||
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/crash/26.3.1/crash-26.3.1
|
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/crash/26.3.1/crash-26.3.1
|
||||||
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/crash/26.4.1/crash-26.4.1
|
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/crash/26.4.1/crash-26.4.1
|
||||||
|
|
|
@ -965,6 +965,21 @@ in {
|
||||||
sha256 = "0lhp66q8rxf8cxylr8g6qjqy6s26prgrnmq133cnwx2r0ciyba53";
|
sha256 = "0lhp66q8rxf8cxylr8g6qjqy6s26prgrnmq133cnwx2r0ciyba53";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
"https://dl.google.com/dl/android/maven2/com/android/support/support-compat/28.0.0/support-compat-28.0.0" =
|
||||||
|
{
|
||||||
|
host = repositories.google;
|
||||||
|
path =
|
||||||
|
"com/android/support/support-compat/28.0.0/support-compat-28.0.0";
|
||||||
|
type = "aar";
|
||||||
|
pom = {
|
||||||
|
sha1 = "ededbbdbfc461c09f992371624bf7fa564748c36";
|
||||||
|
sha256 = "06ln7psm2gm6nskdj48cgd2mrzs1mlk6m0px3jb0zz4249na0ybb";
|
||||||
|
};
|
||||||
|
jar = {
|
||||||
|
sha1 = "d252b640ed832cf8addc35ef0a9f9186dc7738a5";
|
||||||
|
sha256 = "12hi2xc9qshbdr2jw96664i3va9wj0pjjhv9r2hrwgzavc0knzp1";
|
||||||
|
};
|
||||||
|
};
|
||||||
"https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/crash/26.2.1/crash-26.2.1" =
|
"https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/crash/26.2.1/crash-26.2.1" =
|
||||||
{
|
{
|
||||||
host = repositories.google;
|
host = repositories.google;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
[status-im.i18n :as i18n]
|
[status-im.i18n :as i18n]
|
||||||
[status-im.multiaccounts.update.core :as multiaccounts.update]
|
[status-im.multiaccounts.update.core :as multiaccounts.update]
|
||||||
[status-im.native-module.core :as native-module]
|
[status-im.native-module.core :as native-module]
|
||||||
|
[status-im.notifications.core :as notifications]
|
||||||
[status-im.utils.build :as build]
|
[status-im.utils.build :as build]
|
||||||
[status-im.utils.config :as config]
|
[status-im.utils.config :as config]
|
||||||
[status-im.utils.fx :as fx]
|
[status-im.utils.fx :as fx]
|
||||||
|
@ -65,51 +66,72 @@
|
||||||
{:dev-mode? dev-mode?}
|
{:dev-mode? dev-mode?}
|
||||||
{}))
|
{}))
|
||||||
|
|
||||||
|
(fx/defn switch-notifications
|
||||||
|
{:events [:multiaccounts.ui/notifications-switched]}
|
||||||
|
[cofx notifications-enabled?]
|
||||||
|
(fx/merge cofx
|
||||||
|
{(if notifications-enabled?
|
||||||
|
::notifications/enable
|
||||||
|
::notifications/disable) nil}
|
||||||
|
(multiaccounts.update/multiaccount-update
|
||||||
|
{:notifications-enabled? notifications-enabled?}
|
||||||
|
{})))
|
||||||
|
|
||||||
(fx/defn switch-chaos-mode
|
(fx/defn switch-chaos-mode
|
||||||
[{:keys [db] :as cofx} chaos-mode?]
|
[{:keys [db] :as cofx} chaos-mode?]
|
||||||
(when (:multiaccount db)
|
(when (:multiaccount db)
|
||||||
(fx/merge cofx
|
(fx/merge cofx
|
||||||
{::chaos-mode-changed chaos-mode?}
|
{::chaos-mode-changed chaos-mode?}
|
||||||
(multiaccounts.update/multiaccount-update {:chaos-mode? chaos-mode?}
|
(multiaccounts.update/multiaccount-update
|
||||||
{}))))
|
{:chaos-mode? chaos-mode?}
|
||||||
|
{}))))
|
||||||
|
|
||||||
(fx/defn enable-notifications [cofx desktop-notifications?]
|
(fx/defn enable-notifications [cofx desktop-notifications?]
|
||||||
(multiaccounts.update/multiaccount-update cofx
|
(multiaccounts.update/multiaccount-update
|
||||||
{:desktop-notifications? desktop-notifications?}
|
cofx
|
||||||
{}))
|
{:desktop-notifications? desktop-notifications?}
|
||||||
|
{}))
|
||||||
|
|
||||||
(fx/defn toggle-datasync
|
(fx/defn toggle-datasync
|
||||||
[{:keys [db] :as cofx} enabled?]
|
[{:keys [db] :as cofx} enabled?]
|
||||||
(let [settings (get-in db [:multiaccount :settings])
|
(let [settings (get-in db [:multiaccount :settings])
|
||||||
warning {:utils/show-popup {:title (i18n/label :t/datasync-warning-title)
|
warning {:utils/show-popup
|
||||||
:content (i18n/label :t/datasync-warning-content)}}]
|
{:title (i18n/label :t/datasync-warning-title)
|
||||||
|
:content (i18n/label :t/datasync-warning-content)}}]
|
||||||
|
|
||||||
(fx/merge cofx
|
(fx/merge cofx
|
||||||
(when enabled? warning)
|
(when enabled? warning)
|
||||||
(multiaccounts.update/update-settings (assoc settings :datasync? enabled?)
|
(multiaccounts.update/update-settings
|
||||||
{}))))
|
(assoc settings :datasync? enabled?)
|
||||||
|
{}))))
|
||||||
|
|
||||||
(fx/defn toggle-v1-messages
|
(fx/defn toggle-v1-messages
|
||||||
[{:keys [db] :as cofx} enabled?]
|
[{:keys [db] :as cofx} enabled?]
|
||||||
(let [settings (get-in db [:multiaccount :settings])
|
(let [settings (get-in db [:multiaccount :settings])
|
||||||
warning {:utils/show-popup {:title (i18n/label :t/v1-messages-warning-title)
|
warning {:utils/show-popup
|
||||||
:content (i18n/label :t/v1-messages-warning-content)}}]
|
{:title (i18n/label :t/v1-messages-warning-title)
|
||||||
|
:content (i18n/label :t/v1-messages-warning-content)}}]
|
||||||
|
|
||||||
(fx/merge cofx
|
(fx/merge cofx
|
||||||
(when enabled? warning)
|
(when enabled? warning)
|
||||||
(multiaccounts.update/update-settings (assoc settings :v1-messages? enabled?)
|
(multiaccounts.update/update-settings
|
||||||
{}))))
|
(assoc settings :v1-messages? enabled?)
|
||||||
|
{}))))
|
||||||
|
|
||||||
(fx/defn toggle-disable-discovery-topic
|
(fx/defn toggle-disable-discovery-topic
|
||||||
[{:keys [db] :as cofx} enabled?]
|
[{:keys [db] :as cofx} enabled?]
|
||||||
(let [settings (get-in db [:multiaccount :settings])
|
(let [settings (get-in db [:multiaccount :settings])
|
||||||
warning {:utils/show-popup {:title (i18n/label :t/disable-discovery-topic-warning-title)
|
warning {:utils/show-popup
|
||||||
:content (i18n/label :t/disable-discovery-topic-warning-content)}}]
|
{:title
|
||||||
|
(i18n/label :t/disable-discovery-topic-warning-title)
|
||||||
|
:content
|
||||||
|
(i18n/label :t/disable-discovery-topic-warning-content)}}]
|
||||||
|
|
||||||
(fx/merge cofx
|
(fx/merge cofx
|
||||||
(when enabled? warning)
|
(when enabled? warning)
|
||||||
(multiaccounts.update/update-settings (assoc settings :disable-discovery-topic? enabled?)
|
(multiaccounts.update/update-settings
|
||||||
{}))))
|
(assoc settings :disable-discovery-topic? enabled?)
|
||||||
|
{}))))
|
||||||
|
|
||||||
(fx/defn switch-web3-opt-in-mode
|
(fx/defn switch-web3-opt-in-mode
|
||||||
[{:keys [db] :as cofx} opt-in]
|
[{:keys [db] :as cofx} opt-in]
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
[status-im.i18n :as i18n]
|
[status-im.i18n :as i18n]
|
||||||
[status-im.native-module.core :as status]
|
[status-im.native-module.core :as status]
|
||||||
[status-im.node.core :as node]
|
[status-im.node.core :as node]
|
||||||
|
[status-im.notifications.core :as notifications]
|
||||||
[status-im.protocol.core :as protocol]
|
[status-im.protocol.core :as protocol]
|
||||||
[status-im.stickers.core :as stickers]
|
[status-im.stickers.core :as stickers]
|
||||||
[status-im.ui.screens.mobile-network-settings.events :as mobile-network]
|
[status-im.ui.screens.mobile-network-settings.events :as mobile-network]
|
||||||
|
@ -122,11 +123,12 @@
|
||||||
(when (not= network-id fetched-network-id)
|
(when (not= network-id fetched-network-id)
|
||||||
;;TODO: this shouldn't happen but in case it does
|
;;TODO: this shouldn't happen but in case it does
|
||||||
;;we probably want a better error message
|
;;we probably want a better error message
|
||||||
(utils/show-popup (i18n/label :t/ethereum-node-started-incorrectly-title)
|
(utils/show-popup
|
||||||
(i18n/label :t/ethereum-node-started-incorrectly-description
|
(i18n/label :t/ethereum-node-started-incorrectly-title)
|
||||||
{:network-id network-id
|
(i18n/label :t/ethereum-node-started-incorrectly-description
|
||||||
:fetched-network-id fetched-network-id})
|
{:network-id network-id
|
||||||
#(re-frame/dispatch [::close-app-confirmed]))))}]})
|
:fetched-network-id fetched-network-id})
|
||||||
|
#(re-frame/dispatch [::close-app-confirmed]))))}]})
|
||||||
|
|
||||||
(defn deserialize-config
|
(defn deserialize-config
|
||||||
[{:keys [multiaccount current-network networks]}]
|
[{:keys [multiaccount current-network networks]}]
|
||||||
|
@ -144,14 +146,18 @@
|
||||||
(fx/defn get-config-callback
|
(fx/defn get-config-callback
|
||||||
{:events [::get-config-callback]}
|
{:events [::get-config-callback]}
|
||||||
[{:keys [db] :as cofx} config]
|
[{:keys [db] :as cofx} config]
|
||||||
(let [[{:keys [address] :as multiaccount} current-network networks] (deserialize-config config)
|
(let [[{:keys [address notifications-enabled?] :as multiaccount}
|
||||||
|
current-network networks] (deserialize-config config)
|
||||||
network-id (str (get-in networks [current-network :config :NetworkId]))]
|
network-id (str (get-in networks [current-network :config :NetworkId]))]
|
||||||
(fx/merge cofx
|
(fx/merge cofx
|
||||||
{:db (assoc db
|
(cond-> {:db (assoc db
|
||||||
:networks/current-network current-network
|
:networks/current-network current-network
|
||||||
:networks/networks networks
|
:networks/networks networks
|
||||||
:multiaccount (convert-multiaccount-addresses
|
:multiaccount (convert-multiaccount-addresses
|
||||||
multiaccount))}
|
multiaccount))}
|
||||||
|
(and platform/android?
|
||||||
|
notifications-enabled?)
|
||||||
|
(assoc ::notifications/enable nil))
|
||||||
;; NOTE: initializing mailserver depends on user mailserver
|
;; NOTE: initializing mailserver depends on user mailserver
|
||||||
;; preference which is why we wait for config callback
|
;; preference which is why we wait for config callback
|
||||||
(protocol/initialize-protocol {:default-mailserver true})
|
(protocol/initialize-protocol {:default-mailserver true})
|
||||||
|
@ -169,9 +175,15 @@
|
||||||
[{:keys [db] :as cofx} address password save-password?]
|
[{:keys [db] :as cofx} address password save-password?]
|
||||||
(let [auth-method (:auth-method db)
|
(let [auth-method (:auth-method db)
|
||||||
new-auth-method (if save-password?
|
new-auth-method (if save-password?
|
||||||
(when-not (or (= "biometric" auth-method) (= "password" auth-method))
|
(when-not (or (= "biometric" auth-method)
|
||||||
(if (= auth-method "biometric-prepare") "biometric" "password"))
|
(= "password" auth-method))
|
||||||
(when (and auth-method (not= auth-method "none")) "none"))]
|
(if (= auth-method "biometric-prepare")
|
||||||
|
"biometric"
|
||||||
|
"password"))
|
||||||
|
(when (and auth-method
|
||||||
|
(not= auth-method
|
||||||
|
"none"))
|
||||||
|
"none"))]
|
||||||
(fx/merge cofx
|
(fx/merge cofx
|
||||||
{:db (assoc db :chats/loading? true)
|
{:db (assoc db :chats/loading? true)
|
||||||
::json-rpc/call
|
::json-rpc/call
|
||||||
|
|
|
@ -30,6 +30,12 @@
|
||||||
config
|
config
|
||||||
#(callback (types/json->clj %))))
|
#(callback (types/json->clj %))))
|
||||||
|
|
||||||
|
(defn enable-notifications []
|
||||||
|
(.enableNotifications (status)))
|
||||||
|
|
||||||
|
(defn disable-notifications []
|
||||||
|
(.disableNotifications (status)))
|
||||||
|
|
||||||
(defn save-account-and-login
|
(defn save-account-and-login
|
||||||
"NOTE: beware, the password has to be sha3 hashed"
|
"NOTE: beware, the password has to be sha3 hashed"
|
||||||
[multiaccount-data hashed-password config accounts-data]
|
[multiaccount-data hashed-password config accounts-data]
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
(ns status-im.notifications.core
|
||||||
|
(:require [re-frame.core :as re-frame]
|
||||||
|
[status-im.native-module.core :as status]))
|
||||||
|
|
||||||
|
(re-frame/reg-fx
|
||||||
|
::enable
|
||||||
|
(fn [_]
|
||||||
|
(status/enable-notifications)))
|
||||||
|
|
||||||
|
(re-frame/reg-fx
|
||||||
|
::disable
|
||||||
|
(fn [_]
|
||||||
|
(status/disable-notifications)))
|
|
@ -6,6 +6,7 @@
|
||||||
[status-im.utils.handlers :as handlers]
|
[status-im.utils.handlers :as handlers]
|
||||||
[status-im.ethereum.json-rpc :as json-rpc]
|
[status-im.ethereum.json-rpc :as json-rpc]
|
||||||
[status-im.ethereum.core :as ethereum]
|
[status-im.ethereum.core :as ethereum]
|
||||||
|
[status-im.native-module.core :as status]
|
||||||
[status-im.transport.message.contact :as contact]
|
[status-im.transport.message.contact :as contact]
|
||||||
[status-im.transport.message.protocol :as protocol]
|
[status-im.transport.message.protocol :as protocol]
|
||||||
[status-im.transport.message.transit :as transit]
|
[status-im.transport.message.transit :as transit]
|
||||||
|
@ -197,4 +198,3 @@
|
||||||
:params [confirmations]
|
:params [confirmations]
|
||||||
:on-success #(log/debug "successfully confirmed messages")
|
:on-success #(log/debug "successfully confirmed messages")
|
||||||
:on-failure #(log/error "failed to confirm messages" %)}))))
|
:on-failure #(log/error "failed to confirm messages" %)}))))
|
||||||
|
|
||||||
|
|
|
@ -111,7 +111,7 @@
|
||||||
(defn- flat-list-content
|
(defn- flat-list-content
|
||||||
[preferred-name registrar tribute-to-talk
|
[preferred-name registrar tribute-to-talk
|
||||||
active-contacts-count show-backup-seed?
|
active-contacts-count show-backup-seed?
|
||||||
keycard-account?]
|
keycard-account? notifications-enabled?]
|
||||||
[(cond-> {:title (or (when registrar preferred-name)
|
[(cond-> {:title (or (when registrar preferred-name)
|
||||||
:t/ens-usernames)
|
:t/ens-usernames)
|
||||||
:subtitle (if registrar
|
:subtitle (if registrar
|
||||||
|
@ -151,15 +151,23 @@
|
||||||
[(when show-backup-seed?
|
[(when show-backup-seed?
|
||||||
[components.common/counter {:size 22} 1]) :chevron]
|
[components.common/counter {:size 22} 1]) :chevron]
|
||||||
:on-press #(re-frame/dispatch [:navigate-to :privacy-and-security])}
|
:on-press #(re-frame/dispatch [:navigate-to :privacy-and-security])}
|
||||||
;; TODO commented out for now because it will be enabled for android notifications
|
(when (and platform/android?
|
||||||
#_{:icon :main-icons/notification
|
config/local-notifications?)
|
||||||
|
{:icon :main-icons/notification
|
||||||
:title :t/notifications
|
:title :t/notifications
|
||||||
:accessibility-label :notifications-button
|
:accessibility-label :notifications-button
|
||||||
;; TODO commented out for now, uncomment when notifications-settings view
|
:on-press
|
||||||
;; is populated. Then remove :on-press below
|
#(re-frame/dispatch
|
||||||
;; :on-press #(re-frame/dispatch [:navigate-to :notifications-settings])
|
[:multiaccounts.ui/notifications-switched (not notifications-enabled?)])
|
||||||
:on-press #(.openURL react/linking "app-settings://notification/status-im")
|
:accessories
|
||||||
:accessories [:chevron]}
|
[[react/switch
|
||||||
|
{:track-color #js {:true colors/blue :false nil}
|
||||||
|
:value notifications-enabled?
|
||||||
|
:on-value-change
|
||||||
|
#(re-frame/dispatch
|
||||||
|
[:multiaccounts.ui/notifications-switched
|
||||||
|
(not notifications-enabled?)])
|
||||||
|
:disabled false}]]})
|
||||||
{:icon :main-icons/mobile
|
{:icon :main-icons/mobile
|
||||||
:title :t/sync-settings
|
:title :t/sync-settings
|
||||||
:accessibility-label :sync-settings-button
|
:accessibility-label :sync-settings-button
|
||||||
|
@ -212,7 +220,8 @@
|
||||||
seed-backed-up?
|
seed-backed-up?
|
||||||
mnemonic
|
mnemonic
|
||||||
keycard-key-uid
|
keycard-key-uid
|
||||||
address]
|
address
|
||||||
|
notifications-enabled?]
|
||||||
:as multiaccount} @(re-frame/subscribe [:multiaccount])
|
:as multiaccount} @(re-frame/subscribe [:multiaccount])
|
||||||
active-contacts-count @(re-frame/subscribe [:contacts/active-count])
|
active-contacts-count @(re-frame/subscribe [:contacts/active-count])
|
||||||
tribute-to-talk @(re-frame/subscribe [:tribute-to-talk/profile])
|
tribute-to-talk @(re-frame/subscribe [:tribute-to-talk/profile])
|
||||||
|
@ -225,7 +234,7 @@
|
||||||
(flat-list-content
|
(flat-list-content
|
||||||
preferred-name registrar tribute-to-talk
|
preferred-name registrar tribute-to-talk
|
||||||
active-contacts-count show-backup-seed?
|
active-contacts-count show-backup-seed?
|
||||||
keycard-key-uid)
|
keycard-key-uid notifications-enabled?)
|
||||||
list-ref
|
list-ref
|
||||||
scroll-y]))
|
scroll-y]))
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
(def max-message-delivery-attempts (js/parseInt (get-config :MAX_MESSAGE_DELIVERY_ATTEMPTS "6")))
|
(def max-message-delivery-attempts (js/parseInt (get-config :MAX_MESSAGE_DELIVERY_ATTEMPTS "6")))
|
||||||
(def contract-nodes-enabled? (enabled? (get-config :CONTRACT_NODES "0")))
|
(def contract-nodes-enabled? (enabled? (get-config :CONTRACT_NODES "0")))
|
||||||
(def mobile-ui-for-desktop? (enabled? (get-config :MOBILE_UI_FOR_DESKTOP "0")))
|
(def mobile-ui-for-desktop? (enabled? (get-config :MOBILE_UI_FOR_DESKTOP "0")))
|
||||||
|
;; NOTE: only disabled in releases
|
||||||
|
(def local-notifications? (enabled? (get-config :LOCAL_NOTIFICATIONS "1")))
|
||||||
|
|
||||||
;; CONFIG VALUES
|
;; CONFIG VALUES
|
||||||
(def log-level
|
(def log-level
|
||||||
|
|
Loading…
Reference in New Issue