mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-25 17:27:45 +00:00
Merge pull request #35 from loki-project/custom-server
Custom Public Chats
This commit is contained in:
commit
e8e539c425
@ -479,6 +479,10 @@
|
|||||||
<activity android:name="org.thoughtcrime.securesms.loki.NewConversationActivity"
|
<activity android:name="org.thoughtcrime.securesms.loki.NewConversationActivity"
|
||||||
android:windowSoftInputMode="stateAlwaysVisible"
|
android:windowSoftInputMode="stateAlwaysVisible"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
|
|
||||||
|
<activity android:name="org.thoughtcrime.securesms.loki.AddPublicChatActivity"
|
||||||
|
android:windowSoftInputMode="stateAlwaysVisible"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
<!-- Loki -->
|
<!-- Loki -->
|
||||||
|
|
||||||
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
|
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
|
||||||
|
48
res/layout/activity_add_public_chat.xml
Normal file
48
res/layout/activity_add_public_chat.xml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.LabeledEditText
|
||||||
|
android:id="@+id/urlEditText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
app:labeledEditText_background="@color/loki_darkest_gray"
|
||||||
|
app:labeledEditText_label="@string/fragment_add_public_chat_url_edit_text_label"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/explanationTextView"
|
||||||
|
style="@style/Signal.Text.Body"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="@string/fragment_add_public_chat_explanation" />
|
||||||
|
|
||||||
|
<com.dd.CircularProgressButton
|
||||||
|
android:id="@+id/addButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:background="@color/signal_primary"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:cpb_colorIndicator="@color/white"
|
||||||
|
app:cpb_colorProgress="@color/textsecure_primary"
|
||||||
|
app:cpb_cornerRadius="4dp"
|
||||||
|
app:cpb_selectorIdle="@drawable/progress_button_state"
|
||||||
|
app:cpb_textIdle="@string/fragment_add_public_chat_add_button_title_1" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
@ -1,7 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item android:title="@string/text_secure_normal__menu_new_group"
|
<item android:title="@string/activity_conversation_list_add_public_chat_button_title"
|
||||||
|
android:id="@+id/menu_conversation_list_add_public_chat_option"
|
||||||
|
android:icon="@drawable/ic_group_white_24dp"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
<!-- <item android:title="@string/text_secure_normal__menu_new_group"
|
||||||
android:id="@+id/menu_new_group" />
|
android:id="@+id/menu_new_group" />
|
||||||
|
|
||||||
<item android:title="@string/text_secure_normal__menu_clear_passphrase"
|
<item android:title="@string/text_secure_normal__menu_clear_passphrase"
|
||||||
@ -17,6 +23,6 @@
|
|||||||
android:id="@+id/menu_settings" />
|
android:id="@+id/menu_settings" />
|
||||||
|
|
||||||
<item android:title="@string/text_secure_normal__help"
|
<item android:title="@string/text_secure_normal__help"
|
||||||
android:id="@+id/menu_help"/>
|
android:id="@+id/menu_help"/> -->
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
@ -1598,6 +1598,14 @@
|
|||||||
<string name="fragment_new_conversation_next_button_title">Next</string>
|
<string name="fragment_new_conversation_next_button_title">Next</string>
|
||||||
<string name="fragment_new_conversation_invalid_public_key_message">Invalid public key</string>
|
<string name="fragment_new_conversation_invalid_public_key_message">Invalid public key</string>
|
||||||
<string name="fragment_new_conversation_note_to_self_not_supported_message">Please enter the public key of the person you\'d like to message</string>
|
<string name="fragment_new_conversation_note_to_self_not_supported_message">Please enter the public key of the person you\'d like to message</string>
|
||||||
|
<!-- Add public chat activity -->
|
||||||
|
<string name="fragment_add_public_chat_title">Add Public Chat</string>
|
||||||
|
<string name="fragment_add_public_chat_url_edit_text_label">URL</string>
|
||||||
|
<string name="fragment_add_public_chat_explanation">Enter the URL of the public chat you\'d like to join. The Loki Public Chat URL is https://chat.lokinet.org.</string>
|
||||||
|
<string name="fragment_add_public_chat_add_button_title_1">Add</string>
|
||||||
|
<string name="fragment_add_public_chat_add_button_title_2">Adding Server...</string>
|
||||||
|
<string name="fragment_add_public_chat_invalid_url_message">Invalid URL</string>
|
||||||
|
<string name="fragment_add_public_chat_connection_failed_message">Couldn\'t Connect</string>
|
||||||
<!-- Friend request view -->
|
<!-- Friend request view -->
|
||||||
<string name="view_friend_request_accept_button_title">Accept</string>
|
<string name="view_friend_request_accept_button_title">Accept</string>
|
||||||
<string name="view_friend_request_reject_button_title">Reject</string>
|
<string name="view_friend_request_reject_button_title">Reject</string>
|
||||||
@ -1631,5 +1639,7 @@
|
|||||||
<string name="fragment_scan_qr_code_camera_permission_dialog_message">Loki Messenger needs camera access to scan QR codes.</string>
|
<string name="fragment_scan_qr_code_camera_permission_dialog_message">Loki Messenger needs camera access to scan QR codes.</string>
|
||||||
<!-- Conversation activity -->
|
<!-- Conversation activity -->
|
||||||
<string name="activity_conversation_copy_public_key_button_title">Copy public key</string>
|
<string name="activity_conversation_copy_public_key_button_title">Copy public key</string>
|
||||||
|
<!-- Conversation list activity -->
|
||||||
|
<string name="activity_conversation_list_add_public_chat_button_title">Add Public Chat</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -25,6 +25,7 @@ import android.database.ContentObserver;
|
|||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.support.multidex.MultiDexApplication;
|
import android.support.multidex.MultiDexApplication;
|
||||||
|
|
||||||
import com.crashlytics.android.Crashlytics;
|
import com.crashlytics.android.Crashlytics;
|
||||||
@ -61,8 +62,9 @@ import org.thoughtcrime.securesms.logging.PersistentLogger;
|
|||||||
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
|
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
|
||||||
import org.thoughtcrime.securesms.loki.BackgroundPollWorker;
|
import org.thoughtcrime.securesms.loki.BackgroundPollWorker;
|
||||||
import org.thoughtcrime.securesms.loki.LokiAPIDatabase;
|
import org.thoughtcrime.securesms.loki.LokiAPIDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.LokiGroupChatPoller;
|
import org.thoughtcrime.securesms.loki.LokiPublicChatManager;
|
||||||
import org.thoughtcrime.securesms.loki.LokiRSSFeedPoller;
|
import org.thoughtcrime.securesms.loki.LokiRSSFeedPoller;
|
||||||
|
import org.thoughtcrime.securesms.loki.LokiUserDatabase;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
@ -85,8 +87,8 @@ import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
|
|||||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol;
|
import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiGroupChat;
|
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI;
|
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiLongPoller;
|
import org.whispersystems.signalservice.loki.api.LokiLongPoller;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiP2PAPI;
|
import org.whispersystems.signalservice.loki.api.LokiP2PAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiP2PAPIDelegate;
|
import org.whispersystems.signalservice.loki.api.LokiP2PAPIDelegate;
|
||||||
@ -98,6 +100,7 @@ import java.security.Security;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -132,9 +135,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
|
|
||||||
// Loki
|
// Loki
|
||||||
private LokiLongPoller lokiLongPoller = null;
|
private LokiLongPoller lokiLongPoller = null;
|
||||||
private LokiGroupChatPoller lokiPublicChatPoller = null;
|
|
||||||
private LokiRSSFeedPoller lokiNewsFeedPoller = null;
|
private LokiRSSFeedPoller lokiNewsFeedPoller = null;
|
||||||
private LokiRSSFeedPoller lokiMessengerUpdatesFeedPoller = null;
|
private LokiRSSFeedPoller lokiMessengerUpdatesFeedPoller = null;
|
||||||
|
private LokiPublicChatManager lokiPublicChatManager = null;
|
||||||
|
private LokiPublicChatAPI lokiPublicChatAPI = null;
|
||||||
public SignalCommunicationModule communicationModule;
|
public SignalCommunicationModule communicationModule;
|
||||||
public MixpanelAPI mixpanel;
|
public MixpanelAPI mixpanel;
|
||||||
|
|
||||||
@ -147,7 +151,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
LokiGroupChatAPI.Companion.setDebugMode(BuildConfig.DEBUG); // Loki - Set debug mode if needed
|
|
||||||
startKovenant();
|
startKovenant();
|
||||||
Log.i(TAG, "onCreate()");
|
Log.i(TAG, "onCreate()");
|
||||||
initializeSecurityProvider();
|
initializeSecurityProvider();
|
||||||
@ -183,6 +186,8 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
mixpanel.trackMap(event, properties);
|
mixpanel.trackMap(event, properties);
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
};
|
};
|
||||||
|
// Loki - Set up public chat manager
|
||||||
|
lokiPublicChatManager = new LokiPublicChatManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -193,6 +198,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
KeyCachingService.onAppForegrounded(this);
|
KeyCachingService.onAppForegrounded(this);
|
||||||
// Loki - Start long polling if needed
|
// Loki - Start long polling if needed
|
||||||
startLongPollingIfNeeded();
|
startLongPollingIfNeeded();
|
||||||
|
lokiPublicChatManager.startPollersIfNeeded();
|
||||||
setUpStorageAPIIfNeeded();
|
setUpStorageAPIIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,6 +210,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
MessageNotifier.setVisibleThread(-1);
|
MessageNotifier.setVisibleThread(-1);
|
||||||
// Loki - Stop long polling if needed
|
// Loki - Stop long polling if needed
|
||||||
if (lokiLongPoller != null) { lokiLongPoller.stopIfNeeded(); }
|
if (lokiLongPoller != null) { lokiLongPoller.stopIfNeeded(); }
|
||||||
|
if (lokiPublicChatManager != null) { lokiPublicChatManager.stopPollers(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -243,6 +250,21 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
return persistentLogger;
|
return persistentLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LokiPublicChatManager getLokiPublicChatManager() {
|
||||||
|
return lokiPublicChatManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable LokiPublicChatAPI getLokiPublicChatAPI() {
|
||||||
|
if (lokiPublicChatAPI == null && IdentityKeyUtil.hasIdentityKey(this)) {
|
||||||
|
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||||
|
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
|
||||||
|
LokiAPIDatabase apiDatabase = DatabaseFactory.getLokiAPIDatabase(this);
|
||||||
|
LokiUserDatabase userDatabase = DatabaseFactory.getLokiUserDatabase(this);
|
||||||
|
lokiPublicChatAPI = new LokiPublicChatAPI(userHexEncodedPublicKey, userPrivateKey, apiDatabase, userDatabase);
|
||||||
|
}
|
||||||
|
return lokiPublicChatAPI;
|
||||||
|
}
|
||||||
|
|
||||||
private void initializeSecurityProvider() {
|
private void initializeSecurityProvider() {
|
||||||
try {
|
try {
|
||||||
Class.forName("org.signal.aesgcmprovider.AesGcmCipher");
|
Class.forName("org.signal.aesgcmprovider.AesGcmCipher");
|
||||||
@ -471,10 +493,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
if (lokiLongPoller != null) { lokiLongPoller.startIfNeeded(); }
|
if (lokiLongPoller != null) { lokiLongPoller.startIfNeeded(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
private LokiGroupChat lokiPublicChat() {
|
|
||||||
return new LokiGroupChat(LokiGroupChatAPI.getPublicChatServerID(), LokiGroupChatAPI.getPublicChatServer(), "Loki Public Chat", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LokiRSSFeed lokiNewsFeed() {
|
private LokiRSSFeed lokiNewsFeed() {
|
||||||
return new LokiRSSFeed("loki.network.feed", "https://loki.network/feed/", "Loki News", true);
|
return new LokiRSSFeed("loki.network.feed", "https://loki.network/feed/", "Loki News", true);
|
||||||
}
|
}
|
||||||
@ -483,12 +501,22 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
return new LokiRSSFeed("loki.network.messenger-updates.feed", "https://loki.network/category/messenger-updates/feed", "Loki Messenger Updates", false);
|
return new LokiRSSFeed("loki.network.messenger-updates.feed", "https://loki.network/category/messenger-updates/feed", "Loki Messenger Updates", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createGroupChatsIfNeeded() {
|
public void createDefaultPublicChatsIfNeeded() {
|
||||||
LokiGroupChat publicChat = lokiPublicChat();
|
List<LokiPublicChat> defaultPublicChats = LokiPublicChatAPI.Companion.getDefaultChats(BuildConfig.DEBUG);
|
||||||
boolean isChatSetUp = TextSecurePreferences.isChatSetUp(this, publicChat.getId());
|
for (LokiPublicChat publiChat : defaultPublicChats) {
|
||||||
if (!isChatSetUp || !publicChat.isDeletable()) {
|
long threadID = GroupManager.getThreadId(publiChat.getId(), this);
|
||||||
GroupManager.GroupActionResult result = GroupManager.createGroup(publicChat.getId(), this, new HashSet<>(), null, publicChat.getDisplayName(), false);
|
String migrationKey = publiChat.getId() + "_migrated";
|
||||||
TextSecurePreferences.markChatSetUp(this, publicChat.getId());
|
boolean isChatMigrated = TextSecurePreferences.getBooleanPreference(this, migrationKey, false);
|
||||||
|
boolean isChatSetUp = TextSecurePreferences.isChatSetUp(this, publiChat.getId());
|
||||||
|
if (!isChatSetUp || !publiChat.isDeletable()) {
|
||||||
|
lokiPublicChatManager.addChat(publiChat.getServer(), publiChat.getChannel(), publiChat.getDisplayName());
|
||||||
|
TextSecurePreferences.markChatSetUp(this, publiChat.getId());
|
||||||
|
TextSecurePreferences.setBooleanPreference(this, migrationKey, true);
|
||||||
|
} else if (threadID > -1 && !isChatMigrated) {
|
||||||
|
// Migrate the old public chats
|
||||||
|
DatabaseFactory.getLokiThreadDatabase(this).setPublicChat(publiChat, threadID);
|
||||||
|
TextSecurePreferences.setBooleanPreference(this, migrationKey, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,20 +533,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createGroupChatPollersIfNeeded() {
|
|
||||||
// Only create the group chat pollers if their threads aren't deleted
|
|
||||||
LokiGroupChat publicChat = lokiPublicChat();
|
|
||||||
long threadID = GroupManager.getThreadId(publicChat.getId(), this);
|
|
||||||
if (threadID >= 0 && lokiPublicChatPoller == null) {
|
|
||||||
lokiPublicChatPoller = new LokiGroupChatPoller(this, publicChat);
|
|
||||||
// Set up deletion listeners if needed
|
|
||||||
setUpThreadDeletionListeners(threadID, () -> {
|
|
||||||
if (lokiPublicChatPoller != null) lokiPublicChatPoller.stop();
|
|
||||||
lokiPublicChatPoller = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createRSSFeedPollersIfNeeded() {
|
private void createRSSFeedPollersIfNeeded() {
|
||||||
// Only create the RSS feed pollers if their threads aren't deleted
|
// Only create the RSS feed pollers if their threads aren't deleted
|
||||||
LokiRSSFeed lokiNewsFeed = lokiNewsFeed();
|
LokiRSSFeed lokiNewsFeed = lokiNewsFeed();
|
||||||
@ -558,11 +572,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
this.getContentResolver().registerContentObserver(DatabaseContentProviders.Conversation.getUriForThread(threadID), true, observer);
|
this.getContentResolver().registerContentObserver(DatabaseContentProviders.Conversation.getUriForThread(threadID), true, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startGroupChatPollersIfNeeded() {
|
|
||||||
createGroupChatPollersIfNeeded();
|
|
||||||
if (lokiPublicChatPoller != null) lokiPublicChatPoller.startIfNeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startRSSFeedPollersIfNeeded() {
|
public void startRSSFeedPollersIfNeeded() {
|
||||||
createRSSFeedPollersIfNeeded();
|
createRSSFeedPollersIfNeeded();
|
||||||
if (lokiNewsFeedPoller != null) lokiNewsFeedPoller.startIfNeeded();
|
if (lokiNewsFeedPoller != null) lokiNewsFeedPoller.startIfNeeded();
|
||||||
|
@ -29,6 +29,7 @@ import android.support.annotation.NonNull;
|
|||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.support.v7.widget.TooltipCompat;
|
import android.support.v7.widget.TooltipCompat;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -44,6 +45,7 @@ import org.thoughtcrime.securesms.database.Address;
|
|||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
|
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
|
||||||
import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
|
import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
|
||||||
|
import org.thoughtcrime.securesms.loki.AddPublicChatActivity;
|
||||||
import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable;
|
import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable;
|
||||||
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
|
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
@ -82,9 +84,9 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
|||||||
dynamicLanguage.onCreate(this);
|
dynamicLanguage.onCreate(this);
|
||||||
if (TextSecurePreferences.getLocalNumber(this) != null) {
|
if (TextSecurePreferences.getLocalNumber(this) != null) {
|
||||||
ApplicationContext application = ApplicationContext.getInstance(this);
|
ApplicationContext application = ApplicationContext.getInstance(this);
|
||||||
application.createGroupChatsIfNeeded();
|
application.createDefaultPublicChatsIfNeeded();
|
||||||
application.createRSSFeedsIfNeeded();
|
application.createRSSFeedsIfNeeded();
|
||||||
application.startGroupChatPollersIfNeeded();
|
application.getLokiPublicChatManager().startPollersIfNeeded();
|
||||||
application.startRSSFeedPollersIfNeeded();
|
application.startRSSFeedPollersIfNeeded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,18 +129,15 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
return false;
|
|
||||||
/*
|
|
||||||
MenuInflater inflater = this.getMenuInflater();
|
MenuInflater inflater = this.getMenuInflater();
|
||||||
menu.clear();
|
menu.clear();
|
||||||
|
|
||||||
inflater.inflate(R.menu.text_secure_normal, menu);
|
inflater.inflate(R.menu.text_secure_normal, menu);
|
||||||
|
|
||||||
menu.findItem(R.id.menu_clear_passphrase).setVisible(!TextSecurePreferences.isPasswordDisabled(this));
|
// menu.findItem(R.id.menu_clear_passphrase).setVisible(!TextSecurePreferences.isPasswordDisabled(this));
|
||||||
|
|
||||||
super.onPrepareOptionsMenu(menu);
|
super.onPrepareOptionsMenu(menu);
|
||||||
return true;
|
return true;
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeSearchListener() {
|
private void initializeSearchListener() {
|
||||||
@ -235,12 +234,13 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
|||||||
super.onOptionsItemSelected(item);
|
super.onOptionsItemSelected(item);
|
||||||
|
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.menu_new_group: createGroup(); return true;
|
// case R.id.menu_new_group: createGroup(); return true;
|
||||||
case R.id.menu_settings: handleDisplaySettings(); return true;
|
// case R.id.menu_settings: handleDisplaySettings(); return true;
|
||||||
case R.id.menu_clear_passphrase: handleClearPassphrase(); return true;
|
// case R.id.menu_clear_passphrase: handleClearPassphrase(); return true;
|
||||||
case R.id.menu_mark_all_read: handleMarkAllRead(); return true;
|
// case R.id.menu_mark_all_read: handleMarkAllRead(); return true;
|
||||||
case R.id.menu_invite: handleInvite(); return true;
|
// case R.id.menu_invite: handleInvite(); return true;
|
||||||
case R.id.menu_help: handleHelp(); return true;
|
// case R.id.menu_help: handleHelp(); return true;
|
||||||
|
case R.id.menu_conversation_list_add_public_chat_option: addNewPublicChat(); return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -321,4 +321,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
|||||||
Toast.makeText(this, R.string.ConversationListActivity_there_is_no_browser_installed_on_your_device, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.ConversationListActivity_there_is_no_browser_installed_on_your_device, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addNewPublicChat() {
|
||||||
|
startActivity(new Intent(this, AddPublicChatActivity.class));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,7 +273,7 @@ public class ConversationListItem extends RelativeLayout
|
|||||||
|
|
||||||
private @NonNull CharSequence getTrimmedSnippet(@NonNull CharSequence snippet) {
|
private @NonNull CharSequence getTrimmedSnippet(@NonNull CharSequence snippet) {
|
||||||
LokiAPIUtilities.INSTANCE.populateUserHexEncodedPublicKeyCacheIfNeeded(threadId, getContext()); // TODO: Terrible place to do this, but okay for now
|
LokiAPIUtilities.INSTANCE.populateUserHexEncodedPublicKeyCacheIfNeeded(threadId, getContext()); // TODO: Terrible place to do this, but okay for now
|
||||||
snippet = MentionUtilities.highlightMentions(snippet, this.recipient.isGroupRecipient(), getContext());
|
snippet = MentionUtilities.highlightMentions(snippet, threadId, getContext());
|
||||||
return snippet.length() <= MAX_SNIPPET_LENGTH ? snippet : snippet.subSequence(0, MAX_SNIPPET_LENGTH);
|
return snippet.length() <= MAX_SNIPPET_LENGTH ? snippet : snippet.subSequence(0, MAX_SNIPPET_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,15 +35,12 @@ import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
|
|||||||
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
|
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
|
||||||
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
|
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
|
||||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob;
|
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.LokiAPIDatabase;
|
|
||||||
import org.thoughtcrime.securesms.loki.LokiUserDatabase;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||||
@ -61,13 +58,14 @@ import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
|||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
||||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI;
|
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
|
||||||
import org.whispersystems.signalservice.loki.utilities.Analytics;
|
import org.whispersystems.signalservice.loki.utilities.Analytics;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@ -380,12 +378,13 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
|||||||
Analytics.Companion.getShared().track("Display Name Updated");
|
Analytics.Companion.getShared().track("Display Name Updated");
|
||||||
|
|
||||||
TextSecurePreferences.setProfileName(context, name);
|
TextSecurePreferences.setProfileName(context, name);
|
||||||
|
LokiPublicChatAPI publicChatAPI = ApplicationContext.getInstance(context).getLokiPublicChatAPI();
|
||||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
|
if (publicChatAPI != null) {
|
||||||
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).getPrivateKey().serialize();
|
Set<String> servers = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChatServers();
|
||||||
LokiAPIDatabase apiDatabase = DatabaseFactory.getLokiAPIDatabase(context);
|
for (String server : servers) {
|
||||||
LokiUserDatabase userDatabase = DatabaseFactory.getLokiUserDatabase(context);
|
publicChatAPI.setDisplayName(name, server);
|
||||||
new LokiGroupChatAPI(userHexEncodedPublicKey, userPrivateKey, apiDatabase, userDatabase).setDisplayName(name, LokiGroupChatAPI.getPublicChatServer());
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Loki - Original code
|
// Loki - Original code
|
||||||
// ========
|
// ========
|
||||||
|
@ -28,10 +28,10 @@ import org.thoughtcrime.securesms.mms.Slide;
|
|||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI;
|
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -194,19 +194,16 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener
|
|||||||
boolean isOwnNumber = Util.isOwnNumber(getContext(), author.getAddress());
|
boolean isOwnNumber = Util.isOwnNumber(getContext(), author.getAddress());
|
||||||
|
|
||||||
String quoteeDisplayName = author.toShortString();
|
String quoteeDisplayName = author.toShortString();
|
||||||
if (quoteeDisplayName.equals(author.getAddress().toString())) {
|
|
||||||
quoteeDisplayName = DatabaseFactory.getLokiUserDatabase(getContext()).getServerDisplayName(LokiGroupChatAPI.getPublicChatServer() + "." + LokiGroupChatAPI.getPublicChatServerID(), author.getAddress().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're in a group then try and use the display name in the group
|
long threadID = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(conversationRecipient);
|
||||||
if (conversationRecipient.isGroupRecipient()) {
|
String senderHexEncodedPublicKey = author.getAddress().serialize();
|
||||||
try {
|
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadID);
|
||||||
String serverId = GroupUtil.getDecodedStringId(conversationRecipient.getAddress().serialize());
|
if (senderHexEncodedPublicKey.equalsIgnoreCase(TextSecurePreferences.getLocalNumber(getContext()))) {
|
||||||
String senderDisplayName = DatabaseFactory.getLokiUserDatabase(getContext()).getServerDisplayName(serverId, author.getAddress().serialize());
|
quoteeDisplayName = TextSecurePreferences.getProfileName(getContext());
|
||||||
if (senderDisplayName != null) { quoteeDisplayName = senderDisplayName; }
|
} else if (publicChat != null) {
|
||||||
} catch (Exception e) {
|
quoteeDisplayName = DatabaseFactory.getLokiUserDatabase(getContext()).getServerDisplayName(publicChat.getId(), senderHexEncodedPublicKey);
|
||||||
// Do nothing
|
} else {
|
||||||
}
|
quoteeDisplayName = DatabaseFactory.getLokiUserDatabase(getContext()).getDisplayName(senderHexEncodedPublicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
authorView.setText(isOwnNumber ? getContext().getString(R.string.QuoteView_you) : quoteeDisplayName);
|
authorView.setText(isOwnNumber ? getContext().getString(R.string.QuoteView_you) : quoteeDisplayName);
|
||||||
|
@ -157,6 +157,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel;
|
|||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate;
|
import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate;
|
||||||
import org.thoughtcrime.securesms.loki.LokiAPIUtilities;
|
import org.thoughtcrime.securesms.loki.LokiAPIUtilities;
|
||||||
|
import org.thoughtcrime.securesms.loki.LokiThreadDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.LokiThreadDatabaseDelegate;
|
import org.thoughtcrime.securesms.loki.LokiThreadDatabaseDelegate;
|
||||||
import org.thoughtcrime.securesms.loki.LokiUserDatabase;
|
import org.thoughtcrime.securesms.loki.LokiUserDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.MentionCandidateSelectionView;
|
import org.thoughtcrime.securesms.loki.MentionCandidateSelectionView;
|
||||||
@ -2765,24 +2766,25 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
if (isBackspace) {
|
if (isBackspace) {
|
||||||
currentMentionStartIndex = -1;
|
currentMentionStartIndex = -1;
|
||||||
mentionCandidateSelectionView.hide();
|
mentionCandidateSelectionView.hide();
|
||||||
try {
|
ArrayList<Mention> mentionsToRemove = new ArrayList<>();
|
||||||
for (Mention mention : mentions) {
|
for (Mention mention : mentions) {
|
||||||
if (!text.contains(mention.getDisplayName())) {
|
if (!text.contains(mention.getDisplayName())) {
|
||||||
mentions.remove(mention);
|
mentionsToRemove.add(mention);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception exception) {
|
mentions.removeAll(mentionsToRemove);
|
||||||
mentions.clear(); // TODO: Dirty workaround for ConcurrentModificationException
|
|
||||||
}
|
}
|
||||||
} else if (text.length() > 0) {
|
if (text.length() > 0) {
|
||||||
if (currentMentionStartIndex > text.length()) {
|
if (currentMentionStartIndex > text.length()) {
|
||||||
resetMentions(); // Should never occur
|
resetMentions(); // Should never occur
|
||||||
}
|
}
|
||||||
int lastCharacterIndex = text.length() - 1;
|
int lastCharacterIndex = text.length() - 1;
|
||||||
char lastCharacter = text.charAt(lastCharacterIndex);
|
char lastCharacter = text.charAt(lastCharacterIndex);
|
||||||
|
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(ConversationActivity.this);
|
||||||
|
LokiThreadDatabase threadDatabase = DatabaseFactory.getLokiThreadDatabase(ConversationActivity.this);
|
||||||
LokiUserDatabase userDatabase = DatabaseFactory.getLokiUserDatabase(ConversationActivity.this);
|
LokiUserDatabase userDatabase = DatabaseFactory.getLokiUserDatabase(ConversationActivity.this);
|
||||||
if (lastCharacter == '@') {
|
if (lastCharacter == '@') {
|
||||||
List<Mention> mentionCandidates = LokiAPI.Companion.getMentionCandidates("", threadId, userDatabase);
|
List<Mention> mentionCandidates = LokiAPI.Companion.getMentionCandidates("", threadId, userHexEncodedPublicKey, threadDatabase, userDatabase);
|
||||||
currentMentionStartIndex = lastCharacterIndex;
|
currentMentionStartIndex = lastCharacterIndex;
|
||||||
mentionCandidateSelectionView.show(mentionCandidates, threadId);
|
mentionCandidateSelectionView.show(mentionCandidates, threadId);
|
||||||
} else if (Character.isWhitespace(lastCharacter)) {
|
} else if (Character.isWhitespace(lastCharacter)) {
|
||||||
@ -2791,7 +2793,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
} else {
|
} else {
|
||||||
if (currentMentionStartIndex != -1) {
|
if (currentMentionStartIndex != -1) {
|
||||||
String query = text.substring(currentMentionStartIndex + 1); // + 1 to get rid of the @
|
String query = text.substring(currentMentionStartIndex + 1); // + 1 to get rid of the @
|
||||||
List<Mention> mentionCandidates = LokiAPI.Companion.getMentionCandidates(query, threadId, userDatabase);
|
List<Mention> mentionCandidates = LokiAPI.Companion.getMentionCandidates(query, threadId, userHexEncodedPublicKey, threadDatabase, userDatabase);
|
||||||
mentionCandidateSelectionView.show(mentionCandidates, threadId);
|
mentionCandidateSelectionView.show(mentionCandidates, threadId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,6 @@ import org.thoughtcrime.securesms.contactshare.ContactUtil;
|
|||||||
import org.thoughtcrime.securesms.contactshare.SharedContactDetailsActivity;
|
import org.thoughtcrime.securesms.contactshare.SharedContactDetailsActivity;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder;
|
import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener;
|
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||||
@ -81,8 +80,6 @@ import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
|||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate;
|
import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate;
|
||||||
import org.thoughtcrime.securesms.loki.LokiAPIDatabase;
|
|
||||||
import org.thoughtcrime.securesms.loki.LokiUserDatabase;
|
|
||||||
import org.thoughtcrime.securesms.longmessage.LongMessageActivity;
|
import org.thoughtcrime.securesms.longmessage.LongMessageActivity;
|
||||||
import org.thoughtcrime.securesms.mediasend.Media;
|
import org.thoughtcrime.securesms.mediasend.Media;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
@ -105,7 +102,8 @@ import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
|||||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.internal.util.concurrent.SettableFuture;
|
import org.whispersystems.signalservice.internal.util.concurrent.SettableFuture;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI;
|
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -408,14 +406,15 @@ public class ConversationFragment extends Fragment
|
|||||||
boolean isGroupChat = recipient.isGroupRecipient();
|
boolean isGroupChat = recipient.isGroupRecipient();
|
||||||
|
|
||||||
if (isGroupChat) {
|
if (isGroupChat) {
|
||||||
boolean isLokiPublicChat = recipient.getName() != null && recipient.getName().equals("Loki Public Chat");
|
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
|
||||||
|
boolean isPublicChat = publicChat != null;
|
||||||
int selectedMessageCount = messageRecords.size();
|
int selectedMessageCount = messageRecords.size();
|
||||||
boolean isSentByUser = ((MessageRecord)messageRecords.toArray()[0]).isOutgoing();
|
boolean isSentByUser = ((MessageRecord)messageRecords.toArray()[0]).isOutgoing();
|
||||||
menu.findItem(R.id.menu_context_copy_public_key).setVisible(isLokiPublicChat && selectedMessageCount == 1 && !isSentByUser);
|
menu.findItem(R.id.menu_context_copy_public_key).setVisible(isPublicChat && selectedMessageCount == 1 && !isSentByUser);
|
||||||
menu.findItem(R.id.menu_context_reply).setVisible(isLokiPublicChat && selectedMessageCount == 1);
|
menu.findItem(R.id.menu_context_reply).setVisible(isPublicChat && selectedMessageCount == 1);
|
||||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
|
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
|
||||||
boolean userCanModerate = LokiGroupChatAPI.Companion.isUserModerator(userHexEncodedPublicKey, LokiGroupChatAPI.getPublicChatServerID(), LokiGroupChatAPI.getPublicChatServer());
|
boolean userCanModerate = isPublicChat && LokiPublicChatAPI.Companion.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer());
|
||||||
boolean isDeleteOptionVisible = isLokiPublicChat && selectedMessageCount == 1 && (isSentByUser || userCanModerate);
|
boolean isDeleteOptionVisible = isPublicChat && selectedMessageCount == 1 && (isSentByUser || userCanModerate);
|
||||||
menu.findItem(R.id.menu_context_delete_message).setVisible(isDeleteOptionVisible);
|
menu.findItem(R.id.menu_context_delete_message).setVisible(isDeleteOptionVisible);
|
||||||
} else {
|
} else {
|
||||||
menu.findItem(R.id.menu_context_copy_public_key).setVisible(false);
|
menu.findItem(R.id.menu_context_copy_public_key).setVisible(false);
|
||||||
@ -509,8 +508,8 @@ public class ConversationFragment extends Fragment
|
|||||||
builder.setMessage(getActivity().getResources().getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messagesCount, messagesCount));
|
builder.setMessage(getActivity().getResources().getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messagesCount, messagesCount));
|
||||||
builder.setCancelable(true);
|
builder.setCancelable(true);
|
||||||
|
|
||||||
// Loki - The delete option is only visible to the user in a group chat if it's the Loki Public Chat
|
// Loki - The delete option is only visible to the user in a public chat
|
||||||
boolean isLokiPublicChat = this.recipient.isGroupRecipient();
|
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
|
||||||
|
|
||||||
builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
|
builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -524,19 +523,16 @@ public class ConversationFragment extends Fragment
|
|||||||
for (MessageRecord messageRecord : messageRecords) {
|
for (MessageRecord messageRecord : messageRecords) {
|
||||||
boolean isThreadDeleted;
|
boolean isThreadDeleted;
|
||||||
|
|
||||||
if (isLokiPublicChat) {
|
if (publicChat != null) {
|
||||||
final SettableFuture<?>[] future = { new SettableFuture<Unit>() };
|
final SettableFuture<?>[] future = { new SettableFuture<Unit>() };
|
||||||
|
|
||||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
|
LokiPublicChatAPI publicChatAPI = ApplicationContext.getInstance(getContext()).getLokiPublicChatAPI();
|
||||||
LokiAPIDatabase lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(getContext());
|
|
||||||
LokiUserDatabase lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(getContext());
|
|
||||||
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(getContext()).getPrivateKey().serialize();
|
|
||||||
boolean isSentByUser = messageRecord.isOutgoing();
|
boolean isSentByUser = messageRecord.isOutgoing();
|
||||||
Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id);
|
Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id);
|
||||||
|
|
||||||
if (serverID != null) {
|
if (publicChatAPI != null && serverID != null) {
|
||||||
new LokiGroupChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase)
|
publicChatAPI
|
||||||
.deleteMessage(serverID, LokiGroupChatAPI.getPublicChatServerID(), LokiGroupChatAPI.getPublicChatServer(), isSentByUser)
|
.deleteMessage(serverID, publicChat.getChannel(), publicChat.getServer(), isSentByUser)
|
||||||
.success(l -> {
|
.success(l -> {
|
||||||
@SuppressWarnings("unchecked") SettableFuture<Unit> f = (SettableFuture<Unit>) future[0];
|
@SuppressWarnings("unchecked") SettableFuture<Unit> f = (SettableFuture<Unit>) future[0];
|
||||||
f.set(Unit.INSTANCE);
|
f.set(Unit.INSTANCE);
|
||||||
|
@ -113,7 +113,8 @@ import org.thoughtcrime.securesms.util.Util;
|
|||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.views.Stub;
|
import org.thoughtcrime.securesms.util.views.Stub;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI;
|
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -473,7 +474,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
if (isCaptionlessMms(messageRecord)) {
|
if (isCaptionlessMms(messageRecord)) {
|
||||||
bodyText.setVisibility(View.GONE);
|
bodyText.setVisibility(View.GONE);
|
||||||
} else { ;
|
} else { ;
|
||||||
Spannable text = MentionUtilities.highlightMentions(linkifyMessageBody(messageRecord.getDisplayBody(context), batchSelected.isEmpty()), messageRecord.isOutgoing(), isGroupThread, context);
|
Spannable text = MentionUtilities.highlightMentions(linkifyMessageBody(messageRecord.getDisplayBody(context), batchSelected.isEmpty()), messageRecord.isOutgoing(), messageRecord.getThreadId(), context);
|
||||||
text = SearchUtil.getHighlightedSpan(locale, () -> new BackgroundColorSpan(Color.YELLOW), text, searchQuery);
|
text = SearchUtil.getHighlightedSpan(locale, () -> new BackgroundColorSpan(Color.YELLOW), text, searchQuery);
|
||||||
text = SearchUtil.getHighlightedSpan(locale, () -> new ForegroundColorSpan(Color.BLACK), text, searchQuery);
|
text = SearchUtil.getHighlightedSpan(locale, () -> new ForegroundColorSpan(Color.BLACK), text, searchQuery);
|
||||||
|
|
||||||
@ -789,7 +790,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
if (current.isMms() && !current.isMmsNotification() && ((MediaMmsMessageRecord)current).getQuote() != null) {
|
if (current.isMms() && !current.isMmsNotification() && ((MediaMmsMessageRecord)current).getQuote() != null) {
|
||||||
Quote quote = ((MediaMmsMessageRecord)current).getQuote();
|
Quote quote = ((MediaMmsMessageRecord)current).getQuote();
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
String quoteBody = MentionUtilities.highlightMentions(quote.getText(), isGroupThread, context);
|
String quoteBody = MentionUtilities.highlightMentions(quote.getText(), current.getThreadId(), context);
|
||||||
quoteView.setQuote(glideRequests, quote.getId(), Recipient.from(context, quote.getAuthor(), true), quoteBody, quote.isOriginalMissing(), quote.getAttachment(), conversationRecipient);
|
quoteView.setQuote(glideRequests, quote.getId(), Recipient.from(context, quote.getAuthor(), true), quoteBody, quote.isOriginalMissing(), quote.getAttachment(), conversationRecipient);
|
||||||
quoteView.setVisibility(View.VISIBLE);
|
quoteView.setVisibility(View.VISIBLE);
|
||||||
quoteView.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
quoteView.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||||
@ -936,13 +937,14 @@ public class ConversationItem extends LinearLayout
|
|||||||
|
|
||||||
if (!next.isPresent() || next.get().isUpdate() || !current.getRecipient().getAddress().equals(next.get().getRecipient().getAddress())) {
|
if (!next.isPresent() || next.get().isUpdate() || !current.getRecipient().getAddress().equals(next.get().getRecipient().getAddress())) {
|
||||||
contactPhoto.setVisibility(VISIBLE);
|
contactPhoto.setVisibility(VISIBLE);
|
||||||
int visibility;
|
int visibility = View.GONE;
|
||||||
if (conversationRecipient.getName() != null && conversationRecipient.getName().equals("Loki Public Chat")) {
|
|
||||||
boolean isModerator = LokiGroupChatAPI.Companion.isUserModerator(current.getRecipient().getAddress().toString(), LokiGroupChatAPI.getPublicChatServerID(), LokiGroupChatAPI.getPublicChatServer());
|
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(messageRecord.getThreadId());
|
||||||
|
if (publicChat != null) {
|
||||||
|
boolean isModerator = LokiPublicChatAPI.Companion.isUserModerator(current.getRecipient().getAddress().toString(), publicChat.getChannel(), publicChat.getServer());
|
||||||
visibility = isModerator ? View.VISIBLE : View.GONE;
|
visibility = isModerator ? View.VISIBLE : View.GONE;
|
||||||
} else {
|
|
||||||
visibility = View.GONE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
moderatorIconImageView.setVisibility(visibility);
|
moderatorIconImageView.setVisibility(visibility);
|
||||||
} else {
|
} else {
|
||||||
contactPhoto.setVisibility(GONE);
|
contactPhoto.setVisibility(GONE);
|
||||||
|
@ -52,9 +52,17 @@ public class Address implements Parcelable, Comparable<Address> {
|
|||||||
|
|
||||||
private final String address;
|
private final String address;
|
||||||
|
|
||||||
|
// Loki - Special flag to indicate whether this address represents a public chat or not
|
||||||
|
private Boolean isPublicChat;
|
||||||
|
|
||||||
private Address(@NonNull String address) {
|
private Address(@NonNull String address) {
|
||||||
|
this(address, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Address(@NonNull String address, Boolean isPublicChat) {
|
||||||
if (address == null) throw new AssertionError(address);
|
if (address == null) throw new AssertionError(address);
|
||||||
this.address = address;
|
this.address = address;
|
||||||
|
this.isPublicChat = isPublicChat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Address(Parcel in) {
|
public Address(Parcel in) {
|
||||||
@ -69,6 +77,10 @@ public class Address implements Parcelable, Comparable<Address> {
|
|||||||
return Address.fromSerialized(external);
|
return Address.fromSerialized(external);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @NonNull Address fromPublicChatGroupID(@NonNull String serialized) {
|
||||||
|
return new Address(serialized, true);
|
||||||
|
}
|
||||||
|
|
||||||
public static @NonNull List<Address> fromSerializedList(@NonNull String serialized, char delimiter) {
|
public static @NonNull List<Address> fromSerializedList(@NonNull String serialized, char delimiter) {
|
||||||
String[] escapedAddresses = DelimiterUtil.split(serialized, delimiter);
|
String[] escapedAddresses = DelimiterUtil.split(serialized, delimiter);
|
||||||
List<Address> addresses = new LinkedList<>();
|
List<Address> addresses = new LinkedList<>();
|
||||||
@ -131,7 +143,7 @@ public class Address implements Parcelable, Comparable<Address> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull String toPhoneString() {
|
public @NonNull String toPhoneString() {
|
||||||
if (!isPhone()) {
|
if (!isPhone() && !isPublicChat) {
|
||||||
if (isEmail()) throw new AssertionError("Not e164, is email");
|
if (isEmail()) throw new AssertionError("Not e164, is email");
|
||||||
if (isGroup()) throw new AssertionError("Not e164, is group");
|
if (isGroup()) throw new AssertionError("Not e164, is group");
|
||||||
throw new AssertionError("Not e164, unknown");
|
throw new AssertionError("Not e164, unknown");
|
||||||
|
@ -71,7 +71,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
private static final int lokiV2 = 23;
|
private static final int lokiV2 = 23;
|
||||||
private static final int lokiV3 = 24;
|
private static final int lokiV3 = 24;
|
||||||
|
|
||||||
private static final int DATABASE_VERSION = lokiV2; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
private static final int DATABASE_VERSION = lokiV3; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
||||||
private static final String DATABASE_NAME = "signal.db";
|
private static final String DATABASE_NAME = "signal.db";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@ -131,6 +131,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
db.execSQL(LokiMessageDatabase.getCreateTableCommand());
|
db.execSQL(LokiMessageDatabase.getCreateTableCommand());
|
||||||
db.execSQL(LokiThreadDatabase.getCreateFriendRequestTableCommand());
|
db.execSQL(LokiThreadDatabase.getCreateFriendRequestTableCommand());
|
||||||
db.execSQL(LokiThreadDatabase.getCreateSessionResetTableCommand());
|
db.execSQL(LokiThreadDatabase.getCreateSessionResetTableCommand());
|
||||||
|
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
|
||||||
db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand());
|
db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand());
|
||||||
db.execSQL(LokiUserDatabase.getCreateServerDisplayNameTableCommand());
|
db.execSQL(LokiUserDatabase.getCreateServerDisplayNameTableCommand());
|
||||||
|
|
||||||
@ -497,6 +498,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
|
|
||||||
if (oldVersion < lokiV3) {
|
if (oldVersion < lokiV3) {
|
||||||
db.execSQL(LokiAPIDatabase.getCreatePairingAuthorisationTableCommand());
|
db.execSQL(LokiAPIDatabase.getCreatePairingAuthorisationTableCommand());
|
||||||
|
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
|
@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
|||||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||||
@ -44,7 +45,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
|||||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI;
|
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -285,7 +286,16 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
|||||||
|
|
||||||
private @NonNull List<Address> getGroupMessageRecipients(String groupId, long messageId) {
|
private @NonNull List<Address> getGroupMessageRecipients(String groupId, long messageId) {
|
||||||
ArrayList<Address> result = new ArrayList<>();
|
ArrayList<Address> result = new ArrayList<>();
|
||||||
result.add(Address.fromSerialized(LokiGroupChatAPI.getPublicChatServer())); // Loki - All group messages should be directed to the Loki Public Chat for now
|
|
||||||
|
// Loki - All group messages should be directed to their respective servers
|
||||||
|
long threadID = GroupManager.getThreadIdFromGroupId(groupId, context);
|
||||||
|
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID);
|
||||||
|
if (publicChat != null) {
|
||||||
|
// We need to somehow maintain information that will allow the sender to map
|
||||||
|
// a recipient to the correct public chat thread, and so this might be a bit hacky
|
||||||
|
result.add(Address.fromPublicChatGroupID(groupId));
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
74
src/org/thoughtcrime/securesms/loki/AddPublicChatActivity.kt
Normal file
74
src/org/thoughtcrime/securesms/loki/AddPublicChatActivity.kt
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Patterns
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.Toast
|
||||||
|
import kotlinx.android.synthetic.main.activity_add_public_chat.*
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import nl.komponents.kovenant.ui.failUi
|
||||||
|
import nl.komponents.kovenant.ui.successUi
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||||
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicTheme
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
|
|
||||||
|
class AddPublicChatActivity : PassphraseRequiredActionBarActivity() {
|
||||||
|
private val dynamicTheme = DynamicTheme()
|
||||||
|
|
||||||
|
override fun onPreCreate() {
|
||||||
|
dynamicTheme.onCreate(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(bundle: Bundle?, isReady: Boolean) {
|
||||||
|
supportActionBar!!.setTitle(R.string.fragment_add_public_chat_title)
|
||||||
|
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||||
|
setContentView(R.layout.activity_add_public_chat)
|
||||||
|
updateUI(false)
|
||||||
|
addButton.setOnClickListener { addPublicChatIfPossible() }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
dynamicTheme.onResume(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId == android.R.id.home) {
|
||||||
|
onBackPressed()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addPublicChatIfPossible() {
|
||||||
|
val inputMethodManager = getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
inputMethodManager.hideSoftInputFromWindow(urlEditText.windowToken, 0)
|
||||||
|
val url = urlEditText.text.toString().toLowerCase().replace("http://", "https://")
|
||||||
|
if (!Patterns.WEB_URL.matcher(url).matches() || !url.startsWith("https://")) {
|
||||||
|
return Toast.makeText(this, R.string.fragment_add_public_chat_invalid_url_message, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
updateUI(true)
|
||||||
|
val application = ApplicationContext.getInstance(this)
|
||||||
|
val channel: Long = 1
|
||||||
|
val displayName = TextSecurePreferences.getProfileName(this)
|
||||||
|
val lokiPublicChatAPI = application.lokiPublicChatAPI!!
|
||||||
|
application.lokiPublicChatManager.addChat(url, channel).successUi {
|
||||||
|
lokiPublicChatAPI.getMessages(channel, url)
|
||||||
|
lokiPublicChatAPI.setDisplayName(displayName, url)
|
||||||
|
finish()
|
||||||
|
}.failUi {
|
||||||
|
updateUI(false)
|
||||||
|
Toast.makeText(this, R.string.fragment_add_public_chat_connection_failed_message, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUI(isConnecting: Boolean) {
|
||||||
|
addButton.isEnabled = !isConnecting
|
||||||
|
val text = if (isConnecting) R.string.fragment_add_public_chat_add_button_title_2 else R.string.fragment_add_public_chat_add_button_title_1
|
||||||
|
addButton.setText(text)
|
||||||
|
urlEditText.isEnabled = !isConnecting
|
||||||
|
}
|
||||||
|
}
|
@ -50,6 +50,10 @@ fun Cursor.getString(columnName: String): String {
|
|||||||
return getString(getColumnIndexOrThrow(columnName))
|
return getString(getColumnIndexOrThrow(columnName))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Cursor.getLong(columnName: String): Long {
|
||||||
|
return getLong(getColumnIndexOrThrow(columnName))
|
||||||
|
}
|
||||||
|
|
||||||
fun Cursor.getBase64EncodedData(columnName: String): ByteArray {
|
fun Cursor.getBase64EncodedData(columnName: String): ByteArray {
|
||||||
return Base64.decode(getString(columnName))
|
return Base64.decode(getString(columnName))
|
||||||
}
|
}
|
@ -8,11 +8,9 @@ import network.loki.messenger.R
|
|||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||||
import org.thoughtcrime.securesms.ConversationListActivity
|
import org.thoughtcrime.securesms.ConversationListActivity
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher
|
import org.whispersystems.signalservice.api.crypto.ProfileCipher
|
||||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI
|
|
||||||
import org.whispersystems.signalservice.loki.utilities.Analytics
|
import org.whispersystems.signalservice.loki.utilities.Analytics
|
||||||
|
|
||||||
class DisplayNameActivity : BaseActionBarActivity() {
|
class DisplayNameActivity : BaseActionBarActivity() {
|
||||||
@ -45,10 +43,14 @@ class DisplayNameActivity : BaseActionBarActivity() {
|
|||||||
application.setUpStorageAPIIfNeeded()
|
application.setUpStorageAPIIfNeeded()
|
||||||
startActivity(Intent(this, ConversationListActivity::class.java))
|
startActivity(Intent(this, ConversationListActivity::class.java))
|
||||||
finish()
|
finish()
|
||||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this)
|
val publicChatAPI = ApplicationContext.getInstance(this).lokiPublicChatAPI
|
||||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).privateKey.serialize()
|
if (publicChatAPI != null) {
|
||||||
val apiDatabase = DatabaseFactory.getLokiAPIDatabase(this)
|
application.createDefaultPublicChatsIfNeeded()
|
||||||
val userDatabase = DatabaseFactory.getLokiUserDatabase(this)
|
application.createRSSFeedsIfNeeded()
|
||||||
LokiGroupChatAPI(userHexEncodedPublicKey, userPrivateKey, apiDatabase, userDatabase).setDisplayName(name, LokiGroupChatAPI.publicChatServer)
|
application.lokiPublicChatManager.startPollersIfNeeded()
|
||||||
|
application.startRSSFeedPollersIfNeeded()
|
||||||
|
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
|
||||||
|
servers.forEach { publicChatAPI.setDisplayName(name, it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,7 +7,6 @@ import android.support.annotation.ColorRes
|
|||||||
import org.thoughtcrime.securesms.database.Address
|
import org.thoughtcrime.securesms.database.Address
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI
|
|
||||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
|
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@ -24,8 +23,8 @@ fun toPx(dp: Int, resources: Resources): Int {
|
|||||||
return (dp * scale).roundToInt()
|
return (dp * scale).roundToInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isGroupRecipient(recipient: String): Boolean {
|
fun isPublicChat(context: Context, recipient: String): Boolean {
|
||||||
return (LokiGroupChatAPI.publicChatServer == recipient)
|
return DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().values.map { it.server }.contains(recipient)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFriendPublicKeys(context: Context, devicePublicKeys: Set<String>): Set<String> {
|
fun getFriendPublicKeys(context: Context, devicePublicKeys: Set<String>): Set<String> {
|
||||||
|
@ -137,6 +137,12 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
database.insertOrUpdate(lastMessageServerIDCache, row, "$lastMessageServerIDCacheIndex = ?", wrap(index))
|
database.insertOrUpdate(lastMessageServerIDCache, row, "$lastMessageServerIDCacheIndex = ?", wrap(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeLastMessageServerID(group: Long, server: String) {
|
||||||
|
val database = databaseHelper.writableDatabase
|
||||||
|
val index = "$server.$group"
|
||||||
|
database.delete(lastMessageServerIDCache,"$lastMessageServerIDCacheIndex = ?", wrap(index))
|
||||||
|
}
|
||||||
|
|
||||||
override fun getLastDeletionServerID(group: Long, server: String): Long? {
|
override fun getLastDeletionServerID(group: Long, server: String): Long? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
val index = "$server.$group"
|
val index = "$server.$group"
|
||||||
@ -152,6 +158,12 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
database.insertOrUpdate(lastDeletionServerIDCache, row, "$lastDeletionServerIDCacheIndex = ?", wrap(index))
|
database.insertOrUpdate(lastDeletionServerIDCache, row, "$lastDeletionServerIDCacheIndex = ?", wrap(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeLastDeletionServerID(group: Long, server: String) {
|
||||||
|
val database = databaseHelper.writableDatabase
|
||||||
|
val index = "$server.$group"
|
||||||
|
database.delete(lastDeletionServerIDCache,"$lastDeletionServerIDCacheIndex = ?", wrap(index))
|
||||||
|
}
|
||||||
|
|
||||||
override fun getPairingAuthorisations(hexEncodedPublicKey: String): List<PairingAuthorisation> {
|
override fun getPairingAuthorisations(hexEncodedPublicKey: String): List<PairingAuthorisation> {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.getAll(pairingAuthorisationCache, "$primaryDevicePublicKey = ? OR $secondaryDevicePublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey )) { cursor ->
|
return database.getAll(pairingAuthorisationCache, "$primaryDevicePublicKey = ? OR $secondaryDevicePublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey )) { cursor ->
|
||||||
|
118
src/org/thoughtcrime/securesms/loki/LokiPublicChatManager.kt
Normal file
118
src/org/thoughtcrime/securesms/loki/LokiPublicChatManager.kt
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.ContentObserver
|
||||||
|
import android.text.TextUtils
|
||||||
|
import nl.komponents.kovenant.Promise
|
||||||
|
import nl.komponents.kovenant.functional.bind
|
||||||
|
import nl.komponents.kovenant.functional.map
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseContentProviders
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupManager
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
|
import org.thoughtcrime.securesms.util.Util
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiPublicChat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class LokiPublicChatManager(private val context: Context) {
|
||||||
|
private var chats = mutableMapOf<Long, LokiPublicChat>()
|
||||||
|
private val pollers = mutableMapOf<Long, LokiPublicChatPoller>()
|
||||||
|
private val observers = mutableMapOf<Long, ContentObserver>()
|
||||||
|
private var isPolling = false
|
||||||
|
|
||||||
|
public fun startPollersIfNeeded() {
|
||||||
|
refreshChatsAndPollers()
|
||||||
|
|
||||||
|
for ((threadId, chat) in chats) {
|
||||||
|
val poller = pollers[threadId] ?: LokiPublicChatPoller(context, chat)
|
||||||
|
poller.startIfNeeded()
|
||||||
|
listenToThreadDeletion(threadId)
|
||||||
|
if (!pollers.containsKey(threadId)) { pollers[threadId] = poller }
|
||||||
|
}
|
||||||
|
isPolling = true
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun stopPollers() {
|
||||||
|
pollers.values.forEach { it.stop() }
|
||||||
|
isPolling = false
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun addChat(server: String, channel: Long): Promise<LokiPublicChat, Exception> {
|
||||||
|
val groupChatAPI = ApplicationContext.getInstance(context).lokiPublicChatAPI ?: return Promise.ofFail(IllegalStateException("LokiPublicChatAPI is not set!"))
|
||||||
|
return groupChatAPI.getAuthToken(server).bind {
|
||||||
|
groupChatAPI.getChannelInfo(channel, server)
|
||||||
|
}.map {
|
||||||
|
addChat(server, channel, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun addChat(server: String, channel: Long, name: String): LokiPublicChat {
|
||||||
|
val chat = LokiPublicChat(channel, server, name, true)
|
||||||
|
var threadID = GroupManager.getThreadId(chat.id, context)
|
||||||
|
// Create the group if we don't have one
|
||||||
|
if (threadID < 0) {
|
||||||
|
val result = GroupManager.createGroup(chat.id, context, HashSet(), null, chat.displayName, false)
|
||||||
|
threadID = result.threadId
|
||||||
|
}
|
||||||
|
DatabaseFactory.getLokiThreadDatabase(context).setPublicChat(chat, threadID)
|
||||||
|
// Set our name on the server
|
||||||
|
val displayName = TextSecurePreferences.getProfileName(context)
|
||||||
|
if (!TextUtils.isEmpty(displayName)) {
|
||||||
|
ApplicationContext.getInstance(context).lokiPublicChatAPI?.setDisplayName(displayName, server)
|
||||||
|
}
|
||||||
|
// Start polling
|
||||||
|
Util.runOnMain{ startPollersIfNeeded() }
|
||||||
|
|
||||||
|
return chat
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshChatsAndPollers() {
|
||||||
|
val chatsInDB = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats()
|
||||||
|
val removedChatThreadIds = chats.keys.filter { !chatsInDB.keys.contains(it) }
|
||||||
|
removedChatThreadIds.forEach { pollers.remove(it)?.stop() }
|
||||||
|
|
||||||
|
// Only append to chats if we have a thread for the chat
|
||||||
|
chats = chatsInDB.filter { GroupManager.getThreadId(it.value.id, context) > -1 }.toMutableMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun listenToThreadDeletion(threadID: Long) {
|
||||||
|
if (threadID < 0 || observers[threadID] != null) { return }
|
||||||
|
val observer = createDeletionObserver(threadID, Runnable {
|
||||||
|
val chat = chats[threadID]
|
||||||
|
|
||||||
|
// Reset last message cache
|
||||||
|
if (chat != null) {
|
||||||
|
val apiDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
|
apiDatabase.removeLastDeletionServerID(chat.channel, chat.server)
|
||||||
|
apiDatabase.removeLastMessageServerID(chat.channel, chat.server)
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseFactory.getLokiThreadDatabase(context).removePublicChat(threadID)
|
||||||
|
pollers.remove(threadID)?.stop()
|
||||||
|
observers.remove(threadID)
|
||||||
|
startPollersIfNeeded()
|
||||||
|
})
|
||||||
|
observers[threadID] = observer
|
||||||
|
|
||||||
|
context.applicationContext.contentResolver.registerContentObserver(DatabaseContentProviders.Conversation.getUriForThread(threadID), true, observer)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDeletionObserver(threadID: Long, onDelete: Runnable): ContentObserver {
|
||||||
|
return object : ContentObserver(null) {
|
||||||
|
|
||||||
|
override fun onChange(selfChange: Boolean) {
|
||||||
|
super.onChange(selfChange)
|
||||||
|
// Stop the poller if thread is deleted
|
||||||
|
try {
|
||||||
|
if (!DatabaseFactory.getThreadDatabase(context).hasThread(threadID)) {
|
||||||
|
onDelete.run()
|
||||||
|
context.applicationContext.contentResolver.unregisterContentObserver(this)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// TODO: Handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,23 +21,23 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
|||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||||
import org.whispersystems.signalservice.loki.api.LokiGroupChat
|
import org.whispersystems.signalservice.loki.api.LokiPublicChat
|
||||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI
|
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI
|
||||||
import org.whispersystems.signalservice.loki.api.LokiGroupMessage
|
import org.whispersystems.signalservice.loki.api.LokiPublicChatMessage
|
||||||
|
|
||||||
class LokiGroupChatPoller(private val context: Context, private val group: LokiGroupChat) {
|
class LokiPublicChatPoller(private val context: Context, private val group: LokiPublicChat) {
|
||||||
private val handler = Handler()
|
private val handler = Handler()
|
||||||
private var hasStarted = false
|
private var hasStarted = false
|
||||||
|
|
||||||
// region Convenience
|
// region Convenience
|
||||||
private val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
private val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
|
|
||||||
private val api: LokiGroupChatAPI
|
private val api: LokiPublicChatAPI
|
||||||
get() = {
|
get() = {
|
||||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
||||||
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
val lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(context)
|
val lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(context)
|
||||||
LokiGroupChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase)
|
LokiPublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase)
|
||||||
}()
|
}()
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ class LokiGroupChatPoller(private val context: Context, private val group: LokiG
|
|||||||
|
|
||||||
// region Polling
|
// region Polling
|
||||||
private fun pollForNewMessages() {
|
private fun pollForNewMessages() {
|
||||||
fun processIncomingMessage(message: LokiGroupMessage) {
|
fun processIncomingMessage(message: LokiPublicChatMessage) {
|
||||||
val id = group.id.toByteArray()
|
val id = group.id.toByteArray()
|
||||||
val x1 = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, null, null, null)
|
val x1 = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, null, null, null)
|
||||||
val quote: SignalServiceDataMessage.Quote?
|
val quote: SignalServiceDataMessage.Quote?
|
||||||
@ -113,7 +113,7 @@ class LokiGroupChatPoller(private val context: Context, private val group: LokiG
|
|||||||
PushDecryptJob(context).handleTextMessage(x3, x2, Optional.absent(), Optional.of(message.serverID))
|
PushDecryptJob(context).handleTextMessage(x3, x2, Optional.absent(), Optional.of(message.serverID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun processOutgoingMessage(message: LokiGroupMessage) {
|
fun processOutgoingMessage(message: LokiPublicChatMessage) {
|
||||||
val messageServerID = message.serverID ?: return
|
val messageServerID = message.serverID ?: return
|
||||||
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
||||||
val isDuplicate = lokiMessageDatabase.getMessageID(messageServerID) != null
|
val isDuplicate = lokiMessageDatabase.getMessageID(messageServerID) != null
|
||||||
@ -161,7 +161,7 @@ class LokiGroupChatPoller(private val context: Context, private val group: LokiG
|
|||||||
finalize()
|
finalize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
api.getMessages(group.serverID, group.server).success { messages ->
|
api.getMessages(group.channel, group.server).success { messages ->
|
||||||
messages.forEach { message ->
|
messages.forEach { message ->
|
||||||
if (message.hexEncodedPublicKey != userHexEncodedPublicKey) {
|
if (message.hexEncodedPublicKey != userHexEncodedPublicKey) {
|
||||||
processIncomingMessage(message)
|
processIncomingMessage(message)
|
||||||
@ -170,12 +170,12 @@ class LokiGroupChatPoller(private val context: Context, private val group: LokiG
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.fail {
|
}.fail {
|
||||||
Log.d("Loki", "Failed to get messages for group chat with ID: ${group.serverID} on server: ${group.server}.")
|
Log.d("Loki", "Failed to get messages for group chat with ID: ${group.channel} on server: ${group.server}.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pollForDeletedMessages() {
|
private fun pollForDeletedMessages() {
|
||||||
api.getDeletedMessageServerIDs(group.serverID, group.server).success { deletedMessageServerIDs ->
|
api.getDeletedMessageServerIDs(group.channel, group.server).success { deletedMessageServerIDs ->
|
||||||
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
||||||
val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { lokiMessageDatabase.getMessageID(it) }
|
val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { lokiMessageDatabase.getMessageID(it) }
|
||||||
val smsMessageDatabase = DatabaseFactory.getSmsDatabase(context)
|
val smsMessageDatabase = DatabaseFactory.getSmsDatabase(context)
|
||||||
@ -185,12 +185,12 @@ class LokiGroupChatPoller(private val context: Context, private val group: LokiG
|
|||||||
mmsMessageDatabase.delete(it)
|
mmsMessageDatabase.delete(it)
|
||||||
}
|
}
|
||||||
}.fail {
|
}.fail {
|
||||||
Log.d("Loki", "Failed to get deleted messages for group chat with ID: ${group.serverID} on server: ${group.server}.")
|
Log.d("Loki", "Failed to get deleted messages for group chat with ID: ${group.channel} on server: ${group.server}.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pollForModerators() {
|
private fun pollForModerators() {
|
||||||
api.getModerators(group.serverID, group.server)
|
api.getModerators(group.channel, group.server)
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
@ -2,11 +2,14 @@ package org.thoughtcrime.securesms.loki
|
|||||||
|
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.database.Cursor
|
||||||
import org.thoughtcrime.securesms.database.Address
|
import org.thoughtcrime.securesms.database.Address
|
||||||
import org.thoughtcrime.securesms.database.Database
|
import org.thoughtcrime.securesms.database.Database
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiPublicChat
|
||||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadDatabaseProtocol
|
import org.whispersystems.signalservice.loki.messaging.LokiThreadDatabaseProtocol
|
||||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
|
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
|
||||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus
|
import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus
|
||||||
@ -17,11 +20,14 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
|||||||
companion object {
|
companion object {
|
||||||
private val friendRequestTableName = "loki_thread_friend_request_database"
|
private val friendRequestTableName = "loki_thread_friend_request_database"
|
||||||
private val sessionResetTableName = "loki_thread_session_reset_database"
|
private val sessionResetTableName = "loki_thread_session_reset_database"
|
||||||
private val threadID = "thread_id"
|
public val publicChatTableName = "loki_public_chat_database"
|
||||||
|
public val threadID = "thread_id"
|
||||||
private val friendRequestStatus = "friend_request_status"
|
private val friendRequestStatus = "friend_request_status"
|
||||||
private val sessionResetStatus = "session_reset_status"
|
private val sessionResetStatus = "session_reset_status"
|
||||||
|
public val publicChat = "public_chat"
|
||||||
@JvmStatic val createFriendRequestTableCommand = "CREATE TABLE $friendRequestTableName ($threadID INTEGER PRIMARY KEY, $friendRequestStatus INTEGER DEFAULT 0);"
|
@JvmStatic val createFriendRequestTableCommand = "CREATE TABLE $friendRequestTableName ($threadID INTEGER PRIMARY KEY, $friendRequestStatus INTEGER DEFAULT 0);"
|
||||||
@JvmStatic val createSessionResetTableCommand = "CREATE TABLE $sessionResetTableName ($threadID INTEGER PRIMARY KEY, $sessionResetStatus INTEGER DEFAULT 0);"
|
@JvmStatic val createSessionResetTableCommand = "CREATE TABLE $sessionResetTableName ($threadID INTEGER PRIMARY KEY, $sessionResetStatus INTEGER DEFAULT 0);"
|
||||||
|
@JvmStatic val createPublicChatTableCommand = "CREATE TABLE $publicChatTableName ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getThreadID(hexEncodedPublicKey: String): Long {
|
override fun getThreadID(hexEncodedPublicKey: String): Long {
|
||||||
@ -30,7 +36,7 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
|||||||
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getThreadID(messageID: Long): Long {
|
fun getThreadID(messageID: Long): Long {
|
||||||
return DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID)
|
return DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,4 +90,50 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
|||||||
notifyConversationListListeners()
|
notifyConversationListListeners()
|
||||||
notifyConversationListeners(threadID)
|
notifyConversationListeners(threadID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAllPublicChats(): Map<Long, LokiPublicChat> {
|
||||||
|
val database = databaseHelper.readableDatabase
|
||||||
|
var cursor: Cursor? = null
|
||||||
|
val result = mutableMapOf<Long, LokiPublicChat>()
|
||||||
|
try {
|
||||||
|
cursor = database.rawQuery("select * from $publicChatTableName", null)
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
val threadID = cursor.getLong(threadID)
|
||||||
|
val string = cursor.getString(publicChat)
|
||||||
|
val publicChat = LokiPublicChat.fromJSON(string)
|
||||||
|
if (publicChat != null) { result[threadID] = publicChat }
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Do nothing
|
||||||
|
} finally {
|
||||||
|
cursor?.close()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllPublicChatServers(): Set<String> {
|
||||||
|
return getAllPublicChats().values.fold(setOf()) { set, chat -> set.plus(chat.server) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPublicChat(threadID: Long): LokiPublicChat? {
|
||||||
|
if (threadID < 0) { return null }
|
||||||
|
val database = databaseHelper.readableDatabase
|
||||||
|
return database.get(publicChatTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor ->
|
||||||
|
val publicChatAsJSON = cursor.getString(publicChat)
|
||||||
|
LokiPublicChat.fromJSON(publicChatAsJSON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setPublicChat(publicChat: LokiPublicChat, threadID: Long) {
|
||||||
|
if (threadID < 0) { return }
|
||||||
|
val database = databaseHelper.writableDatabase
|
||||||
|
val contentValues = ContentValues(2)
|
||||||
|
contentValues.put(Companion.threadID, threadID)
|
||||||
|
contentValues.put(Companion.publicChat, JsonUtil.toJson(publicChat.toJSON()))
|
||||||
|
database.insertOrUpdate(publicChatTableName, contentValues, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removePublicChat(threadID: Long) {
|
||||||
|
databaseHelper.writableDatabase.delete(publicChatTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
|
||||||
|
}
|
||||||
}
|
}
|
@ -13,7 +13,10 @@ import org.whispersystems.signalservice.loki.messaging.Mention
|
|||||||
class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : ListView(context, attrs, defStyleAttr) {
|
class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : ListView(context, attrs, defStyleAttr) {
|
||||||
private var mentionCandidates = listOf<Mention>()
|
private var mentionCandidates = listOf<Mention>()
|
||||||
set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.mentionCandidates = newValue }
|
set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.mentionCandidates = newValue }
|
||||||
private var hasGroupContext = false
|
var publicChatServer: String? = null
|
||||||
|
set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.publicChatServer = publicChatServer }
|
||||||
|
var publicChatChannel: Long? = null
|
||||||
|
set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.publicChatChannel = publicChatChannel }
|
||||||
var onMentionCandidateSelected: ((Mention) -> Unit)? = null
|
var onMentionCandidateSelected: ((Mention) -> Unit)? = null
|
||||||
|
|
||||||
private val mentionCandidateSelectionViewAdapter by lazy { Adapter(context) }
|
private val mentionCandidateSelectionViewAdapter by lazy { Adapter(context) }
|
||||||
@ -21,7 +24,8 @@ class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defS
|
|||||||
private class Adapter(private val context: Context) : BaseAdapter() {
|
private class Adapter(private val context: Context) : BaseAdapter() {
|
||||||
var mentionCandidates = listOf<Mention>()
|
var mentionCandidates = listOf<Mention>()
|
||||||
set(newValue) { field = newValue; notifyDataSetChanged() }
|
set(newValue) { field = newValue; notifyDataSetChanged() }
|
||||||
var hasGroupContext = false
|
var publicChatServer: String? = null
|
||||||
|
var publicChatChannel: Long? = null
|
||||||
|
|
||||||
override fun getCount(): Int {
|
override fun getCount(): Int {
|
||||||
return mentionCandidates.count()
|
return mentionCandidates.count()
|
||||||
@ -39,7 +43,8 @@ class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defS
|
|||||||
val cell = cellToBeReused as MentionCandidateSelectionViewCell? ?: MentionCandidateSelectionViewCell.inflate(LayoutInflater.from(context), parent)
|
val cell = cellToBeReused as MentionCandidateSelectionViewCell? ?: MentionCandidateSelectionViewCell.inflate(LayoutInflater.from(context), parent)
|
||||||
val mentionCandidate = getItem(position)
|
val mentionCandidate = getItem(position)
|
||||||
cell.mentionCandidate = mentionCandidate
|
cell.mentionCandidate = mentionCandidate
|
||||||
cell.hasGroupContext = hasGroupContext
|
cell.publicChatServer = publicChatServer
|
||||||
|
cell.publicChatChannel = publicChatChannel
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,7 +61,11 @@ class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defS
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun show(mentionCandidates: List<Mention>, threadID: Long) {
|
fun show(mentionCandidates: List<Mention>, threadID: Long) {
|
||||||
hasGroupContext = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadID)!!.isGroupRecipient
|
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
||||||
|
if (publicChat != null) {
|
||||||
|
publicChatServer = publicChat.server
|
||||||
|
publicChatChannel = publicChat.channel
|
||||||
|
}
|
||||||
this.mentionCandidates = mentionCandidates
|
this.mentionCandidates = mentionCandidates
|
||||||
val layoutParams = this.layoutParams as ViewGroup.LayoutParams
|
val layoutParams = this.layoutParams as ViewGroup.LayoutParams
|
||||||
layoutParams.height = toPx(6 + Math.min(mentionCandidates.count(), 4) * 52, resources)
|
layoutParams.height = toPx(6 + Math.min(mentionCandidates.count(), 4) * 52, resources)
|
||||||
|
@ -10,13 +10,14 @@ import android.view.ViewOutlineProvider
|
|||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import kotlinx.android.synthetic.main.cell_mention_candidate_selection_view.view.*
|
import kotlinx.android.synthetic.main.cell_mention_candidate_selection_view.view.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI
|
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI
|
||||||
import org.whispersystems.signalservice.loki.messaging.Mention
|
import org.whispersystems.signalservice.loki.messaging.Mention
|
||||||
|
|
||||||
class MentionCandidateSelectionViewCell(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {
|
class MentionCandidateSelectionViewCell(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
var mentionCandidate = Mention("", "")
|
var mentionCandidate = Mention("", "")
|
||||||
set(newValue) { field = newValue; update() }
|
set(newValue) { field = newValue; update() }
|
||||||
var hasGroupContext = false
|
var publicChatServer: String? = null
|
||||||
|
var publicChatChannel: Long? = null
|
||||||
|
|
||||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||||
constructor(context: Context) : this(context, null)
|
constructor(context: Context) : this(context, null)
|
||||||
@ -42,7 +43,11 @@ class MentionCandidateSelectionViewCell(context: Context, attrs: AttributeSet?,
|
|||||||
private fun update() {
|
private fun update() {
|
||||||
displayNameTextView.text = mentionCandidate.displayName
|
displayNameTextView.text = mentionCandidate.displayName
|
||||||
profilePictureImageView.update(mentionCandidate.hexEncodedPublicKey)
|
profilePictureImageView.update(mentionCandidate.hexEncodedPublicKey)
|
||||||
val isUserModerator = LokiGroupChatAPI.isUserModerator(mentionCandidate.hexEncodedPublicKey, LokiGroupChatAPI.publicChatServerID, LokiGroupChatAPI.publicChatServer)
|
if (publicChatServer != null && publicChatChannel != null) {
|
||||||
moderatorIconImageView.visibility = if (isUserModerator && hasGroupContext) View.VISIBLE else View.GONE
|
val isUserModerator = LokiPublicChatAPI.isUserModerator(mentionCandidate.hexEncodedPublicKey, publicChatChannel!!, publicChatServer!!)
|
||||||
|
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
|
||||||
|
} else {
|
||||||
|
moderatorIconImageView.visibility = View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,31 +8,32 @@ import android.util.Range
|
|||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI
|
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
object MentionUtilities {
|
object MentionUtilities {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun highlightMentions(text: CharSequence, isGroupThread: Boolean, context: Context): String {
|
fun highlightMentions(text: CharSequence, threadID: Long, context: Context): String {
|
||||||
return MentionUtilities.highlightMentions(text, false, isGroupThread, context).toString() // isOutgoingMessage is irrelevant
|
return MentionUtilities.highlightMentions(text, false, threadID, context).toString() // isOutgoingMessage is irrelevant
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun highlightMentions(text: CharSequence, isOutgoingMessage: Boolean, isGroupThread: Boolean, context: Context): SpannableString {
|
fun highlightMentions(text: CharSequence, isOutgoingMessage: Boolean, threadID: Long, context: Context): SpannableString {
|
||||||
var text = text
|
var text = text
|
||||||
val pattern = Pattern.compile("@[0-9a-fA-F]*")
|
val pattern = Pattern.compile("@[0-9a-fA-F]*")
|
||||||
var matcher = pattern.matcher(text)
|
var matcher = pattern.matcher(text)
|
||||||
val mentions = mutableListOf<Range<Int>>()
|
val mentions = mutableListOf<Range<Int>>()
|
||||||
var startIndex = 0
|
var startIndex = 0
|
||||||
if (matcher.find(startIndex) && isGroupThread) {
|
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
||||||
|
if (matcher.find(startIndex)) {
|
||||||
while (true) {
|
while (true) {
|
||||||
val hexEncodedPublicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @
|
val hexEncodedPublicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @
|
||||||
val userDisplayName: String? = if (hexEncodedPublicKey.toLowerCase() == TextSecurePreferences.getLocalNumber(context).toLowerCase()) {
|
val userDisplayName: String? = if (hexEncodedPublicKey.toLowerCase() == TextSecurePreferences.getLocalNumber(context).toLowerCase()) {
|
||||||
TextSecurePreferences.getProfileName(context)
|
TextSecurePreferences.getProfileName(context)
|
||||||
|
} else if (publicChat != null) {
|
||||||
|
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, hexEncodedPublicKey)
|
||||||
} else {
|
} else {
|
||||||
val publicChatID = LokiGroupChatAPI.publicChatServer + "." + LokiGroupChatAPI.publicChatServerID
|
DatabaseFactory.getLokiUserDatabase(context).getDisplayName(hexEncodedPublicKey)
|
||||||
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChatID, hexEncodedPublicKey)
|
|
||||||
}
|
}
|
||||||
if (userDisplayName != null) {
|
if (userDisplayName != null) {
|
||||||
text = text.subSequence(0, matcher.start()).toString() + "@" + userDisplayName + text.subSequence(matcher.end(), text.length)
|
text = text.subSequence(0, matcher.start()).toString() + "@" + userDisplayName + text.subSequence(matcher.end(), text.length)
|
||||||
|
@ -213,7 +213,7 @@ public class MessageSender {
|
|||||||
|
|
||||||
// Just send the message normally if it's a group message
|
// Just send the message normally if it's a group message
|
||||||
String recipientPublicKey = recipient.getAddress().serialize();
|
String recipientPublicKey = recipient.getAddress().serialize();
|
||||||
if (GeneralUtilitiesKt.isGroupRecipient(recipientPublicKey)) {
|
if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey)) {
|
||||||
jobManager.add(new PushTextSendJob(messageId, recipient.getAddress()));
|
jobManager.add(new PushTextSendJob(messageId, recipient.getAddress()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -243,7 +243,7 @@ public class MessageSender {
|
|||||||
|
|
||||||
// Just send the message normally if it's a group message
|
// Just send the message normally if it's a group message
|
||||||
String recipientPublicKey = recipient.getAddress().serialize();
|
String recipientPublicKey = recipient.getAddress().serialize();
|
||||||
if (GeneralUtilitiesKt.isGroupRecipient(recipientPublicKey)) {
|
if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey)) {
|
||||||
PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress());
|
PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user