mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
Merge branch 'dev' into trusted_attachment_download
# Conflicts: # app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt # app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt
This commit is contained in:
commit
842cfc25a1
@ -143,8 +143,8 @@ dependencies {
|
||||
testImplementation 'org.robolectric:shadows-multidex:4.2'
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 193
|
||||
def canonicalVersionName = "1.11.2"
|
||||
def canonicalVersionCode = 198
|
||||
def canonicalVersionName = "1.11.3"
|
||||
|
||||
def postFixSize = 10
|
||||
def abiPostFix = ['armeabi-v7a' : 1,
|
||||
|
@ -55,6 +55,12 @@
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" tools:node="remove"/>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.media.action.IMAGE_CAPTURE" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<!-- The allowBackup="false" below is important to guard against potential malicious backups -->
|
||||
|
||||
<application
|
||||
@ -87,80 +93,80 @@
|
||||
android:value="false" />
|
||||
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.LandingActivity"
|
||||
android:name="org.thoughtcrime.securesms.onboarding.LandingActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.RegisterActivity"
|
||||
android:name="org.thoughtcrime.securesms.onboarding.RegisterActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.RecoveryPhraseRestoreActivity"
|
||||
android:name="org.thoughtcrime.securesms.onboarding.RecoveryPhraseRestoreActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.LinkDeviceActivity"
|
||||
android:name="org.thoughtcrime.securesms.onboarding.LinkDeviceActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.DisplayNameActivity"
|
||||
android:name="org.thoughtcrime.securesms.onboarding.DisplayNameActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.PNModeActivity"
|
||||
android:name="org.thoughtcrime.securesms.onboarding.PNModeActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.HomeActivity"
|
||||
android:name="org.thoughtcrime.securesms.home.HomeActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/Theme.Session.DayNight.NoActionBar" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.SettingsActivity"
|
||||
android:name="org.thoughtcrime.securesms.preferences.SettingsActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:label="@string/activity_settings_title" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.PathActivity"
|
||||
android:name="org.thoughtcrime.securesms.home.PathActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.QRCodeActivity"
|
||||
android:name="org.thoughtcrime.securesms.preferences.QRCodeActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.CreatePrivateChatActivity"
|
||||
android:name="org.thoughtcrime.securesms.dms.CreatePrivateChatActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.CreateClosedGroupActivity"
|
||||
android:name="org.thoughtcrime.securesms.groups.CreateClosedGroupActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity"
|
||||
android:name="org.thoughtcrime.securesms.groups.EditClosedGroupActivity"
|
||||
android:label="@string/activity_edit_closed_group_title"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.JoinPublicChatActivity"
|
||||
android:name="org.thoughtcrime.securesms.groups.JoinPublicChatActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.SeedActivity"
|
||||
android:name="org.thoughtcrime.securesms.onboarding.SeedActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.SelectContactsActivity"
|
||||
android:name="org.thoughtcrime.securesms.contacts.SelectContactsActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.PrivacySettingsActivity"
|
||||
android:name="org.thoughtcrime.securesms.preferences.PrivacySettingsActivity"
|
||||
android:label="@string/activity_privacy_settings_title"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.NotificationSettingsActivity"
|
||||
android:name="org.thoughtcrime.securesms.preferences.NotificationSettingsActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.ChatSettingsActivity"
|
||||
android:name="org.thoughtcrime.securesms.preferences.ChatSettingsActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<activity
|
||||
@ -192,7 +198,7 @@
|
||||
<activity-alias
|
||||
android:name=".RoutingActivity"
|
||||
android:exported="true"
|
||||
android:targetActivity="org.thoughtcrime.securesms.loki.activities.HomeActivity">
|
||||
android:targetActivity="org.thoughtcrime.securesms.home.HomeActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@ -209,14 +215,14 @@
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2"
|
||||
android:screenOrientation="portrait"
|
||||
android:parentActivityName="org.thoughtcrime.securesms.loki.activities.HomeActivity"
|
||||
android:parentActivityName="org.thoughtcrime.securesms.home.HomeActivity"
|
||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.thoughtcrime.securesms.loki.activities.HomeActivity" />
|
||||
android:value="org.thoughtcrime.securesms.home.HomeActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.OpenGroupGuidelinesActivity"
|
||||
android:name="org.thoughtcrime.securesms.groups.OpenGroupGuidelinesActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.TextSecure.DayNight" />
|
||||
<activity
|
||||
@ -285,7 +291,7 @@
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.Session.DayNight.NoActionBar" />
|
||||
<service
|
||||
android:name="org.thoughtcrime.securesms.loki.api.PushNotificationService"
|
||||
android:name="org.thoughtcrime.securesms.notifications.PushNotificationService"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
@ -405,7 +411,7 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name="org.thoughtcrime.securesms.loki.api.BackgroundPollWorker$BootBroadcastReceiver"
|
||||
android:name="org.thoughtcrime.securesms.notifications.BackgroundPollWorker$BootBroadcastReceiver"
|
||||
android:enabled="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
|
@ -27,10 +27,10 @@ import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
import androidx.multidex.MultiDexApplication;
|
||||
|
||||
import org.conscrypt.Conscrypt;
|
||||
import org.session.libsession.avatars.AvatarHelper;
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration;
|
||||
import org.session.libsession.messaging.contacts.Contact;
|
||||
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2;
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.Poller;
|
||||
@ -42,11 +42,11 @@ import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||
import org.session.libsession.utilities.dynamiclanguage.LocaleParser;
|
||||
import org.session.libsession.utilities.recipients.Recipient;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.session.libsignal.utilities.ThreadUtils;
|
||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
||||
import org.thoughtcrime.securesms.crypto.KeyPairUtilities;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule;
|
||||
@ -58,17 +58,14 @@ import org.thoughtcrime.securesms.jobs.JobManagerFactories;
|
||||
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
||||
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
||||
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
|
||||
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
||||
import org.thoughtcrime.securesms.loki.api.BackgroundPollWorker;
|
||||
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager;
|
||||
import org.thoughtcrime.securesms.loki.api.OpenGroupManager;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.SessionContactDatabase;
|
||||
import org.thoughtcrime.securesms.loki.utilities.Broadcaster;
|
||||
import org.thoughtcrime.securesms.loki.utilities.ContactUtilities;
|
||||
import org.thoughtcrime.securesms.loki.utilities.FcmUtils;
|
||||
import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities;
|
||||
import org.thoughtcrime.securesms.home.HomeActivity;
|
||||
import org.thoughtcrime.securesms.notifications.BackgroundPollWorker;
|
||||
import org.thoughtcrime.securesms.notifications.LokiPushNotificationManager;
|
||||
import org.thoughtcrime.securesms.groups.OpenGroupManager;
|
||||
import org.thoughtcrime.securesms.database.LokiAPIDatabase;
|
||||
import org.thoughtcrime.securesms.util.Broadcaster;
|
||||
import org.thoughtcrime.securesms.notifications.FcmUtils;
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities;
|
||||
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier;
|
||||
@ -84,12 +81,14 @@ import org.webrtc.PeerConnectionFactory;
|
||||
import org.webrtc.PeerConnectionFactory.InitializationOptions;
|
||||
import org.webrtc.voiceengine.WebRtcAudioManager;
|
||||
import org.webrtc.voiceengine.WebRtcAudioUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.security.Security;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import dagger.ObjectGraph;
|
||||
import kotlin.Unit;
|
||||
import kotlinx.coroutines.Job;
|
||||
@ -154,8 +153,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
conversationListNotificationHandler = new Handler(Looper.getMainLooper());
|
||||
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
||||
MessagingModuleConfiguration.Companion.configure(this,
|
||||
DatabaseFactory.getStorage(this),
|
||||
DatabaseFactory.getAttachmentProvider(this));
|
||||
DatabaseFactory.getStorage(this),
|
||||
DatabaseFactory.getAttachmentProvider(this),
|
||||
()-> KeyPairUtilities.INSTANCE.getUserED25519KeyPair(this)
|
||||
);
|
||||
SnodeModule.Companion.configure(apiDB, broadcaster);
|
||||
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||
if (userPublicKey != null) {
|
||||
@ -181,27 +182,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
Log.i(TAG, "App is now visible.");
|
||||
KeyCachingService.onAppForegrounded(this);
|
||||
|
||||
boolean hasPerformedContactMigration = TextSecurePreferences.INSTANCE.hasPerformedContactMigration(this);
|
||||
if (!hasPerformedContactMigration) {
|
||||
TextSecurePreferences.INSTANCE.setPerformedContactMigration(this);
|
||||
Set<Recipient> allContacts = ContactUtilities.getAllContacts(this);
|
||||
SessionContactDatabase contactDB = DatabaseFactory.getSessionContactDatabase(this);
|
||||
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
|
||||
for (Recipient recipient : allContacts) {
|
||||
if (recipient.isGroupRecipient()) { continue; }
|
||||
String sessionID = recipient.getAddress().serialize();
|
||||
Contact contact = contactDB.getContactWithSessionID(sessionID);
|
||||
if (contact == null) {
|
||||
contact = new Contact(sessionID);
|
||||
String name = userDB.getDisplayName(sessionID);
|
||||
contact.setName(name);
|
||||
contact.setProfilePictureURL(recipient.getProfileAvatar());
|
||||
contact.setProfilePictureEncryptionKey(recipient.getProfileKey());
|
||||
contact.setTrusted(true);
|
||||
}
|
||||
contactDB.setContact(contact);
|
||||
}
|
||||
}
|
||||
if (poller != null) {
|
||||
poller.setCaughtUp(false);
|
||||
}
|
||||
@ -487,7 +467,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
boolean isUsingFCM = TextSecurePreferences.isUsingFCM(this);
|
||||
TextSecurePreferences.clearAll(this);
|
||||
if (isMigratingToV2KeyPair) {
|
||||
TextSecurePreferences.setIsMigratingKeyPair(this, true);
|
||||
TextSecurePreferences.setIsUsingFCM(this, isUsingFCM);
|
||||
TextSecurePreferences.setProfileName(this, displayName);
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
CharSequence relativeTimeSpan;
|
||||
|
||||
if (mediaItem.date > 0) {
|
||||
relativeTimeSpan = DateUtils.getExtendedRelativeTimeSpanString(this, Locale.getDefault(), mediaItem.date);
|
||||
relativeTimeSpan = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), mediaItem.date);
|
||||
} else {
|
||||
relativeTimeSpan = getString(R.string.MediaPreviewActivity_draft);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.loki.views.UserView;
|
||||
import org.thoughtcrime.securesms.contacts.UserView;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.session.libsession.utilities.recipients.Recipient;
|
||||
import org.session.libsession.utilities.Conversions;
|
||||
|
@ -39,7 +39,7 @@ import android.widget.ImageView;
|
||||
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import androidx.core.os.CancellationSignal;
|
||||
|
||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||
import org.thoughtcrime.securesms.util.AnimationCompleteListener;
|
||||
import org.thoughtcrime.securesms.components.AnimatingToggle;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
|
@ -12,8 +12,8 @@ import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
||||
import org.thoughtcrime.securesms.loki.activities.LandingActivity;
|
||||
import org.thoughtcrime.securesms.home.HomeActivity;
|
||||
import org.thoughtcrime.securesms.onboarding.LandingActivity;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
|
||||
|
@ -42,9 +42,8 @@ import org.session.libsession.utilities.Address;
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.thoughtcrime.securesms.loki.fragments.ContactSelectionListFragment;
|
||||
import org.thoughtcrime.securesms.loki.fragments.ContactSelectionListLoader.DisplayMode;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListFragment;
|
||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListLoader.DisplayMode;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.session.libsession.utilities.recipients.Recipient;
|
||||
@ -54,7 +53,6 @@ import org.session.libsession.utilities.ViewUtil;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
|
@ -11,7 +11,7 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.session.libsession.utilities.Address;
|
||||
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
||||
import org.thoughtcrime.securesms.home.HomeActivity;
|
||||
import org.session.libsession.utilities.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.backup;
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
@ -9,17 +9,13 @@ import android.os.Bundle
|
||||
import android.provider.OpenableColumns
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.text.style.ClickableSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@ -28,18 +24,17 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
import org.thoughtcrime.securesms.backup.FullBackupImporter
|
||||
import org.thoughtcrime.securesms.backup.FullBackupImporter.DatabaseDowngradeException
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
|
||||
import org.thoughtcrime.securesms.loki.utilities.show
|
||||
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
|
||||
import org.thoughtcrime.securesms.util.show
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.util.BackupUtil
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.home.HomeActivity
|
||||
|
||||
class BackupRestoreActivity : BaseActionBarActivity() {
|
||||
|
||||
@ -188,7 +183,6 @@ class BackupRestoreViewModel(application: Application): AndroidViewModel(applica
|
||||
TextSecurePreferences.setRestorationTime(context, System.currentTimeMillis())
|
||||
TextSecurePreferences.setHasViewedSeed(context, true)
|
||||
TextSecurePreferences.setHasSeenWelcomeScreen(context, true)
|
||||
val application = ApplicationContext.getInstance(context)
|
||||
|
||||
BackupRestoreResult.SUCCESS
|
||||
} catch (e: DatabaseDowngradeException) {
|
@ -21,8 +21,7 @@ import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream
|
||||
import org.thoughtcrime.securesms.database.*
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase
|
||||
import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase
|
||||
import org.thoughtcrime.securesms.database.LokiBackupFilesDatabase
|
||||
import org.thoughtcrime.securesms.util.BackupUtil
|
||||
import org.session.libsession.utilities.Util
|
||||
import org.session.libsignal.crypto.kdf.HKDFv3
|
||||
|
@ -19,7 +19,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
|
||||
|
||||
import org.thoughtcrime.securesms.loki.utilities.AvatarPlaceholderGenerator;
|
||||
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
package org.thoughtcrime.securesms.components
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
@ -9,7 +9,7 @@ import android.view.LayoutInflater
|
||||
import android.widget.RelativeLayout
|
||||
import kotlinx.android.synthetic.main.view_separator.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import org.session.libsession.utilities.ThemeUtil
|
||||
|
||||
class LabeledSeparatorView : RelativeLayout {
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
package org.thoughtcrime.securesms.components
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@ -17,7 +17,7 @@ import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.utilities.AvatarPlaceholderGenerator
|
||||
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
|
||||
class ProfilePictureView : RelativeLayout {
|
@ -22,8 +22,8 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import org.session.libsession.messaging.contacts.Contact;
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.loki.database.SessionContactDatabase;
|
||||
import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities;
|
||||
import org.thoughtcrime.securesms.database.SessionContactDatabase;
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
|
@ -16,7 +16,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||
import org.thoughtcrime.securesms.util.AnimationCompleteListener;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
|
@ -8,7 +8,7 @@ import org.session.libsession.messaging.messages.control.TypingIndicator;
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
|
||||
import org.thoughtcrime.securesms.util.SessionMetaProtocol;
|
||||
import org.session.libsession.utilities.recipients.Recipient;
|
||||
import org.session.libsession.utilities.Util;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.fragments
|
||||
package org.thoughtcrime.securesms.contacts
|
||||
|
||||
import android.content.Context
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@ -7,7 +7,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.contact_selection_list_divider.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.loki.views.UserView
|
||||
import org.thoughtcrime.securesms.contacts.UserView
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.fragments
|
||||
package org.thoughtcrime.securesms.contacts
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
@ -11,10 +11,11 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.contact_selection_list_fragment.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem
|
||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListLoader
|
||||
|
||||
class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks<List<ContactSelectionListItem>>, ContactClickListener {
|
||||
private var cursorFilter: String? = null
|
||||
@ -98,7 +99,7 @@ class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks<L
|
||||
update(listOf())
|
||||
}
|
||||
|
||||
private fun update(items: List<ContactSelectionListItem>) {
|
||||
private fun update(items: List<ContactSelectionListItem>) {
|
||||
if (activity?.isDestroyed == true) {
|
||||
Log.e(ContactSelectionListFragment::class.java.name,
|
||||
"Received a loader callback after the fragment was detached from the activity.",
|
@ -1,8 +1,8 @@
|
||||
package org.thoughtcrime.securesms.loki.fragments
|
||||
package org.thoughtcrime.securesms.contacts
|
||||
|
||||
import android.content.Context
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
|
||||
import org.thoughtcrime.securesms.util.ContactUtilities
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.AsyncLoader
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.contacts
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
@ -40,8 +40,8 @@ class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderMana
|
||||
setContentView(R.layout.activity_select_contacts)
|
||||
supportActionBar!!.title = resources.getString(R.string.activity_select_contacts_title)
|
||||
|
||||
usersToExclude = intent.getStringArrayExtra(Companion.usersToExcludeKey)?.toSet() ?: setOf()
|
||||
val emptyStateText = intent.getStringExtra(Companion.emptyStateTextKey)
|
||||
usersToExclude = intent.getStringArrayExtra(usersToExcludeKey)?.toSet() ?: setOf()
|
||||
val emptyStateText = intent.getStringExtra(emptyStateTextKey)
|
||||
if (emptyStateText != null) {
|
||||
emptyStateMessageTextView.text = emptyStateText
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.contacts
|
||||
|
||||
import android.content.Context
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.ViewGroup
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.thoughtcrime.securesms.loki.views.UserView
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
|
@ -1,7 +1,7 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.contacts
|
||||
|
||||
import android.content.Context
|
||||
import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
|
||||
import org.thoughtcrime.securesms.util.ContactUtilities
|
||||
import org.thoughtcrime.securesms.util.AsyncLoader
|
||||
|
||||
class SelectContactsLoader(context: Context, val usersToExclude: Set<String>) : AsyncLoader<List<String>>(context) {
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
package org.thoughtcrime.securesms.contacts
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@ -10,7 +10,7 @@ import kotlinx.android.synthetic.main.view_user.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionManagerUtilities
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
|
@ -23,6 +23,8 @@ import android.widget.RelativeLayout
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.DimenRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
@ -40,6 +42,7 @@ import kotlinx.android.synthetic.main.view_conversation.view.*
|
||||
import kotlinx.android.synthetic.main.view_input_bar.view.*
|
||||
import kotlinx.android.synthetic.main.view_input_bar_recording.*
|
||||
import kotlinx.android.synthetic.main.view_input_bar_recording.view.*
|
||||
import kotlinx.android.synthetic.main.view_visible_message.view.*
|
||||
import network.loki.messenger.R
|
||||
import nl.komponents.kovenant.ui.failUi
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
@ -69,7 +72,6 @@ import org.session.libsession.utilities.concurrent.SimpleTask
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsession.utilities.recipients.RecipientModifiedListener
|
||||
import org.session.libsignal.utilities.ListenableFuture
|
||||
import org.session.libsignal.utilities.SettableFuture
|
||||
import org.session.libsignal.utilities.guava.Optional
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
@ -84,8 +86,7 @@ import org.thoughtcrime.securesms.conversation.v2.input_bar.mentions.MentionCand
|
||||
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallback
|
||||
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallbackDelegate
|
||||
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper
|
||||
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentViewDelegate
|
||||
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
|
||||
import org.thoughtcrime.securesms.conversation.v2.messages.*
|
||||
import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar
|
||||
import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager
|
||||
@ -100,20 +101,16 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState
|
||||
import org.thoughtcrime.securesms.loki.activities.SelectContactsActivity
|
||||
import org.thoughtcrime.securesms.loki.activities.SelectContactsActivity.Companion.selectedContactsKey
|
||||
import org.thoughtcrime.securesms.loki.utilities.ActivityDispatcher
|
||||
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities
|
||||
import org.thoughtcrime.securesms.loki.utilities.push
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.contacts.SelectContactsActivity
|
||||
import org.thoughtcrime.securesms.contacts.SelectContactsActivity.Companion.selectedContactsKey
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivity
|
||||
import org.thoughtcrime.securesms.mms.*
|
||||
import org.thoughtcrime.securesms.notifications.MarkReadReceiver
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutionException
|
||||
import kotlin.math.*
|
||||
@ -125,7 +122,7 @@ import kotlin.math.*
|
||||
class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate,
|
||||
InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher,
|
||||
ConversationActionModeCallbackDelegate, VisibleMessageContentViewDelegate, RecipientModifiedListener,
|
||||
SearchBottomBar.EventListener {
|
||||
SearchBottomBar.EventListener, VoiceMessageViewDelegate {
|
||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||
private var linkPreviewViewModel: LinkPreviewViewModel? = null
|
||||
private var threadID: Long = -1
|
||||
@ -213,6 +210,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
threadID = DatabaseFactory.getThreadDatabase(this).getOrCreateThreadIdFor(recipient)
|
||||
}
|
||||
this.threadID = threadID
|
||||
val thread = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID)
|
||||
if (thread == null) {
|
||||
Toast.makeText(this, "This thread has been deleted.", Toast.LENGTH_LONG).show()
|
||||
return finish()
|
||||
}
|
||||
setUpRecyclerView()
|
||||
setUpToolBar()
|
||||
setUpInputBar()
|
||||
@ -232,6 +234,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
scrollToFirstUnreadMessageIfNeeded()
|
||||
markAllAsRead()
|
||||
showOrHideInputIfNeeded()
|
||||
if (this.thread.isOpenGroupRecipient) {
|
||||
val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID)
|
||||
if (openGroup == null) {
|
||||
Toast.makeText(this, "This thread has been deleted.", Toast.LENGTH_LONG).show()
|
||||
return finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@ -849,6 +858,21 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
conversationRecyclerView.scrollToPosition(lastSeenItemPosition)
|
||||
}
|
||||
|
||||
override fun playNextAudioIfPossible(current: Int) {
|
||||
if (current > 0) {
|
||||
val nextVisibleMessageView = conversationRecyclerView[current - 1] as? VisibleMessageView
|
||||
nextVisibleMessageView?.let { visibleMessageView ->
|
||||
visibleMessageView.messageContentView.mainContainer.children.forEach { child ->
|
||||
val nextVoiceMessageView = child as? VoiceMessageView
|
||||
nextVoiceMessageView?.let { voiceMessageView ->
|
||||
voiceMessageView.togglePlayback()
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun sendMessage() {
|
||||
if (thread.isContactRecipient && thread.isBlocked) {
|
||||
BlockedDialog(thread).show(supportFragmentManager, "Blocked Dialog")
|
||||
@ -1129,7 +1153,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
for (message in sortedMessages) {
|
||||
val body = MentionUtilities.highlightMentions(message.body, message.threadId, this)
|
||||
if (TextUtils.isEmpty(body)) { continue }
|
||||
val formattedTimestamp = DateUtils.getExtendedRelativeTimeSpanString(this, Locale.getDefault(), message.timestamp)
|
||||
val formattedTimestamp = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), message.timestamp)
|
||||
builder.append("$formattedTimestamp: $body").append('\n')
|
||||
}
|
||||
if (builder.isNotEmpty() && builder[builder.length - 1] == '\n') {
|
||||
@ -1265,7 +1289,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
}
|
||||
|
||||
private fun saveDraft() {
|
||||
val text = inputBar.text.trim()
|
||||
val text = inputBar?.text?.trim() ?: return
|
||||
if (text.isEmpty()) { return }
|
||||
val drafts = Drafts()
|
||||
drafts.add(DraftDatabase.Draft(DraftDatabase.Draft.TEXT, text))
|
||||
|
@ -72,6 +72,7 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr
|
||||
view.snIsSelected = isSelected
|
||||
view.messageTimestampTextView.isVisible = isSelected
|
||||
val position = viewHolder.adapterPosition
|
||||
view.viewHolderIndex = position
|
||||
view.bind(message, getMessageBefore(position, cursor), getMessageAfter(position, cursor), glide, searchQuery)
|
||||
view.onPress = { event -> onItemPress(message, viewHolder.adapterPosition, view, event) }
|
||||
view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) }
|
||||
|
@ -8,8 +8,8 @@ import android.view.VelocityTracker
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.activity_conversation_v2.*
|
||||
import org.thoughtcrime.securesms.loki.utilities.disableClipping
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.util.disableClipping
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
|
||||
|
@ -23,7 +23,7 @@ import org.thoughtcrime.securesms.MediaPreviewActivity
|
||||
import org.thoughtcrime.securesms.components.CornerMask
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.loki.utilities.ActivityDispatcher
|
||||
import org.thoughtcrime.securesms.util.ActivityDispatcher
|
||||
import org.thoughtcrime.securesms.longmessage.LongMessageActivity
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.mms.Slide
|
||||
|
@ -8,7 +8,7 @@ import androidx.core.view.isVisible
|
||||
import kotlinx.android.synthetic.main.view_link_preview_draft.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
package org.thoughtcrime.securesms.conversation.v2.components
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@ -8,7 +8,7 @@ import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.ListView
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.session.libsession.messaging.mentions.Mention
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
package org.thoughtcrime.securesms.conversation.v2.components
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
@ -8,8 +8,8 @@ import android.widget.FrameLayout
|
||||
import kotlinx.android.synthetic.main.view_open_group_guidelines.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.loki.activities.OpenGroupGuidelinesActivity
|
||||
import org.thoughtcrime.securesms.loki.utilities.push
|
||||
import org.thoughtcrime.securesms.groups.OpenGroupGuidelinesActivity
|
||||
import org.thoughtcrime.securesms.util.push
|
||||
|
||||
class OpenGroupGuidelinesView : FrameLayout {
|
||||
|
||||
|
@ -9,12 +9,11 @@ import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import kotlinx.android.synthetic.main.dialog_join_open_group.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupV2
|
||||
import org.session.libsession.utilities.OpenGroupUrlParser
|
||||
import org.session.libsignal.utilities.ThreadUtils
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
||||
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||
|
||||
/** Shown upon tapping an open group invitation. */
|
||||
class JoinOpenGroupDialog(private val name: String, private val url: String) : BaseDialog() {
|
||||
@ -38,7 +37,7 @@ class JoinOpenGroupDialog(private val name: String, private val url: String) : B
|
||||
val activity = requireContext() as AppCompatActivity
|
||||
ThreadUtils.queue {
|
||||
OpenGroupManager.add(openGroup.server, openGroup.room, openGroup.serverPublicKey, activity)
|
||||
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(activity)
|
||||
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(activity)
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
@ -19,8 +19,8 @@ import org.thoughtcrime.securesms.conversation.v2.messages.QuoteView
|
||||
import org.thoughtcrime.securesms.conversation.v2.messages.QuoteViewDelegate
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.loki.utilities.toDp
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.util.toDp
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
|
@ -17,10 +17,9 @@ import android.widget.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.annotation.DrawableRes
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
|
||||
import org.thoughtcrime.securesms.loki.views.InputBarButtonImageViewContainer
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
import org.thoughtcrime.securesms.util.GlowViewUtilities
|
||||
import org.thoughtcrime.securesms.util.InputBarButtonImageViewContainer
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
|
||||
|
@ -9,7 +9,7 @@ import android.util.Log
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
@ -13,9 +13,9 @@ import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.isVisible
|
||||
import kotlinx.android.synthetic.main.view_input_bar_recording.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.loki.utilities.animateSizeChange
|
||||
import org.thoughtcrime.securesms.loki.utilities.disableClipping
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.util.animateSizeChange
|
||||
import org.thoughtcrime.securesms.util.disableClipping
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import java.util.*
|
||||
|
||||
|
@ -8,7 +8,7 @@ import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.ListView
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.session.libsession.messaging.mentions.Mention
|
||||
|
||||
|
@ -31,6 +31,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
|
||||
if (selectedItems.isEmpty()) { return }
|
||||
val firstMessage = selectedItems.iterator().next()
|
||||
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID)
|
||||
val thread = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadID)!!
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||
fun userCanDeleteSelectedItems(): Boolean {
|
||||
if (openGroup == null) { return true }
|
||||
@ -54,7 +55,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
|
||||
menu.findItem(R.id.menu_context_copy).isVisible = !containsControlMessage && hasText
|
||||
// Copy Session ID
|
||||
menu.findItem(R.id.menu_context_copy_public_key).isVisible =
|
||||
(openGroup != null && selectedItems.size == 1 && firstMessage.recipient.address.toString() != userPublicKey)
|
||||
(thread.isGroupRecipient && selectedItems.size == 1 && firstMessage.recipient.address.toString() != userPublicKey)
|
||||
// Resend
|
||||
menu.findItem(R.id.menu_context_resend).isVisible = (selectedItems.size == 1 && firstMessage.isFailed)
|
||||
// Save media
|
||||
|
@ -38,10 +38,10 @@ import org.session.libsignal.utilities.toHexString
|
||||
import org.thoughtcrime.securesms.*
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity
|
||||
import org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity.Companion.groupIDKey
|
||||
import org.thoughtcrime.securesms.loki.activities.SelectContactsActivity
|
||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
||||
import org.thoughtcrime.securesms.groups.EditClosedGroupActivity
|
||||
import org.thoughtcrime.securesms.groups.EditClosedGroupActivity.Companion.groupIDKey
|
||||
import org.thoughtcrime.securesms.contacts.SelectContactsActivity
|
||||
import org.thoughtcrime.securesms.util.getColorWithID
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil
|
||||
import java.io.IOException
|
||||
|
||||
|
@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtiliti
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide
|
||||
|
||||
|
@ -1,11 +1,8 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.messages
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Resources
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RelativeLayout
|
||||
@ -13,22 +10,19 @@ import androidx.annotation.ColorInt
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.marginStart
|
||||
import com.google.android.exoplayer2.util.MimeTypes
|
||||
import kotlinx.android.synthetic.main.view_link_preview.view.*
|
||||
import kotlinx.android.synthetic.main.view_quote.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.messaging.utilities.UpdateMessageData
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
@ -6,7 +6,6 @@ import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.style.BackgroundColorSpan
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.text.style.URLSpan
|
||||
import android.text.util.Linkify
|
||||
import android.util.AttributeSet
|
||||
@ -23,7 +22,6 @@ import androidx.core.graphics.BlendModeColorFilterCompat
|
||||
import androidx.core.graphics.BlendModeCompat
|
||||
import androidx.core.text.getSpans
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.core.text.util.LinkifyCompat
|
||||
import kotlinx.android.synthetic.main.view_link_preview.view.*
|
||||
import kotlinx.android.synthetic.main.view_visible_message_content.view.*
|
||||
import network.loki.messenger.R
|
||||
@ -32,15 +30,18 @@ import org.session.libsession.utilities.ViewUtil
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.conversation.v2.components.AlbumThumbnailView
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.conversation.v2.dialogs.OpenURLDialog
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.SearchUtil
|
||||
import org.thoughtcrime.securesms.util.SearchUtil.StyleFactory
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@ -48,6 +49,7 @@ class VisibleMessageContentView : LinearLayout {
|
||||
var onContentClick: ((event: MotionEvent) -> Unit)? = null
|
||||
var onContentDoubleTap: (() -> Unit)? = null
|
||||
var delegate: VisibleMessageContentViewDelegate? = null
|
||||
var viewHolderIndex: Int = -1
|
||||
|
||||
// region Lifecycle
|
||||
constructor(context: Context) : super(context) { initialize() }
|
||||
@ -109,7 +111,8 @@ class VisibleMessageContentView : LinearLayout {
|
||||
// Audio attachment
|
||||
if (contactIsTrusted || message.isOutgoing) {
|
||||
val voiceMessageView = VoiceMessageView(context)
|
||||
voiceMessageView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster)
|
||||
voiceMessageView.index = viewHolderIndex
|
||||
voiceMessageView.delegate = context as? ConversationActivityV2voiceMessageView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster)
|
||||
mainContainer.addView(voiceMessageView)
|
||||
// We have to use onContentClick (rather than a click listener directly on the voice
|
||||
// message view) so as to not interfere with all the other gestures.
|
||||
|
@ -24,12 +24,8 @@ import org.session.libsignal.utilities.ThreadUtils
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.loki.utilities.disableClipping
|
||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
||||
import org.thoughtcrime.securesms.loki.utilities.toDp
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
@ -47,6 +43,7 @@ class VisibleMessageView : LinearLayout {
|
||||
private var longPressCallback: Runnable? = null
|
||||
private var onDownTimestamp = 0L
|
||||
private var onDoubleTap: (() -> Unit)? = null
|
||||
var viewHolderIndex: Int = -1
|
||||
var snIsSelected = false
|
||||
set(value) { field = value; handleIsSelectedChanged()}
|
||||
var onPress: ((event: MotionEvent) -> Unit)? = null
|
||||
@ -82,7 +79,7 @@ class VisibleMessageView : LinearLayout {
|
||||
val senderSessionID = sender.address.serialize()
|
||||
val threadID = message.threadId
|
||||
val threadDB = DatabaseFactory.getThreadDatabase(context)
|
||||
val thread = threadDB.getRecipientForThreadId(threadID)!!
|
||||
val thread = threadDB.getRecipientForThreadId(threadID) ?: return
|
||||
val contactDB = DatabaseFactory.getSessionContactDatabase(context)
|
||||
val contact = contactDB.getContactWithSessionID(senderSessionID)
|
||||
val isGroupThread = thread.isGroupRecipient
|
||||
@ -96,7 +93,7 @@ class VisibleMessageView : LinearLayout {
|
||||
profilePictureView.glide = glide
|
||||
profilePictureView.update()
|
||||
if (thread.isOpenGroupRecipient) {
|
||||
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID)!!
|
||||
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID) ?: return
|
||||
val isModerator = OpenGroupAPIV2.isUserModerator(senderSessionID, openGroup.room, openGroup.server)
|
||||
moderatorIconImageView.visibility = if (isModerator) View.VISIBLE else View.INVISIBLE
|
||||
} else {
|
||||
@ -110,11 +107,11 @@ class VisibleMessageView : LinearLayout {
|
||||
senderNameTextView.visibility = View.GONE
|
||||
}
|
||||
// Date break
|
||||
val showDateBreak = (previous == null || !DateUtils.isSameDay(message.timestamp, previous.timestamp))
|
||||
val showDateBreak = (previous == null || !DateUtils.isSameHour(message.timestamp, previous.timestamp))
|
||||
dateBreakTextView.isVisible = showDateBreak
|
||||
dateBreakTextView.text = if (showDateBreak) DateUtils.getRelativeDate(context, Locale.getDefault(), message.timestamp) else ""
|
||||
dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else ""
|
||||
// Timestamp
|
||||
messageTimestampTextView.text = DateUtils.getExtendedRelativeTimeSpanString(context, Locale.getDefault(), message.timestamp)
|
||||
messageTimestampTextView.text = DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp)
|
||||
// Margins
|
||||
val startPadding: Int
|
||||
if (isGroupThread) {
|
||||
@ -152,6 +149,7 @@ class VisibleMessageView : LinearLayout {
|
||||
var maxWidth = screenWidth - startPadding - endPadding
|
||||
if (profilePictureContainer.visibility != View.GONE) { maxWidth -= profilePictureContainer.width }
|
||||
// Populate content view
|
||||
messageContentView.viewHolderIndex = viewHolderIndex
|
||||
messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth, thread, searchQuery, isGroupThread || (contact?.isTrusted ?: false))
|
||||
messageContentView.delegate = contentViewDelegate
|
||||
onDoubleTap = { messageContentView.onContentDoubleTap?.invoke() }
|
||||
@ -176,10 +174,10 @@ class VisibleMessageView : LinearLayout {
|
||||
|
||||
private fun isEndOfMessageCluster(current: MessageRecord, next: MessageRecord?, isGroupThread: Boolean): Boolean {
|
||||
return if (isGroupThread) {
|
||||
next == null || next.isUpdate || !DateUtils.isSameDay(current.timestamp, next.timestamp)
|
||||
next == null || next.isUpdate || !DateUtils.isSameHour(current.timestamp, next.timestamp)
|
||||
|| current.recipient.address != next.recipient.address
|
||||
} else {
|
||||
next == null || next.isUpdate || !DateUtils.isSameDay(current.timestamp, next.timestamp)
|
||||
next == null || next.isUpdate || !DateUtils.isSameHour(current.timestamp, next.timestamp)
|
||||
|| current.isOutgoing != next.isOutgoing
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ class VoiceMessageView : LinearLayout, AudioSlidePlayer.Listener {
|
||||
private var duration = 0L
|
||||
private var player: AudioSlidePlayer? = null
|
||||
private var isPreparing = false
|
||||
var delegate: VoiceMessageViewDelegate? = null
|
||||
var index = -1
|
||||
|
||||
// region Lifecycle
|
||||
constructor(context: Context) : super(context) { initialize() }
|
||||
@ -84,6 +86,7 @@ class VoiceMessageView : LinearLayout, AudioSlidePlayer.Listener {
|
||||
if (progress == 1.0) {
|
||||
togglePlayback()
|
||||
handleProgressChanged(0.0)
|
||||
delegate?.playNextAudioIfPossible(index)
|
||||
} else {
|
||||
handleProgressChanged(progress)
|
||||
}
|
||||
@ -133,3 +136,8 @@ class VoiceMessageView : LinearLayout, AudioSlidePlayer.Listener {
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
||||
interface VoiceMessageViewDelegate {
|
||||
|
||||
fun playNextAudioIfPossible(current: Int)
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
|
||||
open class BaseDialog : DialogFragment() {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.utilities
|
||||
package org.thoughtcrime.securesms.conversation.v2.utilities
|
||||
|
||||
import android.content.Context
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.utilities
|
||||
package org.thoughtcrime.securesms.conversation.v2.utilities
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
@ -13,6 +13,7 @@ import nl.komponents.kovenant.combine.Tuple2
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object MentionUtilities {
|
@ -15,21 +15,22 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.session.libsession.utilities;
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.session.libsignal.crypto.ecc.ECPublicKey;
|
||||
import org.session.libsignal.crypto.IdentityKey;
|
||||
import org.session.libsignal.crypto.IdentityKeyPair;
|
||||
import org.session.libsignal.exceptions.InvalidKeyException;
|
||||
import org.session.libsignal.crypto.ecc.Curve;
|
||||
import org.session.libsignal.crypto.ecc.ECKeyPair;
|
||||
import org.session.libsignal.crypto.ecc.ECPrivateKey;
|
||||
|
||||
import org.session.libsignal.crypto.ecc.ECPublicKey;
|
||||
import org.session.libsignal.exceptions.InvalidKeyException;
|
||||
import org.session.libsignal.utilities.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -45,19 +46,41 @@ public class IdentityKeyUtil {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = IdentityKeyUtil.class.getSimpleName();
|
||||
private static final String ENCRYPTED_SUFFIX = "_encrypted";
|
||||
|
||||
public static final String IDENTITY_PUBLIC_KEY_PREF = "pref_identity_public_v3";
|
||||
public static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private_v3";
|
||||
public static final String ED25519_PUBLIC_KEY = "pref_ed25519_public_key";
|
||||
public static final String ED25519_SECRET_KEY = "pref_ed25519_secret_key";
|
||||
public static final String LOKI_SEED = "loki_seed";
|
||||
public static final String HAS_MIGRATED_KEY = "has_migrated_keys";
|
||||
|
||||
private static SharedPreferences getSharedPreferences(Context context) {
|
||||
return context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0);
|
||||
}
|
||||
|
||||
public static boolean hasIdentityKey(Context context) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0);
|
||||
SharedPreferences preferences = getSharedPreferences(context);
|
||||
|
||||
return
|
||||
preferences.contains(IDENTITY_PUBLIC_KEY_PREF) &&
|
||||
preferences.contains(IDENTITY_PRIVATE_KEY_PREF);
|
||||
return (preferences.contains(IDENTITY_PUBLIC_KEY_PREF) &&
|
||||
preferences.contains(IDENTITY_PRIVATE_KEY_PREF))
|
||||
|| (preferences.contains(IDENTITY_PUBLIC_KEY_PREF+ENCRYPTED_SUFFIX) &&
|
||||
preferences.contains(IDENTITY_PRIVATE_KEY_PREF+ENCRYPTED_SUFFIX));
|
||||
}
|
||||
|
||||
public static void checkUpdate(Context context) {
|
||||
SharedPreferences preferences = getSharedPreferences(context);
|
||||
// check if any keys are not migrated
|
||||
if (hasIdentityKey(context) && !preferences.getBoolean(HAS_MIGRATED_KEY, false)) {
|
||||
// this will retrieve and force upgrade if possible
|
||||
// retrieve will force upgrade if available
|
||||
retrieve(context,IDENTITY_PUBLIC_KEY_PREF);
|
||||
retrieve(context,IDENTITY_PRIVATE_KEY_PREF);
|
||||
retrieve(context,ED25519_PUBLIC_KEY);
|
||||
retrieve(context,ED25519_SECRET_KEY);
|
||||
retrieve(context,LOKI_SEED);
|
||||
preferences.edit().putBoolean(HAS_MIGRATED_KEY, true).apply();
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull IdentityKey getIdentityKey(@NonNull Context context) {
|
||||
@ -94,14 +117,56 @@ public class IdentityKeyUtil {
|
||||
|
||||
public static String retrieve(Context context, String key) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0);
|
||||
return preferences.getString(key, null);
|
||||
|
||||
String unencryptedSecret = preferences.getString(key, null);
|
||||
String encryptedSecret = preferences.getString(key+ENCRYPTED_SUFFIX, null);
|
||||
|
||||
if (unencryptedSecret != null) return getUnencryptedSecret(key, unencryptedSecret, context);
|
||||
else if (encryptedSecret != null) return getEncryptedSecret(encryptedSecret);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getUnencryptedSecret(String key, String unencryptedSecret, Context context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return unencryptedSecret;
|
||||
} else {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(unencryptedSecret.getBytes());
|
||||
|
||||
// save the encrypted suffix secret "key_encrypted"
|
||||
save(context,key+ENCRYPTED_SUFFIX,encryptedSecret.serialize());
|
||||
// delete the regular secret "key"
|
||||
delete(context,key);
|
||||
|
||||
return unencryptedSecret;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getEncryptedSecret(String encryptedSecret) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
throw new AssertionError("OS downgrade not supported. KeyStore sealed data exists on platform < M!");
|
||||
} else {
|
||||
KeyStoreHelper.SealedData sealedData = KeyStoreHelper.SealedData.fromString(encryptedSecret);
|
||||
return new String(KeyStoreHelper.unseal(sealedData));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void save(Context context, String key, String value) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0);
|
||||
Editor preferencesEditor = preferences.edit();
|
||||
|
||||
preferencesEditor.putString(key, value);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
boolean isEncryptedSuffix = key.endsWith(ENCRYPTED_SUFFIX);
|
||||
if (isEncryptedSuffix) {
|
||||
preferencesEditor.putString(key, value);
|
||||
} else {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(value.getBytes());
|
||||
preferencesEditor.putString(key+ENCRYPTED_SUFFIX, encryptedSecret.serialize());
|
||||
}
|
||||
} else {
|
||||
preferencesEditor.putString(key, value);
|
||||
}
|
||||
if (!preferencesEditor.commit()) throw new AssertionError("failed to save identity key/value to shared preferences");
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.session.libsession.utilities
|
||||
package org.thoughtcrime.securesms.crypto
|
||||
|
||||
import android.content.Context
|
||||
import com.goterl.lazysodium.LazySodiumAndroid
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.utilities
|
||||
package org.thoughtcrime.securesms.crypto
|
||||
|
||||
import android.content.Context
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.database
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.net.Uri
|
||||
import java.util.*
|
@ -25,13 +25,8 @@ import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.SessionJobDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.SessionContactDatabase;
|
||||
import org.thoughtcrime.securesms.database.LokiAPIDatabase;
|
||||
import org.thoughtcrime.securesms.database.SessionJobDatabase;
|
||||
|
||||
public class DatabaseFactory {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.utilities
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import androidx.core.database.getStringOrNull
|
@ -1,23 +1,26 @@
|
||||
package org.thoughtcrime.securesms.loki.database
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import org.session.libsession.utilities.IdentityKeyUtil
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.crypto.ecc.DjbECPrivateKey
|
||||
import org.session.libsignal.crypto.ecc.DjbECPublicKey
|
||||
import org.session.libsignal.crypto.ecc.ECKeyPair
|
||||
import org.session.libsignal.utilities.Snode
|
||||
import org.session.libsignal.database.LokiAPIDatabaseProtocol
|
||||
import org.session.libsignal.utilities.PublicKeyValidation
|
||||
import org.session.libsignal.utilities.removing05PrefixIfNeeded
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import org.session.libsignal.utilities.Hex
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.database.Database
|
||||
import org.session.libsignal.utilities.*
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.database.*
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
import java.util.*
|
||||
import kotlin.Array
|
||||
import kotlin.Boolean
|
||||
import kotlin.Int
|
||||
import kotlin.Long
|
||||
import kotlin.Pair
|
||||
import kotlin.String
|
||||
import kotlin.arrayOf
|
||||
import kotlin.to
|
||||
|
||||
class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiAPIDatabaseProtocol {
|
||||
|
@ -1,10 +1,9 @@
|
||||
package org.thoughtcrime.securesms.loki.database
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import org.thoughtcrime.securesms.database.Database
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.util.*
|
@ -1,14 +1,10 @@
|
||||
package org.thoughtcrime.securesms.loki.database
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import net.sqlcipher.database.SQLiteDatabase.CONFLICT_REPLACE
|
||||
import org.thoughtcrime.securesms.database.Database
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.session.libsignal.database.LokiMessageDatabaseProtocol
|
||||
import org.session.libsignal.utilities.Log
|
||||
|
||||
class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiMessageDatabaseProtocol {
|
||||
|
||||
@ -63,9 +59,9 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
||||
val database = databaseHelper.writableDatabase
|
||||
|
||||
val serverID = database.get(messageIDTable,
|
||||
"${Companion.messageID} = ? AND ${Companion.messageType} = ?",
|
||||
"${Companion.messageID} = ? AND $messageType = ?",
|
||||
arrayOf(messageID.toString(), (if (isSms) SMS_TYPE else MMS_TYPE).toString())) { cursor ->
|
||||
cursor.getInt(Companion.serverID).toLong()
|
||||
cursor.getInt(serverID).toLong()
|
||||
} ?: return
|
||||
|
||||
database.beginTransaction()
|
||||
@ -89,7 +85,7 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
||||
return database.get(messageIDTable,
|
||||
"$messageID = ? AND ${Companion.serverID} = ?",
|
||||
arrayOf(mappedID.toString(), mappedServerID.toString())) { cursor ->
|
||||
cursor.getInt(Companion.messageID).toLong() to (cursor.getInt(messageType) == SMS_TYPE)
|
||||
cursor.getInt(messageID).toLong() to (cursor.getInt(messageType) == SMS_TYPE)
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,18 +133,18 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
||||
val database = databaseHelper.writableDatabase
|
||||
try {
|
||||
val messages = mutableSetOf<Pair<Long,Long>>()
|
||||
database.get(messageThreadMappingTable, "${Companion.threadID} = ?", arrayOf(threadId.toString())) { cursor ->
|
||||
database.get(messageThreadMappingTable, "$threadID = ?", arrayOf(threadId.toString())) { cursor ->
|
||||
// for each add
|
||||
while (cursor.moveToNext()) {
|
||||
messages.add(cursor.getLong(Companion.messageID) to cursor.getLong(Companion.serverID))
|
||||
messages.add(cursor.getLong(messageID) to cursor.getLong(serverID))
|
||||
}
|
||||
}
|
||||
var deletedCount = 0L
|
||||
database.beginTransaction()
|
||||
messages.forEach { (messageId, serverId) ->
|
||||
deletedCount += database.delete(messageIDTable, "${Companion.messageID} = ? AND ${Companion.serverID} = ?", arrayOf(messageId.toString(), serverId.toString()))
|
||||
deletedCount += database.delete(messageIDTable, "$messageID = ? AND $serverID = ?", arrayOf(messageId.toString(), serverId.toString()))
|
||||
}
|
||||
val mappingDeleted = database.delete(messageThreadMappingTable, "${Companion.threadID} = ?", arrayOf(threadId.toString()))
|
||||
val mappingDeleted = database.delete(messageThreadMappingTable, "$threadID = ?", arrayOf(threadId.toString()))
|
||||
database.setTransactionSuccessful()
|
||||
} finally {
|
||||
database.endTransaction()
|
@ -1,12 +1,9 @@
|
||||
package org.thoughtcrime.securesms.loki.database
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import org.thoughtcrime.securesms.database.Database
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupV2
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
@ -1,9 +1,7 @@
|
||||
package org.thoughtcrime.securesms.loki.database
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.Context
|
||||
import org.thoughtcrime.securesms.database.Database
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.loki.utilities.get
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
|
||||
class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
|
@ -883,6 +883,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
|
||||
public void deleteQuotedFromMessages(MessageRecord toDeleteRecord) {
|
||||
if (toDeleteRecord == null) { return; }
|
||||
String query = THREAD_ID + " = ?";
|
||||
Cursor threadMmsCursor = rawQuery(query, new String[]{String.valueOf(toDeleteRecord.getThreadId())});
|
||||
Reader reader = readerFor(threadMmsCursor);
|
||||
|
@ -1,13 +1,11 @@
|
||||
package org.thoughtcrime.securesms.loki.database
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import net.sqlcipher.Cursor
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.thoughtcrime.securesms.database.Database
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
|
||||
class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
|
||||
|
||||
@ -35,7 +33,7 @@ class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Da
|
||||
|
||||
fun getContactWithSessionID(sessionID: String): Contact? {
|
||||
val database = databaseHelper.readableDatabase
|
||||
return database.get(sessionContactTable, "${SessionContactDatabase.sessionID} = ?", arrayOf( sessionID )) { cursor ->
|
||||
return database.get(sessionContactTable, "${Companion.sessionID} = ?", arrayOf( sessionID )) { cursor ->
|
||||
contactFromCursor(cursor)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.database
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
@ -6,10 +6,10 @@ import net.sqlcipher.Cursor
|
||||
import org.session.libsession.messaging.jobs.*
|
||||
import org.session.libsession.messaging.utilities.Data
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.database.Database
|
||||
import org.thoughtcrime.securesms.database.*
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
|
||||
class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
|
||||
|
@ -26,13 +26,11 @@ import org.session.libsignal.messages.SignalServiceGroup
|
||||
import org.session.libsignal.utilities.KeyHelper
|
||||
import org.session.libsignal.utilities.guava.Optional
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
|
||||
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase
|
||||
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol
|
||||
import org.thoughtcrime.securesms.loki.utilities.get
|
||||
import org.thoughtcrime.securesms.loki.utilities.getString
|
||||
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.util.SessionMetaProtocol
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
|
||||
class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol {
|
||||
|
@ -49,7 +49,7 @@ import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
|
||||
import org.thoughtcrime.securesms.util.SessionMetaProtocol;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
|
||||
|
@ -23,14 +23,13 @@ import org.thoughtcrime.securesms.database.SearchDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.SessionContactDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.SessionJobDatabase;
|
||||
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsMigration;
|
||||
import org.thoughtcrime.securesms.database.LokiAPIDatabase;
|
||||
import org.thoughtcrime.securesms.database.LokiBackupFilesDatabase;
|
||||
import org.thoughtcrime.securesms.database.LokiMessageDatabase;
|
||||
import org.thoughtcrime.securesms.database.LokiThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.LokiUserDatabase;
|
||||
import org.thoughtcrime.securesms.database.SessionContactDatabase;
|
||||
import org.thoughtcrime.securesms.database.SessionJobDatabase;
|
||||
|
||||
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
@ -182,8 +181,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
if (oldVersion < lokiV12) {
|
||||
db.execSQL(LokiAPIDatabase.getCreateLastMessageHashValueTable2Command());
|
||||
db.execSQL(ClosedGroupsMigration.getCreateCurrentClosedGroupRatchetTableCommand());
|
||||
db.execSQL(ClosedGroupsMigration.getCreateClosedGroupPrivateKeyTableCommand());
|
||||
}
|
||||
|
||||
if (oldVersion < lokiV13) {
|
||||
@ -193,10 +190,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
if (oldVersion < lokiV14_BACKUP_FILES) {
|
||||
db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand());
|
||||
}
|
||||
|
||||
if (oldVersion < lokiV15) {
|
||||
db.execSQL(ClosedGroupsMigration.getCreateOldClosedGroupRatchetTableCommand());
|
||||
}
|
||||
|
||||
if (oldVersion < lokiV16) {
|
||||
db.execSQL(LokiAPIDatabase.getCreateOpenGroupProfilePictureTableCommand());
|
||||
@ -217,7 +210,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
if (oldVersion < lokiV19) {
|
||||
db.execSQL(LokiAPIDatabase.getCreateClosedGroupEncryptionKeyPairsTable());
|
||||
db.execSQL(LokiAPIDatabase.getCreateClosedGroupPublicKeysTable());
|
||||
ClosedGroupsMigration.INSTANCE.perform(db);
|
||||
db.execSQL("DROP TABLE identities");
|
||||
deleteJobRecords(db, "RetrieveProfileJob");
|
||||
deleteJobRecords(db,
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.dms
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
@ -32,8 +32,8 @@ import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
|
||||
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate
|
||||
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragment
|
||||
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragmentDelegate
|
||||
|
||||
class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
|
||||
private val adapter = CreatePrivateChatActivityAdapter(this)
|
||||
@ -191,6 +191,7 @@ class EnterPublicKeyFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun handleIsKeyboardShowingChanged() {
|
||||
val optionalContentContainer = optionalContentContainer ?: return
|
||||
optionalContentContainer.isVisible = !isKeyboardShowing
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.dialogs
|
||||
package org.thoughtcrime.securesms.groups
|
||||
|
||||
import android.os.Bundle
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.groups
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@ -19,14 +19,15 @@ import org.session.libsession.messaging.sending_receiving.groupSizeLimit
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.DistributionTypes
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.utilities.fadeIn
|
||||
import org.thoughtcrime.securesms.loki.utilities.fadeOut
|
||||
import org.thoughtcrime.securesms.util.fadeIn
|
||||
import org.thoughtcrime.securesms.util.fadeOut
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.contacts.SelectContactsAdapter
|
||||
import org.thoughtcrime.securesms.contacts.SelectContactsLoader
|
||||
|
||||
//TODO Refactor to avoid using kotlinx.android.synthetic
|
||||
class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> {
|
@ -1,10 +1,11 @@
|
||||
package org.thoughtcrime.securesms.loki.viewmodel
|
||||
package org.thoughtcrime.securesms.groups
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||
import org.thoughtcrime.securesms.util.State
|
||||
|
||||
typealias DefaultGroups = List<OpenGroupAPIV2.DefaultGroup>
|
||||
typealias GroupState = State<DefaultGroups>
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.groups
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@ -24,15 +24,15 @@ import org.session.libsession.messaging.sending_receiving.groupSizeLimit
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.dialogs.ClosedGroupEditingOptionsBottomSheet
|
||||
import org.thoughtcrime.securesms.loki.utilities.fadeIn
|
||||
import org.thoughtcrime.securesms.loki.utilities.fadeOut
|
||||
import org.thoughtcrime.securesms.util.fadeIn
|
||||
import org.thoughtcrime.securesms.util.fadeOut
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.ThemeUtil
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import org.thoughtcrime.securesms.contacts.SelectContactsActivity
|
||||
import java.io.IOException
|
||||
|
||||
class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.groups
|
||||
|
||||
import android.content.Context
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
@ -1,10 +1,10 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.groups
|
||||
|
||||
import android.content.Context
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.ViewGroup
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.thoughtcrime.securesms.loki.views.UserView
|
||||
import org.thoughtcrime.securesms.contacts.UserView
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.groups
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
@ -25,22 +25,20 @@ import network.loki.messenger.R
|
||||
import okhttp3.HttpUrl
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2.DefaultGroup
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.DistributionTypes
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.PublicKeyValidation
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.groups.DefaultGroupsViewModel
|
||||
import org.thoughtcrime.securesms.groups.GroupManager
|
||||
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
|
||||
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate
|
||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
||||
import org.thoughtcrime.securesms.loki.viewmodel.DefaultGroupsViewModel
|
||||
import org.thoughtcrime.securesms.loki.viewmodel.State
|
||||
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragment
|
||||
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragmentDelegate
|
||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||
import org.thoughtcrime.securesms.util.State
|
||||
import java.util.*
|
||||
|
||||
class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
|
||||
@ -108,7 +106,7 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
|
||||
} else {
|
||||
throw Exception("No longer supported.")
|
||||
}
|
||||
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this@JoinPublicChatActivity)
|
||||
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@JoinPublicChatActivity)
|
||||
withContext(Dispatchers.Main) {
|
||||
val recipient = Recipient.from(this@JoinPublicChatActivity, Address.fromSerialized(groupID), false)
|
||||
openConversationActivity(this@JoinPublicChatActivity, threadID, recipient)
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.groups
|
||||
|
||||
import android.os.Bundle
|
||||
import kotlinx.android.synthetic.main.activity_open_group_guidelines.*
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.api
|
||||
package org.thoughtcrime.securesms.groups
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
@ -11,7 +11,6 @@ import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPolle
|
||||
import org.session.libsession.utilities.Util
|
||||
import org.session.libsignal.utilities.ThreadUtils
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.groups.GroupManager
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@ -116,6 +115,7 @@ object OpenGroupManager {
|
||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
lokiThreadDB.removeOpenGroupChat(threadID)
|
||||
ThreadUtils.queue {
|
||||
threadDB.deleteConversation(threadID) // Must be invoked on a background thread
|
||||
GroupManager.deleteGroup(groupID, context) // Must be invoked on a background thread
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.utilities
|
||||
package org.thoughtcrime.securesms.groups
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.dialogs
|
||||
package org.thoughtcrime.securesms.home
|
||||
|
||||
import android.os.Bundle
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
package org.thoughtcrime.securesms.home
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
@ -15,8 +15,8 @@ import kotlinx.android.synthetic.main.view_conversation.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded
|
||||
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities.highlightMentions
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import java.util.*
|
||||
@ -58,7 +58,7 @@ class ConversationView : LinearLayout {
|
||||
profilePictureView.update(thread.recipient, thread.threadId)
|
||||
val senderDisplayName = getUserDisplayName(thread.recipient) ?: thread.recipient.address.toString()
|
||||
conversationViewDisplayNameTextView.text = senderDisplayName
|
||||
timestampTextView.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), thread.date)
|
||||
timestampTextView.text = DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), thread.date)
|
||||
muteIndicatorImageView.visibility = if (thread.recipient.isMuted) VISIBLE else GONE
|
||||
val rawSnippet = thread.getDisplayBody(context)
|
||||
val snippet = highlightMentions(rawSnippet, thread.threadId, context)
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.home
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.BroadcastReceiver
|
||||
@ -29,28 +29,31 @@ import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.mentions.MentionsManager
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPollerV2
|
||||
import org.session.libsession.utilities.*
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import org.session.libsession.utilities.Util
|
||||
import org.session.libsignal.utilities.ThreadUtils
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.loki.dialogs.*
|
||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.thoughtcrime.securesms.loki.views.ConversationView
|
||||
import org.thoughtcrime.securesms.loki.views.NewConversationButtonSetViewDelegate
|
||||
import org.thoughtcrime.securesms.loki.views.SeedReminderViewDelegate
|
||||
import org.thoughtcrime.securesms.dms.CreatePrivateChatActivity
|
||||
import org.thoughtcrime.securesms.groups.CreateClosedGroupActivity
|
||||
import org.thoughtcrime.securesms.groups.JoinPublicChatActivity
|
||||
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.onboarding.SeedActivity
|
||||
import org.thoughtcrime.securesms.preferences.SettingsActivity
|
||||
import org.thoughtcrime.securesms.util.IP2Country
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickListener, SeedReminderViewDelegate, NewConversationButtonSetViewDelegate {
|
||||
private lateinit var glide: GlideRequests
|
||||
@ -156,8 +159,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (TextSecurePreferences.getLocalNumber(this) == null) {
|
||||
return; } // This can be the case after a secondary device is auto-cleared
|
||||
if (TextSecurePreferences.getLocalNumber(this) == null) { return; } // This can be the case after a secondary device is auto-cleared
|
||||
IdentityKeyUtil.checkUpdate(this)
|
||||
profileButton.recycle() // clear cached image before update tje profilePictureView
|
||||
profileButton.update()
|
||||
val hasViewedSeed = TextSecurePreferences.getHasViewedSeed(this)
|
||||
@ -166,7 +169,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis
|
||||
}
|
||||
if (TextSecurePreferences.getConfigurationMessageSynced(this)) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
MultiDeviceProtocol.syncConfigurationIfNeeded(this@HomeActivity)
|
||||
ConfigurationMessageUtilities.syncConfigurationIfNeeded(this@HomeActivity)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.home
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
@ -7,7 +7,6 @@ import android.view.ViewGroup
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||
import org.thoughtcrime.securesms.loki.views.ConversationView
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
|
||||
class HomeAdapter(context: Context, cursor: Cursor) : CursorRecyclerViewAdapter<HomeAdapter.ViewHolder>(context, cursor) {
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.home
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
package org.thoughtcrime.securesms.home
|
||||
|
||||
import android.animation.FloatEvaluator
|
||||
import android.animation.PointFEvaluator
|
||||
@ -16,7 +16,9 @@ import android.widget.RelativeLayout
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
import org.thoughtcrime.securesms.util.GlowViewUtilities
|
||||
import org.thoughtcrime.securesms.util.NewConversationButtonImageView
|
||||
|
||||
class NewConversationButtonSetView : RelativeLayout {
|
||||
private var expandedButton: Button? = null
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
package org.thoughtcrime.securesms.home
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
@ -22,9 +22,10 @@ import network.loki.messenger.R
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsignal.utilities.Snode
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
|
||||
import org.thoughtcrime.securesms.loki.views.PathDotView
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
import org.thoughtcrime.securesms.util.GlowViewUtilities
|
||||
import org.thoughtcrime.securesms.util.IP2Country
|
||||
import org.thoughtcrime.securesms.util.PathDotView
|
||||
|
||||
class PathActivity : PassphraseRequiredActionBarActivity() {
|
||||
private val broadcastReceivers = mutableListOf<BroadcastReceiver>()
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
package org.thoughtcrime.securesms.home
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
@ -12,8 +12,8 @@ import android.view.View
|
||||
import androidx.annotation.ColorInt
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.util.getColorWithID
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
|
||||
class PathStatusView : View {
|
||||
private val broadcastReceivers = mutableListOf<BroadcastReceiver>()
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.dialogs
|
||||
package org.thoughtcrime.securesms.home
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipData
|
||||
@ -17,7 +17,6 @@ import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsession.utilities.SSKEnvironment
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
|
@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
|
||||
import org.thoughtcrime.securesms.loki.api.PrepareAttachmentAudioExtrasJob;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -6,7 +6,7 @@ import org.session.libsession.messaging.utilities.Data;
|
||||
import org.session.libsignal.utilities.NoExternalStorageException;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.thoughtcrime.securesms.loki.database.BackupFileRecord;
|
||||
import org.thoughtcrime.securesms.database.BackupFileRecord;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
||||
import org.thoughtcrime.securesms.util.BackupUtil;
|
||||
|
@ -1,9 +1,7 @@
|
||||
package org.thoughtcrime.securesms.loki.api
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import android.media.MediaDataSource
|
||||
import android.os.Build
|
||||
import org.session.libsignal.utilities.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.session.libsession.messaging.utilities.Data
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment
|
||||
@ -13,9 +11,7 @@ import org.session.libsession.utilities.DecodedAudio
|
||||
import org.session.libsession.utilities.InputStreamMediaDataSource
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobs.BaseJob
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import java.io.InputStream
|
||||
import java.lang.IllegalStateException
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
@ -1,71 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki.api
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.*
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
|
||||
|
||||
/**
|
||||
* Delegates the [OpenGroupUtilities.updateGroupInfo] call to the work manager.
|
||||
*/
|
||||
class PublicChatInfoUpdateWorker(val context: Context, params: WorkerParameters) : Worker(context, params) {
|
||||
|
||||
companion object {
|
||||
const val TAG = "PublicChatInfoUpdateWorker"
|
||||
|
||||
private const val DATA_KEY_SERVER_URL = "server_uRL"
|
||||
private const val DATA_KEY_CHANNEL = "channel"
|
||||
private const val DATA_KEY_ROOM = "room"
|
||||
|
||||
@JvmStatic
|
||||
fun scheduleInstant(context: Context, serverUrl: String, room :String) {
|
||||
val workRequest = OneTimeWorkRequestBuilder<PublicChatInfoUpdateWorker>()
|
||||
.setConstraints(Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
)
|
||||
.setInputData(workDataOf(
|
||||
DATA_KEY_SERVER_URL to serverUrl,
|
||||
DATA_KEY_ROOM to room
|
||||
))
|
||||
.build()
|
||||
|
||||
WorkManager
|
||||
.getInstance(context)
|
||||
.enqueue(workRequest)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun scheduleInstant(context: Context, serverURL: String, channel: Long) {
|
||||
val workRequest = OneTimeWorkRequestBuilder<PublicChatInfoUpdateWorker>()
|
||||
.setConstraints(Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
)
|
||||
.setInputData(workDataOf(
|
||||
DATA_KEY_SERVER_URL to serverURL,
|
||||
DATA_KEY_CHANNEL to channel
|
||||
))
|
||||
.build()
|
||||
|
||||
WorkManager
|
||||
.getInstance(context)
|
||||
.enqueue(workRequest)
|
||||
}
|
||||
}
|
||||
|
||||
override fun doWork(): Result {
|
||||
val serverUrl = inputData.getString(DATA_KEY_SERVER_URL)!!
|
||||
val room = inputData.getString(DATA_KEY_ROOM)
|
||||
val openGroupId = "$serverUrl.$room"
|
||||
return try {
|
||||
Log.v(TAG, "Updating open group info for $openGroupId.")
|
||||
OpenGroupUtilities.updateGroupInfo(context, serverUrl, room!!)
|
||||
Log.v(TAG, "Open group info was successfully updated for $openGroupId.")
|
||||
Result.success()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to update open group info for $openGroupId", e)
|
||||
Result.failure()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki.dialogs
|
||||
|
||||
import android.os.Bundle
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.fragment_device_list_bottom_sheet.*
|
||||
import network.loki.messenger.R
|
||||
|
||||
public class DeviceEditingOptionsBottomSheet : BottomSheetDialogFragment() {
|
||||
var onEditTapped: (() -> Unit)? = null
|
||||
var onUnlinkTapped: (() -> Unit)? = null
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_device_list_bottom_sheet, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
editDisplayNameText.setOnClickListener { onEditTapped?.invoke() }
|
||||
unlinkDeviceText.setOnClickListener { onUnlinkTapped?.invoke() }
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki.protocol
|
||||
|
||||
import android.content.ContentValues
|
||||
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase
|
||||
import org.thoughtcrime.securesms.loki.utilities.get
|
||||
import org.thoughtcrime.securesms.loki.utilities.getAll
|
||||
import org.thoughtcrime.securesms.loki.utilities.getString
|
||||
import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate
|
||||
import org.session.libsignal.utilities.Hex
|
||||
import org.session.libsignal.crypto.ecc.DjbECPrivateKey
|
||||
import org.session.libsignal.crypto.ecc.DjbECPublicKey
|
||||
import org.session.libsignal.crypto.ecc.ECKeyPair
|
||||
import org.session.libsignal.utilities.PublicKeyValidation
|
||||
import org.session.libsignal.utilities.removing05PrefixIfNeeded
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import java.util.*
|
||||
|
||||
object ClosedGroupsMigration {
|
||||
|
||||
public val closedGroupPublicKey = "closed_group_public_key"
|
||||
// Ratchets
|
||||
private val oldClosedGroupRatchetTable = "old_closed_group_ratchet_table"
|
||||
private val currentClosedGroupRatchetTable = "closed_group_ratchet_table"
|
||||
private val senderPublicKey = "sender_public_key"
|
||||
private val chainKey = "chain_key"
|
||||
private val keyIndex = "key_index"
|
||||
private val messageKeys = "message_keys"
|
||||
@JvmStatic val createOldClosedGroupRatchetTableCommand
|
||||
= "CREATE TABLE $oldClosedGroupRatchetTable ($closedGroupPublicKey STRING, $senderPublicKey STRING, $chainKey STRING, " +
|
||||
"$keyIndex INTEGER DEFAULT 0, $messageKeys TEXT, PRIMARY KEY ($closedGroupPublicKey, $senderPublicKey));"
|
||||
// Private keys
|
||||
@JvmStatic val createCurrentClosedGroupRatchetTableCommand
|
||||
= "CREATE TABLE $currentClosedGroupRatchetTable ($closedGroupPublicKey STRING, $senderPublicKey STRING, $chainKey STRING, " +
|
||||
"$keyIndex INTEGER DEFAULT 0, $messageKeys TEXT, PRIMARY KEY ($closedGroupPublicKey, $senderPublicKey));"
|
||||
// Private keys
|
||||
public val closedGroupPrivateKeyTable = "closed_group_private_key_table"
|
||||
public val closedGroupPrivateKey = "closed_group_private_key"
|
||||
@JvmStatic val createClosedGroupPrivateKeyTableCommand
|
||||
= "CREATE TABLE $closedGroupPrivateKeyTable ($closedGroupPublicKey STRING PRIMARY KEY, $closedGroupPrivateKey STRING);"
|
||||
|
||||
|
||||
fun perform(database: net.sqlcipher.database.SQLiteDatabase) {
|
||||
val publicKeys = database.getAll(closedGroupPrivateKeyTable, null, null) { cursor ->
|
||||
cursor.getString(closedGroupPublicKey)
|
||||
}.filter {
|
||||
PublicKeyValidation.isValid(it)
|
||||
}
|
||||
val keyPairs = mutableListOf<ECKeyPair>()
|
||||
for (publicKey in publicKeys) {
|
||||
val query = "${closedGroupPublicKey} = ?"
|
||||
val privateKey = database.get(closedGroupPrivateKeyTable, query, arrayOf( publicKey )) { cursor ->
|
||||
cursor.getString(closedGroupPrivateKey)
|
||||
}
|
||||
val keyPair = ECKeyPair(DjbECPublicKey(Hex.fromStringCondensed(publicKey.removing05PrefixIfNeeded())), DjbECPrivateKey(Hex.fromStringCondensed(privateKey)))
|
||||
keyPairs.add(keyPair)
|
||||
val row = ContentValues(1)
|
||||
row.put(LokiAPIDatabase.groupPublicKey, publicKey)
|
||||
database.insertOrUpdate(LokiAPIDatabase.closedGroupPublicKeysTable, row, "${LokiAPIDatabase.groupPublicKey} = ?", arrayOf( publicKey ))
|
||||
}
|
||||
for (keyPair in keyPairs) {
|
||||
// In this particular case keyPair.publicKey == groupPublicKey
|
||||
val timestamp = Date().time.toString()
|
||||
val index = "${keyPair.publicKey.serialize().toHexString()}-$timestamp"
|
||||
val encryptionKeyPairPublicKey = keyPair.publicKey.serialize().toHexString().removing05PrefixIfNeeded()
|
||||
val encryptionKeyPairPrivateKey = keyPair.privateKey.serialize().toHexString()
|
||||
val row = ContentValues(3)
|
||||
row.put(LokiAPIDatabase.closedGroupsEncryptionKeyPairIndex, index)
|
||||
row.put(LokiAPIDatabase.encryptionKeyPairPublicKey, encryptionKeyPairPublicKey)
|
||||
row.put(LokiAPIDatabase.encryptionKeyPairPrivateKey, encryptionKeyPairPrivateKey)
|
||||
database.insertOrUpdate(LokiAPIDatabase.closedGroupEncryptionKeyPairsTable, row, "${LokiAPIDatabase.closedGroupsEncryptionKeyPairIndex} = ?", arrayOf( index ))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,392 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki.protocol
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.google.protobuf.ByteString
|
||||
import org.session.libsession.messaging.sending_receiving.*
|
||||
import org.session.libsignal.crypto.ecc.DjbECPrivateKey
|
||||
import org.session.libsignal.crypto.ecc.DjbECPublicKey
|
||||
import org.session.libsignal.crypto.ecc.ECKeyPair
|
||||
import org.session.libsignal.messages.SignalServiceGroup
|
||||
import org.session.libsignal.protos.SignalServiceProtos
|
||||
import org.session.libsignal.protos.SignalServiceProtos.DataMessage
|
||||
import org.session.libsignal.utilities.removing05PrefixIfNeeded
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase
|
||||
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager
|
||||
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager.ClosedGroupOperation
|
||||
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase
|
||||
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.GroupRecord
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
|
||||
import java.util.*
|
||||
|
||||
object ClosedGroupsProtocolV2 {
|
||||
|
||||
@JvmStatic
|
||||
fun handleMessage(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
|
||||
if (!isValid(context, closedGroupUpdate, senderPublicKey, sentTimestamp)) { return }
|
||||
when (closedGroupUpdate.type) {
|
||||
DataMessage.ClosedGroupControlMessage.Type.NEW -> handleNewClosedGroup(context, closedGroupUpdate, senderPublicKey, sentTimestamp)
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBERS_REMOVED -> handleClosedGroupMembersRemoved(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED -> handleClosedGroupMembersAdded(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||
DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -> handleClosedGroupNameChange(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> handleClosedGroupMemberLeft(context, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||
DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> handleGroupEncryptionKeyPair(context, closedGroupUpdate, groupPublicKey, senderPublicKey)
|
||||
else -> {
|
||||
Log.d("Loki","Can't handle closed group update of unknown type: ${closedGroupUpdate.type}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isValid(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, senderPublicKey: String, sentTimestamp: Long): Boolean {
|
||||
val record = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(sentTimestamp, senderPublicKey)
|
||||
if (record != null) return false
|
||||
|
||||
return when (closedGroupUpdate.type) {
|
||||
DataMessage.ClosedGroupControlMessage.Type.NEW -> {
|
||||
(!closedGroupUpdate.publicKey.isEmpty && !closedGroupUpdate.name.isNullOrEmpty() && !(closedGroupUpdate.encryptionKeyPair.privateKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty
|
||||
&& !(closedGroupUpdate.encryptionKeyPair.publicKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty && closedGroupUpdate.membersCount > 0 && closedGroupUpdate.adminsCount > 0)
|
||||
}
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED,
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBERS_REMOVED -> {
|
||||
closedGroupUpdate.membersCount > 0
|
||||
}
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> {
|
||||
senderPublicKey.isNotEmpty()
|
||||
}
|
||||
DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -> {
|
||||
!closedGroupUpdate.name.isNullOrEmpty()
|
||||
}
|
||||
DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
public fun handleNewClosedGroup(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, senderPublicKey: String, sentTimestamp: Long) {
|
||||
// Prepare
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
// Unwrap the message
|
||||
val groupPublicKey = closedGroupUpdate.publicKey.toByteArray().toHexString()
|
||||
val name = closedGroupUpdate.name
|
||||
val encryptionKeyPairAsProto = closedGroupUpdate.encryptionKeyPair
|
||||
val members = closedGroupUpdate.membersList.map { it.toByteArray().toHexString() }
|
||||
val admins = closedGroupUpdate.adminsList.map { it.toByteArray().toHexString() }
|
||||
// Create the group
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||
val prevGroup = groupDB.getGroup(groupID).orNull()
|
||||
if (prevGroup != null) {
|
||||
// Update the group
|
||||
groupDB.updateTitle(groupID, name)
|
||||
groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) })
|
||||
} else {
|
||||
groupDB.create(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }),
|
||||
null, null, LinkedList(admins.map { Address.fromSerialized(it) }), sentTimestamp)
|
||||
}
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.from(context, Address.fromSerialized(groupID), false), true)
|
||||
// Add the group to the user's set of public keys to poll for
|
||||
apiDB.addClosedGroupPublicKey(groupPublicKey)
|
||||
// Store the encryption key pair
|
||||
val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray().removing05PrefixIfNeeded()), DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray()))
|
||||
apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey)
|
||||
// Notify the user (if we didn't make the group)
|
||||
if (userPublicKey != senderPublicKey) {
|
||||
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp)
|
||||
} else if (prevGroup == null) {
|
||||
// only notify if we created this group
|
||||
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
||||
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, threadID, sentTimestamp)
|
||||
}
|
||||
// Notify the PN server
|
||||
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
||||
}
|
||||
|
||||
fun handleClosedGroupMembersRemoved(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
|
||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = groupDB.getGroup(groupID).orNull()
|
||||
if (group == null || !group.isActive) {
|
||||
Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.")
|
||||
return
|
||||
}
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||
val name = group.title
|
||||
// Check common group update logic
|
||||
val members = group.members.map { it.serialize() }
|
||||
val admins = group.admins.map { it.toString() }
|
||||
|
||||
// Users that are part of this remove update
|
||||
val updateMembers = closedGroupUpdate.membersList.map { it.toByteArray().toHexString() }
|
||||
|
||||
if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) {
|
||||
return
|
||||
}
|
||||
// If admin leaves the group is disbanded
|
||||
val didAdminLeave = admins.any { it in updateMembers }
|
||||
// newMembers to save is old members minus removed members
|
||||
val newMembers = members - updateMembers
|
||||
// user should be posting MEMBERS_LEFT so this should not be encountered
|
||||
val senderLeft = senderPublicKey in updateMembers
|
||||
if (senderLeft) {
|
||||
Log.d("Loki", "Received a MEMBERS_REMOVED instead of a MEMBERS_LEFT from sender $senderPublicKey")
|
||||
}
|
||||
val wasCurrentUserRemoved = userPublicKey in updateMembers
|
||||
|
||||
// admin should send a MEMBERS_LEFT message but handled here in case
|
||||
if (didAdminLeave || wasCurrentUserRemoved) {
|
||||
disableLocalGroupAndUnsubscribe(context, apiDB, groupPublicKey, groupDB, groupID, userPublicKey)
|
||||
} else {
|
||||
val isCurrentUserAdmin = admins.contains(userPublicKey)
|
||||
groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
|
||||
if (isCurrentUserAdmin) {
|
||||
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers)
|
||||
}
|
||||
}
|
||||
val type =
|
||||
if (senderLeft) SignalServiceGroup.Type.QUIT
|
||||
else SignalServiceGroup.Type.UPDATE
|
||||
if (userPublicKey == senderPublicKey) {
|
||||
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
||||
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, type, name, members, admins, threadID, sentTimestamp)
|
||||
} else {
|
||||
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, members, admins, sentTimestamp)
|
||||
}
|
||||
}
|
||||
|
||||
fun handleClosedGroupMembersAdded(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = groupDB.getGroup(groupID).orNull()
|
||||
if (group == null || !group.isActive) {
|
||||
Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.")
|
||||
return
|
||||
}
|
||||
// Check common group update logic
|
||||
if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) {
|
||||
return
|
||||
}
|
||||
val name = group.title
|
||||
val members = group.members.map { it.serialize() }
|
||||
val admins = group.admins.map { it.serialize() }
|
||||
// Users that are part of this add update
|
||||
val updateMembers = closedGroupUpdate.membersList.map { it.toByteArray().toHexString() }
|
||||
// newMembers to save is old members plus members included in this update
|
||||
val newMembers = members + updateMembers
|
||||
groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
|
||||
if (userPublicKey == senderPublicKey) {
|
||||
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
||||
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, threadID, sentTimestamp)
|
||||
} else {
|
||||
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp)
|
||||
}
|
||||
if (userPublicKey in admins) {
|
||||
// send current encryption key to the latest added members
|
||||
val encryptionKeyPair = pendingKeyPairs[groupPublicKey]?.orNull()
|
||||
?: apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey)
|
||||
if (encryptionKeyPair == null) {
|
||||
Log.d("Loki", "Couldn't get encryption key pair for closed group.")
|
||||
} else {
|
||||
for (user in updateMembers) {
|
||||
MessageSender.sendEncryptionKeyPair(groupPublicKey, encryptionKeyPair, setOf(user), targetUser = user, force = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleClosedGroupNameChange(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
|
||||
// Check that the sender is a member of the group (before the update)
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = groupDB.getGroup(groupID).orNull()
|
||||
if (group == null || !group.isActive) {
|
||||
Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.")
|
||||
return
|
||||
}
|
||||
// Check common group update logic
|
||||
if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) {
|
||||
return
|
||||
}
|
||||
val members = group.members.map { it.serialize() }
|
||||
val admins = group.admins.map { it.serialize() }
|
||||
val name = closedGroupUpdate.name
|
||||
groupDB.updateTitle(groupID, name)
|
||||
// Notify the user
|
||||
if (userPublicKey == senderPublicKey) {
|
||||
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
||||
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, threadID, sentTimestamp)
|
||||
} else {
|
||||
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleClosedGroupMemberLeft(context: Context, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
|
||||
// Check the user leaving isn't us, will already be handled
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = groupDB.getGroup(groupID).orNull()
|
||||
if (group == null || !group.isActive) {
|
||||
Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.")
|
||||
return
|
||||
}
|
||||
val name = group.title
|
||||
// Check common group update logic
|
||||
val members = group.members.map { it.serialize() }
|
||||
val admins = group.admins.map { it.toString() }
|
||||
if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) {
|
||||
return
|
||||
}
|
||||
// If the admin leaves the group is disbanded
|
||||
val didAdminLeave = admins.contains(senderPublicKey)
|
||||
val updatedMemberList = members - senderPublicKey
|
||||
val userLeft = (userPublicKey == senderPublicKey)
|
||||
|
||||
// if the admin left, we left, or we are the only remaining member: remove the group
|
||||
if (didAdminLeave || userLeft) {
|
||||
disableLocalGroupAndUnsubscribe(context, apiDB, groupPublicKey, groupDB, groupID, userPublicKey)
|
||||
} else {
|
||||
val isCurrentUserAdmin = admins.contains(userPublicKey)
|
||||
groupDB.updateMembers(groupID, updatedMemberList.map { Address.fromSerialized(it) })
|
||||
if (isCurrentUserAdmin) {
|
||||
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMemberList)
|
||||
}
|
||||
}
|
||||
// Notify user
|
||||
if (userLeft) {
|
||||
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
||||
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.QUIT, name, members, admins, threadID, sentTimestamp)
|
||||
} else {
|
||||
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.QUIT, name, members, admins, sentTimestamp)
|
||||
}
|
||||
}
|
||||
|
||||
private fun disableLocalGroupAndUnsubscribe(context: Context, apiDB: LokiAPIDatabase, groupPublicKey: String, groupDB: GroupDatabase, groupID: String, userPublicKey: String) {
|
||||
apiDB.removeClosedGroupPublicKey(groupPublicKey)
|
||||
// Remove the key pairs
|
||||
apiDB.removeAllClosedGroupEncryptionKeyPairs(groupPublicKey)
|
||||
// Mark the group as inactive
|
||||
groupDB.setActive(groupID, false)
|
||||
groupDB.removeMember(groupID, Address.fromSerialized(userPublicKey))
|
||||
// Notify the PN server
|
||||
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey)
|
||||
}
|
||||
|
||||
private fun isValidGroupUpdate(group: GroupRecord,
|
||||
sentTimestamp: Long,
|
||||
senderPublicKey: String): Boolean {
|
||||
val oldMembers = group.members.map { it.serialize() }
|
||||
// Check that the message isn't from before the group was created
|
||||
// TODO: We should check that formationTimestamp is the sent timestamp of the closed group update that created the group
|
||||
if (group.formationTimestamp > sentTimestamp) {
|
||||
Log.d("Loki", "Ignoring closed group update from before thread was created.")
|
||||
return false
|
||||
}
|
||||
// Check that the sender is a member of the group (before the update)
|
||||
if (senderPublicKey !in oldMembers) {
|
||||
Log.d("Loki", "Ignoring closed group info message from non-member.")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun handleGroupEncryptionKeyPair(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, groupPublicKey: String, senderPublicKey: String) {
|
||||
// Prepare
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
val userKeyPair = apiDB.getUserX25519KeyPair()
|
||||
// Unwrap the message
|
||||
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||
val groupPublicKeyToUse = when {
|
||||
groupPublicKey.isNotEmpty() -> groupPublicKey
|
||||
!closedGroupUpdate.publicKey.isEmpty -> closedGroupUpdate.publicKey.toByteArray().toHexString()
|
||||
else -> ""
|
||||
}
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKeyToUse)
|
||||
val group = groupDB.getGroup(groupID).orNull()
|
||||
if (group == null) {
|
||||
Log.d("Loki", "Ignoring closed group encryption key pair message for nonexistent group.")
|
||||
return
|
||||
}
|
||||
if (!group.admins.map { it.toString() }.contains(senderPublicKey)) {
|
||||
Log.d("Loki", "Ignoring closed group encryption key pair from non-admin.")
|
||||
return
|
||||
}
|
||||
// Find our wrapper and decrypt it if possible
|
||||
val wrapper = closedGroupUpdate.wrappersList.firstOrNull { it.publicKey.toByteArray().toHexString() == userPublicKey } ?: return
|
||||
val encryptedKeyPair = wrapper.encryptedKeyPair.toByteArray()
|
||||
val plaintext = MessageDecrypter.decrypt(encryptedKeyPair, userKeyPair).first
|
||||
// Parse it
|
||||
val proto = SignalServiceProtos.KeyPair.parseFrom(plaintext)
|
||||
val keyPair = ECKeyPair(DjbECPublicKey(proto.publicKey.toByteArray().removing05PrefixIfNeeded()), DjbECPrivateKey(proto.privateKey.toByteArray()))
|
||||
// Store it
|
||||
apiDB.addClosedGroupEncryptionKeyPair(keyPair, groupPublicKeyToUse)
|
||||
Log.d("Loki", "Received a new closed group encryption key pair")
|
||||
}
|
||||
|
||||
// region Deprecated
|
||||
private fun handleClosedGroupUpdate(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
|
||||
// Prepare
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
// Unwrap the message
|
||||
val name = closedGroupUpdate.name
|
||||
val members = closedGroupUpdate.membersList.map { it.toByteArray().toHexString() }
|
||||
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = groupDB.getGroup(groupID).orNull()
|
||||
if (group == null || !group.isActive) {
|
||||
Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.")
|
||||
return
|
||||
}
|
||||
val oldMembers = group.members.map { it.serialize() }
|
||||
// Check common group update logic
|
||||
if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) {
|
||||
return
|
||||
}
|
||||
// Check that the admin wasn't removed unless the group was destroyed entirely
|
||||
if (!members.contains(group.admins.first().toString()) && members.isNotEmpty()) {
|
||||
Log.d("Loki", "Ignoring invalid closed group update message.")
|
||||
return
|
||||
}
|
||||
// Remove the group from the user's set of public keys to poll for if the current user was removed
|
||||
val wasCurrentUserRemoved = !members.contains(userPublicKey)
|
||||
if (wasCurrentUserRemoved) {
|
||||
disableLocalGroupAndUnsubscribe(context, apiDB, groupPublicKey, groupDB, groupID, userPublicKey)
|
||||
}
|
||||
// Generate and distribute a new encryption key pair if needed
|
||||
val wasAnyUserRemoved = (members.toSet().intersect(oldMembers) != oldMembers.toSet())
|
||||
val isCurrentUserAdmin = group.admins.map { it.toString() }.contains(userPublicKey)
|
||||
if (wasAnyUserRemoved && isCurrentUserAdmin) {
|
||||
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, members)
|
||||
}
|
||||
// Update the group
|
||||
groupDB.updateTitle(groupID, name)
|
||||
if (!wasCurrentUserRemoved) {
|
||||
// The call below sets isActive to true, so if the user is leaving we have to use groupDB.remove(...) instead
|
||||
groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) })
|
||||
}
|
||||
// Notify the user
|
||||
val wasSenderRemoved = !members.contains(senderPublicKey)
|
||||
val type = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE
|
||||
val admins = group.admins.map { it.toString() }
|
||||
if (userPublicKey == senderPublicKey) {
|
||||
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID)
|
||||
DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, type, name, members, admins, threadID, sentTimestamp)
|
||||
} else {
|
||||
DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, members, admins, sentTimestamp)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
@ -1,335 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.drawable.AnimatedVectorDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.View.OnTouchListener
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlinx.coroutines.*
|
||||
import network.loki.messenger.R
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer
|
||||
import org.thoughtcrime.securesms.components.AnimatingToggle
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.events.PartProgressEvent
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.loki.api.PrepareAttachmentAudioExtrasJob
|
||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AudioViewKt"
|
||||
}
|
||||
|
||||
private val controlToggle: AnimatingToggle
|
||||
private val container: ViewGroup
|
||||
private val playButton: ImageView
|
||||
private val pauseButton: ImageView
|
||||
private val downloadButton: ImageView
|
||||
private val downloadProgress: ProgressBar
|
||||
private val seekBar: WaveformSeekBar
|
||||
private val totalDuration: TextView
|
||||
|
||||
private var downloadListener: SlideClickListener? = null
|
||||
private var audioSlidePlayer: AudioSlidePlayer? = null
|
||||
|
||||
/** Background coroutine scope that is available when the view is attached to a window. */
|
||||
private var asyncCoroutineScope: CoroutineScope? = null
|
||||
|
||||
private val loadingAnimation: SeekBarLoadingAnimation
|
||||
|
||||
constructor(context: Context): this(context, null)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr) {
|
||||
View.inflate(context, R.layout.message_audio_view, this)
|
||||
container = findViewById(R.id.audio_widget_container)
|
||||
controlToggle = findViewById(R.id.control_toggle)
|
||||
playButton = findViewById(R.id.play)
|
||||
pauseButton = findViewById(R.id.pause)
|
||||
downloadButton = findViewById(R.id.download)
|
||||
downloadProgress = findViewById(R.id.download_progress)
|
||||
seekBar = findViewById(R.id.seek)
|
||||
totalDuration = findViewById(R.id.total_duration)
|
||||
|
||||
playButton.setOnClickListener {
|
||||
try {
|
||||
Log.d(TAG, "playbutton onClick")
|
||||
if (audioSlidePlayer != null) {
|
||||
togglePlayToPause()
|
||||
|
||||
// Restart the playback if progress bar is nearly at the end.
|
||||
val progress = if (seekBar.progress < 0.99f) seekBar.progress.toDouble() else 0.0
|
||||
|
||||
audioSlidePlayer!!.play(progress)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
}
|
||||
pauseButton.setOnClickListener {
|
||||
Log.d(TAG, "pausebutton onClick")
|
||||
if (audioSlidePlayer != null) {
|
||||
togglePauseToPlay()
|
||||
audioSlidePlayer!!.stop()
|
||||
}
|
||||
}
|
||||
seekBar.isEnabled = false
|
||||
seekBar.progressChangeListener = object : WaveformSeekBar.ProgressChangeListener {
|
||||
override fun onProgressChanged(waveformSeekBar: WaveformSeekBar, progress: Float, fromUser: Boolean) {
|
||||
if (fromUser && audioSlidePlayer != null) {
|
||||
synchronized(audioSlidePlayer!!) {
|
||||
audioSlidePlayer!!.seekTo(progress.toDouble())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.play_icon))
|
||||
pauseButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.pause_icon))
|
||||
playButton.background = ContextCompat.getDrawable(context, R.drawable.ic_circle_fill_white_48dp)
|
||||
pauseButton.background = ContextCompat.getDrawable(context, R.drawable.ic_circle_fill_white_48dp)
|
||||
|
||||
if (attrs != null) {
|
||||
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.MessageAudioView, 0, 0)
|
||||
setTint(typedArray.getColor(R.styleable.MessageAudioView_foregroundTintColor, Color.WHITE),
|
||||
typedArray.getColor(R.styleable.MessageAudioView_waveformFillColor, Color.WHITE),
|
||||
typedArray.getColor(R.styleable.MessageAudioView_waveformBackgroundColor, Color.WHITE))
|
||||
container.setBackgroundColor(typedArray.getColor(R.styleable.MessageAudioView_widgetBackground, Color.TRANSPARENT))
|
||||
typedArray.recycle()
|
||||
}
|
||||
|
||||
loadingAnimation = SeekBarLoadingAnimation(this, seekBar)
|
||||
loadingAnimation.start()
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
if (!EventBus.getDefault().isRegistered(this)) EventBus.getDefault().register(this)
|
||||
|
||||
asyncCoroutineScope = CoroutineScope(Job() + Dispatchers.IO)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
// Cancel all the background operations.
|
||||
asyncCoroutineScope!!.cancel()
|
||||
asyncCoroutineScope = null
|
||||
}
|
||||
|
||||
fun setAudio(audio: AudioSlide, showControls: Boolean) {
|
||||
when {
|
||||
showControls && audio.isPendingDownload -> {
|
||||
controlToggle.displayQuick(downloadButton)
|
||||
seekBar.isEnabled = false
|
||||
downloadButton.setOnClickListener { v -> downloadListener?.onClick(v, audio) }
|
||||
if (downloadProgress.isIndeterminate) {
|
||||
downloadProgress.isIndeterminate = false
|
||||
downloadProgress.progress = 0
|
||||
}
|
||||
}
|
||||
(showControls && audio.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED) -> {
|
||||
controlToggle.displayQuick(downloadProgress)
|
||||
seekBar.isEnabled = false
|
||||
downloadProgress.isIndeterminate = true
|
||||
}
|
||||
else -> {
|
||||
controlToggle.displayQuick(playButton)
|
||||
seekBar.isEnabled = true
|
||||
if (downloadProgress.isIndeterminate) {
|
||||
downloadProgress.isIndeterminate = false
|
||||
downloadProgress.progress = 100
|
||||
}
|
||||
|
||||
// Post to make sure it executes only when the view is attached to a window.
|
||||
post(::updateFromAttachmentAudioExtras)
|
||||
}
|
||||
}
|
||||
audioSlidePlayer = AudioSlidePlayer.createFor(context, audio, this)
|
||||
}
|
||||
|
||||
fun cleanup() {
|
||||
if (audioSlidePlayer != null && pauseButton.visibility == View.VISIBLE) {
|
||||
audioSlidePlayer!!.stop()
|
||||
}
|
||||
}
|
||||
|
||||
fun setDownloadClickListener(listener: SlideClickListener?) {
|
||||
downloadListener = listener
|
||||
}
|
||||
|
||||
fun setTint(@ColorInt foregroundTint: Int, @ColorInt waveformFill: Int, @ColorInt waveformBackground: Int) {
|
||||
playButton.backgroundTintList = ColorStateList.valueOf(resources.getColorWithID(R.color.white, context.theme))
|
||||
playButton.imageTintList = ColorStateList.valueOf(resources.getColorWithID(R.color.black, context.theme))
|
||||
pauseButton.backgroundTintList = ColorStateList.valueOf(resources.getColorWithID(R.color.white, context.theme))
|
||||
pauseButton.imageTintList = ColorStateList.valueOf(resources.getColorWithID(R.color.black, context.theme))
|
||||
|
||||
downloadButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN)
|
||||
|
||||
downloadProgress.backgroundTintList = ColorStateList.valueOf(resources.getColorWithID(R.color.white, context.theme))
|
||||
downloadProgress.progressTintList = ColorStateList.valueOf(resources.getColorWithID(R.color.black, context.theme))
|
||||
downloadProgress.indeterminateTintList = ColorStateList.valueOf(resources.getColorWithID(R.color.black, context.theme))
|
||||
|
||||
totalDuration.setTextColor(foregroundTint)
|
||||
|
||||
seekBar.barProgressColor = waveformFill
|
||||
seekBar.barBackgroundColor = waveformBackground
|
||||
}
|
||||
|
||||
override fun onPlayerStart(player: AudioSlidePlayer) {
|
||||
if (pauseButton.visibility != View.VISIBLE) {
|
||||
togglePlayToPause()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPlayerStop(player: AudioSlidePlayer) {
|
||||
if (playButton.visibility != View.VISIBLE) {
|
||||
togglePauseToPlay()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPlayerProgress(player: AudioSlidePlayer, progress: Double, millis: Long) {
|
||||
seekBar.progress = progress.toFloat()
|
||||
}
|
||||
|
||||
override fun setFocusable(focusable: Boolean) {
|
||||
super.setFocusable(focusable)
|
||||
playButton.isFocusable = focusable
|
||||
pauseButton.isFocusable = focusable
|
||||
seekBar.isFocusable = focusable
|
||||
seekBar.isFocusableInTouchMode = focusable
|
||||
downloadButton.isFocusable = focusable
|
||||
}
|
||||
|
||||
override fun setClickable(clickable: Boolean) {
|
||||
super.setClickable(clickable)
|
||||
playButton.isClickable = clickable
|
||||
pauseButton.isClickable = clickable
|
||||
seekBar.isClickable = clickable
|
||||
seekBar.setOnTouchListener(if (clickable) null else
|
||||
OnTouchListener { _, _ -> return@OnTouchListener true }) // Suppress touch events.
|
||||
downloadButton.isClickable = clickable
|
||||
}
|
||||
|
||||
override fun setEnabled(enabled: Boolean) {
|
||||
super.setEnabled(enabled)
|
||||
playButton.isEnabled = enabled
|
||||
pauseButton.isEnabled = enabled
|
||||
downloadButton.isEnabled = enabled
|
||||
}
|
||||
|
||||
private fun togglePlayToPause() {
|
||||
controlToggle.displayQuick(pauseButton)
|
||||
val playToPauseDrawable = ContextCompat.getDrawable(context, R.drawable.play_to_pause_animation) as AnimatedVectorDrawable
|
||||
pauseButton.setImageDrawable(playToPauseDrawable)
|
||||
playToPauseDrawable.start()
|
||||
}
|
||||
|
||||
private fun togglePauseToPlay() {
|
||||
controlToggle.displayQuick(playButton)
|
||||
val pauseToPlayDrawable = ContextCompat.getDrawable(context, R.drawable.pause_to_play_animation) as AnimatedVectorDrawable
|
||||
playButton.setImageDrawable(pauseToPlayDrawable)
|
||||
pauseToPlayDrawable.start()
|
||||
}
|
||||
|
||||
private fun obtainDatabaseAttachment(): DatabaseAttachment? {
|
||||
audioSlidePlayer ?: return null
|
||||
val attachment = audioSlidePlayer!!.audioSlide.asAttachment()
|
||||
return if (attachment is DatabaseAttachment) attachment else null
|
||||
}
|
||||
|
||||
private fun updateFromAttachmentAudioExtras() {
|
||||
val attachment = obtainDatabaseAttachment() ?: return
|
||||
|
||||
val audioExtras = DatabaseFactory.getAttachmentDatabase(context)
|
||||
.getAttachmentAudioExtras(attachment.attachmentId)
|
||||
|
||||
// Schedule a job request if no audio extras were generated yet.
|
||||
if (audioExtras == null) {
|
||||
ApplicationContext.getInstance(context).jobManager
|
||||
.add(PrepareAttachmentAudioExtrasJob(attachment.attachmentId))
|
||||
return
|
||||
}
|
||||
|
||||
loadingAnimation.stop()
|
||||
seekBar.sampleData = audioExtras.visualSamples
|
||||
|
||||
if (audioExtras.durationMs > 0) {
|
||||
totalDuration.visibility = View.VISIBLE
|
||||
totalDuration.text = String.format("%02d:%02d",
|
||||
TimeUnit.MILLISECONDS.toMinutes(audioExtras.durationMs),
|
||||
TimeUnit.MILLISECONDS.toSeconds(audioExtras.durationMs))
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
fun onEvent(event: PartProgressEvent) {
|
||||
if (audioSlidePlayer != null && event.attachment == audioSlidePlayer!!.audioSlide.asAttachment()) {
|
||||
val progress = ((event.progress.toFloat() / event.total) * 100f).toInt()
|
||||
downloadProgress.progress = progress
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEvent(event: PrepareAttachmentAudioExtrasJob.AudioExtrasUpdatedEvent) {
|
||||
if (event.attachmentId == obtainDatabaseAttachment()?.attachmentId) {
|
||||
updateFromAttachmentAudioExtras()
|
||||
}
|
||||
}
|
||||
|
||||
private class SeekBarLoadingAnimation(
|
||||
private val hostView: View,
|
||||
private val seekBar: WaveformSeekBar): Runnable {
|
||||
|
||||
private var active = false
|
||||
|
||||
companion object {
|
||||
private const val UPDATE_PERIOD = 250L // In milliseconds.
|
||||
private val random = Random()
|
||||
}
|
||||
|
||||
fun start() {
|
||||
stop()
|
||||
active = true
|
||||
hostView.postDelayed(this, UPDATE_PERIOD)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
active = false
|
||||
hostView.removeCallbacks(this)
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
if (!active) return
|
||||
|
||||
// Generate a random samples with values up to the 50% of the maximum value.
|
||||
seekBar.sampleData = ByteArray(PrepareAttachmentAudioExtrasJob.VISUAL_RMS_FRAMES)
|
||||
{ (random.nextInt(127) - 64).toByte() }
|
||||
hostView.postDelayed(this, UPDATE_PERIOD)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.utilities.OpenGroupUrlParser
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
||||
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
|
||||
|
||||
class OpenGroupInvitationView : FrameLayout {
|
||||
private val joinButton: ImageView
|
||||
private val openGroupIconContainer: RelativeLayout
|
||||
private val openGroupIconImageView: ImageView
|
||||
private val nameTextView: TextView
|
||||
private val urlTextView: TextView
|
||||
private var url: String = ""
|
||||
|
||||
constructor(context: Context): this(context, null)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr) {
|
||||
View.inflate(context, R.layout.open_group_invitation_view, this)
|
||||
joinButton = findViewById(R.id.join_open_group_button)
|
||||
openGroupIconContainer = findViewById(R.id.open_group_icon_image_view_container)
|
||||
openGroupIconImageView = findViewById(R.id.open_group_icon_image_view)
|
||||
nameTextView = findViewById(R.id.name_text_view)
|
||||
urlTextView = findViewById(R.id.url_text_view)
|
||||
joinButton.setOnClickListener { joinOpenGroup(url) }
|
||||
}
|
||||
|
||||
fun setOpenGroup(name: String, url: String, isOutgoing: Boolean = false) {
|
||||
nameTextView.text = name
|
||||
urlTextView.text = OpenGroupUrlParser.trimQueryParameter(url)
|
||||
this.url = url
|
||||
joinButton.visibility = if (isOutgoing) View.GONE else View.VISIBLE
|
||||
openGroupIconContainer.visibility = if (isOutgoing) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun joinOpenGroup(url: String) {
|
||||
val openGroup = OpenGroupUrlParser.parseUrl(url)
|
||||
val builder = AlertDialog.Builder(context)
|
||||
builder.setTitle(context.getString(R.string.ConversationActivity_join_open_group, nameTextView.text.toString()))
|
||||
builder.setCancelable(true)
|
||||
val message: String =
|
||||
context.getString(R.string.ConversationActivity_join_open_group_confirmation_message, nameTextView.text.toString())
|
||||
builder.setMessage(message)
|
||||
builder.setPositiveButton(R.string.yes) { dialog, _ ->
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
dialog.dismiss()
|
||||
OpenGroupManager.add(openGroup.server, openGroup.room, openGroup.serverPublicKey, context)
|
||||
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(context)
|
||||
} catch (e: Exception) {
|
||||
Log.e("Loki", "Failed to join open group.", e)
|
||||
Toast.makeText(context, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.setNegativeButton(R.string.no, null)
|
||||
builder.show()
|
||||
}
|
||||
}
|
@ -1,315 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewConfiguration
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import androidx.core.math.MathUtils
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.utilities.byteToNormalizedFloat
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class WaveformSeekBar : View {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun dp(context: Context, dp: Float): Float {
|
||||
return TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
dp,
|
||||
context.resources.displayMetrics
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val sampleDataHolder = SampleDataHolder(::invalidate)
|
||||
/** An array of signed byte values representing the audio signal. */
|
||||
var sampleData: ByteArray?
|
||||
get() {
|
||||
return sampleDataHolder.getSamples()
|
||||
}
|
||||
set(value) {
|
||||
sampleDataHolder.setSamples(value)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
/** Indicates whether the user is currently interacting with the view and performing a seeking gesture. */
|
||||
private var userSeeking = false
|
||||
private var _progress: Float = 0f
|
||||
/** In [0..1] range. */
|
||||
var progress: Float
|
||||
set(value) {
|
||||
// Do not let to modify the progress value from the outside
|
||||
// when the user is currently interacting with the view.
|
||||
if (userSeeking) return
|
||||
|
||||
_progress = value
|
||||
invalidate()
|
||||
progressChangeListener?.onProgressChanged(this, _progress, false)
|
||||
}
|
||||
get() {
|
||||
return _progress
|
||||
}
|
||||
|
||||
var barBackgroundColor: Int = Color.LTGRAY
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var barProgressColor: Int = Color.WHITE
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var barGap: Float = dp(context, 2f)
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var barWidth: Float = dp(context, 5f)
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var barMinHeight: Float = barWidth
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var barCornerRadius: Float = dp(context, 2.5f)
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var barGravity: WaveGravity = WaveGravity.CENTER
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var progressChangeListener: ProgressChangeListener? = null
|
||||
|
||||
private val barPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val barRect = RectF()
|
||||
|
||||
private var canvasWidth = 0
|
||||
private var canvasHeight = 0
|
||||
|
||||
private var touchDownX = 0f
|
||||
private var touchDownProgress: Float = 0f
|
||||
private var scaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||
|
||||
constructor(context: Context) : this(context, null)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
|
||||
: super(context, attrs, defStyleAttr) {
|
||||
|
||||
val typedAttrs = context.obtainStyledAttributes(attrs, R.styleable.WaveformSeekBar)
|
||||
barWidth = typedAttrs.getDimension(R.styleable.WaveformSeekBar_bar_width, barWidth)
|
||||
barGap = typedAttrs.getDimension(R.styleable.WaveformSeekBar_bar_gap, barGap)
|
||||
barCornerRadius = typedAttrs.getDimension(
|
||||
R.styleable.WaveformSeekBar_bar_corner_radius,
|
||||
barCornerRadius)
|
||||
barMinHeight =
|
||||
typedAttrs.getDimension(R.styleable.WaveformSeekBar_bar_min_height, barMinHeight)
|
||||
barBackgroundColor = typedAttrs.getColor(
|
||||
R.styleable.WaveformSeekBar_bar_background_color,
|
||||
barBackgroundColor)
|
||||
barProgressColor =
|
||||
typedAttrs.getColor(R.styleable.WaveformSeekBar_bar_progress_color, barProgressColor)
|
||||
progress = typedAttrs.getFloat(R.styleable.WaveformSeekBar_progress, progress)
|
||||
barGravity = WaveGravity.fromString(
|
||||
typedAttrs.getString(R.styleable.WaveformSeekBar_bar_gravity))
|
||||
|
||||
typedAttrs.recycle()
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
canvasWidth = w
|
||||
canvasHeight = h
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
val totalWidth = getAvailableWidth()
|
||||
val barAmount = (totalWidth / (barWidth + barGap)).toInt()
|
||||
|
||||
var lastBarRight = paddingLeft.toFloat()
|
||||
|
||||
(0 until barAmount).forEach { barIdx ->
|
||||
// Convert a signed byte to a [0..1] float.
|
||||
val barValue = byteToNormalizedFloat(sampleDataHolder.computeBarValue(barIdx, barAmount))
|
||||
|
||||
val barHeight = max(barMinHeight, getAvailableHeight() * barValue)
|
||||
|
||||
val top: Float = when (barGravity) {
|
||||
WaveGravity.TOP -> paddingTop.toFloat()
|
||||
WaveGravity.CENTER -> paddingTop + getAvailableHeight() * 0.5f - barHeight * 0.5f
|
||||
WaveGravity.BOTTOM -> canvasHeight - paddingBottom - barHeight
|
||||
}
|
||||
|
||||
barRect.set(lastBarRight, top, lastBarRight + barWidth, top + barHeight)
|
||||
|
||||
barPaint.color = if (barRect.right <= totalWidth * progress)
|
||||
barProgressColor else barBackgroundColor
|
||||
|
||||
canvas.drawRoundRect(barRect, barCornerRadius, barCornerRadius, barPaint)
|
||||
|
||||
lastBarRight = barRect.right + barGap
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
if (!isEnabled) return false
|
||||
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
userSeeking = true
|
||||
touchDownX = event.x
|
||||
touchDownProgress = progress
|
||||
updateProgress(event, false)
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
// Prevent any parent scrolling if the user scrolled more
|
||||
// than scaledTouchSlop on horizontal axis.
|
||||
if (abs(event.x - touchDownX) > scaledTouchSlop) {
|
||||
parent.requestDisallowInterceptTouchEvent(true)
|
||||
}
|
||||
updateProgress(event, false)
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
userSeeking = false
|
||||
updateProgress(event, true)
|
||||
performClick()
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
updateProgress(touchDownProgress, false)
|
||||
userSeeking = false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun updateProgress(event: MotionEvent, notify: Boolean) {
|
||||
updateProgress(event.x / getAvailableWidth(), notify)
|
||||
}
|
||||
|
||||
private fun updateProgress(progress: Float, notify: Boolean) {
|
||||
_progress = MathUtils.clamp(progress, 0f, 1f)
|
||||
invalidate()
|
||||
|
||||
if (notify) {
|
||||
progressChangeListener?.onProgressChanged(this, _progress, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun performClick(): Boolean {
|
||||
super.performClick()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getAvailableWidth() = canvasWidth - paddingLeft - paddingRight
|
||||
private fun getAvailableHeight() = canvasHeight - paddingTop - paddingBottom
|
||||
|
||||
private class SampleDataHolder(private val invalidateDelegate: () -> Any) {
|
||||
|
||||
private var sampleDataFrom: ByteArray? = null
|
||||
private var sampleDataTo: ByteArray? = null
|
||||
private var progress = 1f // Mix between from and to values.
|
||||
|
||||
private var animation: ValueAnimator? = null
|
||||
|
||||
fun computeBarValue(barIdx: Int, barAmount: Int): Byte {
|
||||
/** @return The array's value at the interpolated index. */
|
||||
fun getSampleValue(sampleData: ByteArray?): Byte {
|
||||
if (sampleData == null || sampleData.isEmpty())
|
||||
return Byte.MIN_VALUE
|
||||
else {
|
||||
val sampleIdx = (barIdx * (sampleData.size / barAmount.toFloat())).toInt()
|
||||
return sampleData[sampleIdx]
|
||||
}
|
||||
}
|
||||
|
||||
if (progress == 1f) {
|
||||
return getSampleValue(sampleDataTo)
|
||||
}
|
||||
|
||||
val fromValue = getSampleValue(sampleDataFrom)
|
||||
val toValue = getSampleValue(sampleDataTo)
|
||||
val rawResultValue = fromValue * (1f - progress) + toValue * progress
|
||||
return rawResultValue.roundToInt().toByte()
|
||||
}
|
||||
|
||||
fun setSamples(sampleData: ByteArray?) {
|
||||
/** @return a mix between [sampleDataFrom] and [sampleDataTo] arrays according to the current [progress] value. */
|
||||
fun computeNewDataFromArray(): ByteArray? {
|
||||
if (sampleDataTo == null) return null
|
||||
if (sampleDataFrom == null) return sampleDataTo
|
||||
|
||||
val sampleSize = min(sampleDataFrom!!.size, sampleDataTo!!.size)
|
||||
return ByteArray(sampleSize) { i -> computeBarValue(i, sampleSize) }
|
||||
}
|
||||
|
||||
sampleDataFrom = computeNewDataFromArray()
|
||||
sampleDataTo = sampleData
|
||||
progress = 0f
|
||||
|
||||
animation?.cancel()
|
||||
animation = ValueAnimator.ofFloat(0f, 1f).apply {
|
||||
addUpdateListener { animation ->
|
||||
progress = animation.animatedValue as Float
|
||||
invalidateDelegate()
|
||||
}
|
||||
interpolator = DecelerateInterpolator(3f)
|
||||
duration = 500
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
fun getSamples(): ByteArray? {
|
||||
return sampleDataTo
|
||||
}
|
||||
}
|
||||
|
||||
enum class WaveGravity {
|
||||
TOP,
|
||||
CENTER,
|
||||
BOTTOM,
|
||||
;
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromString(gravity: String?): WaveGravity = when (gravity) {
|
||||
"1" -> TOP
|
||||
"2" -> CENTER
|
||||
else -> BOTTOM
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ProgressChangeListener {
|
||||
fun onProgressChanged(waveformSeekBar: WaveformSeekBar, progress: Float, fromUser: Boolean)
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ import org.session.libsession.utilities.Address;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsession.utilities.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities;
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.api
|
||||
package org.thoughtcrime.securesms.notifications
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
@ -57,9 +57,8 @@ import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.loki.api.OpenGroupManager;
|
||||
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
|
||||
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities;
|
||||
import org.thoughtcrime.securesms.util.SessionMetaProtocol;
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||
|
@ -1,5 +1,5 @@
|
||||
@file:JvmName("FcmUtils")
|
||||
package org.thoughtcrime.securesms.loki.utilities
|
||||
package org.thoughtcrime.securesms.notifications
|
||||
|
||||
import com.google.android.gms.tasks.Task
|
||||
import com.google.firebase.iid.FirebaseInstanceId
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.api
|
||||
package org.thoughtcrime.securesms.notifications
|
||||
|
||||
import android.content.Context
|
||||
import nl.komponents.kovenant.functional.map
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user