mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-28 20:45:17 +00:00
feat: add a wrapper hash to track home diff util changes for wrapper contact recipient info, add test for dirty state in double set
This commit is contained in:
parent
1781d7e85f
commit
01fef6a0a4
@ -62,13 +62,14 @@ public class RecipientDatabase extends Database {
|
|||||||
private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode";
|
private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode";
|
||||||
private static final String FORCE_SMS_SELECTION = "force_sms_selection";
|
private static final String FORCE_SMS_SELECTION = "force_sms_selection";
|
||||||
private static final String NOTIFY_TYPE = "notify_type"; // all, mentions only, none
|
private static final String NOTIFY_TYPE = "notify_type"; // all, mentions only, none
|
||||||
|
private static final String WRAPPER_HASH = "wrapper_hash";
|
||||||
|
|
||||||
private static final String[] RECIPIENT_PROJECTION = new String[] {
|
private static final String[] RECIPIENT_PROJECTION = new String[] {
|
||||||
BLOCK, APPROVED, APPROVED_ME, NOTIFICATION, CALL_RINGTONE, VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, EXPIRE_MESSAGES, REGISTERED,
|
BLOCK, APPROVED, APPROVED_ME, NOTIFICATION, CALL_RINGTONE, VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, EXPIRE_MESSAGES, REGISTERED,
|
||||||
PROFILE_KEY, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_CONTACT_URI,
|
PROFILE_KEY, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_CONTACT_URI,
|
||||||
SIGNAL_PROFILE_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL,
|
SIGNAL_PROFILE_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL,
|
||||||
UNIDENTIFIED_ACCESS_MODE,
|
UNIDENTIFIED_ACCESS_MODE,
|
||||||
FORCE_SMS_SELECTION, NOTIFY_TYPE,
|
FORCE_SMS_SELECTION, NOTIFY_TYPE, WRAPPER_HASH
|
||||||
};
|
};
|
||||||
|
|
||||||
static final List<String> TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION)
|
static final List<String> TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION)
|
||||||
@ -136,6 +137,11 @@ public class RecipientDatabase extends Database {
|
|||||||
"OR "+ADDRESS+" IN (SELECT "+GroupDatabase.TABLE_NAME+"."+GroupDatabase.ADMINS+" FROM "+GroupDatabase.TABLE_NAME+")))";
|
"OR "+ADDRESS+" IN (SELECT "+GroupDatabase.TABLE_NAME+"."+GroupDatabase.ADMINS+" FROM "+GroupDatabase.TABLE_NAME+")))";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getAddWrapperHash() {
|
||||||
|
return "ALTER TABLE "+TABLE_NAME+" "+
|
||||||
|
"ADD COLUMN "+WRAPPER_HASH+" TEXT DEFAULT NULL;";
|
||||||
|
}
|
||||||
|
|
||||||
public static final int NOTIFY_TYPE_ALL = 0;
|
public static final int NOTIFY_TYPE_ALL = 0;
|
||||||
public static final int NOTIFY_TYPE_MENTIONS = 1;
|
public static final int NOTIFY_TYPE_MENTIONS = 1;
|
||||||
public static final int NOTIFY_TYPE_NONE = 2;
|
public static final int NOTIFY_TYPE_NONE = 2;
|
||||||
@ -190,6 +196,7 @@ public class RecipientDatabase extends Database {
|
|||||||
String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL));
|
String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL));
|
||||||
int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE));
|
int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE));
|
||||||
boolean forceSmsSelection = cursor.getInt(cursor.getColumnIndexOrThrow(FORCE_SMS_SELECTION)) == 1;
|
boolean forceSmsSelection = cursor.getInt(cursor.getColumnIndexOrThrow(FORCE_SMS_SELECTION)) == 1;
|
||||||
|
String wrapperHash = cursor.getString(cursor.getColumnIndexOrThrow(WRAPPER_HASH));
|
||||||
|
|
||||||
MaterialColor color;
|
MaterialColor color;
|
||||||
byte[] profileKey = null;
|
byte[] profileKey = null;
|
||||||
@ -221,7 +228,7 @@ public class RecipientDatabase extends Database {
|
|||||||
systemPhoneLabel, systemContactUri,
|
systemPhoneLabel, systemContactUri,
|
||||||
signalProfileName, signalProfileAvatar, profileSharing,
|
signalProfileName, signalProfileAvatar, profileSharing,
|
||||||
notificationChannel, Recipient.UnidentifiedAccessMode.fromMode(unidentifiedAccessMode),
|
notificationChannel, Recipient.UnidentifiedAccessMode.fromMode(unidentifiedAccessMode),
|
||||||
forceSmsSelection));
|
forceSmsSelection, wrapperHash));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setColor(@NonNull Recipient recipient, @NonNull MaterialColor color) {
|
public void setColor(@NonNull Recipient recipient, @NonNull MaterialColor color) {
|
||||||
@ -258,6 +265,14 @@ public class RecipientDatabase extends Database {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRecipientHash(@NonNull Recipient recipient, String recipientHash) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(WRAPPER_HASH, recipientHash);
|
||||||
|
updateOrInsert(recipient.getAddress(), values);
|
||||||
|
recipient.resolve().setWrapperHash(recipientHash);
|
||||||
|
notifyRecipientListeners();
|
||||||
|
}
|
||||||
|
|
||||||
public void setApproved(@NonNull Recipient recipient, boolean approved) {
|
public void setApproved(@NonNull Recipient recipient, boolean approved) {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(APPROVED, approved ? 1 : 0);
|
values.put(APPROVED, approved ? 1 : 0);
|
||||||
|
@ -218,6 +218,7 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
|
|||||||
override fun markConversationAsRead(threadId: Long, lastSeenTime: Long) {
|
override fun markConversationAsRead(threadId: Long, lastSeenTime: Long) {
|
||||||
val threadDb = DatabaseComponent.get(context).threadDatabase()
|
val threadDb = DatabaseComponent.get(context).threadDatabase()
|
||||||
getRecipientForThread(threadId)?.let { recipient ->
|
getRecipientForThread(threadId)?.let { recipient ->
|
||||||
|
val currentLastRead = threadDb.getLastSeenAndHasSent(threadId).first()
|
||||||
// don't set the last read in the volatile if we didn't set it in the DB
|
// don't set the last read in the volatile if we didn't set it in the DB
|
||||||
if (!threadDb.markAllAsRead(threadId, recipient.isGroupRecipient, lastSeenTime)) return
|
if (!threadDb.markAllAsRead(threadId, recipient.isGroupRecipient, lastSeenTime)) return
|
||||||
|
|
||||||
@ -246,6 +247,10 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
|
|||||||
else -> throw NullPointerException("Weren't expecting to have a convo with address ${recipient.address.serialize()}")
|
else -> throw NullPointerException("Weren't expecting to have a convo with address ${recipient.address.serialize()}")
|
||||||
}
|
}
|
||||||
convo.lastRead = lastSeenTime
|
convo.lastRead = lastSeenTime
|
||||||
|
if (convo.unread) {
|
||||||
|
convo.unread = lastSeenTime <= currentLastRead
|
||||||
|
notifyConversationListListeners()
|
||||||
|
}
|
||||||
config.set(convo)
|
config.set(convo)
|
||||||
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
|
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
|
||||||
}
|
}
|
||||||
@ -1088,8 +1093,11 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
|
|||||||
|
|
||||||
override fun setContact(contact: Contact) {
|
override fun setContact(contact: Contact) {
|
||||||
DatabaseComponent.get(context).sessionContactDatabase().setContact(contact)
|
DatabaseComponent.get(context).sessionContactDatabase().setContact(contact)
|
||||||
if (!getRecipientApproved(Address.fromSerialized(contact.sessionID))) return
|
val address = fromSerialized(contact.sessionID)
|
||||||
SSKEnvironment.shared.profileManager.contactUpdatedInternal(contact)
|
if (!getRecipientApproved(address)) return
|
||||||
|
val recipientHash = SSKEnvironment.shared.profileManager.contactUpdatedInternal(contact)
|
||||||
|
val recipient = Recipient.from(context, address, false)
|
||||||
|
setRecipientHash(recipient, recipientHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRecipientForThread(threadId: Long): Recipient? {
|
override fun getRecipientForThread(threadId: Long): Recipient? {
|
||||||
@ -1140,6 +1148,7 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
|
|||||||
setPinned(conversationThreadId, contact.priority == PRIORITY_PINNED)
|
setPinned(conversationThreadId, contact.priority == PRIORITY_PINNED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setRecipientHash(recipient, contact.hashCode().toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1184,6 +1193,11 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setRecipientHash(recipient: Recipient, recipientHash: String?) {
|
||||||
|
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
|
||||||
|
recipientDb.setRecipientHash(recipient, recipientHash)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getLastUpdated(threadID: Long): Long {
|
override fun getLastUpdated(threadID: Long): Long {
|
||||||
val threadDB = DatabaseComponent.get(context).threadDatabase()
|
val threadDB = DatabaseComponent.get(context).threadDatabase()
|
||||||
return threadDB.getLastUpdated(threadID)
|
return threadDB.getLastUpdated(threadID)
|
||||||
|
@ -89,9 +89,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
private static final int lokiV39 = 60;
|
private static final int lokiV39 = 60;
|
||||||
private static final int lokiV40 = 61;
|
private static final int lokiV40 = 61;
|
||||||
private static final int lokiV41 = 62;
|
private static final int lokiV41 = 62;
|
||||||
|
private static final int lokiV42 = 63;
|
||||||
|
|
||||||
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
||||||
private static final int DATABASE_VERSION = lokiV41;
|
private static final int DATABASE_VERSION = lokiV42;
|
||||||
private static final int MIN_DATABASE_VERSION = lokiV7;
|
private static final int MIN_DATABASE_VERSION = lokiV7;
|
||||||
private static final String CIPHER3_DATABASE_NAME = "signal.db";
|
private static final String CIPHER3_DATABASE_NAME = "signal.db";
|
||||||
public static final String DATABASE_NAME = "signal_v4.db";
|
public static final String DATABASE_NAME = "signal_v4.db";
|
||||||
@ -359,7 +360,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
executeStatements(db, ReactionDatabase.CREATE_INDEXS);
|
executeStatements(db, ReactionDatabase.CREATE_INDEXS);
|
||||||
|
|
||||||
executeStatements(db, ReactionDatabase.CREATE_REACTION_TRIGGERS);
|
executeStatements(db, ReactionDatabase.CREATE_REACTION_TRIGGERS);
|
||||||
// db.execSQL(ThreadDatabase.getCreateLastSeenTrigger());
|
db.execSQL(RecipientDatabase.getAddWrapperHash());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -600,6 +601,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
TextSecurePreferences.setForceNewConfig(context);
|
TextSecurePreferences.setForceNewConfig(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < lokiV42) {
|
||||||
|
db.execSQL(RecipientDatabase.getAddWrapperHash());
|
||||||
|
}
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
|
@ -7,10 +7,15 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import network.loki.messenger.databinding.FragmentConversationBottomSheetBinding
|
import network.loki.messenger.databinding.FragmentConversationBottomSheetBinding
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||||
|
import org.thoughtcrime.securesms.util.getConversationUnread
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
class ConversationOptionsBottomSheet(private val parentContext: Context) : BottomSheetDialogFragment(), View.OnClickListener {
|
class ConversationOptionsBottomSheet(private val parentContext: Context) : BottomSheetDialogFragment(), View.OnClickListener {
|
||||||
private lateinit var binding: FragmentConversationBottomSheetBinding
|
private lateinit var binding: FragmentConversationBottomSheetBinding
|
||||||
//FIXME AC: Supplying a threadRecord directly into the field from an activity
|
//FIXME AC: Supplying a threadRecord directly into the field from an activity
|
||||||
@ -19,6 +24,8 @@ class ConversationOptionsBottomSheet(private val parentContext: Context) : Botto
|
|||||||
// if we want to use dialog fragments properly.
|
// if we want to use dialog fragments properly.
|
||||||
lateinit var thread: ThreadRecord
|
lateinit var thread: ThreadRecord
|
||||||
|
|
||||||
|
@Inject lateinit var configFactory: ConfigFactory
|
||||||
|
|
||||||
var onViewDetailsTapped: (() -> Unit?)? = null
|
var onViewDetailsTapped: (() -> Unit?)? = null
|
||||||
var onCopyConversationId: (() -> Unit?)? = null
|
var onCopyConversationId: (() -> Unit?)? = null
|
||||||
var onPinTapped: (() -> Unit)? = null
|
var onPinTapped: (() -> Unit)? = null
|
||||||
@ -77,7 +84,7 @@ class ConversationOptionsBottomSheet(private val parentContext: Context) : Botto
|
|||||||
binding.notificationsTextView.isVisible = recipient.isGroupRecipient && !recipient.isMuted
|
binding.notificationsTextView.isVisible = recipient.isGroupRecipient && !recipient.isMuted
|
||||||
binding.notificationsTextView.setOnClickListener(this)
|
binding.notificationsTextView.setOnClickListener(this)
|
||||||
binding.deleteTextView.setOnClickListener(this)
|
binding.deleteTextView.setOnClickListener(this)
|
||||||
binding.markAllAsReadTextView.isVisible = thread.unreadCount > 0
|
binding.markAllAsReadTextView.isVisible = thread.unreadCount > 0 || configFactory.convoVolatile?.getConversationUnread(thread) == true
|
||||||
binding.markAllAsReadTextView.setOnClickListener(this)
|
binding.markAllAsReadTextView.setOnClickListener(this)
|
||||||
binding.pinTextView.isVisible = !thread.isPinned
|
binding.pinTextView.isVisible = !thread.isPinned
|
||||||
binding.unpinTextView.isVisible = thread.isPinned
|
binding.unpinTextView.isVisible = thread.isPinned
|
||||||
|
@ -11,6 +11,7 @@ import android.widget.LinearLayout
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import network.loki.messenger.databinding.ViewConversationBinding
|
import network.loki.messenger.databinding.ViewConversationBinding
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
@ -18,12 +19,19 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.hig
|
|||||||
import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_ALL
|
import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_ALL
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_NONE
|
import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_NONE
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
import org.thoughtcrime.securesms.util.DateUtils
|
import org.thoughtcrime.securesms.util.DateUtils
|
||||||
import org.thoughtcrime.securesms.util.getAccentColor
|
import org.thoughtcrime.securesms.util.getAccentColor
|
||||||
|
import org.thoughtcrime.securesms.util.getConversationUnread
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
class ConversationView : LinearLayout {
|
class ConversationView : LinearLayout {
|
||||||
|
|
||||||
|
@Inject lateinit var configFactory: ConfigFactory
|
||||||
|
|
||||||
private val binding: ViewConversationBinding by lazy { ViewConversationBinding.bind(this) }
|
private val binding: ViewConversationBinding by lazy { ViewConversationBinding.bind(this) }
|
||||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||||
var thread: ThreadRecord? = null
|
var thread: ThreadRecord? = null
|
||||||
@ -70,7 +78,7 @@ class ConversationView : LinearLayout {
|
|||||||
// This would also not trigger the disappearing message timer which may or may not be desirable
|
// This would also not trigger the disappearing message timer which may or may not be desirable
|
||||||
binding.accentView.visibility = if (unreadCount > 0 && !thread.isRead) View.VISIBLE else View.INVISIBLE
|
binding.accentView.visibility = if (unreadCount > 0 && !thread.isRead) View.VISIBLE else View.INVISIBLE
|
||||||
}
|
}
|
||||||
val formattedUnreadCount = if (thread.isRead) {
|
val formattedUnreadCount = if (unreadCount == 0) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
if (unreadCount < 10000) unreadCount.toString() else "9999+"
|
if (unreadCount < 10000) unreadCount.toString() else "9999+"
|
||||||
@ -79,6 +87,7 @@ class ConversationView : LinearLayout {
|
|||||||
val textSize = if (unreadCount < 1000) 12.0f else 10.0f
|
val textSize = if (unreadCount < 1000) 12.0f else 10.0f
|
||||||
binding.unreadCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
|
binding.unreadCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
|
||||||
binding.unreadCountIndicator.isVisible = (unreadCount != 0 && !thread.isRead)
|
binding.unreadCountIndicator.isVisible = (unreadCount != 0 && !thread.isRead)
|
||||||
|
|| (configFactory.convoVolatile?.getConversationUnread(thread) == true)
|
||||||
binding.unreadMentionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
|
binding.unreadMentionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
|
||||||
binding.unreadMentionIndicator.isVisible = (thread.unreadMentionCount != 0 && thread.recipient.address.isGroup)
|
binding.unreadMentionIndicator.isVisible = (thread.unreadMentionCount != 0 && thread.recipient.address.isGroup)
|
||||||
val senderDisplayName = getUserDisplayName(thread.recipient)
|
val senderDisplayName = getUserDisplayName(thread.recipient)
|
||||||
|
@ -106,7 +106,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
get() = textSecurePreferences.getLocalNumber()!!
|
get() = textSecurePreferences.getLocalNumber()!!
|
||||||
|
|
||||||
private val homeAdapter: HomeAdapter by lazy {
|
private val homeAdapter: HomeAdapter by lazy {
|
||||||
HomeAdapter(context = this, listener = this)
|
HomeAdapter(context = this, configFactory = configFactory, listener = this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val globalSearchAdapter = GlobalSearchAdapter { model ->
|
private val globalSearchAdapter = GlobalSearchAdapter { model ->
|
||||||
|
@ -10,10 +10,12 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import androidx.recyclerview.widget.RecyclerView.NO_ID
|
import androidx.recyclerview.widget.RecyclerView.NO_ID
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
|
|
||||||
class HomeAdapter(
|
class HomeAdapter(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
|
private val configFactory: ConfigFactory,
|
||||||
private val listener: ConversationClickListener
|
private val listener: ConversationClickListener
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), ListUpdateCallback {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), ListUpdateCallback {
|
||||||
|
|
||||||
@ -29,7 +31,7 @@ class HomeAdapter(
|
|||||||
get() = _data.toList()
|
get() = _data.toList()
|
||||||
set(newData) {
|
set(newData) {
|
||||||
val previousData = _data.toList()
|
val previousData = _data.toList()
|
||||||
val diff = HomeDiffUtil(previousData, newData, context)
|
val diff = HomeDiffUtil(previousData, newData, context, configFactory)
|
||||||
val diffResult = DiffUtil.calculateDiff(diff)
|
val diffResult = DiffUtil.calculateDiff(diff)
|
||||||
_data = newData
|
_data = newData
|
||||||
diffResult.dispatchUpdatesTo(this as ListUpdateCallback)
|
diffResult.dispatchUpdatesTo(this as ListUpdateCallback)
|
||||||
|
@ -3,11 +3,14 @@ package org.thoughtcrime.securesms.home
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
|
import org.thoughtcrime.securesms.util.getConversationUnread
|
||||||
|
|
||||||
class HomeDiffUtil(
|
class HomeDiffUtil(
|
||||||
private val old: List<ThreadRecord>,
|
private val old: List<ThreadRecord>,
|
||||||
private val new: List<ThreadRecord>,
|
private val new: List<ThreadRecord>,
|
||||||
private val context: Context
|
private val context: Context,
|
||||||
|
private val configFactory: ConfigFactory
|
||||||
): DiffUtil.Callback() {
|
): DiffUtil.Callback() {
|
||||||
|
|
||||||
override fun getOldListSize(): Int = old.size
|
override fun getOldListSize(): Int = old.size
|
||||||
@ -42,7 +45,9 @@ class HomeDiffUtil(
|
|||||||
oldItem.isFailed == newItem.isFailed &&
|
oldItem.isFailed == newItem.isFailed &&
|
||||||
oldItem.isDelivered == newItem.isDelivered &&
|
oldItem.isDelivered == newItem.isDelivered &&
|
||||||
oldItem.isSent == newItem.isSent &&
|
oldItem.isSent == newItem.isSent &&
|
||||||
oldItem.isPending == newItem.isPending
|
oldItem.isPending == newItem.isPending &&
|
||||||
|
oldItem.lastSeen == newItem.lastSeen &&
|
||||||
|
configFactory.convoVolatile?.getConversationUnread(newItem) != true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,12 +83,12 @@ class ProfileManager(private val context: Context, private val configFactory: Co
|
|||||||
database.setUnidentifiedAccessMode(recipient, unidentifiedAccessMode)
|
database.setUnidentifiedAccessMode(recipient, unidentifiedAccessMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun contactUpdatedInternal(contact: Contact) {
|
override fun contactUpdatedInternal(contact: Contact): String? {
|
||||||
val contactConfig = configFactory.contacts ?: return
|
val contactConfig = configFactory.contacts ?: return null
|
||||||
if (contact.sessionID == TextSecurePreferences.getLocalNumber(context)) return
|
if (contact.sessionID == TextSecurePreferences.getLocalNumber(context)) return null
|
||||||
val sessionId = SessionId(contact.sessionID)
|
val sessionId = SessionId(contact.sessionID)
|
||||||
if (sessionId.prefix != IdPrefix.STANDARD) return // only internally store standard session IDs
|
if (sessionId.prefix != IdPrefix.STANDARD) return null // only internally store standard session IDs
|
||||||
if (contactConfig.get(contact.sessionID) == null) return // don't insert, only update
|
if (contactConfig.get(contact.sessionID) == null) return null // don't insert, only update
|
||||||
contactConfig.upsertContact(contact.sessionID) {
|
contactConfig.upsertContact(contact.sessionID) {
|
||||||
this.name = contact.name.orEmpty()
|
this.name = contact.name.orEmpty()
|
||||||
this.nickname = contact.nickname.orEmpty()
|
this.nickname = contact.nickname.orEmpty()
|
||||||
@ -103,6 +103,7 @@ class ProfileManager(private val context: Context, private val configFactory: Co
|
|||||||
if (contactConfig.needsPush()) {
|
if (contactConfig.needsPush()) {
|
||||||
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
|
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
|
||||||
}
|
}
|
||||||
|
return contactConfig.get(contact.sessionID)?.hashCode()?.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package org.thoughtcrime.securesms.util
|
||||||
|
|
||||||
|
import network.loki.messenger.libsession_util.ConversationVolatileConfig
|
||||||
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
|
import org.session.libsession.utilities.GroupUtil
|
||||||
|
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||||
|
|
||||||
|
fun ConversationVolatileConfig.getConversationUnread(thread: ThreadRecord): Boolean {
|
||||||
|
val recipient = thread.recipient
|
||||||
|
if (recipient.isContactRecipient) {
|
||||||
|
return getOneToOne(recipient.address.serialize())?.unread == true
|
||||||
|
} else if (recipient.isClosedGroupRecipient) {
|
||||||
|
return getLegacyClosedGroup(GroupUtil.doubleDecodeGroupId(recipient.address.toGroupString()))?.unread == true
|
||||||
|
} else if (recipient.isOpenGroupRecipient) {
|
||||||
|
val openGroup = MessagingModuleConfiguration.shared.storage.getOpenGroup(thread.threadId) ?: return false
|
||||||
|
return getCommunity(openGroup.server, openGroup.room)?.unread == true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@ -47,6 +47,22 @@ class InstrumentedTests {
|
|||||||
assertArrayEquals(kp.secretKey.take(32).toByteArray(), seed)
|
assertArrayEquals(kp.secretKey.take(32).toByteArray(), seed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
private fun testDirtyEmptyString() {
|
||||||
|
val contacts = Contacts.newInstance(keyPair.secretKey)
|
||||||
|
val definitelyRealId = "050000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
val contact = contacts.getOrConstruct(definitelyRealId)
|
||||||
|
contacts.set(contact)
|
||||||
|
assertTrue(contacts.dirty())
|
||||||
|
contacts.set(contact.copy(name = "test"))
|
||||||
|
assertTrue(contacts.dirty())
|
||||||
|
val push = contacts.push()
|
||||||
|
contacts.confirmPushed(push.seqNo, "abc123")
|
||||||
|
contacts.set(contact.copy(name = "test2"))
|
||||||
|
contacts.set(contact.copy(name = "test"))
|
||||||
|
assertFalse(contacts.dirty())
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun jni_contacts() {
|
fun jni_contacts() {
|
||||||
val contacts = Contacts.newInstance(keyPair.secretKey)
|
val contacts = Contacts.newInstance(keyPair.secretKey)
|
||||||
|
@ -156,6 +156,7 @@ interface StorageProtocol {
|
|||||||
// Settings
|
// Settings
|
||||||
fun setProfileSharing(address: Address, value: Boolean)
|
fun setProfileSharing(address: Address, value: Boolean)
|
||||||
|
|
||||||
|
|
||||||
// Thread
|
// Thread
|
||||||
fun getOrCreateThreadIdFor(address: Address): Long
|
fun getOrCreateThreadIdFor(address: Address): Long
|
||||||
fun getThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?, createThread: Boolean): Long?
|
fun getThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?, createThread: Boolean): Long?
|
||||||
@ -217,6 +218,7 @@ interface StorageProtocol {
|
|||||||
fun updateReactionIfNeeded(message: Message, sender: String, openGroupSentTimestamp: Long)
|
fun updateReactionIfNeeded(message: Message, sender: String, openGroupSentTimestamp: Long)
|
||||||
fun deleteReactions(messageId: Long, mms: Boolean)
|
fun deleteReactions(messageId: Long, mms: Boolean)
|
||||||
fun setBlocked(recipients: List<Recipient>, isBlocked: Boolean, fromConfigUpdate: Boolean = false)
|
fun setBlocked(recipients: List<Recipient>, isBlocked: Boolean, fromConfigUpdate: Boolean = false)
|
||||||
|
fun setRecipientHash(recipient: Recipient, recipientHash: String?)
|
||||||
fun blockedContacts(): List<Recipient>
|
fun blockedContacts(): List<Recipient>
|
||||||
|
|
||||||
// Shared configs
|
// Shared configs
|
||||||
|
@ -33,7 +33,7 @@ class SSKEnvironment(
|
|||||||
fun setName(context: Context, recipient: Recipient, name: String?)
|
fun setName(context: Context, recipient: Recipient, name: String?)
|
||||||
fun setProfilePicture(context: Context, recipient: Recipient, profilePictureURL: String?, profileKey: ByteArray?)
|
fun setProfilePicture(context: Context, recipient: Recipient, profilePictureURL: String?, profileKey: ByteArray?)
|
||||||
fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode)
|
fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode)
|
||||||
fun contactUpdatedInternal(contact: Contact)
|
fun contactUpdatedInternal(contact: Contact): String?
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MessageExpirationManagerProtocol {
|
interface MessageExpirationManagerProtocol {
|
||||||
|
@ -99,6 +99,7 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
private boolean profileSharing;
|
private boolean profileSharing;
|
||||||
private String notificationChannel;
|
private String notificationChannel;
|
||||||
private boolean forceSmsSelection;
|
private boolean forceSmsSelection;
|
||||||
|
private String wrapperHash;
|
||||||
|
|
||||||
private @NonNull UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.ENABLED;
|
private @NonNull UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.ENABLED;
|
||||||
|
|
||||||
@ -279,6 +280,7 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
this.profileSharing = details.profileSharing;
|
this.profileSharing = details.profileSharing;
|
||||||
this.unidentifiedAccessMode = details.unidentifiedAccessMode;
|
this.unidentifiedAccessMode = details.unidentifiedAccessMode;
|
||||||
this.forceSmsSelection = details.forceSmsSelection;
|
this.forceSmsSelection = details.forceSmsSelection;
|
||||||
|
this.wrapperHash = details.wrapperHash;
|
||||||
|
|
||||||
this.participants.addAll(details.participants);
|
this.participants.addAll(details.participants);
|
||||||
this.resolving = false;
|
this.resolving = false;
|
||||||
@ -723,6 +725,14 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
return unidentifiedAccessMode;
|
return unidentifiedAccessMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getWrapperHash() {
|
||||||
|
return wrapperHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWrapperHash(String wrapperHash) {
|
||||||
|
this.wrapperHash = wrapperHash;
|
||||||
|
}
|
||||||
|
|
||||||
public void setUnidentifiedAccessMode(@NonNull UnidentifiedAccessMode unidentifiedAccessMode) {
|
public void setUnidentifiedAccessMode(@NonNull UnidentifiedAccessMode unidentifiedAccessMode) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
this.unidentifiedAccessMode = unidentifiedAccessMode;
|
this.unidentifiedAccessMode = unidentifiedAccessMode;
|
||||||
@ -745,12 +755,12 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
Recipient recipient = (Recipient) o;
|
Recipient recipient = (Recipient) o;
|
||||||
return resolving == recipient.resolving && mutedUntil == recipient.mutedUntil && notifyType == recipient.notifyType && blocked == recipient.blocked && approved == recipient.approved && approvedMe == recipient.approvedMe && expireMessages == recipient.expireMessages && address.equals(recipient.address) && Objects.equals(name, recipient.name) && Objects.equals(customLabel, recipient.customLabel) && Objects.equals(groupAvatarId, recipient.groupAvatarId) && Arrays.equals(profileKey, recipient.profileKey) && Objects.equals(profileName, recipient.profileName) && Objects.equals(profileAvatar, recipient.profileAvatar);
|
return resolving == recipient.resolving && mutedUntil == recipient.mutedUntil && notifyType == recipient.notifyType && blocked == recipient.blocked && approved == recipient.approved && approvedMe == recipient.approvedMe && expireMessages == recipient.expireMessages && address.equals(recipient.address) && Objects.equals(name, recipient.name) && Objects.equals(customLabel, recipient.customLabel) && Objects.equals(groupAvatarId, recipient.groupAvatarId) && Arrays.equals(profileKey, recipient.profileKey) && Objects.equals(profileName, recipient.profileName) && Objects.equals(profileAvatar, recipient.profileAvatar) && Objects.equals(wrapperHash, recipient.wrapperHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = Objects.hash(address, name, customLabel, resolving, groupAvatarId, mutedUntil, notifyType, blocked, approved, approvedMe, expireMessages, profileName, profileAvatar);
|
int result = Objects.hash(address, name, customLabel, resolving, groupAvatarId, mutedUntil, notifyType, blocked, approved, approvedMe, expireMessages, profileName, profileAvatar, wrapperHash);
|
||||||
result = 31 * result + Arrays.hashCode(profileKey);
|
result = 31 * result + Arrays.hashCode(profileKey);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -854,6 +864,7 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
private final String notificationChannel;
|
private final String notificationChannel;
|
||||||
private final UnidentifiedAccessMode unidentifiedAccessMode;
|
private final UnidentifiedAccessMode unidentifiedAccessMode;
|
||||||
private final boolean forceSmsSelection;
|
private final boolean forceSmsSelection;
|
||||||
|
private final String wrapperHash;
|
||||||
|
|
||||||
public RecipientSettings(boolean blocked, boolean approved, boolean approvedMe, long muteUntil,
|
public RecipientSettings(boolean blocked, boolean approved, boolean approvedMe, long muteUntil,
|
||||||
int notifyType,
|
int notifyType,
|
||||||
@ -875,7 +886,8 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
boolean profileSharing,
|
boolean profileSharing,
|
||||||
@Nullable String notificationChannel,
|
@Nullable String notificationChannel,
|
||||||
@NonNull UnidentifiedAccessMode unidentifiedAccessMode,
|
@NonNull UnidentifiedAccessMode unidentifiedAccessMode,
|
||||||
boolean forceSmsSelection)
|
boolean forceSmsSelection,
|
||||||
|
String wrapperHash)
|
||||||
{
|
{
|
||||||
this.blocked = blocked;
|
this.blocked = blocked;
|
||||||
this.approved = approved;
|
this.approved = approved;
|
||||||
@ -901,6 +913,7 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
this.notificationChannel = notificationChannel;
|
this.notificationChannel = notificationChannel;
|
||||||
this.unidentifiedAccessMode = unidentifiedAccessMode;
|
this.unidentifiedAccessMode = unidentifiedAccessMode;
|
||||||
this.forceSmsSelection = forceSmsSelection;
|
this.forceSmsSelection = forceSmsSelection;
|
||||||
|
this.wrapperHash = wrapperHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable MaterialColor getColor() {
|
public @Nullable MaterialColor getColor() {
|
||||||
@ -998,6 +1011,11 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
public boolean isForceSmsSelection() {
|
public boolean isForceSmsSelection() {
|
||||||
return forceSmsSelection;
|
return forceSmsSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getWrapperHash() {
|
||||||
|
return wrapperHash;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -177,6 +177,7 @@ class RecipientProvider {
|
|||||||
@Nullable final String notificationChannel;
|
@Nullable final String notificationChannel;
|
||||||
@NonNull final UnidentifiedAccessMode unidentifiedAccessMode;
|
@NonNull final UnidentifiedAccessMode unidentifiedAccessMode;
|
||||||
final boolean forceSmsSelection;
|
final boolean forceSmsSelection;
|
||||||
|
final String wrapperHash;
|
||||||
|
|
||||||
RecipientDetails(@Nullable String name, @Nullable Long groupAvatarId,
|
RecipientDetails(@Nullable String name, @Nullable Long groupAvatarId,
|
||||||
boolean systemContact, boolean isLocalNumber, @Nullable RecipientSettings settings,
|
boolean systemContact, boolean isLocalNumber, @Nullable RecipientSettings settings,
|
||||||
@ -209,6 +210,7 @@ class RecipientProvider {
|
|||||||
this.notificationChannel = settings != null ? settings.getNotificationChannel() : null;
|
this.notificationChannel = settings != null ? settings.getNotificationChannel() : null;
|
||||||
this.unidentifiedAccessMode = settings != null ? settings.getUnidentifiedAccessMode() : UnidentifiedAccessMode.DISABLED;
|
this.unidentifiedAccessMode = settings != null ? settings.getUnidentifiedAccessMode() : UnidentifiedAccessMode.DISABLED;
|
||||||
this.forceSmsSelection = settings != null && settings.isForceSmsSelection();
|
this.forceSmsSelection = settings != null && settings.isForceSmsSelection();
|
||||||
|
this.wrapperHash = settings != null ? settings.getWrapperHash() : null;
|
||||||
|
|
||||||
if (name == null && settings != null) this.name = settings.getSystemDisplayName();
|
if (name == null && settings != null) this.name = settings.getSystemDisplayName();
|
||||||
else this.name = name;
|
else this.name = name;
|
||||||
|
Loading…
Reference in New Issue
Block a user