feat: add contact syncing, UI improvements for profile syncing and conversation threads in the home screen

This commit is contained in:
jubb 2021-03-01 17:16:15 +11:00
parent 11c122e376
commit 3a09d23337
10 changed files with 90 additions and 14 deletions

View File

@ -1,5 +1,4 @@
buildscript {
ext.kotlin_version = "1.4.0"
ext.kovenant_version = "3.3.0"
repositories {
@ -124,8 +123,8 @@ dependencies {
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion"
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2"
implementation "nl.komponents.kovenant:kovenant:$kovenantVersion"
implementation "nl.komponents.kovenant:kovenant-android:$kovenantVersion"
implementation "com.github.lelloman:android-identicons:v11"

View File

@ -249,7 +249,14 @@ public class RecipientDatabase extends Database {
recipient.resolve().setProfileAvatar(profileAvatar);
}
public void setProfileSharing(@NonNull Recipient recipient, @SuppressWarnings("SameParameterValue") boolean enabled) {
public void setProfileName(@NonNull Recipient recipient, @Nullable String profileName) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(SYSTEM_DISPLAY_NAME, profileName);
updateOrInsert(recipient.getAddress(), contentValues);
recipient.resolve().setProfileName(profileName);
}
public void setProfileSharing(@NonNull Recipient recipient, boolean enabled) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(PROFILE_SHARING, enabled ? 1 : 0);
updateOrInsert(recipient.getAddress(), contentValues);

View File

@ -506,6 +506,10 @@ public class ThreadDatabase extends Database {
notifyConversationListeners(threadId);
}
public void notifyUpdatedFromConfig() {
notifyConversationListListeners();
}
public boolean update(long threadId, boolean unarchive) {
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context);
long count = mmsSmsDatabase.getConversationCount(threadId);

View File

@ -22,9 +22,11 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import network.loki.messenger.R
import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.ConversationActivity
@ -46,7 +48,10 @@ import org.thoughtcrime.securesms.loki.dialogs.*
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2
import java.io.IOException
class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListener, SeedReminderViewDelegate, NewConversationButtonSetViewDelegate {
class HomeActivity : PassphraseRequiredActionBarActivity,
ConversationClickListener,
SeedReminderViewDelegate,
NewConversationButtonSetViewDelegate {
private lateinit var glide: GlideRequests
private var broadcastReceiver: BroadcastReceiver? = null
@ -71,9 +76,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
glide = GlideApp.with(this)
// Set up toolbar buttons
profileButton.glide = glide
profileButton.publicKey = publicKey
profileButton.displayName = TextSecurePreferences.getProfileName(this)
profileButton.update()
updateProfileButton()
profileButton.setOnClickListener { openSettings() }
pathStatusViewContainer.disableClipping()
pathStatusViewContainer.setOnClickListener { showPath() }
@ -149,6 +152,12 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
}
this.broadcastReceiver = broadcastReceiver
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter("blockedContactsChanged"))
lifecycleScope.launchWhenResumed {
// update things based on TextSecurePrefs (profile info etc)
TextSecurePreferences.events.filter { it == TextSecurePreferences.PROFILE_NAME_PREF }.collect {
updateProfileButton()
}
}
}
override fun onResume() {
@ -198,6 +207,13 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
val threadCount = (recyclerView.adapter as HomeAdapter).itemCount
emptyStateContainer.visibility = if (threadCount == 0) View.VISIBLE else View.GONE
}
private fun updateProfileButton() {
profileButton.publicKey = publicKey
profileButton.displayName = TextSecurePreferences.getProfileName(this)
profileButton.recycle()
profileButton.update()
}
// endregion
// region Interaction
@ -356,7 +372,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
private fun openSettings() {
val intent = Intent(this, SettingsActivity::class.java)
show(intent)
show(intent, isForResult = true)
}
private fun showPath() {

View File

@ -68,6 +68,10 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
return TextSecurePreferences.getLocalNumber(this)!!
}
companion object {
const val updatedProfileResultCode = 1234
}
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)

View File

@ -12,12 +12,18 @@ import org.session.libsignal.service.api.push.SignalServiceAddress
import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.logging.Log
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
import org.thoughtcrime.securesms.sskenvironment.ProfileManager
import java.util.*
object MultiDeviceProtocol {
@ -76,7 +82,7 @@ object MultiDeviceProtocol {
// TODO: remove this after we migrate to new message receiving pipeline
@JvmStatic
fun handleConfigurationMessage(context: Context, content: SignalServiceProtos.Content, senderPublicKey: String, timestamp: Long) {
if (TextSecurePreferences.getConfigurationMessageSynced(context)) return
// if (TextSecurePreferences.getConfigurationMessageSynced(context)) return
val configurationMessage = ConfigurationMessage.fromProto(content) ?: return
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
if (senderPublicKey != userPublicKey) return
@ -103,6 +109,37 @@ object MultiDeviceProtocol {
if (allOpenGroups.contains(openGroup)) continue
OpenGroupUtilities.addGroup(context, openGroup, 1)
}
if (configurationMessage.profileKey.isNotEmpty()) {
val profileKey = Base64.encodeBytes(configurationMessage.profileKey)
TextSecurePreferences.setProfileKey(context, profileKey)
}
if (!configurationMessage.profilePicture.isNullOrEmpty()) {
TextSecurePreferences.setProfilePictureURL(context, configurationMessage.profilePicture)
}
if (configurationMessage.displayName.isNotEmpty()) {
TextSecurePreferences.setProfileName(context, configurationMessage.displayName)
}
val threadDatabase = DatabaseFactory.getThreadDatabase(context)
val recipientDatabase = DatabaseFactory.getRecipientDatabase(context)
for (contact in configurationMessage.contacts) {
val address = Address.fromSerialized(contact.publicKey)
val recipient = Recipient.from(context, address, true)
if (!contact.profilePicture.isNullOrEmpty()) {
recipientDatabase.setProfileAvatar(recipient, contact.profilePicture)
}
if (contact.profileKey?.isNotEmpty() == true) {
recipientDatabase.setProfileKey(recipient, contact.profileKey)
}
if (contact.name.isNotEmpty()) {
recipientDatabase.setProfileName(recipient, contact.name)
}
recipientDatabase.setProfileSharing(recipient, true)
// create Thread if needed
threadDatabase.getOrCreateThreadIdFor(recipient)
}
if (configurationMessage.contacts.isNotEmpty()) {
threadDatabase.notifyUpdatedFromConfig()
}
// TODO: handle new configuration message fields or handle in new pipeline
TextSecurePreferences.setConfigurationMessageSynced(context, true)
}

View File

@ -58,6 +58,7 @@ allprojects {
}
project.ext {
kotlin_version = "1.4.31"
androidBuildToolsVersion = '29.0.3'
androidCompileSdkVersion = 29 // This is also our target SDK.
androidMinSdkVersion = 21

View File

@ -38,7 +38,7 @@ dependencies {
// Remote:
implementation 'org.greenrobot:eventbus:3.0.0'
implementation "com.goterl.lazycode:lazysodium-android:4.2.0@aar"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
@ -61,10 +61,10 @@ dependencies {
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
implementation "org.threeten:threetenbp:1.3.6"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2"
implementation "nl.komponents.kovenant:kovenant:$kovenantVersion"
testImplementation "junit:junit:3.8.2"

View File

@ -123,7 +123,8 @@ class ConfigurationMessage(val closedGroups: List<ClosedGroup>, val openGroups:
val displayName = configurationProto.displayName
val profilePicture = configurationProto.profilePicture
val profileKey = configurationProto.profileKey
return ConfigurationMessage(closedGroups, openGroups, listOf(), displayName, profilePicture, profileKey.toByteArray())
val contacts = configurationProto.contactsList.mapNotNull { Contact.fromProto(it) }
return ConfigurationMessage(closedGroups, openGroups, contacts, displayName, profilePicture, profileKey.toByteArray())
}
}

View File

@ -7,6 +7,9 @@ import android.preference.PreferenceManager.getDefaultSharedPreferences
import android.provider.Settings
import androidx.annotation.ArrayRes
import androidx.core.app.NotificationCompat
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import org.session.libsession.R
import org.session.libsession.utilities.preferences.NotificationPrivacyPreference
import org.session.libsignal.utilities.logging.Log
@ -16,6 +19,9 @@ import java.util.*
object TextSecurePreferences {
private val TAG = TextSecurePreferences::class.simpleName
private val _events = MutableSharedFlow<String>(0, 64, BufferOverflow.DROP_OLDEST)
val events get() = _events.asSharedFlow()
const val DISABLE_PASSPHRASE_PREF = "pref_disable_passphrase"
const val THEME_PREF = "pref_theme"
const val LANGUAGE_PREF = "pref_language"
@ -321,6 +327,7 @@ object TextSecurePreferences {
@JvmStatic
fun setProfileName(context: Context, name: String?) {
setStringPreference(context, PROFILE_NAME_PREF, name)
_events.tryEmit(PROFILE_NAME_PREF)
}
@JvmStatic