mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-03 11:12:40 +00:00
Remove the concept of friend requests
This commit is contained in:
@@ -8,9 +8,8 @@ class CreateClosedGroupLoader(context: Context) : AsyncLoader<List<String>>(cont
|
||||
|
||||
override fun loadInBackground(): List<String> {
|
||||
val contacts = ContactUtilities.getAllContacts(context)
|
||||
// Only show the master devices of the users we are friends with
|
||||
return contacts.filter { contact ->
|
||||
!contact.recipient.isGroupRecipient && contact.isFriend && !contact.isOurDevice && !contact.isSlave
|
||||
!contact.recipient.isGroupRecipient && !contact.isOurDevice && !contact.isSlave
|
||||
}.map {
|
||||
it.recipient.address.toPhoneString()
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||
import org.thoughtcrime.securesms.loki.dialogs.PNModeBottomSheet
|
||||
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol
|
||||
import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation
|
||||
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.thoughtcrime.securesms.loki.views.ConversationView
|
||||
import org.thoughtcrime.securesms.loki.views.NewConversationButtonSetViewDelegate
|
||||
@@ -47,18 +47,16 @@ import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.syncmessages.SyncMessagesProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
import kotlin.math.abs
|
||||
|
||||
class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListener, SeedReminderViewDelegate, NewConversationButtonSetViewDelegate {
|
||||
private lateinit var glide: GlideRequests
|
||||
|
||||
private val hexEncodedPublicKey: String
|
||||
private val publicKey: String
|
||||
get() {
|
||||
val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this)
|
||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||
return masterHexEncodedPublicKey ?: userHexEncodedPublicKey
|
||||
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this)
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||
return masterPublicKey ?: userPublicKey
|
||||
}
|
||||
|
||||
// region Lifecycle
|
||||
@@ -94,7 +92,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
||||
glide = GlideApp.with(this)
|
||||
// Set up toolbar buttons
|
||||
profileButton.glide = glide
|
||||
profileButton.hexEncodedPublicKey = hexEncodedPublicKey
|
||||
profileButton.publicKey = publicKey
|
||||
profileButton.update()
|
||||
profileButton.setOnClickListener { openSettings() }
|
||||
pathStatusViewContainer.setOnClickListener { showPath() }
|
||||
@@ -156,45 +154,23 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
||||
val threadDB = DatabaseFactory.getLokiThreadDatabase(this)
|
||||
val userDB = DatabaseFactory.getLokiUserDatabase(this)
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||
val sessionResetImpl = LokiSessionResetImplementation(this)
|
||||
val sessionResetImpl = SessionResetImplementation(this)
|
||||
if (userPublicKey != null) {
|
||||
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB)
|
||||
SessionMetaProtocol.configureIfNeeded(apiDB, userPublicKey)
|
||||
SyncMessagesProtocol.configureIfNeeded(apiDB, userPublicKey)
|
||||
application.lokiPublicChatManager.startPollersIfNeeded()
|
||||
application.publicChatManager.startPollersIfNeeded()
|
||||
}
|
||||
SessionManagementProtocol.configureIfNeeded(sessionResetImpl, threadDB, application)
|
||||
MultiDeviceProtocol.configureIfNeeded(apiDB)
|
||||
IP2Country.configureIfNeeded(this)
|
||||
// Preload device links to make message sending quicker
|
||||
val publicKeys = ContactUtilities.getAllContacts(this).filter { contact ->
|
||||
!contact.recipient.isGroupRecipient && contact.isFriend && !contact.isOurDevice && !contact.isSlave
|
||||
!contact.recipient.isGroupRecipient && !contact.isOurDevice && !contact.isSlave
|
||||
}.map {
|
||||
it.recipient.address.toPhoneString()
|
||||
}.toSet()
|
||||
FileServerAPI.shared.getDeviceLinks(publicKeys)
|
||||
// TODO: Temporary hack to unbork existing clients
|
||||
val allContacts = DatabaseFactory.getRecipientDatabase(this).allAddresses.map {
|
||||
MultiDeviceProtocol.shared.getMasterDevice(it.serialize()) ?: it.serialize()
|
||||
}.toSet()
|
||||
val lokiMessageDB = DatabaseFactory.getLokiMessageDatabase(this)
|
||||
for (contact in allContacts) {
|
||||
val slaveDeviceHasPendingFR = MultiDeviceProtocol.shared.getSlaveDevices(contact).any {
|
||||
val slaveDeviceThreadID = DatabaseFactory.getThreadDatabase(this).getThreadIdFor(recipient(this, it))
|
||||
DatabaseFactory.getLokiThreadDatabase(this).getFriendRequestStatus(slaveDeviceThreadID) == LokiThreadFriendRequestStatus.REQUEST_RECEIVED
|
||||
}
|
||||
val masterDeviceThreadID = DatabaseFactory.getThreadDatabase(this).getThreadIdFor(recipient(this, contact))
|
||||
val masterDeviceHasNoPendingFR = (DatabaseFactory.getLokiThreadDatabase(this).getFriendRequestStatus(masterDeviceThreadID) == LokiThreadFriendRequestStatus.NONE)
|
||||
if (slaveDeviceHasPendingFR && masterDeviceHasNoPendingFR) {
|
||||
val lastMessageID = org.thoughtcrime.securesms.loki.protocol.FriendRequestProtocol.getLastMessageID(this, masterDeviceThreadID)
|
||||
if (lastMessageID != null) {
|
||||
val lastMessageFRStatus = lokiMessageDB.getFriendRequestStatus(lastMessageID)
|
||||
if (lastMessageFRStatus != LokiMessageFriendRequestStatus.REQUEST_PENDING) {
|
||||
lokiMessageDB.setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_PENDING)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||
import org.thoughtcrime.securesms.logging.Log
|
||||
import org.thoughtcrime.securesms.loki.dialogs.LinkDeviceSlaveModeDialog
|
||||
import org.thoughtcrime.securesms.loki.dialogs.LinkDeviceSlaveModeDialogDelegate
|
||||
import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation
|
||||
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation
|
||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
||||
import org.thoughtcrime.securesms.loki.utilities.push
|
||||
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
|
||||
@@ -109,7 +109,7 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
|
||||
val threadDB = DatabaseFactory.getLokiThreadDatabase(this)
|
||||
val userDB = DatabaseFactory.getLokiUserDatabase(this)
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||
val sessionResetImpl = LokiSessionResetImplementation(this)
|
||||
val sessionResetImpl = SessionResetImplementation(this)
|
||||
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB)
|
||||
SessionMetaProtocol.configureIfNeeded(apiDB, userPublicKey)
|
||||
org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol.configureIfNeeded(apiDB)
|
||||
|
||||
@@ -73,7 +73,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
showQRCodeButton.setOnClickListener { showQRCode() }
|
||||
glide = GlideApp.with(this)
|
||||
profilePictureView.glide = glide
|
||||
profilePictureView.hexEncodedPublicKey = hexEncodedPublicKey
|
||||
profilePictureView.publicKey = hexEncodedPublicKey
|
||||
profilePictureView.isLarge = true
|
||||
profilePictureView.update()
|
||||
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
|
||||
|
||||
@@ -47,7 +47,7 @@ class BackgroundPollWorker : PersistentAlarmManagerListener() {
|
||||
}
|
||||
val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { it.value }
|
||||
for (openGroup in openGroups) {
|
||||
val poller = LokiPublicChatPoller(context, openGroup)
|
||||
val poller = PublicChatPoller(context, openGroup)
|
||||
poller.stop()
|
||||
poller.pollForNewMessages()
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
|
||||
|
||||
class LokiPublicChatManager(private val context: Context) {
|
||||
class PublicChatManager(private val context: Context) {
|
||||
private var chats = mutableMapOf<Long, PublicChat>()
|
||||
private val pollers = mutableMapOf<Long, LokiPublicChatPoller>()
|
||||
private val pollers = mutableMapOf<Long, PublicChatPoller>()
|
||||
private val observers = mutableMapOf<Long, ContentObserver>()
|
||||
private var isPolling = false
|
||||
|
||||
@@ -24,7 +24,7 @@ class LokiPublicChatManager(private val context: Context) {
|
||||
var areAllCaughtUp = true
|
||||
refreshChatsAndPollers()
|
||||
for ((threadID, chat) in chats) {
|
||||
val poller = pollers[threadID] ?: LokiPublicChatPoller(context, chat)
|
||||
val poller = pollers[threadID] ?: PublicChatPoller(context, chat)
|
||||
areAllCaughtUp = areAllCaughtUp && poller.isCaughtUp
|
||||
}
|
||||
return areAllCaughtUp
|
||||
@@ -33,7 +33,7 @@ class LokiPublicChatManager(private val context: Context) {
|
||||
public fun markAllAsNotCaughtUp() {
|
||||
refreshChatsAndPollers()
|
||||
for ((threadID, chat) in chats) {
|
||||
val poller = pollers[threadID] ?: LokiPublicChatPoller(context, chat)
|
||||
val poller = pollers[threadID] ?: PublicChatPoller(context, chat)
|
||||
poller.isCaughtUp = false
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class LokiPublicChatManager(private val context: Context) {
|
||||
refreshChatsAndPollers()
|
||||
|
||||
for ((threadId, chat) in chats) {
|
||||
val poller = pollers[threadId] ?: LokiPublicChatPoller(context, chat)
|
||||
val poller = pollers[threadId] ?: PublicChatPoller(context, chat)
|
||||
poller.startIfNeeded()
|
||||
listenToThreadDeletion(threadId)
|
||||
if (!pollers.containsKey(threadId)) { pollers[threadId] = poller }
|
||||
@@ -27,11 +27,10 @@ import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
|
||||
import org.whispersystems.signalservice.loki.api.opengroups.PublicChatAPI
|
||||
import org.whispersystems.signalservice.loki.api.opengroups.PublicChatMessage
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
|
||||
class LokiPublicChatPoller(private val context: Context, private val group: PublicChat) {
|
||||
class PublicChatPoller(private val context: Context, private val group: PublicChat) {
|
||||
private val handler = Handler()
|
||||
private var hasStarted = false
|
||||
public var isCaughtUp = false
|
||||
@@ -182,13 +181,6 @@ class LokiPublicChatPoller(private val context: Context, private val group: Publ
|
||||
database.setProfileKey(senderAsRecipient, profileKey)
|
||||
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderAsRecipient, url))
|
||||
}
|
||||
} else if (senderAsRecipient.profileAvatar.orEmpty().isNotEmpty()) {
|
||||
// Clear the profile picture if we had a profile picture before and we're not friends with the person
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(senderAsRecipient)
|
||||
val friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID)
|
||||
if (friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) {
|
||||
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderAsRecipient, ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
fun processOutgoingMessage(message: PublicChatMessage) {
|
||||
@@ -11,12 +11,11 @@ import org.thoughtcrime.securesms.loki.utilities.getInt
|
||||
import org.thoughtcrime.securesms.loki.utilities.getString
|
||||
import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate
|
||||
import org.whispersystems.signalservice.loki.database.LokiMessageDatabaseProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
||||
|
||||
class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiMessageDatabaseProtocol {
|
||||
|
||||
companion object {
|
||||
private val messageFriendRequestTable = "loki_message_friend_request_database"
|
||||
private val messageIDTable = "loki_message_friend_request_database"
|
||||
private val messageThreadMappingTable = "loki_message_thread_mapping_database"
|
||||
private val errorMessageTable = "loki_error_message_database"
|
||||
private val messageID = "message_id"
|
||||
@@ -24,7 +23,7 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
||||
private val friendRequestStatus = "friend_request_status"
|
||||
private val threadID = "thread_id"
|
||||
private val errorMessage = "error_message"
|
||||
@JvmStatic val createMessageFriendRequestTableCommand = "CREATE TABLE $messageFriendRequestTable ($messageID INTEGER PRIMARY KEY, $serverID INTEGER DEFAULT 0, $friendRequestStatus INTEGER DEFAULT 0);"
|
||||
@JvmStatic val createMessageIDTableCommand = "CREATE TABLE $messageIDTable ($messageID INTEGER PRIMARY KEY, $serverID INTEGER DEFAULT 0, $friendRequestStatus INTEGER DEFAULT 0);"
|
||||
@JvmStatic val createMessageToThreadMappingTableCommand = "CREATE TABLE IF NOT EXISTS $messageThreadMappingTable ($messageID INTEGER PRIMARY KEY, $threadID INTEGER);"
|
||||
@JvmStatic val createErrorMessageTableCommand = "CREATE TABLE IF NOT EXISTS $errorMessageTable ($messageID INTEGER PRIMARY KEY, $errorMessage STRING);"
|
||||
}
|
||||
@@ -36,14 +35,14 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
||||
|
||||
fun getServerID(messageID: Long): Long? {
|
||||
val database = databaseHelper.readableDatabase
|
||||
return database.get(messageFriendRequestTable, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor ->
|
||||
return database.get(messageIDTable, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor ->
|
||||
cursor.getInt(serverID)
|
||||
}?.toLong()
|
||||
}
|
||||
|
||||
fun getMessageID(serverID: Long): Long? {
|
||||
val database = databaseHelper.readableDatabase
|
||||
return database.get(messageFriendRequestTable, "${Companion.serverID} = ?", arrayOf( serverID.toString() )) { cursor ->
|
||||
return database.get(messageIDTable, "${Companion.serverID} = ?", arrayOf( serverID.toString() )) { cursor ->
|
||||
cursor.getInt(messageID)
|
||||
}?.toLong()
|
||||
}
|
||||
@@ -53,7 +52,7 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
||||
val contentValues = ContentValues(2)
|
||||
contentValues.put(Companion.messageID, messageID)
|
||||
contentValues.put(Companion.serverID, serverID)
|
||||
database.insertOrUpdate(messageFriendRequestTable, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() ))
|
||||
database.insertOrUpdate(messageIDTable, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() ))
|
||||
}
|
||||
|
||||
fun getOriginalThreadID(messageID: Long): Long {
|
||||
@@ -71,32 +70,6 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
||||
database.insertOrUpdate(messageThreadMappingTable, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() ))
|
||||
}
|
||||
|
||||
fun getFriendRequestStatus(messageID: Long): LokiMessageFriendRequestStatus {
|
||||
val database = databaseHelper.readableDatabase
|
||||
val result = database.get(messageFriendRequestTable, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor ->
|
||||
cursor.getInt(friendRequestStatus)
|
||||
}
|
||||
return if (result != null) {
|
||||
LokiMessageFriendRequestStatus.values().first { it.rawValue == result }
|
||||
} else {
|
||||
LokiMessageFriendRequestStatus.NONE
|
||||
}
|
||||
}
|
||||
|
||||
fun setFriendRequestStatus(messageID: Long, friendRequestStatus: LokiMessageFriendRequestStatus) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
val contentValues = ContentValues(2)
|
||||
contentValues.put(Companion.messageID, messageID)
|
||||
contentValues.put(Companion.friendRequestStatus, friendRequestStatus.rawValue)
|
||||
database.insertOrUpdate(messageFriendRequestTable, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() ))
|
||||
val threadID = DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID)
|
||||
notifyConversationListeners(threadID)
|
||||
}
|
||||
|
||||
fun isFriendRequest(messageID: Long): Boolean {
|
||||
return getFriendRequestStatus(messageID) != LokiMessageFriendRequestStatus.NONE
|
||||
}
|
||||
|
||||
fun getErrorMessage(messageID: Long): String? {
|
||||
val database = databaseHelper.readableDatabase
|
||||
return database.get(errorMessageTable, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor ->
|
||||
|
||||
@@ -15,21 +15,18 @@ import org.whispersystems.libsignal.loki.SessionResetStatus
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
|
||||
import org.whispersystems.signalservice.loki.database.LokiThreadDatabaseProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
|
||||
|
||||
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol {
|
||||
var delegate: LokiThreadDatabaseDelegate? = null
|
||||
|
||||
companion object {
|
||||
private val friendRequestTable = "loki_thread_friend_request_database"
|
||||
private val sessionResetTable = "loki_thread_session_reset_database"
|
||||
val publicChatTable = "loki_public_chat_database"
|
||||
val threadID = "thread_id"
|
||||
private val friendRequestStatus = "friend_request_status"
|
||||
private val sessionResetStatus = "session_reset_status"
|
||||
val publicChat = "public_chat"
|
||||
@JvmStatic val createFriendRequestTableCommand = "CREATE TABLE $friendRequestTable ($threadID INTEGER PRIMARY KEY, $friendRequestStatus INTEGER DEFAULT 0);"
|
||||
@JvmStatic val createSessionResetTableCommand = "CREATE TABLE $sessionResetTable ($threadID INTEGER PRIMARY KEY, $sessionResetStatus INTEGER DEFAULT 0);"
|
||||
@JvmStatic val createPublicChatTableCommand = "CREATE TABLE $publicChatTable ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);"
|
||||
}
|
||||
@@ -44,34 +41,6 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
||||
return DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID)
|
||||
}
|
||||
|
||||
fun getFriendRequestStatus(threadID: Long): LokiThreadFriendRequestStatus {
|
||||
if (threadID < 0) { return LokiThreadFriendRequestStatus.NONE }
|
||||
val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadID)
|
||||
if (recipient != null && recipient.isGroupRecipient) { return LokiThreadFriendRequestStatus.FRIENDS; }
|
||||
val database = databaseHelper.readableDatabase
|
||||
val result = database.get(friendRequestTable, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor ->
|
||||
cursor.getInt(friendRequestStatus)
|
||||
}
|
||||
return if (result != null) {
|
||||
LokiThreadFriendRequestStatus.values().first { it.rawValue == result }
|
||||
} else {
|
||||
LokiThreadFriendRequestStatus.NONE
|
||||
}
|
||||
}
|
||||
|
||||
fun setFriendRequestStatus(threadID: Long, friendRequestStatus: LokiThreadFriendRequestStatus) {
|
||||
if (threadID < 0) { return }
|
||||
Log.d("Loki", "Setting FR status for thread with ID $threadID to $friendRequestStatus.")
|
||||
val database = databaseHelper.writableDatabase
|
||||
val contentValues = ContentValues(2)
|
||||
contentValues.put(Companion.threadID, threadID)
|
||||
contentValues.put(Companion.friendRequestStatus, friendRequestStatus.rawValue)
|
||||
database.insertOrUpdate(friendRequestTable, contentValues, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
|
||||
notifyConversationListListeners()
|
||||
notifyConversationListeners(threadID)
|
||||
delegate?.handleThreadFriendRequestStatusChanged(threadID)
|
||||
}
|
||||
|
||||
fun getSessionResetStatus(hexEncodedPublicKey: String): SessionResetStatus {
|
||||
val threadID = getThreadID(hexEncodedPublicKey)
|
||||
val database = databaseHelper.readableDatabase
|
||||
|
||||
@@ -2,6 +2,5 @@ package org.thoughtcrime.securesms.loki.database
|
||||
|
||||
interface LokiThreadDatabaseDelegate {
|
||||
|
||||
fun handleThreadFriendRequestStatusChanged(threadID: Long)
|
||||
fun handleSessionRestoreDevicesChanged(threadID: Long)
|
||||
}
|
||||
@@ -15,10 +15,10 @@ sealed class ContactSelectionListItem {
|
||||
class ContactSelectionListLoader(context: Context, val mode: Int, val filter: String?) : AsyncLoader<List<ContactSelectionListItem>>(context) {
|
||||
|
||||
object DisplayMode {
|
||||
const val FLAG_FRIENDS = 1
|
||||
const val FLAG_CONTACTS = 1
|
||||
const val FLAG_CLOSED_GROUPS = 1 shl 1
|
||||
const val FLAG_OPEN_GROUPS = 1 shl 2
|
||||
const val FLAG_ALL = FLAG_FRIENDS or FLAG_CLOSED_GROUPS or FLAG_OPEN_GROUPS
|
||||
const val FLAG_ALL = FLAG_CONTACTS or FLAG_CLOSED_GROUPS or FLAG_OPEN_GROUPS
|
||||
}
|
||||
|
||||
private fun isFlagSet(flag: Int): Boolean {
|
||||
@@ -39,15 +39,15 @@ class ContactSelectionListLoader(context: Context, val mode: Int, val filter: St
|
||||
if (isFlagSet(DisplayMode.FLAG_OPEN_GROUPS)) {
|
||||
list.addAll(getOpenGroups(contacts))
|
||||
}
|
||||
if (isFlagSet(DisplayMode.FLAG_FRIENDS)) {
|
||||
list.addAll(getFriends(contacts))
|
||||
if (isFlagSet(DisplayMode.FLAG_CONTACTS)) {
|
||||
list.addAll(getContacts(contacts))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
private fun getFriends(contacts: List<Contact>): List<ContactSelectionListItem> {
|
||||
private fun getContacts(contacts: List<Contact>): List<ContactSelectionListItem> {
|
||||
return getItems(contacts, context.getString(R.string.fragment_contact_selection_contacts_title)) {
|
||||
!it.recipient.isGroupRecipient && it.isFriend && !it.isOurDevice && !it.isSlave
|
||||
!it.recipient.isGroupRecipient && !it.isOurDevice && !it.isSlave
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,7 @@ class EphemeralMessage private constructor(val data: Map<*, *>) {
|
||||
fun create(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey ))
|
||||
|
||||
@JvmStatic
|
||||
fun createUnlinkingRequest(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey, "unpairingRequest" to true ))
|
||||
|
||||
@JvmStatic
|
||||
fun createSessionRestorationRequest(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey, "friendRequest" to true, "sessionRestore" to true ))
|
||||
fun createDeviceUnlinkingRequest(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey, "unpairingRequest" to true ))
|
||||
|
||||
@JvmStatic
|
||||
fun createSessionRequest(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey, "friendRequest" to true, "sessionRequest" to true ))
|
||||
|
||||
@@ -1,316 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki.protocol
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.utilities.recipient
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
|
||||
object FriendRequestProtocol {
|
||||
|
||||
@JvmStatic
|
||||
fun acceptFriendRequest(context: Context, recipient: Recipient) {
|
||||
if (recipient.isGroupRecipient) { return; }
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
|
||||
// Accept all outstanding friend requests associated with this user and try to establish sessions with the
|
||||
// subset of their devices that haven't sent a friend request.
|
||||
val allContactDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.address.serialize())
|
||||
val threadDB = DatabaseFactory.getThreadDatabase(context)
|
||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
for (device in allContactDevices) {
|
||||
val deviceAsRecipient = recipient(context, device)
|
||||
val deviceThreadID = threadDB.getThreadIdFor(deviceAsRecipient)
|
||||
val deviceFRStatus = lokiThreadDB.getFriendRequestStatus(deviceThreadID)
|
||||
if (deviceFRStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) {
|
||||
lokiThreadDB.setFriendRequestStatus(deviceThreadID, LokiThreadFriendRequestStatus.FRIENDS)
|
||||
val lastMessageID = getLastMessageID(context, deviceThreadID)
|
||||
if (lastMessageID != null) {
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED)
|
||||
}
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient(context, device), true)
|
||||
val ephemeralMessage = EphemeralMessage.create(device)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
||||
// Sync contact if needed
|
||||
if (allUserDevices.contains(device)) { return }
|
||||
val deviceToSync = MultiDeviceProtocol.shared.getMasterDevice(device) ?: device
|
||||
SyncMessagesProtocol.syncContact(context, Address.fromSerialized(deviceToSync))
|
||||
} else if (deviceFRStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
|
||||
// Do nothing
|
||||
} else if (!allUserDevices.contains(device)
|
||||
&& (deviceFRStatus == LokiThreadFriendRequestStatus.NONE || deviceFRStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED)) {
|
||||
sendAutoGeneratedFriendRequest(context, device)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun rejectFriendRequest(context: Context, recipient: Recipient) {
|
||||
if (recipient.isGroupRecipient) { return; }
|
||||
val linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.address.serialize())
|
||||
val threadDB = DatabaseFactory.getThreadDatabase(context)
|
||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
for (device in linkedDevices) {
|
||||
val deviceAsRecipient = recipient(context, device)
|
||||
val deviceThreadID = threadDB.getThreadIdFor(deviceAsRecipient)
|
||||
val deviceFRStatus = lokiThreadDB.getFriendRequestStatus(deviceThreadID)
|
||||
// We only want to decline incoming requests
|
||||
if (deviceFRStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) {
|
||||
// Delete the pre key bundle for the given contact. This ensures that if we send a
|
||||
// new message after this, it restarts the friend request process from scratch.
|
||||
DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(device)
|
||||
lokiThreadDB.setFriendRequestStatus(deviceThreadID, LokiThreadFriendRequestStatus.NONE)
|
||||
val lastMessageID = getLastMessageID(context, deviceThreadID)
|
||||
if (lastMessageID != null) {
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_REJECTED)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun shouldInputPanelBeEnabled(context: Context, recipient: Recipient): Boolean {
|
||||
// Friend requests have nothing to do with groups, so if this is a group thread the input panel should be enabled
|
||||
if (recipient.isGroupRecipient) { return true }
|
||||
// If this is a note to self the input panel should be enabled
|
||||
if (SessionMetaProtocol.shared.isNoteToSelf(recipient.address.serialize())) { return true }
|
||||
// Gather friend request statuses
|
||||
val linkedDeviceFRStatuses = mutableSetOf<LokiThreadFriendRequestStatus>()
|
||||
val linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.address.serialize())
|
||||
val threadDB = DatabaseFactory.getThreadDatabase(context)
|
||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
for (device in linkedDevices) {
|
||||
val deviceAsRecipient = recipient(context, device)
|
||||
val deviceThreadID = threadDB.getThreadIdFor(deviceAsRecipient)
|
||||
val deviceFRStatus = lokiThreadDB.getFriendRequestStatus(deviceThreadID)
|
||||
linkedDeviceFRStatuses.add(deviceFRStatus)
|
||||
}
|
||||
// If the user is friends with any of the other user's devices the input panel should be enabled
|
||||
if (linkedDeviceFRStatuses.contains(LokiThreadFriendRequestStatus.FRIENDS)) { return true }
|
||||
// If no friend request has been sent the input panel should be enabled
|
||||
if (linkedDeviceFRStatuses.all { it == LokiThreadFriendRequestStatus.NONE || it == LokiThreadFriendRequestStatus.REQUEST_EXPIRED }) { return true }
|
||||
// There must be a pending friend request
|
||||
return false
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun shouldAttachmentButtonBeEnabled(context: Context, recipient: Recipient): Boolean {
|
||||
// Friend requests have nothing to do with groups, so if this is a group thread the attachment button should be enabled
|
||||
if (recipient.isGroupRecipient) { return true }
|
||||
// If this is a note to self the attachment button should be enabled
|
||||
if (SessionMetaProtocol.shared.isNoteToSelf(recipient.address.serialize())) { return true }
|
||||
// Gather friend request statuses
|
||||
val linkedDeviceFRStatuses = mutableSetOf<LokiThreadFriendRequestStatus>()
|
||||
val linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.address.serialize())
|
||||
val threadDB = DatabaseFactory.getThreadDatabase(context)
|
||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
for (device in linkedDevices) {
|
||||
val deviceAsRecipient = recipient(context, device)
|
||||
val deviceThreadID = threadDB.getThreadIdFor(deviceAsRecipient)
|
||||
val deviceFRStatus = lokiThreadDB.getFriendRequestStatus(deviceThreadID)
|
||||
linkedDeviceFRStatuses.add(deviceFRStatus)
|
||||
}
|
||||
// If the user is friends with any of the other user's devices the attachment button should be enabled
|
||||
if (linkedDeviceFRStatuses.contains(LokiThreadFriendRequestStatus.FRIENDS)) { return true }
|
||||
// Otherwise don't allow attachments
|
||||
return false
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getLastMessageID(context: Context, threadID: Long): Long? {
|
||||
val db = DatabaseFactory.getSmsDatabase(context)
|
||||
val messageCount = db.getMessageCountForThread(threadID)
|
||||
if (messageCount == 0) { return null }
|
||||
return db.getIDForMessageAtIndex(threadID, messageCount - 1)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun handleFriendRequestAcceptanceIfNeeded(context: Context, publicKey: String, content: SignalServiceContent) {
|
||||
// If we get an envelope that isn't a friend request, then we can infer that we had to use
|
||||
// Signal cipher decryption and thus that we have a session with the other person.
|
||||
val recipient = recipient(context, publicKey)
|
||||
// Friend requests don't apply to groups
|
||||
if (recipient.isGroupRecipient) { return }
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val threadFRStatus = lokiThreadDB.getFriendRequestStatus(threadID)
|
||||
// Guard against invalid state transitions
|
||||
if (threadFRStatus != LokiThreadFriendRequestStatus.REQUEST_SENDING && threadFRStatus != LokiThreadFriendRequestStatus.REQUEST_SENT
|
||||
&& threadFRStatus != LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { return }
|
||||
Log.d("Loki", "Received a friend request accepted message from $publicKey.")
|
||||
lokiThreadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS)
|
||||
val lastMessageID = getLastMessageID(context, threadID)
|
||||
if (lastMessageID != null) {
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED)
|
||||
}
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true)
|
||||
// Send a contact sync message if needed
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
|
||||
if (allUserDevices.contains(publicKey)) { return }
|
||||
val deviceToSync = MultiDeviceProtocol.shared.getMasterDevice(publicKey) ?: publicKey
|
||||
SyncMessagesProtocol.syncContact(context, Address.fromSerialized(deviceToSync))
|
||||
}
|
||||
|
||||
private fun canFriendRequestBeAutoAccepted(context: Context, publicKey: String): Boolean {
|
||||
val recipient = recipient(context, publicKey)
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val threadFRStatus = lokiThreadDB.getFriendRequestStatus(threadID)
|
||||
if (threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
|
||||
// This can happen if Alice sent Bob a friend request, Bob declined, but then Bob changed his
|
||||
// mind and sent a friend request to Alice. In this case we want Alice to auto-accept the request
|
||||
// and send a friend request accepted message back to Bob. We don't check that sending the
|
||||
// friend request accepted message succeeds. Even if it doesn't, the thread's current friend
|
||||
// request status will be set to FRIENDS for Alice making it possible for Alice to send messages
|
||||
// to Bob. When Bob receives a message, his thread's friend request status will then be set to
|
||||
// FRIENDS. If we do check for a successful send before updating Alice's thread's friend request
|
||||
// status to FRIENDS, we can end up in a deadlock where both users' threads' friend request statuses
|
||||
// are SENT.
|
||||
return true
|
||||
}
|
||||
// Auto-accept any friend requests from the user's own linked devices
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
|
||||
if (allUserDevices.contains(publicKey)) { return true }
|
||||
// Auto-accept if the user is friends with any of the sender's linked devices.
|
||||
val allSenderDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(publicKey)
|
||||
if (allSenderDevices.any { device ->
|
||||
val deviceAsRecipient = recipient(context, publicKey)
|
||||
val deviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(deviceAsRecipient)
|
||||
lokiThreadDB.getFriendRequestStatus(deviceThreadID) == LokiThreadFriendRequestStatus.FRIENDS
|
||||
}) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun handleFriendRequestMessageIfNeeded(context: Context, publicKey: String, content: SignalServiceContent) {
|
||||
val recipient = recipient(context, publicKey)
|
||||
// Friend requests don't apply to groups
|
||||
if (recipient.isGroupRecipient) { return }
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val threadFRStatus = lokiThreadDB.getFriendRequestStatus(threadID)
|
||||
if (canFriendRequestBeAutoAccepted(context, publicKey)) {
|
||||
Log.d("Loki", "Auto-accepting friend request from $publicKey.")
|
||||
lokiThreadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS)
|
||||
val lastMessageID = getLastMessageID(context, threadID)
|
||||
if (lastMessageID != null) {
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED)
|
||||
}
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true)
|
||||
val ephemeralMessage = EphemeralMessage.create(publicKey)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
||||
} else if (threadFRStatus != LokiThreadFriendRequestStatus.FRIENDS) {
|
||||
Log.d("Loki", "Handling friend request from $publicKey.")
|
||||
// Checking that the sender of the message isn't already a friend is necessary because otherwise
|
||||
// the following situation can occur: Alice and Bob are friends. Bob loses his database and his
|
||||
// friend request status is reset to NONE. Bob now sends Alice a friend request. Alice's thread's
|
||||
// friend request status is reset to RECEIVED
|
||||
lokiThreadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_RECEIVED)
|
||||
val masterPublicKey = MultiDeviceProtocol.shared.getMasterDevice(publicKey) ?: publicKey
|
||||
val masterThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient(context, masterPublicKey))
|
||||
val masterThreadLastMessageID = getLastMessageID(context, masterThreadID) // Messages get routed into the master thread
|
||||
if (masterThreadLastMessageID != null) {
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(masterThreadLastMessageID, LokiMessageFriendRequestStatus.REQUEST_PENDING)
|
||||
} else {
|
||||
// Device link fetching could fail, in which case the message could get routed into the slave thread
|
||||
val slaveThreadLastMessageID = getLastMessageID(context, threadID)
|
||||
if (slaveThreadLastMessageID != null) {
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(slaveThreadLastMessageID, LokiMessageFriendRequestStatus.REQUEST_PENDING)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isFriendRequestFromBeforeRestoration(context: Context, content: SignalServiceContent): Boolean {
|
||||
return content.timestamp < TextSecurePreferences.getRestorationTime(context)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun shouldUpdateFriendRequestStatusFromOutgoingTextMessage(context: Context, message: OutgoingTextMessage): Boolean {
|
||||
// The order of these checks matters
|
||||
if (message.recipient.isGroupRecipient) { return false }
|
||||
if (message.recipient.address.serialize() == TextSecurePreferences.getLocalNumber(context)) { return false }
|
||||
// TODO: Return true if the message is a device linking request
|
||||
// TODO: Return false if the message is a session request
|
||||
return message.isFriendRequest
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun shouldUpdateFriendRequestStatusFromOutgoingMediaMessage(context: Context, message: OutgoingMediaMessage): Boolean {
|
||||
// The order of these checks matters
|
||||
if (message.recipient.isGroupRecipient) { return false }
|
||||
if (message.recipient.address.serialize() == TextSecurePreferences.getLocalNumber(context)) { return false }
|
||||
// TODO: Return true if the message is a device linking request
|
||||
// TODO: Return false if the message is a session request
|
||||
return message.isFriendRequest
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setFriendRequestStatusToSendingIfNeeded(context: Context, messageID: Long, threadID: Long) {
|
||||
val messageDB = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
val messageFRStatus = messageDB.getFriendRequestStatus(messageID)
|
||||
if (messageFRStatus == LokiMessageFriendRequestStatus.NONE || messageFRStatus == LokiMessageFriendRequestStatus.REQUEST_EXPIRED) {
|
||||
messageDB.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_SENDING)
|
||||
}
|
||||
val threadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val threadFRStatus = threadDB.getFriendRequestStatus(threadID)
|
||||
if (threadFRStatus == LokiThreadFriendRequestStatus.NONE || threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
|
||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_SENDING)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setFriendRequestStatusToSentIfNeeded(context: Context, messageID: Long, threadID: Long) {
|
||||
val messageDB = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
val messageFRStatus = messageDB.getFriendRequestStatus(messageID)
|
||||
if (messageFRStatus == LokiMessageFriendRequestStatus.NONE || messageFRStatus == LokiMessageFriendRequestStatus.REQUEST_EXPIRED
|
||||
|| messageFRStatus == LokiMessageFriendRequestStatus.REQUEST_SENDING) {
|
||||
messageDB.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING)
|
||||
}
|
||||
val threadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val threadFRStatus = threadDB.getFriendRequestStatus(threadID)
|
||||
if (threadFRStatus == LokiThreadFriendRequestStatus.NONE || threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED
|
||||
|| threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING) {
|
||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_SENT)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setFriendRequestStatusToFailedIfNeeded(context: Context, messageID: Long, threadID: Long) {
|
||||
val messageDB = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
val messageFRStatus = messageDB.getFriendRequestStatus(messageID)
|
||||
if (messageFRStatus == LokiMessageFriendRequestStatus.REQUEST_SENDING) {
|
||||
messageDB.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_FAILED)
|
||||
}
|
||||
val threadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val threadFRStatus = threadDB.getFriendRequestStatus(threadID)
|
||||
if (threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING) {
|
||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.NONE)
|
||||
}
|
||||
}
|
||||
|
||||
fun sendAutoGeneratedFriendRequest(context: Context, publicKey: String) {
|
||||
val recipient = recipient(context, publicKey)
|
||||
val message = OutgoingEncryptedMessage(recipient, "Please accept to enable messages to be synced across devices", 0)
|
||||
message.isFriendRequest = true
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
MessageSender.send(context, message, threadID, false, null)
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLinkingSession
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
|
||||
|
||||
object MultiDeviceProtocol {
|
||||
@@ -40,36 +39,6 @@ object MultiDeviceProtocol {
|
||||
sendMessagePush(context, recipient, messageID, MessageType.Media, false)
|
||||
}
|
||||
|
||||
private fun sendMessagePushToDevice(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType, isEndSession: Boolean): PushSendJob {
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
val threadFRStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID)
|
||||
val isNoteToSelf = SessionMetaProtocol.shared.isNoteToSelf(recipient.address.serialize())
|
||||
val isContactFriend = (threadFRStatus == LokiThreadFriendRequestStatus.FRIENDS || isNoteToSelf) // In the note to self case the device linking request was the FR
|
||||
val isFRMessage = !isContactFriend
|
||||
val hasVisibleContent = when (messageType) {
|
||||
MessageType.Text -> DatabaseFactory.getSmsDatabase(context).getMessage(messageID).body.isNotBlank()
|
||||
MessageType.Media -> {
|
||||
val outgoingMediaMessage = DatabaseFactory.getMmsDatabase(context).getOutgoingMessage(messageID)
|
||||
outgoingMediaMessage.body.isNotBlank() || outgoingMediaMessage.attachments.isNotEmpty()
|
||||
}
|
||||
}
|
||||
val shouldSendAutoGeneratedFR = !isContactFriend && !isFRMessage
|
||||
&& !isNoteToSelf && !recipient.address.isGroup // Group threads work through session requests
|
||||
&& hasVisibleContent && !isEndSession
|
||||
if (!shouldSendAutoGeneratedFR) {
|
||||
when (messageType) {
|
||||
MessageType.Text -> return PushTextSendJob(messageID, messageID, recipient.address, isFRMessage, null)
|
||||
MessageType.Media -> return PushMediaSendJob(messageID, messageID, recipient.address, isFRMessage, null)
|
||||
}
|
||||
} else {
|
||||
val autoGeneratedFRMessage = "Please accept to enable messages to be synced across devices"
|
||||
when (messageType) {
|
||||
MessageType.Text -> return PushTextSendJob(messageID, messageID, recipient.address, true, autoGeneratedFRMessage)
|
||||
MessageType.Media -> return PushMediaSendJob(messageID, messageID, recipient.address, true, autoGeneratedFRMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendMessagePush(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType, isEndSession: Boolean) {
|
||||
val jobManager = ApplicationContext.getInstance(context).jobManager
|
||||
val isMultiDeviceRequired = !recipient.address.isOpenGroup
|
||||
@@ -82,7 +51,12 @@ object MultiDeviceProtocol {
|
||||
val publicKey = recipient.address.serialize()
|
||||
FileServerAPI.shared.getDeviceLinks(publicKey).success {
|
||||
val devices = MultiDeviceProtocol.shared.getAllLinkedDevices(publicKey)
|
||||
val jobs = devices.map { sendMessagePushToDevice(context, recipient(context, it), messageID, messageType, isEndSession) }
|
||||
val jobs = devices.map {
|
||||
when (messageType) {
|
||||
MessageType.Text -> PushTextSendJob(messageID, messageID, recipient(context, it).address) as PushSendJob
|
||||
MessageType.Media -> PushMediaSendJob(messageID, messageID, recipient(context, it).address) as PushSendJob
|
||||
}
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
when (messageType) {
|
||||
MessageType.Text -> jobManager.startChain(jobs).enqueue()
|
||||
|
||||
@@ -27,8 +27,8 @@ object SessionManagementProtocol {
|
||||
val smsDB = DatabaseFactory.getSmsDatabase(context)
|
||||
val devices = lokiThreadDB.getSessionRestoreDevices(threadID)
|
||||
for (device in devices) {
|
||||
val sessionRestorationRequest = EphemeralMessage.createSessionRestorationRequest(recipient.address.serialize())
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRestorationRequest))
|
||||
val sessionRequest = EphemeralMessage.createSessionRequest(recipient.address.serialize())
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRequest))
|
||||
}
|
||||
val infoMessage = OutgoingTextMessage(recipient, "", 0, 0)
|
||||
val infoMessageID = smsDB.insertMessageOutbox(threadID, infoMessage, false, System.currentTimeMillis(), null)
|
||||
|
||||
@@ -11,7 +11,6 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
import java.security.MessageDigest
|
||||
|
||||
object SessionMetaProtocol {
|
||||
@@ -75,31 +74,23 @@ object SessionMetaProtocol {
|
||||
* Should be invoked for the recipient's master device.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun canUserReplyToNotification(recipient: Recipient, context: Context): Boolean {
|
||||
val isGroup = recipient.isGroupRecipient
|
||||
if (isGroup) { return !recipient.address.isRSSFeed }
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
return DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS
|
||||
fun canUserReplyToNotification(recipient: Recipient): Boolean {
|
||||
return !recipient.address.isRSSFeed
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be invoked for the recipient's master device.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun shouldSendReadReceipt(address: Address, context: Context): Boolean {
|
||||
if (address.isGroup) { return false }
|
||||
val recipient = Recipient.from(context, address,false)
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
return DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS
|
||||
fun shouldSendReadReceipt(address: Address): Boolean {
|
||||
return !address.isGroup
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be invoked for the recipient's master device.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun shouldSendTypingIndicator(recipient: Recipient?, context: Context): Boolean {
|
||||
if (recipient == null || recipient.isGroupRecipient) { return false }
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
return DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS
|
||||
fun shouldSendTypingIndicator(address: Address): Boolean {
|
||||
return !address.isGroup
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import org.whispersystems.libsignal.loki.SessionResetProtocol
|
||||
import org.whispersystems.libsignal.loki.SessionResetStatus
|
||||
import org.whispersystems.libsignal.protocol.PreKeySignalMessage
|
||||
|
||||
class LokiSessionResetImplementation(private val context: Context) : SessionResetProtocol {
|
||||
class SessionResetImplementation(private val context: Context) : SessionResetProtocol {
|
||||
|
||||
override fun getSessionResetStatus(publicKey: String): SessionResetStatus {
|
||||
return DatabaseFactory.getLokiThreadDatabase(context).getSessionResetStatus(publicKey)
|
||||
@@ -24,8 +24,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsI
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream
|
||||
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
|
||||
import java.util.*
|
||||
|
||||
@@ -67,9 +65,7 @@ object SyncMessagesProtocol {
|
||||
if (!PublicKeyValidation.isValid(address.serialize())) { return false }
|
||||
if (address.serialize() == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) { return false }
|
||||
if (address.serialize() == TextSecurePreferences.getLocalNumber(context)) { return false }
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, address, false))
|
||||
val isFriend = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS
|
||||
return isFriend
|
||||
return true
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@@ -99,29 +95,9 @@ object SyncMessagesProtocol {
|
||||
for (contactPublicKey in contactPublicKeys) {
|
||||
if (contactPublicKey == userPublicKey || !PublicKeyValidation.isValid(contactPublicKey)) { return }
|
||||
val recipient = recipient(context, contactPublicKey)
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val threadFRStatus = lokiThreadDB.getFriendRequestStatus(threadID)
|
||||
when (threadFRStatus) {
|
||||
LokiThreadFriendRequestStatus.NONE, LokiThreadFriendRequestStatus.REQUEST_EXPIRED -> {
|
||||
val contactLinkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(contactPublicKey)
|
||||
for (device in contactLinkedDevices) {
|
||||
FriendRequestProtocol.sendAutoGeneratedFriendRequest(context, device)
|
||||
}
|
||||
}
|
||||
LokiThreadFriendRequestStatus.REQUEST_RECEIVED -> {
|
||||
FriendRequestProtocol.acceptFriendRequest(context, recipient(context, contactPublicKey)) // Takes into account multi device internally
|
||||
lokiThreadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS)
|
||||
val lastMessageID = FriendRequestProtocol.getLastMessageID(context, threadID)
|
||||
if (lastMessageID != null) {
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED)
|
||||
}
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient(context, contactPublicKey), true)
|
||||
}
|
||||
else -> {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
val applicationContext = context.applicationContext as ApplicationContext
|
||||
applicationContext.sendSessionRequestIfNeeded(contactPublicKey)
|
||||
// TODO: Make the thread visible
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,13 +112,13 @@ object SyncMessagesProtocol {
|
||||
val closedGroups = closedGroupsInputStream.readAll()
|
||||
for (closedGroup in closedGroups) {
|
||||
val signalServiceGroup = SignalServiceGroup(
|
||||
SignalServiceGroup.Type.UPDATE,
|
||||
closedGroup.id,
|
||||
SignalServiceGroup.GroupType.SIGNAL,
|
||||
closedGroup.name.orNull(),
|
||||
closedGroup.members,
|
||||
closedGroup.avatar.orNull(),
|
||||
closedGroup.admins
|
||||
SignalServiceGroup.Type.UPDATE,
|
||||
closedGroup.id,
|
||||
SignalServiceGroup.GroupType.SIGNAL,
|
||||
closedGroup.name.orNull(),
|
||||
closedGroup.members,
|
||||
closedGroup.avatar.orNull(),
|
||||
closedGroup.admins
|
||||
)
|
||||
val signalServiceDataMessage = SignalServiceDataMessage(content.timestamp, signalServiceGroup, null, null)
|
||||
// This establishes sessions internally
|
||||
|
||||
@@ -5,11 +5,9 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
|
||||
data class Contact(
|
||||
val recipient: Recipient,
|
||||
val isFriend: Boolean,
|
||||
val isSlave: Boolean,
|
||||
val isOurDevice: Boolean
|
||||
) {
|
||||
@@ -31,10 +29,9 @@ object ContactUtilities {
|
||||
@JvmStatic
|
||||
fun getAllContacts(context: Context): Set<Contact> {
|
||||
val threadDatabase = DatabaseFactory.getThreadDatabase(context)
|
||||
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
val userDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userHexEncodedPublicKey)
|
||||
val userDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
|
||||
val cursor = threadDatabase.conversationList
|
||||
val result = mutableSetOf<Contact>()
|
||||
threadDatabase.readerFor(cursor).use { reader ->
|
||||
@@ -43,13 +40,12 @@ object ContactUtilities {
|
||||
val recipient = thread.recipient
|
||||
val publicKey = recipient.address.serialize()
|
||||
val isUserDevice = userDevices.contains(publicKey)
|
||||
val isFriend = lokiThreadDatabase.getFriendRequestStatus(thread.threadId) == LokiThreadFriendRequestStatus.FRIENDS
|
||||
var isSlave = false
|
||||
if (!recipient.isGroupRecipient) {
|
||||
val deviceLinks = lokiAPIDatabase.getDeviceLinks(publicKey)
|
||||
isSlave = deviceLinks.find { it.slavePublicKey == publicKey } != null
|
||||
}
|
||||
result.add(Contact(recipient, isFriend, isSlave, isUserDevice))
|
||||
result.add(Contact(recipient, isSlave, isUserDevice))
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -8,11 +8,11 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
||||
fun getOpenGroupDisplayName(recipient: Recipient, threadRecipient: Recipient, context: Context): String {
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(threadRecipient)
|
||||
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
||||
val hexEncodedPublicKey = recipient.address.toString()
|
||||
val publicKey = recipient.address.toString()
|
||||
val displayName = if (publicChat != null) {
|
||||
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, hexEncodedPublicKey)
|
||||
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, publicKey)
|
||||
} else {
|
||||
DatabaseFactory.getLokiUserDatabase(context).getDisplayName(hexEncodedPublicKey)
|
||||
DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey)
|
||||
}
|
||||
return displayName ?: hexEncodedPublicKey
|
||||
return displayName ?: publicKey
|
||||
}
|
||||
@@ -22,7 +22,7 @@ object OpenGroupUtilities {
|
||||
val application = ApplicationContext.getInstance(context)
|
||||
val displayName = TextSecurePreferences.getProfileName(context)
|
||||
val lokiPublicChatAPI = application.publicChatAPI ?: throw Error("LokiPublicChatAPI is not initialized.")
|
||||
return application.lokiPublicChatManager.addChat(url, channel).then { group ->
|
||||
return application.publicChatManager.addChat(url, channel).then { group ->
|
||||
DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(channel, url)
|
||||
DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(channel, url)
|
||||
lokiPublicChatAPI.getMessages(channel, url)
|
||||
|
||||
@@ -50,18 +50,18 @@ class ConversationView : LinearLayout {
|
||||
unreadMessagesIndicatorView.visibility = if (thread.unreadCount > 0) View.VISIBLE else View.INVISIBLE
|
||||
if (thread.recipient.isGroupRecipient) {
|
||||
if ("Session Public Chat" == thread.recipient.name) {
|
||||
profilePictureView.hexEncodedPublicKey = ""
|
||||
profilePictureView.publicKey = ""
|
||||
profilePictureView.isRSSFeed = true
|
||||
} else {
|
||||
val users = MentionsManager.shared.userPublicKeyCache[thread.threadId]?.toList() ?: listOf()
|
||||
val randomUsers = users.sorted() // Sort to provide a level of stability
|
||||
profilePictureView.hexEncodedPublicKey = randomUsers.getOrNull(0) ?: ""
|
||||
profilePictureView.additionalHexEncodedPublicKey = randomUsers.getOrNull(1) ?: ""
|
||||
profilePictureView.publicKey = randomUsers.getOrNull(0) ?: ""
|
||||
profilePictureView.additionalPublicKey = randomUsers.getOrNull(1) ?: ""
|
||||
profilePictureView.isRSSFeed = thread.recipient.name == "Loki News" || thread.recipient.name == "Session Updates"
|
||||
}
|
||||
} else {
|
||||
profilePictureView.hexEncodedPublicKey = thread.recipient.address.toString()
|
||||
profilePictureView.additionalHexEncodedPublicKey = null
|
||||
profilePictureView.publicKey = thread.recipient.address.toString()
|
||||
profilePictureView.additionalPublicKey = null
|
||||
profilePictureView.isRSSFeed = false
|
||||
}
|
||||
profilePictureView.glide = glide
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import com.github.ybq.android.spinkit.style.DoubleBounce
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
||||
|
||||
class FriendRequestView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
private var isUISetUp = false
|
||||
private var message: MessageRecord? = null
|
||||
var delegate: FriendRequestViewDelegate? = null
|
||||
|
||||
// region Components
|
||||
private val topSpacer by lazy {
|
||||
val result = View(context)
|
||||
result.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, toPx(12, resources))
|
||||
result
|
||||
}
|
||||
|
||||
private val label by lazy {
|
||||
val result = TextView(context)
|
||||
result.setTextColor(resources.getColorWithID(R.color.text, context.theme))
|
||||
result.textAlignment = TextView.TEXT_ALIGNMENT_CENTER
|
||||
result.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.small_font_size))
|
||||
result
|
||||
}
|
||||
|
||||
private val buttonLinearLayout by lazy {
|
||||
val result = LinearLayout(context)
|
||||
result.orientation = HORIZONTAL
|
||||
result.setPadding(0, resources.getDimension(R.dimen.medium_spacing).toInt(), 0, 0)
|
||||
result
|
||||
}
|
||||
|
||||
private val loaderContainer by lazy {
|
||||
val result = LinearLayout(context)
|
||||
val layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, toPx(50, resources))
|
||||
result.layoutParams = layoutParams
|
||||
result.gravity = Gravity.CENTER
|
||||
result
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Initialization
|
||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||
constructor(context: Context) : this(context, null)
|
||||
// endregion
|
||||
|
||||
// region Updating
|
||||
fun update(message: MessageRecord) {
|
||||
this.message = message
|
||||
setUpUIIfNeeded()
|
||||
updateUI()
|
||||
}
|
||||
|
||||
private fun setUpUIIfNeeded() {
|
||||
if (isUISetUp) { return }
|
||||
isUISetUp = true
|
||||
orientation = VERTICAL
|
||||
setPadding(toPx(48, resources), 0, toPx(48, resources), 0)
|
||||
addView(topSpacer)
|
||||
addView(label)
|
||||
if (!message!!.isOutgoing) {
|
||||
val loader = ProgressBar(context)
|
||||
loader.isIndeterminate = true
|
||||
loader.indeterminateDrawable = DoubleBounce()
|
||||
val loaderLayoutParams = LayoutParams(LayoutParams.MATCH_PARENT, toPx(24, resources))
|
||||
loader.layoutParams = loaderLayoutParams
|
||||
loaderContainer.addView(loader)
|
||||
addView(loaderContainer)
|
||||
fun button(): Button {
|
||||
val result = Button(context)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
result.elevation = 0f
|
||||
result.stateListAnimator = null
|
||||
}
|
||||
result.setTextColor(resources.getColorWithID(R.color.text, context.theme))
|
||||
result.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.small_font_size))
|
||||
result.isAllCaps = false
|
||||
result.setPadding(0, 0, 0, 0)
|
||||
val buttonLayoutParams = LayoutParams(0, resources.getDimension(R.dimen.small_button_height).toInt())
|
||||
buttonLayoutParams.weight = 1f
|
||||
result.layoutParams = buttonLayoutParams
|
||||
return result
|
||||
}
|
||||
val rejectButton = button()
|
||||
rejectButton.text = resources.getString(R.string.view_friend_request_reject_button_title)
|
||||
rejectButton.setBackgroundResource(R.drawable.unimportant_dialog_button_background)
|
||||
rejectButton.setOnClickListener { reject() }
|
||||
buttonLinearLayout.addView(rejectButton)
|
||||
val acceptButton = button()
|
||||
acceptButton.text = resources.getString(R.string.view_friend_request_accept_button_title)
|
||||
acceptButton.setBackgroundResource(R.drawable.prominent_dialog_button_background)
|
||||
val acceptButtonLayoutParams = acceptButton.layoutParams as LayoutParams
|
||||
acceptButtonLayoutParams.setMargins(resources.getDimension(R.dimen.medium_spacing).toInt(), 0, 0, 0)
|
||||
acceptButton.layoutParams = acceptButtonLayoutParams
|
||||
acceptButton.setOnClickListener { accept() }
|
||||
buttonLinearLayout.addView(acceptButton)
|
||||
buttonLinearLayout.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, toPx(50, resources))
|
||||
addView(buttonLinearLayout)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateUI() {
|
||||
val message = message
|
||||
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
val contactID = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(message!!.threadId)!!.address.toString()
|
||||
val contactDisplayName = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(contactID) ?: contactID
|
||||
val friendRequestStatus = lokiMessageDatabase.getFriendRequestStatus(message.id)
|
||||
if (message is MediaMmsMessageRecord) {
|
||||
visibility = View.GONE
|
||||
return
|
||||
}
|
||||
if (!message.isOutgoing) {
|
||||
visibility = if (friendRequestStatus == LokiMessageFriendRequestStatus.NONE) View.GONE else View.VISIBLE
|
||||
buttonLinearLayout.visibility = if (friendRequestStatus != LokiMessageFriendRequestStatus.REQUEST_PENDING) View.GONE else View.VISIBLE
|
||||
loaderContainer.visibility = if (friendRequestStatus == LokiMessageFriendRequestStatus.REQUEST_SENDING) View.VISIBLE else View.GONE
|
||||
val formatID = when (friendRequestStatus) {
|
||||
LokiMessageFriendRequestStatus.NONE, LokiMessageFriendRequestStatus.REQUEST_SENDING, LokiMessageFriendRequestStatus.REQUEST_FAILED -> return
|
||||
LokiMessageFriendRequestStatus.REQUEST_PENDING -> R.string.view_friend_request_incoming_pending_message
|
||||
LokiMessageFriendRequestStatus.REQUEST_ACCEPTED -> R.string.view_friend_request_incoming_accepted_message
|
||||
LokiMessageFriendRequestStatus.REQUEST_REJECTED -> R.string.view_friend_request_incoming_declined_message
|
||||
LokiMessageFriendRequestStatus.REQUEST_EXPIRED -> R.string.view_friend_request_incoming_expired_message
|
||||
}
|
||||
label.text = resources.getString(formatID, contactDisplayName)
|
||||
} else {
|
||||
visibility = if (friendRequestStatus == LokiMessageFriendRequestStatus.NONE) View.GONE else View.VISIBLE
|
||||
buttonLinearLayout.visibility = View.GONE
|
||||
loaderContainer.visibility = View.GONE
|
||||
val formatID = when (friendRequestStatus) {
|
||||
LokiMessageFriendRequestStatus.NONE -> return
|
||||
LokiMessageFriendRequestStatus.REQUEST_SENDING, LokiMessageFriendRequestStatus.REQUEST_FAILED -> null
|
||||
LokiMessageFriendRequestStatus.REQUEST_PENDING, LokiMessageFriendRequestStatus.REQUEST_REJECTED -> R.string.view_friend_request_outgoing_pending_message
|
||||
LokiMessageFriendRequestStatus.REQUEST_ACCEPTED -> R.string.view_friend_request_outgoing_accepted_message
|
||||
LokiMessageFriendRequestStatus.REQUEST_EXPIRED -> R.string.view_friend_request_outgoing_expired_message
|
||||
}
|
||||
if (formatID != null) {
|
||||
label.text = resources.getString(formatID, contactDisplayName)
|
||||
}
|
||||
label.visibility = if (formatID != null) View.VISIBLE else View.GONE
|
||||
topSpacer.visibility = label.visibility
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Interaction
|
||||
private fun accept() {
|
||||
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
lokiMessageDatabase.setFriendRequestStatus(message!!.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED)
|
||||
updateUI()
|
||||
delegate?.acceptFriendRequest(message!!)
|
||||
}
|
||||
|
||||
private fun reject() {
|
||||
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
lokiMessageDatabase.setFriendRequestStatus(message!!.id, LokiMessageFriendRequestStatus.REQUEST_REJECTED)
|
||||
updateUI()
|
||||
delegate?.rejectFriendRequest(message!!)
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
||||
// region Delegate
|
||||
interface FriendRequestViewDelegate {
|
||||
/**
|
||||
* Implementations of this method should update the thread's friend request status
|
||||
* and send a friend request accepted message.
|
||||
*/
|
||||
fun acceptFriendRequest(friendRequest: MessageRecord)
|
||||
/**
|
||||
* Implementations of this method should update the thread's friend request status
|
||||
* and remove the pre keys associated with the contact.
|
||||
*/
|
||||
fun rejectFriendRequest(friendRequest: MessageRecord)
|
||||
}
|
||||
// endregion
|
||||
@@ -31,8 +31,8 @@ class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr:
|
||||
|
||||
private fun update() {
|
||||
displayNameTextView.text = mentionCandidate.displayName
|
||||
profilePictureView.hexEncodedPublicKey = mentionCandidate.publicKey
|
||||
profilePictureView.additionalHexEncodedPublicKey = null
|
||||
profilePictureView.publicKey = mentionCandidate.publicKey
|
||||
profilePictureView.additionalPublicKey = null
|
||||
profilePictureView.isRSSFeed = false
|
||||
profilePictureView.glide = glide!!
|
||||
profilePictureView.update()
|
||||
|
||||
@@ -21,8 +21,8 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
class ProfilePictureView : RelativeLayout {
|
||||
lateinit var glide: GlideRequests
|
||||
var hexEncodedPublicKey: String? = null
|
||||
var additionalHexEncodedPublicKey: String? = null
|
||||
var publicKey: String? = null
|
||||
var additionalPublicKey: String? = null
|
||||
var isRSSFeed = false
|
||||
var isLarge = false
|
||||
|
||||
@@ -52,11 +52,11 @@ class ProfilePictureView : RelativeLayout {
|
||||
|
||||
// region Updating
|
||||
fun update() {
|
||||
val hexEncodedPublicKey = hexEncodedPublicKey ?: return
|
||||
val additionalHexEncodedPublicKey = additionalHexEncodedPublicKey
|
||||
doubleModeImageViewContainer.visibility = if (additionalHexEncodedPublicKey != null && !isRSSFeed) View.VISIBLE else View.INVISIBLE
|
||||
singleModeImageViewContainer.visibility = if (additionalHexEncodedPublicKey == null && !isRSSFeed && !isLarge) View.VISIBLE else View.INVISIBLE
|
||||
largeSingleModeImageViewContainer.visibility = if (additionalHexEncodedPublicKey == null && !isRSSFeed && isLarge) View.VISIBLE else View.INVISIBLE
|
||||
val publicKey = publicKey ?: return
|
||||
val additionalPublicKey = additionalPublicKey
|
||||
doubleModeImageViewContainer.visibility = if (additionalPublicKey != null && !isRSSFeed) View.VISIBLE else View.INVISIBLE
|
||||
singleModeImageViewContainer.visibility = if (additionalPublicKey == null && !isRSSFeed && !isLarge) View.VISIBLE else View.INVISIBLE
|
||||
largeSingleModeImageViewContainer.visibility = if (additionalPublicKey == null && !isRSSFeed && isLarge) View.VISIBLE else View.INVISIBLE
|
||||
rssImageView.visibility = if (isRSSFeed) View.VISIBLE else View.INVISIBLE
|
||||
fun setProfilePictureIfNeeded(imageView: ImageView, hexEncodedPublicKey: String, @DimenRes sizeID: Int) {
|
||||
glide.clear(imageView)
|
||||
@@ -76,10 +76,10 @@ class ProfilePictureView : RelativeLayout {
|
||||
imageView.setImageDrawable(null)
|
||||
}
|
||||
}
|
||||
setProfilePictureIfNeeded(doubleModeImageView1, hexEncodedPublicKey, R.dimen.small_profile_picture_size)
|
||||
setProfilePictureIfNeeded(doubleModeImageView2, additionalHexEncodedPublicKey ?: "", R.dimen.small_profile_picture_size)
|
||||
setProfilePictureIfNeeded(singleModeImageView, hexEncodedPublicKey, R.dimen.medium_profile_picture_size)
|
||||
setProfilePictureIfNeeded(largeSingleModeImageView, hexEncodedPublicKey, R.dimen.large_profile_picture_size)
|
||||
setProfilePictureIfNeeded(doubleModeImageView1, publicKey, R.dimen.small_profile_picture_size)
|
||||
setProfilePictureIfNeeded(doubleModeImageView2, additionalPublicKey ?: "", R.dimen.small_profile_picture_size)
|
||||
setProfilePictureIfNeeded(singleModeImageView, publicKey, R.dimen.medium_profile_picture_size)
|
||||
setProfilePictureIfNeeded(largeSingleModeImageView, publicKey, R.dimen.large_profile_picture_size)
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
@@ -48,20 +48,20 @@ class UserView : LinearLayout {
|
||||
val address = user.address.serialize()
|
||||
if (user.isGroupRecipient) {
|
||||
if ("Session Public Chat" == user.name || user.address.isRSSFeed) {
|
||||
profilePictureView.hexEncodedPublicKey = ""
|
||||
profilePictureView.additionalHexEncodedPublicKey = null
|
||||
profilePictureView.publicKey = ""
|
||||
profilePictureView.additionalPublicKey = null
|
||||
profilePictureView.isRSSFeed = true
|
||||
} else {
|
||||
val threadID = GroupManager.getThreadIDFromGroupID(address, context)
|
||||
val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toList() ?: listOf()
|
||||
val randomUsers = users.sorted() // Sort to provide a level of stability
|
||||
profilePictureView.hexEncodedPublicKey = randomUsers.getOrNull(0) ?: ""
|
||||
profilePictureView.additionalHexEncodedPublicKey = randomUsers.getOrNull(1) ?: ""
|
||||
profilePictureView.publicKey = randomUsers.getOrNull(0) ?: ""
|
||||
profilePictureView.additionalPublicKey = randomUsers.getOrNull(1) ?: ""
|
||||
profilePictureView.isRSSFeed = false
|
||||
}
|
||||
} else {
|
||||
profilePictureView.hexEncodedPublicKey = address
|
||||
profilePictureView.additionalHexEncodedPublicKey = null
|
||||
profilePictureView.publicKey = address
|
||||
profilePictureView.additionalPublicKey = null
|
||||
profilePictureView.isRSSFeed = false
|
||||
}
|
||||
profilePictureView.glide = glide
|
||||
|
||||
Reference in New Issue
Block a user