mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 16:57:50 +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"
|
||||
android:windowSoftInputMode="stateAlwaysVisible"
|
||||
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 -->
|
||||
|
||||
<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"?>
|
||||
<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" />
|
||||
|
||||
<item android:title="@string/text_secure_normal__menu_clear_passphrase"
|
||||
@ -17,6 +23,6 @@
|
||||
android:id="@+id/menu_settings" />
|
||||
|
||||
<item android:title="@string/text_secure_normal__help"
|
||||
android:id="@+id/menu_help"/>
|
||||
android:id="@+id/menu_help"/> -->
|
||||
|
||||
</menu>
|
||||
|
@ -1598,6 +1598,14 @@
|
||||
<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_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 -->
|
||||
<string name="view_friend_request_accept_button_title">Accept</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>
|
||||
<!-- Conversation activity -->
|
||||
<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>
|
||||
|
@ -42,8 +42,8 @@
|
||||
android:icon="@drawable/icon_qr_code"/>
|
||||
|
||||
<Preference android:key="preference_category_link_device"
|
||||
android:title="@string/activity_settings_link_device_button_title"
|
||||
android:icon="@drawable/icon_link"/>
|
||||
android:title="@string/activity_settings_link_device_button_title"
|
||||
android:icon="@drawable/icon_link"/>
|
||||
|
||||
<Preference android:key="preference_category_seed"
|
||||
android:title="@string/activity_settings_show_seed_button_title"
|
||||
|
@ -25,6 +25,7 @@ import android.database.ContentObserver;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.multidex.MultiDexApplication;
|
||||
|
||||
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.loki.BackgroundPollWorker;
|
||||
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.LokiUserDatabase;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
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.internal.push.SignalServiceProtos;
|
||||
import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol;
|
||||
import org.whispersystems.signalservice.loki.api.LokiGroupChat;
|
||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
|
||||
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiLongPoller;
|
||||
import org.whispersystems.signalservice.loki.api.LokiP2PAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiP2PAPIDelegate;
|
||||
@ -98,6 +100,7 @@ import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -132,9 +135,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
|
||||
// Loki
|
||||
private LokiLongPoller lokiLongPoller = null;
|
||||
private LokiGroupChatPoller lokiPublicChatPoller = null;
|
||||
private LokiRSSFeedPoller lokiNewsFeedPoller = null;
|
||||
private LokiRSSFeedPoller lokiMessengerUpdatesFeedPoller = null;
|
||||
private LokiPublicChatManager lokiPublicChatManager = null;
|
||||
private LokiPublicChatAPI lokiPublicChatAPI = null;
|
||||
public SignalCommunicationModule communicationModule;
|
||||
public MixpanelAPI mixpanel;
|
||||
|
||||
@ -147,7 +151,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
LokiGroupChatAPI.Companion.setDebugMode(BuildConfig.DEBUG); // Loki - Set debug mode if needed
|
||||
startKovenant();
|
||||
Log.i(TAG, "onCreate()");
|
||||
initializeSecurityProvider();
|
||||
@ -183,6 +186,8 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
mixpanel.trackMap(event, properties);
|
||||
return Unit.INSTANCE;
|
||||
};
|
||||
// Loki - Set up public chat manager
|
||||
lokiPublicChatManager = new LokiPublicChatManager(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -193,6 +198,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
KeyCachingService.onAppForegrounded(this);
|
||||
// Loki - Start long polling if needed
|
||||
startLongPollingIfNeeded();
|
||||
lokiPublicChatManager.startPollersIfNeeded();
|
||||
setUpStorageAPIIfNeeded();
|
||||
}
|
||||
|
||||
@ -204,6 +210,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
MessageNotifier.setVisibleThread(-1);
|
||||
// Loki - Stop long polling if needed
|
||||
if (lokiLongPoller != null) { lokiLongPoller.stopIfNeeded(); }
|
||||
if (lokiPublicChatManager != null) { lokiPublicChatManager.stopPollers(); }
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -243,6 +250,21 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
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() {
|
||||
try {
|
||||
Class.forName("org.signal.aesgcmprovider.AesGcmCipher");
|
||||
@ -471,10 +493,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
if (lokiLongPoller != null) { lokiLongPoller.startIfNeeded(); }
|
||||
}
|
||||
|
||||
private LokiGroupChat lokiPublicChat() {
|
||||
return new LokiGroupChat(LokiGroupChatAPI.getPublicChatServerID(), LokiGroupChatAPI.getPublicChatServer(), "Loki Public Chat", true);
|
||||
}
|
||||
|
||||
private LokiRSSFeed lokiNewsFeed() {
|
||||
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);
|
||||
}
|
||||
|
||||
public void createGroupChatsIfNeeded() {
|
||||
LokiGroupChat publicChat = lokiPublicChat();
|
||||
boolean isChatSetUp = TextSecurePreferences.isChatSetUp(this, publicChat.getId());
|
||||
if (!isChatSetUp || !publicChat.isDeletable()) {
|
||||
GroupManager.GroupActionResult result = GroupManager.createGroup(publicChat.getId(), this, new HashSet<>(), null, publicChat.getDisplayName(), false);
|
||||
TextSecurePreferences.markChatSetUp(this, publicChat.getId());
|
||||
public void createDefaultPublicChatsIfNeeded() {
|
||||
List<LokiPublicChat> defaultPublicChats = LokiPublicChatAPI.Companion.getDefaultChats(BuildConfig.DEBUG);
|
||||
for (LokiPublicChat publiChat : defaultPublicChats) {
|
||||
long threadID = GroupManager.getThreadId(publiChat.getId(), this);
|
||||
String migrationKey = publiChat.getId() + "_migrated";
|
||||
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() {
|
||||
// Only create the RSS feed pollers if their threads aren't deleted
|
||||
LokiRSSFeed lokiNewsFeed = lokiNewsFeed();
|
||||
@ -558,11 +572,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
this.getContentResolver().registerContentObserver(DatabaseContentProviders.Conversation.getUriForThread(threadID), true, observer);
|
||||
}
|
||||
|
||||
public void startGroupChatPollersIfNeeded() {
|
||||
createGroupChatPollersIfNeeded();
|
||||
if (lokiPublicChatPoller != null) lokiPublicChatPoller.startIfNeeded();
|
||||
}
|
||||
|
||||
public void startRSSFeedPollersIfNeeded() {
|
||||
createRSSFeedPollersIfNeeded();
|
||||
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.TooltipCompat;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
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.MessagingDatabase.MarkedMessageInfo;
|
||||
import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
|
||||
import org.thoughtcrime.securesms.loki.AddPublicChatActivity;
|
||||
import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable;
|
||||
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
@ -82,9 +84,9 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
dynamicLanguage.onCreate(this);
|
||||
if (TextSecurePreferences.getLocalNumber(this) != null) {
|
||||
ApplicationContext application = ApplicationContext.getInstance(this);
|
||||
application.createGroupChatsIfNeeded();
|
||||
application.createDefaultPublicChatsIfNeeded();
|
||||
application.createRSSFeedsIfNeeded();
|
||||
application.startGroupChatPollersIfNeeded();
|
||||
application.getLokiPublicChatManager().startPollersIfNeeded();
|
||||
application.startRSSFeedPollersIfNeeded();
|
||||
}
|
||||
}
|
||||
@ -127,18 +129,15 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
return false;
|
||||
/*
|
||||
MenuInflater inflater = this.getMenuInflater();
|
||||
menu.clear();
|
||||
|
||||
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);
|
||||
return true;
|
||||
*/
|
||||
}
|
||||
|
||||
private void initializeSearchListener() {
|
||||
@ -235,12 +234,13 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
super.onOptionsItemSelected(item);
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_new_group: createGroup(); return true;
|
||||
case R.id.menu_settings: handleDisplaySettings(); 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_invite: handleInvite(); return true;
|
||||
case R.id.menu_help: handleHelp(); return true;
|
||||
// case R.id.menu_new_group: createGroup(); return true;
|
||||
// case R.id.menu_settings: handleDisplaySettings(); 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_invite: handleInvite(); 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;
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -35,15 +35,12 @@ import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
|
||||
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob;
|
||||
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.permissions.Permissions;
|
||||
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.crypto.ProfileCipher;
|
||||
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 java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@ -380,12 +378,13 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
||||
Analytics.Companion.getShared().track("Display Name Updated");
|
||||
|
||||
TextSecurePreferences.setProfileName(context, name);
|
||||
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
|
||||
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).getPrivateKey().serialize();
|
||||
LokiAPIDatabase apiDatabase = DatabaseFactory.getLokiAPIDatabase(context);
|
||||
LokiUserDatabase userDatabase = DatabaseFactory.getLokiUserDatabase(context);
|
||||
new LokiGroupChatAPI(userHexEncodedPublicKey, userPrivateKey, apiDatabase, userDatabase).setDisplayName(name, LokiGroupChatAPI.getPublicChatServer());
|
||||
LokiPublicChatAPI publicChatAPI = ApplicationContext.getInstance(context).getLokiPublicChatAPI();
|
||||
if (publicChatAPI != null) {
|
||||
Set<String> servers = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChatServers();
|
||||
for (String server : servers) {
|
||||
publicChatAPI.setDisplayName(name, server);
|
||||
}
|
||||
}
|
||||
|
||||
// Loki - Original code
|
||||
// ========
|
||||
|
@ -28,10 +28,10 @@ import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
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.Util;
|
||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -194,19 +194,16 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener
|
||||
boolean isOwnNumber = Util.isOwnNumber(getContext(), author.getAddress());
|
||||
|
||||
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
|
||||
if (conversationRecipient.isGroupRecipient()) {
|
||||
try {
|
||||
String serverId = GroupUtil.getDecodedStringId(conversationRecipient.getAddress().serialize());
|
||||
String senderDisplayName = DatabaseFactory.getLokiUserDatabase(getContext()).getServerDisplayName(serverId, author.getAddress().serialize());
|
||||
if (senderDisplayName != null) { quoteeDisplayName = senderDisplayName; }
|
||||
} catch (Exception e) {
|
||||
// Do nothing
|
||||
}
|
||||
long threadID = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(conversationRecipient);
|
||||
String senderHexEncodedPublicKey = author.getAddress().serialize();
|
||||
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadID);
|
||||
if (senderHexEncodedPublicKey.equalsIgnoreCase(TextSecurePreferences.getLocalNumber(getContext()))) {
|
||||
quoteeDisplayName = TextSecurePreferences.getProfileName(getContext());
|
||||
} else if (publicChat != null) {
|
||||
quoteeDisplayName = DatabaseFactory.getLokiUserDatabase(getContext()).getServerDisplayName(publicChat.getId(), senderHexEncodedPublicKey);
|
||||
} else {
|
||||
quoteeDisplayName = DatabaseFactory.getLokiUserDatabase(getContext()).getDisplayName(senderHexEncodedPublicKey);
|
||||
}
|
||||
|
||||
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.loki.FriendRequestViewDelegate;
|
||||
import org.thoughtcrime.securesms.loki.LokiAPIUtilities;
|
||||
import org.thoughtcrime.securesms.loki.LokiThreadDatabase;
|
||||
import org.thoughtcrime.securesms.loki.LokiThreadDatabaseDelegate;
|
||||
import org.thoughtcrime.securesms.loki.LokiUserDatabase;
|
||||
import org.thoughtcrime.securesms.loki.MentionCandidateSelectionView;
|
||||
@ -2765,24 +2766,25 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
if (isBackspace) {
|
||||
currentMentionStartIndex = -1;
|
||||
mentionCandidateSelectionView.hide();
|
||||
try {
|
||||
for (Mention mention : mentions) {
|
||||
if (!text.contains(mention.getDisplayName())) {
|
||||
mentions.remove(mention);
|
||||
}
|
||||
ArrayList<Mention> mentionsToRemove = new ArrayList<>();
|
||||
for (Mention mention : mentions) {
|
||||
if (!text.contains(mention.getDisplayName())) {
|
||||
mentionsToRemove.add(mention);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
mentions.clear(); // TODO: Dirty workaround for ConcurrentModificationException
|
||||
}
|
||||
} else if (text.length() > 0) {
|
||||
mentions.removeAll(mentionsToRemove);
|
||||
}
|
||||
if (text.length() > 0) {
|
||||
if (currentMentionStartIndex > text.length()) {
|
||||
resetMentions(); // Should never occur
|
||||
}
|
||||
int lastCharacterIndex = text.length() - 1;
|
||||
char lastCharacter = text.charAt(lastCharacterIndex);
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(ConversationActivity.this);
|
||||
LokiThreadDatabase threadDatabase = DatabaseFactory.getLokiThreadDatabase(ConversationActivity.this);
|
||||
LokiUserDatabase userDatabase = DatabaseFactory.getLokiUserDatabase(ConversationActivity.this);
|
||||
if (lastCharacter == '@') {
|
||||
List<Mention> mentionCandidates = LokiAPI.Companion.getMentionCandidates("", threadId, userDatabase);
|
||||
List<Mention> mentionCandidates = LokiAPI.Companion.getMentionCandidates("", threadId, userHexEncodedPublicKey, threadDatabase, userDatabase);
|
||||
currentMentionStartIndex = lastCharacterIndex;
|
||||
mentionCandidateSelectionView.show(mentionCandidates, threadId);
|
||||
} else if (Character.isWhitespace(lastCharacter)) {
|
||||
@ -2791,7 +2793,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
} else {
|
||||
if (currentMentionStartIndex != -1) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,6 @@ import org.thoughtcrime.securesms.contactshare.ContactUtil;
|
||||
import org.thoughtcrime.securesms.contactshare.SharedContactDetailsActivity;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
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.logging.Log;
|
||||
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.mediasend.Media;
|
||||
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.whispersystems.libsignal.util.guava.Optional;
|
||||
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.InputStream;
|
||||
@ -408,14 +406,15 @@ public class ConversationFragment extends Fragment
|
||||
boolean isGroupChat = recipient.isGroupRecipient();
|
||||
|
||||
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();
|
||||
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_reply).setVisible(isLokiPublicChat && selectedMessageCount == 1);
|
||||
menu.findItem(R.id.menu_context_copy_public_key).setVisible(isPublicChat && selectedMessageCount == 1 && !isSentByUser);
|
||||
menu.findItem(R.id.menu_context_reply).setVisible(isPublicChat && selectedMessageCount == 1);
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
|
||||
boolean userCanModerate = LokiGroupChatAPI.Companion.isUserModerator(userHexEncodedPublicKey, LokiGroupChatAPI.getPublicChatServerID(), LokiGroupChatAPI.getPublicChatServer());
|
||||
boolean isDeleteOptionVisible = isLokiPublicChat && selectedMessageCount == 1 && (isSentByUser || userCanModerate);
|
||||
boolean userCanModerate = isPublicChat && LokiPublicChatAPI.Companion.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer());
|
||||
boolean isDeleteOptionVisible = isPublicChat && selectedMessageCount == 1 && (isSentByUser || userCanModerate);
|
||||
menu.findItem(R.id.menu_context_delete_message).setVisible(isDeleteOptionVisible);
|
||||
} else {
|
||||
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.setCancelable(true);
|
||||
|
||||
// Loki - The delete option is only visible to the user in a group chat if it's the Loki Public Chat
|
||||
boolean isLokiPublicChat = this.recipient.isGroupRecipient();
|
||||
// Loki - The delete option is only visible to the user in a public chat
|
||||
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
|
||||
|
||||
builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
@ -524,19 +523,16 @@ public class ConversationFragment extends Fragment
|
||||
for (MessageRecord messageRecord : messageRecords) {
|
||||
boolean isThreadDeleted;
|
||||
|
||||
if (isLokiPublicChat) {
|
||||
if (publicChat != null) {
|
||||
final SettableFuture<?>[] future = { new SettableFuture<Unit>() };
|
||||
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
|
||||
LokiAPIDatabase lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(getContext());
|
||||
LokiUserDatabase lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(getContext());
|
||||
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(getContext()).getPrivateKey().serialize();
|
||||
LokiPublicChatAPI publicChatAPI = ApplicationContext.getInstance(getContext()).getLokiPublicChatAPI();
|
||||
boolean isSentByUser = messageRecord.isOutgoing();
|
||||
Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id);
|
||||
|
||||
if (serverID != null) {
|
||||
new LokiGroupChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase)
|
||||
.deleteMessage(serverID, LokiGroupChatAPI.getPublicChatServerID(), LokiGroupChatAPI.getPublicChatServer(), isSentByUser)
|
||||
if (publicChatAPI != null && serverID != null) {
|
||||
publicChatAPI
|
||||
.deleteMessage(serverID, publicChat.getChannel(), publicChat.getServer(), isSentByUser)
|
||||
.success(l -> {
|
||||
@SuppressWarnings("unchecked") SettableFuture<Unit> f = (SettableFuture<Unit>) future[0];
|
||||
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.views.Stub;
|
||||
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.HashSet;
|
||||
@ -473,7 +474,7 @@ public class ConversationItem extends LinearLayout
|
||||
if (isCaptionlessMms(messageRecord)) {
|
||||
bodyText.setVisibility(View.GONE);
|
||||
} 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 ForegroundColorSpan(Color.BLACK), text, searchQuery);
|
||||
|
||||
@ -789,7 +790,7 @@ public class ConversationItem extends LinearLayout
|
||||
if (current.isMms() && !current.isMmsNotification() && ((MediaMmsMessageRecord)current).getQuote() != null) {
|
||||
Quote quote = ((MediaMmsMessageRecord)current).getQuote();
|
||||
//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.setVisibility(View.VISIBLE);
|
||||
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())) {
|
||||
contactPhoto.setVisibility(VISIBLE);
|
||||
int visibility;
|
||||
if (conversationRecipient.getName() != null && conversationRecipient.getName().equals("Loki Public Chat")) {
|
||||
boolean isModerator = LokiGroupChatAPI.Companion.isUserModerator(current.getRecipient().getAddress().toString(), LokiGroupChatAPI.getPublicChatServerID(), LokiGroupChatAPI.getPublicChatServer());
|
||||
int visibility = View.GONE;
|
||||
|
||||
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;
|
||||
} else {
|
||||
visibility = View.GONE;
|
||||
}
|
||||
|
||||
moderatorIconImageView.setVisibility(visibility);
|
||||
} else {
|
||||
contactPhoto.setVisibility(GONE);
|
||||
|
@ -52,9 +52,17 @@ public class Address implements Parcelable, Comparable<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) {
|
||||
this(address, false);
|
||||
}
|
||||
|
||||
private Address(@NonNull String address, Boolean isPublicChat) {
|
||||
if (address == null) throw new AssertionError(address);
|
||||
this.address = address;
|
||||
this.isPublicChat = isPublicChat;
|
||||
}
|
||||
|
||||
public Address(Parcel in) {
|
||||
@ -69,6 +77,10 @@ public class Address implements Parcelable, Comparable<Address> {
|
||||
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) {
|
||||
String[] escapedAddresses = DelimiterUtil.split(serialized, delimiter);
|
||||
List<Address> addresses = new LinkedList<>();
|
||||
@ -131,7 +143,7 @@ public class Address implements Parcelable, Comparable<Address> {
|
||||
}
|
||||
|
||||
public @NonNull String toPhoneString() {
|
||||
if (!isPhone()) {
|
||||
if (!isPhone() && !isPublicChat) {
|
||||
if (isEmail()) throw new AssertionError("Not e164, is email");
|
||||
if (isGroup()) throw new AssertionError("Not e164, is group");
|
||||
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 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 final Context context;
|
||||
@ -131,6 +131,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(LokiMessageDatabase.getCreateTableCommand());
|
||||
db.execSQL(LokiThreadDatabase.getCreateFriendRequestTableCommand());
|
||||
db.execSQL(LokiThreadDatabase.getCreateSessionResetTableCommand());
|
||||
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
|
||||
db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand());
|
||||
db.execSQL(LokiUserDatabase.getCreateServerDisplayNameTableCommand());
|
||||
|
||||
@ -497,6 +498,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
if (oldVersion < lokiV3) {
|
||||
db.execSQL(LokiAPIDatabase.getCreatePairingAuthorisationTableCommand());
|
||||
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
|
||||
}
|
||||
|
||||
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.NetworkFailure;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
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.push.SignalServiceAddress;
|
||||
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.util.ArrayList;
|
||||
@ -285,7 +286,16 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
|
||||
private @NonNull List<Address> getGroupMessageRecipients(String groupId, long messageId) {
|
||||
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;
|
||||
|
||||
/*
|
||||
|
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))
|
||||
}
|
||||
|
||||
fun Cursor.getLong(columnName: String): Long {
|
||||
return getLong(getColumnIndexOrThrow(columnName))
|
||||
}
|
||||
|
||||
fun Cursor.getBase64EncodedData(columnName: String): ByteArray {
|
||||
return Base64.decode(getString(columnName))
|
||||
}
|
@ -8,11 +8,9 @@ import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
import org.thoughtcrime.securesms.ConversationListActivity
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher
|
||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI
|
||||
import org.whispersystems.signalservice.loki.utilities.Analytics
|
||||
|
||||
class DisplayNameActivity : BaseActionBarActivity() {
|
||||
@ -45,10 +43,14 @@ class DisplayNameActivity : BaseActionBarActivity() {
|
||||
application.setUpStorageAPIIfNeeded()
|
||||
startActivity(Intent(this, ConversationListActivity::class.java))
|
||||
finish()
|
||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).privateKey.serialize()
|
||||
val apiDatabase = DatabaseFactory.getLokiAPIDatabase(this)
|
||||
val userDatabase = DatabaseFactory.getLokiUserDatabase(this)
|
||||
LokiGroupChatAPI(userHexEncodedPublicKey, userPrivateKey, apiDatabase, userDatabase).setDisplayName(name, LokiGroupChatAPI.publicChatServer)
|
||||
val publicChatAPI = ApplicationContext.getInstance(this).lokiPublicChatAPI
|
||||
if (publicChatAPI != null) {
|
||||
application.createDefaultPublicChatsIfNeeded()
|
||||
application.createRSSFeedsIfNeeded()
|
||||
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.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@ -24,8 +23,8 @@ fun toPx(dp: Int, resources: Resources): Int {
|
||||
return (dp * scale).roundToInt()
|
||||
}
|
||||
|
||||
fun isGroupRecipient(recipient: String): Boolean {
|
||||
return (LokiGroupChatAPI.publicChatServer == recipient)
|
||||
fun isPublicChat(context: Context, recipient: String): Boolean {
|
||||
return DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().values.map { it.server }.contains(recipient)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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? {
|
||||
val database = databaseHelper.readableDatabase
|
||||
val index = "$server.$group"
|
||||
@ -152,6 +158,12 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
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> {
|
||||
val database = databaseHelper.readableDatabase
|
||||
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.SignalServiceGroup
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.loki.api.LokiGroupChat
|
||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI
|
||||
import org.whispersystems.signalservice.loki.api.LokiGroupMessage
|
||||
import org.whispersystems.signalservice.loki.api.LokiPublicChat
|
||||
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI
|
||||
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 var hasStarted = false
|
||||
|
||||
// region Convenience
|
||||
private val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
|
||||
private val api: LokiGroupChatAPI
|
||||
private val api: LokiPublicChatAPI
|
||||
get() = {
|
||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
||||
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
val lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(context)
|
||||
LokiGroupChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase)
|
||||
LokiPublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase)
|
||||
}()
|
||||
// endregion
|
||||
|
||||
@ -94,7 +94,7 @@ class LokiGroupChatPoller(private val context: Context, private val group: LokiG
|
||||
|
||||
// region Polling
|
||||
private fun pollForNewMessages() {
|
||||
fun processIncomingMessage(message: LokiGroupMessage) {
|
||||
fun processIncomingMessage(message: LokiPublicChatMessage) {
|
||||
val id = group.id.toByteArray()
|
||||
val x1 = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, null, null, null)
|
||||
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))
|
||||
}
|
||||
}
|
||||
fun processOutgoingMessage(message: LokiGroupMessage) {
|
||||
fun processOutgoingMessage(message: LokiPublicChatMessage) {
|
||||
val messageServerID = message.serverID ?: return
|
||||
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
val isDuplicate = lokiMessageDatabase.getMessageID(messageServerID) != null
|
||||
@ -161,7 +161,7 @@ class LokiGroupChatPoller(private val context: Context, private val group: LokiG
|
||||
finalize()
|
||||
}
|
||||
}
|
||||
api.getMessages(group.serverID, group.server).success { messages ->
|
||||
api.getMessages(group.channel, group.server).success { messages ->
|
||||
messages.forEach { message ->
|
||||
if (message.hexEncodedPublicKey != userHexEncodedPublicKey) {
|
||||
processIncomingMessage(message)
|
||||
@ -170,12 +170,12 @@ class LokiGroupChatPoller(private val context: Context, private val group: LokiG
|
||||
}
|
||||
}
|
||||
}.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() {
|
||||
api.getDeletedMessageServerIDs(group.serverID, group.server).success { deletedMessageServerIDs ->
|
||||
api.getDeletedMessageServerIDs(group.channel, group.server).success { deletedMessageServerIDs ->
|
||||
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { lokiMessageDatabase.getMessageID(it) }
|
||||
val smsMessageDatabase = DatabaseFactory.getSmsDatabase(context)
|
||||
@ -185,12 +185,12 @@ class LokiGroupChatPoller(private val context: Context, private val group: LokiG
|
||||
mmsMessageDatabase.delete(it)
|
||||
}
|
||||
}.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() {
|
||||
api.getModerators(group.serverID, group.server)
|
||||
api.getModerators(group.channel, group.server)
|
||||
}
|
||||
// endregion
|
||||
}
|
@ -2,11 +2,14 @@ package org.thoughtcrime.securesms.loki
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.database.Database
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
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.LokiThreadFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus
|
||||
@ -17,11 +20,14 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
||||
companion object {
|
||||
private val friendRequestTableName = "loki_thread_friend_request_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 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 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 {
|
||||
@ -30,7 +36,7 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
||||
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
}
|
||||
|
||||
override fun getThreadID(messageID: Long): Long {
|
||||
fun getThreadID(messageID: Long): Long {
|
||||
return DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID)
|
||||
}
|
||||
|
||||
@ -84,4 +90,50 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
||||
notifyConversationListListeners()
|
||||
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) {
|
||||
private var mentionCandidates = listOf<Mention>()
|
||||
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
|
||||
|
||||
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() {
|
||||
var mentionCandidates = listOf<Mention>()
|
||||
set(newValue) { field = newValue; notifyDataSetChanged() }
|
||||
var hasGroupContext = false
|
||||
var publicChatServer: String? = null
|
||||
var publicChatChannel: Long? = null
|
||||
|
||||
override fun getCount(): Int {
|
||||
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 mentionCandidate = getItem(position)
|
||||
cell.mentionCandidate = mentionCandidate
|
||||
cell.hasGroupContext = hasGroupContext
|
||||
cell.publicChatServer = publicChatServer
|
||||
cell.publicChatChannel = publicChatChannel
|
||||
return cell
|
||||
}
|
||||
}
|
||||
@ -56,7 +61,11 @@ class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defS
|
||||
}
|
||||
|
||||
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
|
||||
val layoutParams = this.layoutParams as ViewGroup.LayoutParams
|
||||
layoutParams.height = toPx(6 + Math.min(mentionCandidates.count(), 4) * 52, resources)
|
||||
|
@ -10,13 +10,14 @@ import android.view.ViewOutlineProvider
|
||||
import android.widget.LinearLayout
|
||||
import kotlinx.android.synthetic.main.cell_mention_candidate_selection_view.view.*
|
||||
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
|
||||
|
||||
class MentionCandidateSelectionViewCell(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
var mentionCandidate = Mention("", "")
|
||||
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) : this(context, null)
|
||||
@ -42,7 +43,11 @@ class MentionCandidateSelectionViewCell(context: Context, attrs: AttributeSet?,
|
||||
private fun update() {
|
||||
displayNameTextView.text = mentionCandidate.displayName
|
||||
profilePictureImageView.update(mentionCandidate.hexEncodedPublicKey)
|
||||
val isUserModerator = LokiGroupChatAPI.isUserModerator(mentionCandidate.hexEncodedPublicKey, LokiGroupChatAPI.publicChatServerID, LokiGroupChatAPI.publicChatServer)
|
||||
moderatorIconImageView.visibility = if (isUserModerator && hasGroupContext) View.VISIBLE else View.GONE
|
||||
if (publicChatServer != null && publicChatChannel != null) {
|
||||
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 org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object MentionUtilities {
|
||||
|
||||
@JvmStatic
|
||||
fun highlightMentions(text: CharSequence, isGroupThread: Boolean, context: Context): String {
|
||||
return MentionUtilities.highlightMentions(text, false, isGroupThread, context).toString() // isOutgoingMessage is irrelevant
|
||||
fun highlightMentions(text: CharSequence, threadID: Long, context: Context): String {
|
||||
return MentionUtilities.highlightMentions(text, false, threadID, context).toString() // isOutgoingMessage is irrelevant
|
||||
}
|
||||
|
||||
@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
|
||||
val pattern = Pattern.compile("@[0-9a-fA-F]*")
|
||||
var matcher = pattern.matcher(text)
|
||||
val mentions = mutableListOf<Range<Int>>()
|
||||
var startIndex = 0
|
||||
if (matcher.find(startIndex) && isGroupThread) {
|
||||
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
||||
if (matcher.find(startIndex)) {
|
||||
while (true) {
|
||||
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()) {
|
||||
TextSecurePreferences.getProfileName(context)
|
||||
} else if (publicChat != null) {
|
||||
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, hexEncodedPublicKey)
|
||||
} else {
|
||||
val publicChatID = LokiGroupChatAPI.publicChatServer + "." + LokiGroupChatAPI.publicChatServerID
|
||||
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChatID, hexEncodedPublicKey)
|
||||
DatabaseFactory.getLokiUserDatabase(context).getDisplayName(hexEncodedPublicKey)
|
||||
}
|
||||
if (userDisplayName != null) {
|
||||
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
|
||||
String recipientPublicKey = recipient.getAddress().serialize();
|
||||
if (GeneralUtilitiesKt.isGroupRecipient(recipientPublicKey)) {
|
||||
if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey)) {
|
||||
jobManager.add(new PushTextSendJob(messageId, recipient.getAddress()));
|
||||
return;
|
||||
}
|
||||
@ -243,7 +243,7 @@ public class MessageSender {
|
||||
|
||||
// Just send the message normally if it's a group message
|
||||
String recipientPublicKey = recipient.getAddress().serialize();
|
||||
if (GeneralUtilitiesKt.isGroupRecipient(recipientPublicKey)) {
|
||||
if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey)) {
|
||||
PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress());
|
||||
return;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user