diff --git a/app/build.gradle b/app/build.gradle index 31e14eac69..9a9d61e97f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -35,7 +35,6 @@ dependencies { implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.exifinterface:exifinterface:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.1' - implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-common-java8:2.3.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index e2c82814e3..363343696c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -15,6 +15,7 @@ */ package org.thoughtcrime.securesms; +import android.app.Application; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; @@ -26,7 +27,6 @@ import androidx.annotation.NonNull; 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; @@ -104,7 +104,7 @@ import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant; * * @author Moxie Marlinspike */ -public class ApplicationContext extends MultiDexApplication implements DependencyInjector, DefaultLifecycleObserver { +public class ApplicationContext extends Application implements DependencyInjector, DefaultLifecycleObserver { public static final String PREFERENCES_NAME = "SecureSMS-Preferences"; @@ -180,12 +180,15 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc Log.i(TAG, "App is now visible."); KeyCachingService.onAppForegrounded(this); - if (poller != null) { - poller.setCaughtUp(false); - } - startPollingIfNeeded(); + ThreadUtils.queue(()->{ + if (poller != null) { + poller.setCaughtUp(false); + } - OpenGroupManager.INSTANCE.startPolling(); + startPollingIfNeeded(); + + OpenGroupManager.INSTANCE.startPolling(); + }); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 656da5fd66..4a86d5edc5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -87,10 +87,7 @@ import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper 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 -import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog -import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities -import org.thoughtcrime.securesms.conversation.v2.utilities.ResendMessageUtilities +import org.thoughtcrime.securesms.conversation.v2.utilities.* import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.MnemonicUtilities import org.thoughtcrime.securesms.database.DatabaseFactory @@ -230,6 +227,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe setUpRecyclerView() setUpToolBar() setUpInputBar() + setUpLinkPreviewObserver() restoreDraftIfNeeded() addOpenGroupGuidelinesIfNeeded() scrollToBottomButton.setOnClickListener { conversationRecyclerView.smoothScrollToPosition(0) } @@ -240,7 +238,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe updateSubtitle() getLatestOpenGroupInfoIfNeeded() setUpBlockedBanner() - setUpLinkPreviewObserver() searchBottomBar.setEventListener(this) setUpSearchResultObserver() scrollToFirstUnreadMessageIfNeeded() @@ -322,6 +319,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val size = resources.getDimension(sizeID).roundToInt() profilePictureView.layoutParams = LinearLayout.LayoutParams(size, size) profilePictureView.glide = glide + MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded(threadID, this) profilePictureView.update(thread, threadID) } @@ -1206,7 +1204,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val sortedMessages = messages.sortedBy { it.dateSent } val builder = StringBuilder() for (message in sortedMessages) { - val body = MentionUtilities.highlightMentions(message.body, message.threadId, this) + val body = MentionUtilities.highlightMentions(message.body, threadID, this) if (TextUtils.isEmpty(body)) { continue } val formattedTimestamp = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), message.timestamp) builder.append("$formattedTimestamp: $body").append('\n') diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt index 1798b07f0a..0f6fe2773d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt @@ -10,7 +10,6 @@ import androidx.annotation.ColorInt import androidx.core.content.res.ResourcesCompat import androidx.core.text.toSpannable import androidx.core.view.isVisible -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 01c7f105bd..106528ee25 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -192,7 +192,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { if (oldVersion < lokiV14_BACKUP_FILES) { db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand()); } - + if (oldVersion < lokiV16) { db.execSQL(LokiAPIDatabase.getCreateOpenGroupProfilePictureTableCommand()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 6d42d80de8..4a59fcdba8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -10,10 +10,9 @@ import android.os.Bundle import android.text.Spannable import android.text.SpannableString import android.text.style.ForegroundColorSpan -import android.util.DisplayMetrics import android.view.View -import android.widget.RelativeLayout import android.widget.Toast +import androidx.core.view.isVisible import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import androidx.loader.app.LoaderManager @@ -21,9 +20,11 @@ import androidx.loader.content.Loader import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.android.synthetic.main.activity_home.* +import kotlinx.android.synthetic.main.seed_reminder_stub.view.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import network.loki.messenger.R import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe @@ -65,8 +66,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis // region Lifecycle override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { super.onCreate(savedInstanceState, isReady) - // Double check that the long poller is up - (applicationContext as ApplicationContext).startPollingIfNeeded() // Set content view setContentView(R.layout.activity_home) // Set custom toolbar @@ -75,21 +74,24 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis glide = GlideApp.with(this) // Set up toolbar buttons profileButton.glide = glide - updateProfileButton() profileButton.setOnClickListener { openSettings() } pathStatusViewContainer.disableClipping() pathStatusViewContainer.setOnClickListener { showPath() } // Set up seed reminder view val hasViewedSeed = TextSecurePreferences.getHasViewedSeed(this) if (!hasViewedSeed) { - val seedReminderViewTitle = SpannableString("You're almost finished! 80%") // Intentionally not yet translated - seedReminderViewTitle.setSpan(ForegroundColorSpan(resources.getColorWithID(R.color.accent, theme)), 24, 27, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - seedReminderView.title = seedReminderViewTitle - seedReminderView.subtitle = resources.getString(R.string.view_seed_reminder_subtitle_1) - seedReminderView.setProgress(80, false) - seedReminderView.delegate = this + seedReminderStub.isVisible = true + seedReminderStub.apply { + val seedReminderView = this.seedReminderView + val seedReminderViewTitle = SpannableString("You're almost finished! 80%") // Intentionally not yet translated + seedReminderViewTitle.setSpan(ForegroundColorSpan(resources.getColorWithID(R.color.accent, theme)), 24, 27, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + seedReminderView.title = seedReminderViewTitle + seedReminderView.subtitle = resources.getString(R.string.view_seed_reminder_subtitle_1) + seedReminderView.setProgress(80, false) + seedReminderView.delegate = this@HomeActivity + } } else { - seedReminderView.visibility = View.GONE + seedReminderStub.isVisible = false } // Set up recycler view val cursor = DatabaseFactory.getThreadDatabase(this).conversationList @@ -101,6 +103,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis recyclerView.layoutManager = LinearLayoutManager(this) // Set up empty state view createNewPrivateChatButton.setOnClickListener { createNewPrivateChat() } + IP2Country.configureIfNeeded(this@HomeActivity) // This is a workaround for the fact that CursorRecyclerViewAdapter doesn't actually auto-update (even though it says it will) LoaderManager.getInstance(this).restartLoader(0, null, object : LoaderManager.LoaderCallbacks { @@ -117,28 +120,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis homeAdapter.changeCursor(null) } }) - // Set up gradient view - val gradientViewLayoutParams = gradientView.layoutParams as RelativeLayout.LayoutParams - val displayMetrics = DisplayMetrics() - windowManager.defaultDisplay.getMetrics(displayMetrics) - val height = displayMetrics.heightPixels - gradientViewLayoutParams.topMargin = (0.15 * height.toFloat()).toInt() // Set up new conversation button set newConversationButtonSet.delegate = this - // Set up typing observer - ApplicationContext.getInstance(this).typingStatusRepository.typingThreads.observe(this, Observer> { threadIDs -> - val adapter = recyclerView.adapter as HomeAdapter - adapter.typingThreadIDs = threadIDs ?: setOf() - }) - // Set up remaining components if needed - val application = ApplicationContext.getInstance(this) - val userPublicKey = TextSecurePreferences.getLocalNumber(this) - if (userPublicKey != null) { - OpenGroupManager.startPolling() - JobQueue.shared.resumePendingJobs() - } - IP2Country.configureIfNeeded(this) - application.registerForFCMIfNeeded(false) // Observe blocked contacts changed events val broadcastReceiver = object : BroadcastReceiver() { @@ -148,10 +131,30 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis } this.broadcastReceiver = broadcastReceiver LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter("blockedContactsChanged")) - lifecycleScope.launch { - // update things based on TextSecurePrefs (profile info etc) - TextSecurePreferences.events.filter { it == TextSecurePreferences.PROFILE_NAME_PREF }.collect { - updateProfileButton() + lifecycleScope.launchWhenStarted { + launch(Dispatchers.IO) { + // Double check that the long poller is up + (applicationContext as ApplicationContext).startPollingIfNeeded() + // update things based on TextSecurePrefs (profile info etc) + // Set up typing observer + withContext(Dispatchers.Main) { + ApplicationContext.getInstance(this@HomeActivity).typingStatusRepository.typingThreads.observe(this@HomeActivity, Observer> { threadIDs -> + val adapter = recyclerView.adapter as HomeAdapter + adapter.typingThreadIDs = threadIDs ?: setOf() + }) + updateProfileButton() + TextSecurePreferences.events.filter { it == TextSecurePreferences.PROFILE_NAME_PREF }.collect { + updateProfileButton() + } + } + // Set up remaining components if needed + val application = ApplicationContext.getInstance(this@HomeActivity) + application.registerForFCMIfNeeded(false) + val userPublicKey = TextSecurePreferences.getLocalNumber(this@HomeActivity) + if (userPublicKey != null) { + OpenGroupManager.startPolling() + JobQueue.shared.resumePendingJobs() + } } } EventBus.getDefault().register(this@HomeActivity) @@ -166,7 +169,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis profileButton.update() val hasViewedSeed = TextSecurePreferences.getHasViewedSeed(this) if (hasViewedSeed) { - seedReminderView.visibility = View.GONE + seedReminderStub.visibility = View.GONE } if (TextSecurePreferences.getConfigurationMessageSynced(this)) { lifecycleScope.launch(Dispatchers.IO) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt index ba718d355f..11ae410a9d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt @@ -9,7 +9,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.mms.GlideRequests -class HomeAdapter(context: Context, cursor: Cursor) : CursorRecyclerViewAdapter(context, cursor) { +class HomeAdapter(context: Context, cursor: Cursor?) : CursorRecyclerViewAdapter(context, cursor) { private val threadDatabase = DatabaseFactory.getThreadDatabase(context) lateinit var glide: GlideRequests var typingThreadIDs = setOf() diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java index bdcf916dd4..e7f8e8e5c8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -40,6 +40,7 @@ import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; +import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Contact; import org.session.libsession.utilities.ServiceUtil; import org.session.libsession.utilities.TextSecurePreferences; @@ -49,6 +50,7 @@ import org.session.libsignal.utilities.Util; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.contactshare.ContactUtil; import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; +import org.thoughtcrime.securesms.conversation.v2.utilities.MentionManagerUtilities; import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; @@ -58,6 +60,7 @@ 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.database.model.Quote; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.SessionMetaProtocol; @@ -66,6 +69,7 @@ import org.thoughtcrime.securesms.util.SpanUtil; import java.util.HashSet; import java.util.List; import java.util.ListIterator; +import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -273,9 +277,13 @@ public class DefaultMessageNotifier implements MessageNotifier { lastAudibleNotification = System.currentTimeMillis(); } - boolean hasMultipleThreads = notificationState.hasMultipleThreads(); - for (long threadId : notificationState.getThreads()) { - sendSingleThreadNotification(context, new NotificationState(notificationState.getNotificationsForThread(threadId)), signal, hasMultipleThreads); + if (notificationState.hasMultipleThreads()) { + for (long threadId : notificationState.getThreads()) { + sendSingleThreadNotification(context, new NotificationState(notificationState.getNotificationsForThread(threadId)), false, true); + } + sendMultipleThreadNotification(context, notificationState, signal); + } else if (notificationState.getMessageCount() > 0){ + sendSingleThreadNotification(context, notificationState, signal, false); } cancelOrphanedNotifications(context, notificationState); @@ -323,8 +331,11 @@ public class DefaultMessageNotifier implements MessageNotifier { builder.setThread(notifications.get(0).getRecipient()); builder.setMessageCount(notificationState.getMessageCount()); + MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(notifications.get(0).getThreadId(),context); builder.setPrimaryMessageBody(recipient, notifications.get(0).getIndividualRecipient(), - MentionUtilities.highlightMentions(notifications.get(0).getText(), notifications.get(0).getThreadId(), context), + MentionUtilities.highlightMentions(notifications.get(0).getText(), + notifications.get(0).getThreadId(), + context), notifications.get(0).getSlideDeck()); builder.setContentIntent(notifications.get(0).getPendingIntent(context)); builder.setDeleteIntent(notificationState.getDeleteIntent(context)); @@ -497,7 +508,15 @@ public class DefaultMessageNotifier implements MessageNotifier { if (threadRecipients == null || !threadRecipients.isMuted()) { if (threadRecipients != null && threadRecipients.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS) { // check if mentioned here - if (body.toString().contains("@"+TextSecurePreferences.getLocalNumber(context))) { + boolean isQuoteMentioned = false; + if (record instanceof MmsMessageRecord) { + Quote quote = ((MmsMessageRecord) record).getQuote(); + Address quoteAddress = quote != null ? quote.getAuthor() : null; + String serializedAddress = quoteAddress != null ? quoteAddress.serialize() : null; + isQuoteMentioned = serializedAddress != null && Objects.equals(TextSecurePreferences.getLocalNumber(context), serializedAddress); + } + if (body.toString().contains("@"+TextSecurePreferences.getLocalNumber(context)) + || isQuoteMentioned) { notificationState.addNotification(new NotificationItem(id, mms, recipient, conversationRecipient, threadRecipients, threadId, body, timestamp, slideDeck)); } } else if (threadRecipients != null && threadRecipients.notifyType == RecipientDatabase.NOTIFY_TYPE_NONE) { diff --git a/app/src/main/res/drawable/home_activity_gradient.xml b/app/src/main/res/drawable/home_activity_gradient.xml index b160d68fee..d85e4873f7 100644 --- a/app/src/main/res/drawable/home_activity_gradient.xml +++ b/app/src/main/res/drawable/home_activity_gradient.xml @@ -4,6 +4,7 @@ android:shape="rectangle"> diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index 4a3e300d94..87bd87eb2d 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -70,10 +70,11 @@ android:background="?android:dividerHorizontal" android:elevation="1dp" /> - + android:layout_height="wrap_content"/> diff --git a/app/src/main/res/layout/seed_reminder_stub.xml b/app/src/main/res/layout/seed_reminder_stub.xml new file mode 100644 index 0000000000..fcaa9c6b48 --- /dev/null +++ b/app/src/main/res/layout/seed_reminder_stub.xml @@ -0,0 +1,6 @@ + +