From d78dc833070d04db99388ed580189dfd842482cf Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 22 Apr 2021 10:48:19 +1000 Subject: [PATCH 01/28] minor refactor on storing display names --- .../org/thoughtcrime/securesms/database/Storage.kt | 14 -------------- .../securesms/sskenvironment/ProfileManager.kt | 14 ++++++++++++-- .../libsession/messaging/StorageProtocol.kt | 3 --- .../sending_receiving/MessageReceiverHandler.kt | 8 +++++--- .../messaging/threads/recipients/Recipient.java | 2 ++ .../session/libsession/utilities/SSKEnvironment.kt | 3 ++- 6 files changed, 21 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 0a58ac42fd..97ba450ddd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -93,12 +93,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return database.getDisplayName(recipientPublicKey) } - override fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) { - val address = Address.fromSerialized(recipientPublicKey) - val recipient = Recipient.from(context, address, false) - DatabaseFactory.getRecipientDatabase(context).setProfileKey(recipient, profileKey) - } - override fun getOrGenerateRegistrationID(): Int { var registrationID = TextSecurePreferences.getLocalRegistrationId(context) if (registrationID == 0) { @@ -529,14 +523,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) } - override fun setDisplayName(publicKey: String, newName: String) { - DatabaseFactory.getLokiUserDatabase(context).setDisplayName(publicKey, newName) - } - - override fun getServerDisplayName(serverID: String, publicKey: String): String? { - return DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(serverID, publicKey) - } - override fun getProfilePictureURL(publicKey: String): String? { return DatabaseFactory.getLokiUserDatabase(context).getProfilePictureURL(publicKey) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index 53781d1ab7..f213bb3057 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -5,12 +5,22 @@ import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.SSKEnvironment import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob class ProfileManager: SSKEnvironment.ProfileManagerProtocol { override fun setDisplayName(context: Context, recipient: Recipient, displayName: String) { - DatabaseFactory.getLokiUserDatabase(context).setDisplayName(recipient.address.serialize(), displayName) + val database = DatabaseFactory.getLokiUserDatabase(context) + val publicKey = recipient.address.serialize() + if (recipient.profileName == null) { + // Migrate the profile name in LokiUserDatabase to recipient + database.getDisplayName(publicKey)?.let { setProfileName(context, recipient, it) } + } + database.setDisplayName(publicKey, displayName) + } + + override fun setProfileName(context: Context, recipient: Recipient, profileName: String) { + val database = DatabaseFactory.getRecipientDatabase(context) + database.setProfileName(recipient, profileName) } override fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt index da84c8f6e8..236555234e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -36,7 +36,6 @@ interface StorageProtocol { fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray? fun getDisplayNameForRecipient(recipientPublicKey: String): String? - fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) // Signal Protocol @@ -145,8 +144,6 @@ interface StorageProtocol { // Loki User fun getDisplayName(publicKey: String): String? - fun setDisplayName(publicKey: String, newName: String) - fun getServerDisplayName(serverID: String, publicKey: String): String? fun getProfilePictureURL(publicKey: String): String? // Recipient diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index fc1b28e1cc..2deea295c5 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -129,14 +129,16 @@ private fun MessageReceiver.handleConfigurationMessage(message: ConfigurationMes if (allOpenGroups.contains(openGroup)) continue storage.addOpenGroup(openGroup, 1) } + val profileManager = SSKEnvironment.shared.profileManager + val recipient = Recipient.from(context, Address.fromSerialized(userPublicKey), false) if (message.displayName.isNotEmpty()) { TextSecurePreferences.setProfileName(context, message.displayName) - storage.setDisplayName(userPublicKey, message.displayName) + profileManager.setProfileName(context, recipient, message.displayName) } if (message.profileKey.isNotEmpty()) { val profileKey = Base64.encodeBytes(message.profileKey) ProfileKeyUtil.setEncodedProfileKey(context, profileKey) - storage.setProfileKeyForRecipient(userPublicKey, message.profileKey) + profileManager.setProfileKey(context, recipient, message.profileKey) // handle profile photo if (!message.profilePicture.isNullOrEmpty() && TextSecurePreferences.getProfilePictureURL(context) != message.profilePicture) { storage.setUserProfilePictureUrl(message.profilePicture!!) @@ -160,7 +162,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS // Update the user's local name if the message came from their master device TextSecurePreferences.setProfileName(context, displayName) } - profileManager.setDisplayName(context, recipient, displayName) + profileManager.setProfileName(context, recipient, displayName) } if (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, newProfile.profileKey)) { profileManager.setProfileKey(context, recipient, newProfile.profileKey!!) diff --git a/libsession/src/main/java/org/session/libsession/messaging/threads/recipients/Recipient.java b/libsession/src/main/java/org/session/libsession/messaging/threads/recipients/Recipient.java index 1c9463ec38..b985eb1c42 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/threads/recipients/Recipient.java +++ b/libsession/src/main/java/org/session/libsession/messaging/threads/recipients/Recipient.java @@ -289,6 +289,8 @@ public class Recipient implements RecipientModifiedListener { String displayName = MessagingConfiguration.shared.getStorage().getDisplayName(this.address.toString()); if (displayName != null) { return displayName; } + if (this.profileName != null) { return this.profileName; } + if (this.name == null && isMmsGroupRecipient()) { List names = new LinkedList<>(); diff --git a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt index 081300bc96..603a449f1a 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt @@ -29,7 +29,8 @@ class SSKEnvironment( const val NAME_PADDED_LENGTH = 26 } - fun setDisplayName(context: Context, recipient: Recipient, displayName: String) + fun setDisplayName(context: Context, recipient: Recipient, displayName: String) // Client-side Nickname + fun setProfileName(context: Context, recipient: Recipient, profileName: String) fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String) fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray) fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode) From b94597a1f6514b918717ab3764d04890945f7fd8 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 22 Apr 2021 14:41:00 +1000 Subject: [PATCH 02/28] make it possible to set a client-side nickname --- .../conversation/ConversationActivity.java | 34 ++++++++++++++++++- .../sskenvironment/ProfileManager.kt | 2 +- .../main/res/layout/conversation_activity.xml | 5 ++- .../threads/recipients/Recipient.java | 4 +-- 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 1582f36227..fa6f9869ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -56,7 +56,9 @@ import android.view.View.OnFocusChangeListener; import android.view.View.OnKeyListener; import android.view.WindowManager; import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; import android.widget.Button; +import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ProgressBar; @@ -91,6 +93,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.threads.DistributionTypes; import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.MediaTypes; +import org.session.libsession.utilities.SSKEnvironment; import org.session.libsignal.libsignal.InvalidMessageException; import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.service.loki.api.opengroups.PublicChat; @@ -258,7 +261,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private ImageButton sendButton; private ImageButton attachButton; private ProfilePictureView profilePictureView; - private TextView titleTextView; + private EditText titleTextView; private ConversationFragment fragment; private Button unblockButton; private Button makeDefaultSmsButton; @@ -374,6 +377,35 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } }); + if (isGroupConversation()) { + titleTextView.setEnabled(false); + } else { + titleTextView.setImeOptions(EditorInfo.IME_ACTION_DONE); + titleTextView.setOnEditorActionListener((v, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_DONE) { + String nickname = v.getText().toString().trim(); + SSKEnvironment.shared.getProfileManager().setDisplayName(this, getRecipient(), nickname); + v.clearFocus(); + return true; + } + return false; + }); + titleTextView.setOnFocusChangeListener((v, hasFocus) -> { + InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); + if (!hasFocus) { + EditText textView = (EditText) v; + if (textView.getText().toString().isEmpty()) { + textView.setText(getRecipient().getName()); + } + imm.hideSoftInputFromWindow(v.getWindowToken(),0); + } else { + String nickname = DatabaseFactory.getStorage(this).getDisplayName(getRecipient().getAddress().serialize()); + titleTextView.setText(nickname); + imm.showSoftInput(v, 0); + } + }); + } + MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(threadId, this); PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index f213bb3057..0332f1c474 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -11,7 +11,7 @@ class ProfileManager: SSKEnvironment.ProfileManagerProtocol { override fun setDisplayName(context: Context, recipient: Recipient, displayName: String) { val database = DatabaseFactory.getLokiUserDatabase(context) val publicKey = recipient.address.serialize() - if (recipient.profileName == null) { + if (recipient.name == null) { // Migrate the profile name in LokiUserDatabase to recipient database.getDisplayName(publicKey)?.let { setProfileName(context, recipient, it) } } diff --git a/app/src/main/res/layout/conversation_activity.xml b/app/src/main/res/layout/conversation_activity.xml index 0b9ff3d077..4b441491ff 100644 --- a/app/src/main/res/layout/conversation_activity.xml +++ b/app/src/main/res/layout/conversation_activity.xml @@ -51,15 +51,18 @@ android:layout_marginStart="@dimen/medium_spacing" android:orientation="vertical"> - names = new LinkedList<>(); From 162b597d4d66ecc0de0551a2de72e31588071ae7 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 22 Apr 2021 15:52:01 +1000 Subject: [PATCH 03/28] enable cancelling editing nickname with simple UI --- .../conversation/ConversationActivity.java | 8 ++++++++ .../main/res/layout/conversation_activity.xml | 20 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index fa6f9869ba..bdd718f088 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -274,6 +274,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private ImageView muteIndicatorImageView; private TextView subtitleTextView; private View homeButtonContainer; + private View cancelButtonContainer; private AttachmentTypeSelector attachmentTypeSelector; private AttachmentManager attachmentManager; @@ -398,10 +399,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity textView.setText(getRecipient().getName()); } imm.hideSoftInputFromWindow(v.getWindowToken(),0); + cancelButtonContainer.setVisibility(View.GONE); } else { String nickname = DatabaseFactory.getStorage(this).getDisplayName(getRecipient().getAddress().serialize()); titleTextView.setText(nickname); imm.showSoftInput(v, 0); + cancelButtonContainer.setVisibility(View.VISIBLE); } }); } @@ -1293,6 +1296,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity muteIndicatorImageView = ViewUtil.findById(this, R.id.muteIndicatorImageView); subtitleTextView = ViewUtil.findById(this, R.id.subtitleTextView); homeButtonContainer = ViewUtil.findById(this, R.id.homeButtonContainer); + cancelButtonContainer = ViewUtil.findById(this, R.id.cancelButtonContainer); ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle); ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button); @@ -1338,6 +1342,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity inlineAttachmentButton.setOnClickListener(v -> handleAddAttachment()); homeButtonContainer.setOnClickListener(v -> onSupportNavigateUp()); + + cancelButtonContainer.setOnClickListener(v -> { + titleTextView.clearFocus(); + }); } protected void initializeActionBar() { diff --git a/app/src/main/res/layout/conversation_activity.xml b/app/src/main/res/layout/conversation_activity.xml index 4b441491ff..d7389e7c34 100644 --- a/app/src/main/res/layout/conversation_activity.xml +++ b/app/src/main/res/layout/conversation_activity.xml @@ -99,6 +99,26 @@ android:layout_height="match_parent" android:layout_weight="1" /> + + + + + + + + From 8001f556b7098973f69db41816db413de3181410 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 22 Apr 2021 16:05:35 +1000 Subject: [PATCH 04/28] minor fix --- .../securesms/conversation/ConversationActivity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index bdd718f088..d48bcc893f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -403,6 +403,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } else { String nickname = DatabaseFactory.getStorage(this).getDisplayName(getRecipient().getAddress().serialize()); titleTextView.setText(nickname); + titleTextView.setSelection(nickname.length()); imm.showSoftInput(v, 0); cancelButtonContainer.setVisibility(View.VISIBLE); } From a9b38bd19c6fa17c632128ac236a86b574512485 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 22 Apr 2021 16:32:58 +1000 Subject: [PATCH 05/28] hide the input panel when editing nicknames --- .../conversation/ConversationActivity.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index d48bcc893f..daed247f8d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -17,6 +17,8 @@ package org.thoughtcrime.securesms.conversation; import android.Manifest; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.ActivityNotFoundException; @@ -399,8 +401,20 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity textView.setText(getRecipient().getName()); } imm.hideSoftInputFromWindow(v.getWindowToken(),0); + inputPanel.animate().alpha(1f).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + inputPanel.setVisibility(View.VISIBLE); + } + }); cancelButtonContainer.setVisibility(View.GONE); } else { + inputPanel.animate().alpha(0f).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + inputPanel.setVisibility(View.INVISIBLE); + } + }); String nickname = DatabaseFactory.getStorage(this).getDisplayName(getRecipient().getAddress().serialize()); titleTextView.setText(nickname); titleTextView.setSelection(nickname.length()); From a7c7d0c10f26dc2680586def531ab2a87ac3d7fd Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 29 Apr 2021 14:59:04 +1000 Subject: [PATCH 06/28] introduce Contact model --- .../libsession/messaging/contacts/Contact.kt | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt diff --git a/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt b/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt new file mode 100644 index 0000000000..a687b3df54 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt @@ -0,0 +1,72 @@ +package org.session.libsession.messaging.contacts + +import org.session.libsession.messaging.threads.recipients.Recipient + +class Contact(val sessionID: String) { + // The URL from which to fetch the contact's profile picture. + var profilePictureURL: String? = null + // The file name of the contact's profile picture on local storage. + var profilePictureFileName: String? = null + // The key with which the profile picture is encrypted. + var profilePictureEncryptionKey: ByteArray? = null + // The ID of the thread associated with this contact. + var threadID: String? = null + // This flag is used to determine whether we should auto-download files sent by this contact. + var isTrusted = false + + // region: Name + // The name of the contact. Use this whenever you need the "real", underlying name of a user (e.g. when sending a message). + var name: String? = null + // The contact's nickname, if the user set one. + var nickname: String? = null + // The name to display in the UI. For local use only. + fun displayName(context: ContactContext): String? { + this.nickname?.let { return it } + return when { + context == ContactContext.REGULAR -> this.name + context == ContactContext.OPEN_GROUP -> { + // In open groups, where it's more likely that multiple users have the same name, + // we display a bit of the Session ID after a user's display name for added context. + this.name?.let { + return "${this.name}${this.sessionID.takeLast(8)}" + } + return null + } + else -> throw Exception("Unknown contact context!") + } + } + //end region + + enum class ContactContext { + REGULAR, OPEN_GROUP + } + + fun isValid(): Boolean { + if (profilePictureURL != null) { return profilePictureEncryptionKey != null } + if (profilePictureEncryptionKey != null) { return profilePictureURL != null} + return true + } + + override fun equals(other: Any?): Boolean { + return if (other is Contact) { + other.sessionID == this.sessionID + } else { + false + } + } + + override fun hashCode(): Int { + return sessionID.hashCode() + } + + override fun toString(): String { + return nickname ?: name ?: sessionID + } + + companion object { + fun contextForRecipient(recipient: Recipient): ContactContext { + return if (recipient.isOpenGroupRecipient) { ContactContext.OPEN_GROUP } + else { ContactContext.REGULAR } + } + } +} \ No newline at end of file From e64ac14b776294abaa28912da57075eea323194f Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 30 Apr 2021 15:36:50 +1000 Subject: [PATCH 07/28] fix crash & empty nickname bug --- .../securesms/conversation/ConversationActivity.java | 4 +++- .../thoughtcrime/securesms/sskenvironment/ProfileManager.kt | 1 + .../libsession/messaging/threads/recipients/Recipient.java | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index b84286ae85..4675f15c4c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -417,7 +417,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity }); String nickname = DatabaseFactory.getStorage(this).getDisplayName(getRecipient().getAddress().serialize()); titleTextView.setText(nickname); - titleTextView.setSelection(nickname.length()); + if (nickname != null) { + titleTextView.setSelection(nickname.length()); + } imm.showSoftInput(v, 0); cancelButtonContainer.setVisibility(View.VISIBLE); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index 0332f1c474..ac8b13e865 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -21,6 +21,7 @@ class ProfileManager: SSKEnvironment.ProfileManagerProtocol { override fun setProfileName(context: Context, recipient: Recipient, profileName: String) { val database = DatabaseFactory.getRecipientDatabase(context) database.setProfileName(recipient, profileName) + recipient.notifyListeners() } override fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/threads/recipients/Recipient.java b/libsession/src/main/java/org/session/libsession/messaging/threads/recipients/Recipient.java index baa0ddae57..f3a0ee5a26 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/threads/recipients/Recipient.java +++ b/libsession/src/main/java/org/session/libsession/messaging/threads/recipients/Recipient.java @@ -288,7 +288,7 @@ public class Recipient implements RecipientModifiedListener { public synchronized @Nullable String getName() { String displayName = MessagingModuleConfiguration.shared.getStorage().getDisplayName(this.address.toString()); - if (displayName != null) { return displayName; } + if (displayName != null && !displayName.isEmpty()) { return displayName; } if (this.name == null && isMmsGroupRecipient()) { List names = new LinkedList<>(); From 9afa0d588692c664afcac332785280bb028c2d73 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 30 Apr 2021 16:00:33 +1000 Subject: [PATCH 08/28] fix the name won't update for the first message --- .../org/thoughtcrime/securesms/database/RecipientDatabase.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 20c9fe83f3..65bc3e813e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -253,6 +253,7 @@ public class RecipientDatabase extends Database { ContentValues contentValues = new ContentValues(1); contentValues.put(SYSTEM_DISPLAY_NAME, profileName); updateOrInsert(recipient.getAddress(), contentValues); + recipient.resolve().setName(profileName); recipient.resolve().setProfileName(profileName); } From 51249d942dfc5fdf07b2b6af5fbb1a1d28af9a42 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 30 Apr 2021 16:08:27 +1000 Subject: [PATCH 09/28] make the cancel button clean the nickname in database --- .../securesms/conversation/ConversationActivity.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 4675f15c4c..3a41bb7435 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -1361,6 +1361,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity homeButtonContainer.setOnClickListener(v -> onSupportNavigateUp()); cancelButtonContainer.setOnClickListener(v -> { + titleTextView.setText(""); + SSKEnvironment.shared.getProfileManager().setDisplayName(this, getRecipient(), ""); titleTextView.clearFocus(); }); } From a16e67d1fd47bb582a7f947b76e602dd611d7d11 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 7 May 2021 16:31:46 +1000 Subject: [PATCH 10/28] add new contact database api --- .../securesms/database/DatabaseFactory.java | 7 ++ .../securesms/database/Storage.kt | 13 ++++ .../database/helpers/SQLCipherOpenHelper.java | 9 ++- .../loki/database/SessionContactDatabase.kt | 76 +++++++++++++++++++ .../sskenvironment/ProfileManager.kt | 33 ++++++-- .../libsession/messaging/StorageProtocol.kt | 6 +- .../libsession/messaging/contacts/Contact.kt | 2 +- 7 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java index ea534c35f8..554a6470b0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; import org.thoughtcrime.securesms.loki.database.SessionJobDatabase; +import org.thoughtcrime.securesms.loki.database.SessionContactDatabase; public class DatabaseFactory { @@ -63,6 +64,7 @@ public class DatabaseFactory { private final LokiUserDatabase lokiUserDatabase; private final LokiBackupFilesDatabase lokiBackupFilesDatabase; private final SessionJobDatabase sessionJobDatabase; + private final SessionContactDatabase sessionContactDatabase; // Refactor private final Storage storage; @@ -157,6 +159,10 @@ public class DatabaseFactory { public static SessionJobDatabase getSessionJobDatabase(Context context) { return getInstance(context).sessionJobDatabase; } + + public static SessionContactDatabase getSessionContactDatabase(Context context) { + return getInstance(context).sessionContactDatabase; + } // endregion // region Refactor @@ -202,6 +208,7 @@ public class DatabaseFactory { this.storage = new Storage(context, databaseHelper); this.attachmentProvider = new DatabaseAttachmentProvider(context, databaseHelper); this.sessionJobDatabase = new SessionJobDatabase(context, databaseHelper); + this.sessionContactDatabase = new SessionContactDatabase(context, databaseHelper); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 1b2722ad5f..5aa12566cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -4,6 +4,7 @@ import android.content.Context import android.net.Uri import okhttp3.HttpUrl import org.session.libsession.messaging.StorageProtocol +import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.JobQueue @@ -624,6 +625,18 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseFactory.getLokiUserDatabase(context).getProfilePictureURL(publicKey) } + override fun getContactWithSessionID(sessionID: String): Contact? { + return DatabaseFactory.getSessionContactDatabase(context).getContactWithSessionID(sessionID) + } + + override fun getAllContacts(): Set { + return DatabaseFactory.getSessionContactDatabase(context).getAllContacts() + } + + override fun setContact(contact: Contact) { + DatabaseFactory.getSessionContactDatabase(context).setContact(contact) + } + override fun getRecipientSettings(address: Address): Recipient.RecipientSettings? { val recipientSettings = DatabaseFactory.getRecipientDatabase(context).getRecipientSettings(address) return if (recipientSettings.isPresent) { recipientSettings.get() } else null diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index ef04bedbb7..8674011d6a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase; import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; +import org.thoughtcrime.securesms.loki.database.SessionContactDatabase; import org.thoughtcrime.securesms.loki.database.SessionJobDatabase; import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsMigration; @@ -56,9 +57,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV22 = 43; private static final int lokiV23 = 44; private static final int lokiV24 = 45; + private static final int lokiV25 = 46; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV24; + private static final int DATABASE_VERSION = lokiV25; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -128,6 +130,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(SessionJobDatabase.getCreateSessionJobTableCommand()); db.execSQL(LokiMessageDatabase.getUpdateMessageIDTableForType()); db.execSQL(LokiMessageDatabase.getUpdateMessageMappingTable()); + db.execSQL(SessionContactDatabase.getCreateSessionContactTableCommand()); executeStatements(db, SmsDatabase.CREATE_INDEXS); executeStatements(db, MmsDatabase.CREATE_INDEXS); @@ -291,6 +294,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiAPIDatabase.getCreateSwarmTableCommand()); } + if (oldVersion < lokiV25) { + db.execSQL(SessionContactDatabase.getCreateSessionContactTableCommand()); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt new file mode 100644 index 0000000000..5a2a4072de --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt @@ -0,0 +1,76 @@ +package org.thoughtcrime.securesms.loki.database + +import android.content.ContentValues +import android.content.Context +import net.sqlcipher.Cursor +import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.messaging.jobs.Job +import org.session.libsignal.utilities.Base64 +import org.thoughtcrime.securesms.database.Database +import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper +import org.thoughtcrime.securesms.loki.utilities.* + +class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) { + companion object { + private const val sessionContactTable = "session_contact_database" + const val sessionID = "session_id" + const val name = "name" + const val nickname = "nickname" + const val profilePictureURL = "profile_picture_url" + const val profilePictureFileName = "profile_picture_file_name" + const val profilePictureEncryptionKey = "profile_picture_encryption_key" + const val threadID = "thread_id" + const val isTrusted = "is_trusted" + @JvmStatic val createSessionContactTableCommand = + "CREATE TABLE $sessionContactTable " + + "($sessionID STRING PRIMARY KEY, " + + "$name TEXT DEFAULT NULL, " + + "$nickname TEXT DEFAULT NULL, " + + "$profilePictureURL TEXT DEFAULT NULL, " + + "$profilePictureFileName TEXT DEFAULT NULL, " + + "$profilePictureEncryptionKey BLOB DEFAULT NULL, " + + "$threadID INTEGER DEFAULT -1, " + + "$isTrusted INTEGER DEFAULT 0);" + } + + fun getContactWithSessionID(sessionID: String): Contact? { + val database = databaseHelper.readableDatabase + return database.get(sessionContactTable, "$sessionID = ?", arrayOf(sessionID)) { cursor -> + contactFromCursor(cursor) + } + } + + fun getAllContacts(): Set { + val database = databaseHelper.readableDatabase + return database.getAll(sessionContactTable, null, null) { cursor -> + contactFromCursor(cursor) + }.toSet() + } + + fun setContact(contact: Contact) { + val database = databaseHelper.writableDatabase + val contentValues = ContentValues(8) + contentValues.put(sessionID, contact.sessionID) + contentValues.put(name, contact.name) + contentValues.put(nickname, contact.nickname) + contentValues.put(profilePictureURL, contact.profilePictureURL) + contentValues.put(profilePictureFileName, contact.profilePictureFileName) + contentValues.put(profilePictureEncryptionKey, Base64.encodeBytes(contact.profilePictureEncryptionKey)) + contentValues.put(threadID, threadID) + contentValues.put(isTrusted, if (contact.isTrusted) 1 else 0) + database.insertOrUpdate(sessionContactTable, contentValues, "$sessionID = ?", arrayOf(contact.sessionID)) + } + + private fun contactFromCursor(cursor: Cursor): Contact { + val sessionID = cursor.getString(sessionID) + val contact = Contact(sessionID) + contact.name = cursor.getString(name) + contact.nickname = cursor.getString(nickname) + contact.profilePictureURL = cursor.getString(profilePictureURL) + contact.profilePictureFileName = cursor.getString(profilePictureFileName) + contact.profilePictureEncryptionKey = Base64.decode(cursor.getString(profilePictureEncryptionKey)) + contact.threadID = cursor.getInt(threadID) + contact.isTrusted = cursor.getInt(isTrusted) != 0 + return contact + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index ac8b13e865..ee7be93dd1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.sskenvironment import android.content.Context +import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.SSKEnvironment import org.thoughtcrime.securesms.ApplicationContext @@ -10,18 +11,31 @@ import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob class ProfileManager: SSKEnvironment.ProfileManagerProtocol { override fun setDisplayName(context: Context, recipient: Recipient, displayName: String) { val database = DatabaseFactory.getLokiUserDatabase(context) - val publicKey = recipient.address.serialize() - if (recipient.name == null) { - // Migrate the profile name in LokiUserDatabase to recipient - database.getDisplayName(publicKey)?.let { setProfileName(context, recipient, it) } + val sessionID = recipient.address.serialize() + database.setDisplayName(sessionID, displayName) + // New API + val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) + var contact = contactDatabase.getContactWithSessionID(sessionID) + if (contact == null) contact = Contact(sessionID) + if (contact.nickname != displayName) { + contact.nickname = displayName + contactDatabase.setContact(contact) } - database.setDisplayName(publicKey, displayName) } override fun setProfileName(context: Context, recipient: Recipient, profileName: String) { val database = DatabaseFactory.getRecipientDatabase(context) database.setProfileName(recipient, profileName) recipient.notifyListeners() + // New API + val sessionID = recipient.address.serialize() + val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) + var contact = contactDatabase.getContactWithSessionID(sessionID) + if (contact == null) contact = Contact(sessionID) + if (contact.name != profileName) { + contact.name = profileName + contactDatabase.setContact(contact) + } } override fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String) { @@ -31,6 +45,15 @@ class ProfileManager: SSKEnvironment.ProfileManagerProtocol { override fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray) { val database = DatabaseFactory.getRecipientDatabase(context) database.setProfileKey(recipient, profileKey) + // New API + val sessionID = recipient.address.serialize() + val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) + var contact = contactDatabase.getContactWithSessionID(sessionID) + if (contact == null) contact = Contact(sessionID) + if (!contact.profilePictureEncryptionKey.contentEquals(profileKey)) { + contact.profilePictureEncryptionKey = profileKey + contactDatabase.setContact(contact) + } } override fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt index e7442389c9..fb57f6d875 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -3,6 +3,7 @@ package org.session.libsession.messaging import android.content.Context import android.net.Uri +import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.MessageSendJob @@ -146,9 +147,12 @@ interface StorageProtocol { fun getSessionRequestProcessedTimestamp(publicKey: String): Long? fun setSessionRequestProcessedTimestamp(publicKey: String, newValue: Long) - // Loki User + // Session Contact (Loki User) fun getDisplayName(publicKey: String): String? fun getProfilePictureURL(publicKey: String): String? + fun getContactWithSessionID(sessionID: String): Contact? + fun getAllContacts(): Set + fun setContact(contact: Contact) // Recipient fun getRecipientSettings(address: Address): RecipientSettings? diff --git a/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt b/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt index a687b3df54..392c57e3d5 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt @@ -10,7 +10,7 @@ class Contact(val sessionID: String) { // The key with which the profile picture is encrypted. var profilePictureEncryptionKey: ByteArray? = null // The ID of the thread associated with this contact. - var threadID: String? = null + var threadID: Int? = null // This flag is used to determine whether we should auto-download files sent by this contact. var isTrusted = false From 2cac49b965a353baa86fd174072ea819687de1c6 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 7 May 2021 16:53:09 +1000 Subject: [PATCH 11/28] update profile picture url --- .../securesms/sskenvironment/ProfileManager.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index ee7be93dd1..0decab0b8f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -40,6 +40,15 @@ class ProfileManager: SSKEnvironment.ProfileManagerProtocol { override fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String) { ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(recipient, profilePictureURL)) + // New API + val sessionID = recipient.address.serialize() + val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) + var contact = contactDatabase.getContactWithSessionID(sessionID) + if (contact == null) contact = Contact(sessionID) + if (contact.profilePictureURL != profilePictureURL) { + contact.profilePictureURL = profilePictureURL + contactDatabase.setContact(contact) + } } override fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray) { From b43000bdd061ce33ea06ab1186a920fe950c83ad Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Tue, 11 May 2021 17:05:04 +1000 Subject: [PATCH 12/28] WIP: enable contact with UI --- .../conversation/ConversationActivity.java | 446 ++++++++---------- .../loki/database/SessionContactDatabase.kt | 19 +- .../loki/dialogs/UserDetailsBottomSheet.kt | 27 +- .../loki/utilities/DatabaseUtilities.kt | 5 + .../sskenvironment/ProfileManager.kt | 12 + .../main/res/layout/conversation_activity.xml | 4 +- .../fragment_user_details_bottom_sheet.xml | 74 ++- .../libsession/utilities/SSKEnvironment.kt | 1 + 8 files changed, 316 insertions(+), 272 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 4c3332fac2..0755bf6a36 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -17,8 +17,6 @@ package org.thoughtcrime.securesms.conversation; import android.Manifest; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.ActivityNotFoundException; @@ -58,9 +56,7 @@ import android.view.View.OnFocusChangeListener; import android.view.View.OnKeyListener; import android.view.WindowManager; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; import android.widget.Button; -import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ProgressBar; @@ -220,14 +216,14 @@ import network.loki.messenger.R; */ @SuppressLint("StaticFieldLeak") public class ConversationActivity extends PassphraseRequiredActionBarActivity - implements ConversationFragment.ConversationFragmentListener, - AttachmentManager.AttachmentListener, - RecipientModifiedListener, - OnKeyboardShownListener, - InputPanel.Listener, - InputPanel.MediaListener, - ComposeText.CursorPositionChangedListener, - ConversationSearchBottomBar.EventListener + implements ConversationFragment.ConversationFragmentListener, + AttachmentManager.AttachmentListener, + RecipientModifiedListener, + OnKeyboardShownListener, + InputPanel.Listener, + InputPanel.MediaListener, + ComposeText.CursorPositionChangedListener, + ConversationSearchBottomBar.EventListener { private static final String TAG = ConversationActivity.class.getSimpleName(); @@ -242,11 +238,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity public static final String LAST_SEEN_EXTRA = "last_seen"; public static final String STARTING_POSITION_EXTRA = "starting_position"; -// private static final int PICK_GALLERY = 1; + // private static final int PICK_GALLERY = 1; private static final int PICK_DOCUMENT = 2; private static final int PICK_AUDIO = 3; private static final int PICK_CONTACT = 4; -// private static final int GET_CONTACT_DETAILS = 5; + // private static final int GET_CONTACT_DETAILS = 5; // private static final int GROUP_EDIT = 6; private static final int TAKE_PHOTO = 7; private static final int ADD_CONTACT = 8; @@ -261,7 +257,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private ImageButton sendButton; private ImageButton attachButton; private ProfilePictureView profilePictureView; - private EditText titleTextView; + private TextView titleTextView; private ConversationFragment fragment; private Button unblockButton; private Button makeDefaultSmsButton; @@ -274,7 +270,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private ImageView muteIndicatorImageView; private TextView subtitleTextView; private View homeButtonContainer; - private View cancelButtonContainer; private AttachmentTypeSelector attachmentTypeSelector; private AttachmentManager attachmentManager; @@ -378,52 +373,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } }); - if (isGroupConversation()) { - titleTextView.setEnabled(false); - } else { - titleTextView.setImeOptions(EditorInfo.IME_ACTION_DONE); - titleTextView.setOnEditorActionListener((v, actionId, event) -> { - if (actionId == EditorInfo.IME_ACTION_DONE) { - String nickname = v.getText().toString().trim(); - SSKEnvironment.shared.getProfileManager().setDisplayName(this, getRecipient(), nickname); - v.clearFocus(); - return true; - } - return false; - }); - titleTextView.setOnFocusChangeListener((v, hasFocus) -> { - InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); - if (!hasFocus) { - EditText textView = (EditText) v; - if (textView.getText().toString().isEmpty()) { - textView.setText(getRecipient().getName()); - } - imm.hideSoftInputFromWindow(v.getWindowToken(),0); - inputPanel.animate().alpha(1f).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - inputPanel.setVisibility(View.VISIBLE); - } - }); - cancelButtonContainer.setVisibility(View.GONE); - } else { - inputPanel.animate().alpha(0f).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - inputPanel.setVisibility(View.INVISIBLE); - } - }); - String nickname = DatabaseFactory.getStorage(this).getDisplayName(getRecipient().getAddress().serialize()); - titleTextView.setText(nickname); - if (nickname != null) { - titleTextView.setSelection(nickname.length()); - } - imm.showSoftInput(v, 0); - cancelButtonContainer.setVisibility(View.VISIBLE); - } - }); - } - MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(threadId, this); OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId); @@ -471,7 +420,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity protected void onNewIntent(Intent intent) { super.onNewIntent(intent); Log.i(TAG, "onNewIntent()"); - + if (isFinishing()) { Log.w(TAG, "Activity is finishing..."); return; @@ -566,85 +515,85 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity super.onActivityResult(reqCode, resultCode, data); if ((data == null && reqCode != TAKE_PHOTO && reqCode != SMS_DEFAULT) || - (resultCode != RESULT_OK && reqCode != SMS_DEFAULT)) + (resultCode != RESULT_OK && reqCode != SMS_DEFAULT)) { updateLinkPreviewState(); return; } switch (reqCode) { - case PICK_DOCUMENT: - setMedia(data.getData(), MediaType.DOCUMENT); - break; - case PICK_AUDIO: - setMedia(data.getData(), MediaType.AUDIO); - break; - case TAKE_PHOTO: - if (attachmentManager.getCaptureUri() != null) { - setMedia(attachmentManager.getCaptureUri(), MediaType.IMAGE); - } - break; - case ADD_CONTACT: - recipient = Recipient.from(this, recipient.getAddress(), true); - recipient.addListener(this); - fragment.reloadList(); - break; + case PICK_DOCUMENT: + setMedia(data.getData(), MediaType.DOCUMENT); + break; + case PICK_AUDIO: + setMedia(data.getData(), MediaType.AUDIO); + break; + case TAKE_PHOTO: + if (attachmentManager.getCaptureUri() != null) { + setMedia(attachmentManager.getCaptureUri(), MediaType.IMAGE); + } + break; + case ADD_CONTACT: + recipient = Recipient.from(this, recipient.getAddress(), true); + recipient.addListener(this); + fragment.reloadList(); + break; /* case PICK_LOCATION: SignalPlace place = new SignalPlace(PlacePicker.getPlace(data, this)); attachmentManager.setLocation(place, getCurrentMediaConstraints()); break; */ - case PICK_GIF: - setMedia(data.getData(), - MediaType.GIF, - data.getIntExtra(GiphyActivity.EXTRA_WIDTH, 0), - data.getIntExtra(GiphyActivity.EXTRA_HEIGHT, 0)); - break; - case SMS_DEFAULT: - initializeSecurity(true, isDefaultSms); - break; - case MEDIA_SENDER: - long expiresIn = recipient.getExpireMessages() * 1000L; - int subscriptionId = -1; - boolean initiating = threadId == -1; - String message = data.getStringExtra(MediaSendActivity.EXTRA_MESSAGE); - SlideDeck slideDeck = new SlideDeck(); + case PICK_GIF: + setMedia(data.getData(), + MediaType.GIF, + data.getIntExtra(GiphyActivity.EXTRA_WIDTH, 0), + data.getIntExtra(GiphyActivity.EXTRA_HEIGHT, 0)); + break; + case SMS_DEFAULT: + initializeSecurity(true, isDefaultSms); + break; + case MEDIA_SENDER: + long expiresIn = recipient.getExpireMessages() * 1000L; + int subscriptionId = -1; + boolean initiating = threadId == -1; + String message = data.getStringExtra(MediaSendActivity.EXTRA_MESSAGE); + SlideDeck slideDeck = new SlideDeck(); - List mediaList = data.getParcelableArrayListExtra(MediaSendActivity.EXTRA_MEDIA); + List mediaList = data.getParcelableArrayListExtra(MediaSendActivity.EXTRA_MEDIA); - for (Media mediaItem : mediaList) { - if (MediaUtil.isVideoType(mediaItem.getMimeType())) { - slideDeck.addSlide(new VideoSlide(this, mediaItem.getUri(), 0, mediaItem.getCaption().orNull())); - } else if (MediaUtil.isGif(mediaItem.getMimeType())) { - slideDeck.addSlide(new GifSlide(this, mediaItem.getUri(), 0, mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.getCaption().orNull())); - } else if (MediaUtil.isImageType(mediaItem.getMimeType())) { - slideDeck.addSlide(new ImageSlide(this, mediaItem.getUri(), 0, mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.getCaption().orNull())); - } else { - Log.w(TAG, "Asked to send an unexpected mimeType: '" + mediaItem.getMimeType() + "'. Skipping."); + for (Media mediaItem : mediaList) { + if (MediaUtil.isVideoType(mediaItem.getMimeType())) { + slideDeck.addSlide(new VideoSlide(this, mediaItem.getUri(), 0, mediaItem.getCaption().orNull())); + } else if (MediaUtil.isGif(mediaItem.getMimeType())) { + slideDeck.addSlide(new GifSlide(this, mediaItem.getUri(), 0, mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.getCaption().orNull())); + } else if (MediaUtil.isImageType(mediaItem.getMimeType())) { + slideDeck.addSlide(new ImageSlide(this, mediaItem.getUri(), 0, mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.getCaption().orNull())); + } else { + Log.w(TAG, "Asked to send an unexpected mimeType: '" + mediaItem.getMimeType() + "'. Skipping."); + } } - } - final Context context = ConversationActivity.this.getApplicationContext(); + final Context context = ConversationActivity.this.getApplicationContext(); - sendMediaMessage(message, - slideDeck, - inputPanel.getQuote().orNull(), - Optional.absent(), - initiating).addListener(new AssertedSuccessListener() { - @Override - public void onSuccess(Void result) { - AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { - Stream.of(slideDeck.getSlides()) - .map(Slide::getUri) - .withoutNulls() - .filter(BlobProvider::isAuthority) - .forEach(uri -> BlobProvider.getInstance().delete(context, uri)); - }); - } - }); + sendMediaMessage(message, + slideDeck, + inputPanel.getQuote().orNull(), + Optional.absent(), + initiating).addListener(new AssertedSuccessListener() { + @Override + public void onSuccess(Void result) { + AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { + Stream.of(slideDeck.getSlides()) + .map(Slide::getUri) + .withoutNulls() + .filter(BlobProvider::isAuthority) + .forEach(uri -> BlobProvider.getInstance().delete(context, uri)); + }); + } + }); - break; + break; } } @@ -722,14 +671,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (isSingleConversation() && getRecipient().getContactUri() == null) { inflater.inflate(R.menu.conversation_add_to_contacts, menu); } - - if (recipient != null && recipient.isLocalNumber()) { if (isSecureText) menu.findItem(R.id.menu_call_secure).setVisible(false); else menu.findItem(R.id.menu_call_insecure).setVisible(false); - MenuItem muteItem = menu.findItem(R.id.menu_mute_notifications); - if (muteItem != null) { muteItem.setVisible(false); } @@ -797,25 +742,25 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity switch (item.getItemId()) { // case R.id.menu_call_secure: handleDial(getRecipient(), true); return true; // case R.id.menu_call_insecure: handleDial(getRecipient(), false); return true; - case R.id.menu_unblock: handleUnblock(); return true; - case R.id.menu_block: handleBlock(); return true; - case R.id.menu_copy_session_id: handleCopySessionID(); return true; - case R.id.menu_view_media: handleViewMedia(); return true; - case R.id.menu_add_shortcut: handleAddShortcut(); return true; - case R.id.menu_search: handleSearch(); return true; + case R.id.menu_unblock: handleUnblock(); return true; + case R.id.menu_block: handleBlock(); return true; + case R.id.menu_copy_session_id: handleCopySessionID(); return true; + case R.id.menu_view_media: handleViewMedia(); return true; + case R.id.menu_add_shortcut: handleAddShortcut(); return true; + case R.id.menu_search: handleSearch(); return true; // case R.id.menu_add_to_contacts: handleAddToContacts(); return true; // case R.id.menu_reset_secure_session: handleResetSecureSession(); return true; // case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true; - case R.id.menu_distribution_broadcast: handleDistributionBroadcastEnabled(item); return true; - case R.id.menu_distribution_conversation: handleDistributionConversationEnabled(item); return true; - case R.id.menu_edit_group: handleEditPushGroup(); return true; - case R.id.menu_leave: handleLeavePushGroup(); return true; - case R.id.menu_mute_notifications: handleMuteNotifications(); return true; - case R.id.menu_unmute_notifications: handleUnmuteNotifications(); return true; + case R.id.menu_distribution_broadcast: handleDistributionBroadcastEnabled(item); return true; + case R.id.menu_distribution_conversation: handleDistributionConversationEnabled(item); return true; + case R.id.menu_edit_group: handleEditPushGroup(); return true; + case R.id.menu_leave: handleLeavePushGroup(); return true; + case R.id.menu_mute_notifications: handleMuteNotifications(); return true; + case R.id.menu_unmute_notifications: handleUnmuteNotifications(); return true; // case R.id.menu_conversation_settings: handleConversationSettings(); return true; - case R.id.menu_expiring_messages_off: - case R.id.menu_expiring_messages: handleSelectMessageExpiration(); return true; - case android.R.id.home: handleReturnToConversationList(); return true; + case R.id.menu_expiring_messages_off: + case R.id.menu_expiring_messages: handleSelectMessageExpiration(); return true; + case android.R.id.home: handleReturnToConversationList(); return true; } return false; @@ -886,7 +831,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected Void doInBackground(Void... params) { DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setMuted(recipient, until); + .setMuted(recipient, until); return null; } @@ -901,7 +846,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected Void doInBackground(Void... params) { DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setMuted(recipient, 0); + .setMuted(recipient, 0); return null; } @@ -913,20 +858,20 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity int bodyRes = R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact; new AlertDialog.Builder(this) - .setTitle(titleRes) - .setMessage(bodyRes) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.ConversationActivity_unblock, (dialog, which) -> { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setBlocked(recipient, false); + .setTitle(titleRes) + .setMessage(bodyRes) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.ConversationActivity_unblock, (dialog, which) -> { + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + DatabaseFactory.getRecipientDatabase(ConversationActivity.this) + .setBlocked(recipient, false); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - }).show(); + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + }).show(); } @TargetApi(Build.VERSION_CODES.KITKAT) @@ -996,7 +941,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (icon == null) { icon = IconCompat.createWithResource(context, recipient.isGroupRecipient() ? R.mipmap.ic_group_shortcut - : R.mipmap.ic_person_shortcut); + : R.mipmap.ic_person_shortcut); } return icon; @@ -1006,14 +951,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity protected void onPostExecute(IconCompat icon) { Context context = getApplicationContext(); String name = Optional.fromNullable(recipient.getName()) - .or(Optional.fromNullable(recipient.getProfileName())) - .or(recipient.toShortString()); + .or(Optional.fromNullable(recipient.getProfileName())) + .or(recipient.toShortString()); ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(context, recipient.getAddress().serialize() + '-' + System.currentTimeMillis()) - .setShortLabel(name) - .setIcon(icon) - .setIntent(ShortcutLauncherActivity.createIntent(context, recipient.getAddress())) - .build(); + .setShortLabel(name) + .setIcon(icon) + .setIntent(ShortcutLauncherActivity.createIntent(context, recipient.getAddress())) + .build(); if (ShortcutManagerCompat.requestPinShortcut(context, shortcutInfo, null)) { Toast.makeText(context, getString(R.string.ConversationActivity_added_to_home_screen), Toast.LENGTH_LONG).show(); @@ -1029,7 +974,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void handleLeavePushGroup() { if (getRecipient() == null) { Toast.makeText(this, getString(R.string.ConversationActivity_invalid_recipient), - Toast.LENGTH_LONG).show(); + Toast.LENGTH_LONG).show(); return; } @@ -1092,7 +1037,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected Void doInBackground(Void... params) { DatabaseFactory.getThreadDatabase(ConversationActivity.this) - .setDistributionType(threadId, DistributionTypes.BROADCAST); + .setDistributionType(threadId, DistributionTypes.BROADCAST); return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -1108,7 +1053,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected Void doInBackground(Void... params) { DatabaseFactory.getThreadDatabase(ConversationActivity.this) - .setDistributionType(threadId, DistributionTypes.CONVERSATION); + .setDistributionType(threadId, DistributionTypes.CONVERSATION); return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -1142,7 +1087,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity /* Loki - We don't support SMS if (!isSecureText && !isPushGroupConversation()) sendButton.disableTransport(Type.TEXTSECURE); if (recipient.isPushGroupRecipient()) sendButton.disableTransport(Type.SMS); - if (!recipient.isPushGroupRecipient() && recipient.isForceSmsSelection()) { sendButton.setDefaultTransport(Type.SMS); } else { @@ -1314,7 +1258,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity muteIndicatorImageView = ViewUtil.findById(this, R.id.muteIndicatorImageView); subtitleTextView = ViewUtil.findById(this, R.id.subtitleTextView); homeButtonContainer = ViewUtil.findById(this, R.id.homeButtonContainer); - cancelButtonContainer = ViewUtil.findById(this, R.id.cancelButtonContainer); ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle); ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button); @@ -1360,12 +1303,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity inlineAttachmentButton.setOnClickListener(v -> handleAddAttachment()); homeButtonContainer.setOnClickListener(v -> onSupportNavigateUp()); - - cancelButtonContainer.setOnClickListener(v -> { - titleTextView.setText(""); - SSKEnvironment.shared.getProfileManager().setDisplayName(this, getRecipient(), ""); - titleTextView.clearFocus(); - }); } protected void initializeActionBar() { @@ -1480,35 +1417,35 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity Log.i(TAG, "Selected: " + type); switch (type) { - case AttachmentTypeSelector.ADD_GALLERY: - AttachmentManager.selectGallery(this, MEDIA_SENDER, recipient, composeText.getTextTrimmed()); break; - case AttachmentTypeSelector.ADD_DOCUMENT: - AttachmentManager.selectDocument(this, PICK_DOCUMENT); break; - case AttachmentTypeSelector.ADD_SOUND: - AttachmentManager.selectAudio(this, PICK_AUDIO); break; - case AttachmentTypeSelector.ADD_CONTACT_INFO: - AttachmentManager.selectContactInfo(this, PICK_CONTACT); break; - case AttachmentTypeSelector.ADD_LOCATION: - AttachmentManager.selectLocation(this, PICK_LOCATION); break; - case AttachmentTypeSelector.TAKE_PHOTO: - attachmentManager.capturePhoto(this, TAKE_PHOTO); break; - case AttachmentTypeSelector.ADD_GIF: - boolean hasSeenGIFMetaDataWarning = TextSecurePreferences.hasSeenGIFMetaDataWarning(this); - if (!hasSeenGIFMetaDataWarning) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle("Search GIFs?"); - builder.setMessage("You will not have full metadata protection when sending GIFs."); - builder.setPositiveButton("OK", (dialog, which) -> { + case AttachmentTypeSelector.ADD_GALLERY: + AttachmentManager.selectGallery(this, MEDIA_SENDER, recipient, composeText.getTextTrimmed()); break; + case AttachmentTypeSelector.ADD_DOCUMENT: + AttachmentManager.selectDocument(this, PICK_DOCUMENT); break; + case AttachmentTypeSelector.ADD_SOUND: + AttachmentManager.selectAudio(this, PICK_AUDIO); break; + case AttachmentTypeSelector.ADD_CONTACT_INFO: + AttachmentManager.selectContactInfo(this, PICK_CONTACT); break; + case AttachmentTypeSelector.ADD_LOCATION: + AttachmentManager.selectLocation(this, PICK_LOCATION); break; + case AttachmentTypeSelector.TAKE_PHOTO: + attachmentManager.capturePhoto(this, TAKE_PHOTO); break; + case AttachmentTypeSelector.ADD_GIF: + boolean hasSeenGIFMetaDataWarning = TextSecurePreferences.hasSeenGIFMetaDataWarning(this); + if (!hasSeenGIFMetaDataWarning) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Search GIFs?"); + builder.setMessage("You will not have full metadata protection when sending GIFs."); + builder.setPositiveButton("OK", (dialog, which) -> { + AttachmentManager.selectGif(this, PICK_GIF); + dialog.dismiss(); + }); + builder.setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss()); + builder.create().show(); + TextSecurePreferences.setHasSeenGIFMetaDataWarning(this); + } else { AttachmentManager.selectGif(this, PICK_GIF); - dialog.dismiss(); - }); - builder.setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss()); - builder.create().show(); - TextSecurePreferences.setHasSeenGIFMetaDataWarning(this); - } else { - AttachmentManager.selectGif(this, PICK_GIF); - } - break; + } + break; } } @@ -1603,8 +1540,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity draftDatabase.insertDrafts(threadId, drafts); threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this), - drafts.getUriSnippet(), - System.currentTimeMillis(), Types.BASE_DRAFT_TYPE, true); + drafts.getUriSnippet(), + System.currentTimeMillis(), Types.BASE_DRAFT_TYPE, true); } else if (threadId > 0) { threadDatabase.update(threadId, false); } @@ -1707,10 +1644,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity String timestamp = new SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.US).format(new Date()); String filename = String.format("signal-%s.txt", timestamp); Uri textUri = BlobProvider.getInstance() - .forData(textData) - .withMimeType(MediaTypes.LONG_TEXT) - .withFileName(filename) - .createForSingleSessionInMemory(); + .forData(textData) + .withMimeType(MediaTypes.LONG_TEXT) + .withFileName(filename) + .createForSingleSessionInMemory(); textSlide = Optional.of(new TextSlide(this, textUri, filename, textData.length)); } @@ -1788,10 +1725,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity boolean needsSplit = message.length() > characterCalculator.calculateCharacters(message).maxPrimaryMessageSize; boolean isMediaMessage = attachmentManager.isAttachmentPresent() || // recipient.isGroupRecipient() || - inputPanel.getQuote().isPresent() || - linkPreviewViewModel.hasLinkPreview() || - LinkPreviewUtil.isValidMediaUrl(message) || // Loki - Send GIFs as media messages - needsSplit; + inputPanel.getQuote().isPresent() || + linkPreviewViewModel.hasLinkPreview() || + LinkPreviewUtil.isValidMediaUrl(message) || // Loki - Send GIFs as media messages + needsSplit; if (isMediaMessage) { sendMediaMessage(initiating); @@ -1812,7 +1749,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void sendMediaMessage(boolean initiating) - throws InvalidMessageException + throws InvalidMessageException { Log.i(TAG, "Sending media message..."); sendMediaMessage(getMessage(), attachmentManager.buildSlideDeck(), inputPanel.getQuote().orNull(), linkPreviewViewModel.getActiveLinkPreview(), initiating); @@ -1872,7 +1809,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void sendTextMessage(final boolean initiating) - throws InvalidMessageException + throws InvalidMessageException { final Context context = getApplicationContext(); final String messageBody = getMessage(); @@ -1933,10 +1870,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void onRecorderPermissionRequired() { Permissions.with(this) - .request(Manifest.permission.RECORD_AUDIO) - .withRationaleDialog(getString(R.string.ConversationActivity_to_send_audio_messages_allow_signal_access_to_your_microphone), R.drawable.ic_baseline_mic_48) - .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_requires_the_microphone_permission_in_order_to_send_audio_messages)) - .execute(); + .request(Manifest.permission.RECORD_AUDIO) + .withRationaleDialog(getString(R.string.ConversationActivity_to_send_audio_messages_allow_signal_access_to_your_microphone), R.drawable.ic_baseline_mic_48) + .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_requires_the_microphone_permission_in_order_to_send_audio_messages)) + .execute(); } @Override @@ -2095,16 +2032,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void onClick(View v) { Permissions.with(ConversationActivity.this) - .request(Manifest.permission.CAMERA) - .withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_baseline_photo_camera_48) - .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video)) - .onAllGranted(() -> { - composeText.clearFocus(); - startActivityForResult(MediaSendActivity.buildCameraIntent(ConversationActivity.this, recipient), MEDIA_SENDER); - overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary); - }) - .onAnyDenied(() -> Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show()) - .execute(); + .request(Manifest.permission.CAMERA) + .withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_baseline_photo_camera_48) + .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video)) + .onAllGranted(() -> { + composeText.clearFocus(); + startActivityForResult(MediaSendActivity.buildCameraIntent(ConversationActivity.this, recipient), MEDIA_SENDER); + overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary); + }) + .onAnyDenied(() -> Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show()) + .execute(); } } @@ -2214,7 +2151,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } if (text.length() > 0) { if (currentMentionStartIndex > text.length()) { - resetMentions(); // Should never occur + resetMentions(); // Should never occur } int lastCharacterIndex = text.length() - 1; char lastCharacter = text.charAt(lastCharacterIndex); @@ -2281,12 +2218,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } inputPanel.setQuote(GlideApp.with(this), - messageRecord.getDateSent(), - author, - body, - slideDeck, - recipient, - threadId); + messageRecord.getDateSent(), + author, + body, + slideDeck, + recipient, + threadId); } else if (messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getLinkPreviews().isEmpty()) { LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0); @@ -2297,26 +2234,26 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } inputPanel.setQuote(GlideApp.with(this), - messageRecord.getDateSent(), - author, - messageRecord.getBody(), - slideDeck, - recipient, - threadId); + messageRecord.getDateSent(), + author, + messageRecord.getBody(), + slideDeck, + recipient, + threadId); } else { inputPanel.setQuote(GlideApp.with(this), - messageRecord.getDateSent(), - author, - messageRecord.getBody(), - messageRecord.isMms() ? ((MmsMessageRecord) messageRecord).getSlideDeck() : new SlideDeck(), - recipient, - threadId); + messageRecord.getDateSent(), + author, + messageRecord.getBody(), + messageRecord.isMms() ? ((MmsMessageRecord) messageRecord).getSlideDeck() : new SlideDeck(), + recipient, + threadId); } } @Override public void onMessageActionToolbarOpened() { - searchViewItem.collapseActionView(); + searchViewItem.collapseActionView(); } @Override @@ -2382,8 +2319,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } else if (recipient.getAddress().toString().toLowerCase().equals(userPublicKey)) { titleTextView.setText(getResources().getString(R.string.note_to_self)); } else { - boolean hasName = (recipient.getName() != null && !recipient.getName().isEmpty()); - titleTextView.setText(hasName ? recipient.getName() : recipient.getAddress().toString()); + String displayName = SSKEnvironment.shared.getProfileManager().getDisplayName(getApplicationContext(), recipient); + boolean hasName = displayName != null; + titleTextView.setText(hasName ? displayName : recipient.getAddress().toString()); } } @@ -2434,7 +2372,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void updateMessageStatusProgressBar() { if (messageStatus != null) { - messageStatusProgressBar.setAlpha(1.0f); + messageStatusProgressBar.setAlpha(1.0f); switch (messageStatus) { case "calculatingPoW": setMessageStatusProgressAnimatedIfPossible(25); break; case "contactingNetwork": setMessageStatusProgressAnimatedIfPossible(50); break; @@ -2471,7 +2409,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity default: return -1; } } else { - return -1; + return -1; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt index 5a2a4072de..4aec4b4a48 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki.database import android.content.ContentValues import android.content.Context +import androidx.core.database.getStringOrNull import net.sqlcipher.Cursor import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.Job @@ -35,7 +36,7 @@ class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Da fun getContactWithSessionID(sessionID: String): Contact? { val database = databaseHelper.readableDatabase - return database.get(sessionContactTable, "$sessionID = ?", arrayOf(sessionID)) { cursor -> + return database.get(sessionContactTable, "${SessionContactDatabase.sessionID} = ?", arrayOf(sessionID)) { cursor -> contactFromCursor(cursor) } } @@ -55,7 +56,9 @@ class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Da contentValues.put(nickname, contact.nickname) contentValues.put(profilePictureURL, contact.profilePictureURL) contentValues.put(profilePictureFileName, contact.profilePictureFileName) - contentValues.put(profilePictureEncryptionKey, Base64.encodeBytes(contact.profilePictureEncryptionKey)) + contact.profilePictureEncryptionKey?.let { + contentValues.put(profilePictureEncryptionKey, Base64.encodeBytes(it)) + } contentValues.put(threadID, threadID) contentValues.put(isTrusted, if (contact.isTrusted) 1 else 0) database.insertOrUpdate(sessionContactTable, contentValues, "$sessionID = ?", arrayOf(contact.sessionID)) @@ -64,11 +67,13 @@ class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Da private fun contactFromCursor(cursor: Cursor): Contact { val sessionID = cursor.getString(sessionID) val contact = Contact(sessionID) - contact.name = cursor.getString(name) - contact.nickname = cursor.getString(nickname) - contact.profilePictureURL = cursor.getString(profilePictureURL) - contact.profilePictureFileName = cursor.getString(profilePictureFileName) - contact.profilePictureEncryptionKey = Base64.decode(cursor.getString(profilePictureEncryptionKey)) + contact.name = cursor.getStringOrNull(name) + contact.nickname = cursor.getStringOrNull(nickname) + contact.profilePictureURL = cursor.getStringOrNull(profilePictureURL) + contact.profilePictureFileName = cursor.getStringOrNull(profilePictureFileName) + cursor.getStringOrNull(profilePictureEncryptionKey)?.let { + contact.profilePictureEncryptionKey = Base64.decode(it) + } contact.threadID = cursor.getInt(threadID) contact.isTrusted = cursor.getInt(isTrusted) != 0 return contact diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt index 3d884f7440..7eb1e20996 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.loki.dialogs import android.content.ClipData import android.content.ClipboardManager import android.content.Context +import android.opengl.Visibility import android.os.Bundle import com.google.android.material.bottomsheet.BottomSheetDialogFragment import android.view.LayoutInflater @@ -12,6 +13,9 @@ import android.widget.Toast import kotlinx.android.synthetic.main.fragment_user_details_bottom_sheet.* import kotlinx.android.synthetic.main.view_conversation.view.* import network.loki.messenger.R +import org.session.libsession.messaging.threads.Address +import org.session.libsession.messaging.threads.recipients.Recipient +import org.session.libsession.utilities.SSKEnvironment import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.mms.GlideApp @@ -24,11 +28,32 @@ public class UserDetailsBottomSheet : BottomSheetDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val publicKey = arguments?.getString("publicKey") ?: return dismiss() + val recipient = Recipient.from(requireContext(), Address.fromSerialized(publicKey), false) profilePictureView.publicKey = publicKey profilePictureView.glide = GlideApp.with(this) profilePictureView.isLarge = true profilePictureView.update() - nameTextView.text = DatabaseFactory.getLokiUserDatabase(requireContext()).getDisplayName(publicKey) ?: "Anonymous" + nameTextViewContainer.visibility = View.VISIBLE + nameTextViewContainer.setOnClickListener { + nameTextViewContainer.visibility = View.INVISIBLE + nameEditContainer.visibility = View.VISIBLE + nameEditText.requestFocus() + } + btnCancelNickNameEdit.setOnClickListener { + nameEditText.clearFocus() + nameTextViewContainer.visibility = View.VISIBLE + nameEditContainer.visibility = View.INVISIBLE + nameEditText.text = null + } + btnSaveNickNameEdit.setOnClickListener { + nameEditText.clearFocus() + nameTextViewContainer.visibility = View.VISIBLE + nameEditContainer.visibility = View.INVISIBLE + var newNickName = null + SSKEnvironment.shared.profileManager.setDisplayName(requireContext(), recipient, newNickName) + nameTextView.text = SSKEnvironment.shared.profileManager.getDisplayName(requireContext(), recipient) ?: "Anonymous" + } + nameTextView.text = SSKEnvironment.shared.profileManager.getDisplayName(requireContext(), recipient) ?: "Anonymous" publicKeyTextView.text = publicKey copyButton.setOnClickListener { val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/DatabaseUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/DatabaseUtilities.kt index 140586930f..23834fb9af 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/DatabaseUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/DatabaseUtilities.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.loki.utilities import android.content.ContentValues +import androidx.core.database.getStringOrNull import net.sqlcipher.Cursor import net.sqlcipher.database.SQLiteDatabase import org.session.libsignal.utilities.Base64 @@ -56,4 +57,8 @@ fun Cursor.getLong(columnName: String): Long { fun Cursor.getBase64EncodedData(columnName: String): ByteArray { return Base64.decode(getString(columnName)) +} + +fun Cursor.getStringOrNull(columnName: String): String? { + return getStringOrNull(getColumnIndexOrThrow(columnName)) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index 0decab0b8f..9ca7fe3c9e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -73,4 +73,16 @@ class ProfileManager: SSKEnvironment.ProfileManagerProtocol { override fun updateOpenGroupProfilePicturesIfNeeded(context: Context) { ApplicationContext.getInstance(context).updateOpenGroupProfilePicturesIfNeeded() } + + override fun getDisplayName(context: Context, recipient: Recipient): String? { + val sessionID = recipient.address.serialize() + val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) + var contact = contactDatabase.getContactWithSessionID(sessionID) + if (contact == null) { + contact = Contact(sessionID) + contact.name = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(sessionID) ?: recipient.profileName ?: recipient.name + contactDatabase.setContact(contact) + } + return contact.displayName(Contact.contextForRecipient(recipient)) + } } \ No newline at end of file diff --git a/app/src/main/res/layout/conversation_activity.xml b/app/src/main/res/layout/conversation_activity.xml index d7389e7c34..7504e23d49 100644 --- a/app/src/main/res/layout/conversation_activity.xml +++ b/app/src/main/res/layout/conversation_activity.xml @@ -51,7 +51,7 @@ android:layout_marginStart="@dimen/medium_spacing" android:orientation="vertical"> - + android:layout_marginTop="@dimen/large_spacing"/> - + android:gravity="center"> + + + + + + + + + + + + + + + + + + Date: Wed, 12 May 2021 15:11:09 +1000 Subject: [PATCH 13/28] make editting nickname work --- .../securesms/loki/dialogs/UserDetailsBottomSheet.kt | 5 ++++- .../thoughtcrime/securesms/loki/views/ConversationView.kt | 3 ++- .../securesms/sskenvironment/ProfileManager.kt | 8 +++++--- .../org/session/libsession/utilities/SSKEnvironment.kt | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt index 7eb1e20996..2a1854c176 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt @@ -49,7 +49,10 @@ public class UserDetailsBottomSheet : BottomSheetDialogFragment() { nameEditText.clearFocus() nameTextViewContainer.visibility = View.VISIBLE nameEditContainer.visibility = View.INVISIBLE - var newNickName = null + var newNickName: String? = null + if (!nameEditText.text.isEmpty()) { + newNickName = nameEditText.text.toString() + } SSKEnvironment.shared.profileManager.setDisplayName(requireContext(), recipient, newNickName) nameTextView.text = SSKEnvironment.shared.profileManager.getDisplayName(requireContext(), recipient) ?: "Anonymous" } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt index e1a308b645..b8e184fd97 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt @@ -9,6 +9,7 @@ import android.view.View import android.widget.LinearLayout import kotlinx.android.synthetic.main.view_conversation.view.* import network.loki.messenger.R +import org.session.libsession.utilities.SSKEnvironment import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded @@ -56,7 +57,7 @@ class ConversationView : LinearLayout { } profilePictureView.glide = glide profilePictureView.update(thread.recipient, thread.threadId) - val senderDisplayName = if (thread.recipient.isLocalNumber) context.getString(R.string.note_to_self) else if (!thread.recipient.name.isNullOrEmpty()) thread.recipient.name else thread.recipient.address.toString() + val senderDisplayName = if (thread.recipient.isLocalNumber) context.getString(R.string.note_to_self) else SSKEnvironment.shared.profileManager.getDisplayName(context, thread.recipient) ?: thread.recipient.address.toString() btnGroupNameDisplay.text = senderDisplayName timestampTextView.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), thread.date) muteIndicatorImageView.visibility = if (thread.recipient.isMuted) VISIBLE else GONE diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index 9ca7fe3c9e..2450ba9ed8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -9,10 +9,8 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob class ProfileManager: SSKEnvironment.ProfileManagerProtocol { - override fun setDisplayName(context: Context, recipient: Recipient, displayName: String) { - val database = DatabaseFactory.getLokiUserDatabase(context) + override fun setDisplayName(context: Context, recipient: Recipient, displayName: String?) { val sessionID = recipient.address.serialize() - database.setDisplayName(sessionID, displayName) // New API val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) var contact = contactDatabase.getContactWithSessionID(sessionID) @@ -21,6 +19,10 @@ class ProfileManager: SSKEnvironment.ProfileManagerProtocol { contact.nickname = displayName contactDatabase.setContact(contact) } + // Old API + if (displayName == null) return + val database = DatabaseFactory.getLokiUserDatabase(context) + database.setDisplayName(sessionID, displayName) } override fun setProfileName(context: Context, recipient: Recipient, profileName: String) { diff --git a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt index 7362cde2c4..3cc20ca90d 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt @@ -30,7 +30,7 @@ class SSKEnvironment( const val NAME_PADDED_LENGTH = 26 } - fun setDisplayName(context: Context, recipient: Recipient, displayName: String) // Client-side Nickname + fun setDisplayName(context: Context, recipient: Recipient, displayName: String?) // Client-side Nickname fun setProfileName(context: Context, recipient: Recipient, profileName: String) fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String) fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray) From d5a57c7dc8ec2c97d982ef3f7168e68b3823c091 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 12 May 2021 16:27:40 +1000 Subject: [PATCH 14/28] minor refactor --- .../securesms/loki/views/ConversationView.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt index b8e184fd97..8ee64d422b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt @@ -9,6 +9,7 @@ import android.view.View import android.widget.LinearLayout import kotlinx.android.synthetic.main.view_conversation.view.* import network.loki.messenger.R +import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.SSKEnvironment import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.ThreadRecord @@ -57,7 +58,7 @@ class ConversationView : LinearLayout { } profilePictureView.glide = glide profilePictureView.update(thread.recipient, thread.threadId) - val senderDisplayName = if (thread.recipient.isLocalNumber) context.getString(R.string.note_to_self) else SSKEnvironment.shared.profileManager.getDisplayName(context, thread.recipient) ?: thread.recipient.address.toString() + val senderDisplayName = getUserDisplayName(thread.recipient) ?: thread.recipient.address.toString() btnGroupNameDisplay.text = senderDisplayName timestampTextView.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), thread.date) muteIndicatorImageView.visibility = if (thread.recipient.isMuted) VISIBLE else GONE @@ -86,9 +87,11 @@ class ConversationView : LinearLayout { profilePictureView.recycle() } - private fun getUserDisplayName(publicKey: String?): String? { - if (TextUtils.isEmpty(publicKey)) return null - return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey!!) + private fun getUserDisplayName(recipient: Recipient): String? { + if (recipient.isLocalNumber) + return context.getString(R.string.note_to_self) + else + return SSKEnvironment.shared.profileManager.getDisplayName(context, recipient) } // endregion } \ No newline at end of file From 267a94b8ef333655d88e9a6c4fefe091a36e6409 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 13 May 2021 10:24:32 +1000 Subject: [PATCH 15/28] minor fix --- .../securesms/loki/database/SessionContactDatabase.kt | 4 +++- .../thoughtcrime/securesms/loki/views/ConversationView.kt | 6 +++--- .../thoughtcrime/securesms/sskenvironment/ProfileManager.kt | 5 +++++ .../org/session/libsession/messaging/contacts/Contact.kt | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt index 4aec4b4a48..a40efb8824 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt @@ -6,6 +6,8 @@ import androidx.core.database.getStringOrNull import net.sqlcipher.Cursor import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.Job +import org.session.libsession.messaging.threads.Address +import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsignal.utilities.Base64 import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper @@ -74,7 +76,7 @@ class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Da cursor.getStringOrNull(profilePictureEncryptionKey)?.let { contact.profilePictureEncryptionKey = Base64.decode(it) } - contact.threadID = cursor.getInt(threadID) + contact.threadID = cursor.getLong(threadID) contact.isTrusted = cursor.getInt(isTrusted) != 0 return contact } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt index 8ee64d422b..46e2f5a039 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt @@ -88,10 +88,10 @@ class ConversationView : LinearLayout { } private fun getUserDisplayName(recipient: Recipient): String? { - if (recipient.isLocalNumber) - return context.getString(R.string.note_to_self) + return if (recipient.isLocalNumber) + context.getString(R.string.note_to_self) else - return SSKEnvironment.shared.profileManager.getDisplayName(context, recipient) + SSKEnvironment.shared.profileManager.getDisplayName(context, recipient) } // endregion } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index 2450ba9ed8..2ddbd40cdc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -15,6 +15,7 @@ class ProfileManager: SSKEnvironment.ProfileManagerProtocol { val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) var contact = contactDatabase.getContactWithSessionID(sessionID) if (contact == null) contact = Contact(sessionID) + contact.threadID = DatabaseFactory.getStorage(context).getThreadIdFor(recipient.address) if (contact.nickname != displayName) { contact.nickname = displayName contactDatabase.setContact(contact) @@ -34,6 +35,7 @@ class ProfileManager: SSKEnvironment.ProfileManagerProtocol { val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) var contact = contactDatabase.getContactWithSessionID(sessionID) if (contact == null) contact = Contact(sessionID) + contact.threadID = DatabaseFactory.getStorage(context).getThreadIdFor(recipient.address) if (contact.name != profileName) { contact.name = profileName contactDatabase.setContact(contact) @@ -47,6 +49,7 @@ class ProfileManager: SSKEnvironment.ProfileManagerProtocol { val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) var contact = contactDatabase.getContactWithSessionID(sessionID) if (contact == null) contact = Contact(sessionID) + contact.threadID = DatabaseFactory.getStorage(context).getThreadIdFor(recipient.address) if (contact.profilePictureURL != profilePictureURL) { contact.profilePictureURL = profilePictureURL contactDatabase.setContact(contact) @@ -61,6 +64,7 @@ class ProfileManager: SSKEnvironment.ProfileManagerProtocol { val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) var contact = contactDatabase.getContactWithSessionID(sessionID) if (contact == null) contact = Contact(sessionID) + contact.threadID = DatabaseFactory.getStorage(context).getThreadIdFor(recipient.address) if (!contact.profilePictureEncryptionKey.contentEquals(profileKey)) { contact.profilePictureEncryptionKey = profileKey contactDatabase.setContact(contact) @@ -82,6 +86,7 @@ class ProfileManager: SSKEnvironment.ProfileManagerProtocol { var contact = contactDatabase.getContactWithSessionID(sessionID) if (contact == null) { contact = Contact(sessionID) + contact.threadID = DatabaseFactory.getStorage(context).getThreadIdFor(recipient.address) contact.name = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(sessionID) ?: recipient.profileName ?: recipient.name contactDatabase.setContact(contact) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt b/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt index 392c57e3d5..05e09a10d3 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt @@ -10,7 +10,7 @@ class Contact(val sessionID: String) { // The key with which the profile picture is encrypted. var profilePictureEncryptionKey: ByteArray? = null // The ID of the thread associated with this contact. - var threadID: Int? = null + var threadID: Long? = null // This flag is used to determine whether we should auto-download files sent by this contact. var isTrusted = false From a0e186e8f61e9adc4f43fc62472941925200d37f Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 13 May 2021 10:25:11 +1000 Subject: [PATCH 16/28] fix keyboard showing/hiding issue --- .../loki/dialogs/UserDetailsBottomSheet.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt index 2a1854c176..08c7183501 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt @@ -1,14 +1,17 @@ package org.thoughtcrime.securesms.loki.dialogs +import android.annotation.SuppressLint import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.opengl.Visibility import android.os.Bundle +import android.text.InputType import com.google.android.material.bottomsheet.BottomSheetDialogFragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager import android.widget.Toast import kotlinx.android.synthetic.main.fragment_user_details_bottom_sheet.* import kotlinx.android.synthetic.main.view_conversation.view.* @@ -38,15 +41,18 @@ public class UserDetailsBottomSheet : BottomSheetDialogFragment() { nameTextViewContainer.visibility = View.INVISIBLE nameEditContainer.visibility = View.VISIBLE nameEditText.requestFocus() + showSoftKeyboard() } btnCancelNickNameEdit.setOnClickListener { nameEditText.clearFocus() + hideSoftKeyboard() nameTextViewContainer.visibility = View.VISIBLE nameEditContainer.visibility = View.INVISIBLE nameEditText.text = null } btnSaveNickNameEdit.setOnClickListener { nameEditText.clearFocus() + hideSoftKeyboard() nameTextViewContainer.visibility = View.VISIBLE nameEditContainer.visibility = View.INVISIBLE var newNickName: String? = null @@ -65,4 +71,15 @@ public class UserDetailsBottomSheet : BottomSheetDialogFragment() { Toast.makeText(requireContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() } } + + @SuppressLint("ServiceCast") + fun showSoftKeyboard() { + val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + imm?.showSoftInput(nameEditText, 0) + } + + fun hideSoftKeyboard() { + val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + imm?.hideSoftInputFromWindow(nameEditText.windowToken, 0) + } } \ No newline at end of file From 85eff702c3dddaf64b05297ff423614220c53aed Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 13 May 2021 14:24:30 +1000 Subject: [PATCH 17/28] clean --- .../loki/dialogs/UserDetailsBottomSheet.kt | 4 - .../fragment_user_details_bottom_sheet.xml | 8 +- libsession/src/main/res/values/styles.xml | 441 ------------------ libsession/src/main/res/values/themes.xml | 294 ------------ 4 files changed, 5 insertions(+), 742 deletions(-) delete mode 100644 libsession/src/main/res/values/styles.xml delete mode 100644 libsession/src/main/res/values/themes.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt index 08c7183501..e060170d5c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt @@ -4,9 +4,7 @@ import android.annotation.SuppressLint import android.content.ClipData import android.content.ClipboardManager import android.content.Context -import android.opengl.Visibility import android.os.Bundle -import android.text.InputType import com.google.android.material.bottomsheet.BottomSheetDialogFragment import android.view.LayoutInflater import android.view.View @@ -14,12 +12,10 @@ import android.view.ViewGroup import android.view.inputmethod.InputMethodManager import android.widget.Toast import kotlinx.android.synthetic.main.fragment_user_details_bottom_sheet.* -import kotlinx.android.synthetic.main.view_conversation.view.* import network.loki.messenger.R import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.SSKEnvironment -import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.mms.GlideApp public class UserDetailsBottomSheet : BottomSheetDialogFragment() { diff --git a/app/src/main/res/layout/fragment_user_details_bottom_sheet.xml b/app/src/main/res/layout/fragment_user_details_bottom_sheet.xml index 48496a4982..f172a42f75 100644 --- a/app/src/main/res/layout/fragment_user_details_bottom_sheet.xml +++ b/app/src/main/res/layout/fragment_user_details_bottom_sheet.xml @@ -29,20 +29,22 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" - android:visibility="invisible"> + android:visibility="visible"> + android:drawableEnd="@drawable/ic_baseline_edit_24" + android:drawablePadding="@dimen/small_spacing" /> diff --git a/libsession/src/main/res/values/styles.xml b/libsession/src/main/res/values/styles.xml deleted file mode 100644 index db1af0eab2..0000000000 --- a/libsession/src/main/res/values/styles.xml +++ /dev/null @@ -1,441 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/libsession/src/main/res/values/themes.xml b/libsession/src/main/res/values/themes.xml deleted file mode 100644 index 4239aebee4..0000000000 --- a/libsession/src/main/res/values/themes.xml +++ /dev/null @@ -1,294 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 22b4ca2a274b40a5a3bbbb8c2d324d79c23c3a63 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 13 May 2021 14:25:09 +1000 Subject: [PATCH 18/28] update home screen immediately --- .../securesms/loki/database/SessionContactDatabase.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt index a40efb8824..bceaff626c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt @@ -64,6 +64,7 @@ class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Da contentValues.put(threadID, threadID) contentValues.put(isTrusted, if (contact.isTrusted) 1 else 0) database.insertOrUpdate(sessionContactTable, contentValues, "$sessionID = ?", arrayOf(contact.sessionID)) + notifyConversationListListeners() } private fun contactFromCursor(cursor: Cursor): Contact { From 1d5f7957ab2c514eb7289aaedcde4dcbf9077ea7 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 13 May 2021 16:20:55 +1000 Subject: [PATCH 19/28] clean --- app/src/main/AndroidManifest.xml | 33 ------------------ .../loki/dialogs/UserDetailsBottomSheet.kt | 34 +++++++++++++------ 2 files changed, 24 insertions(+), 43 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2d8473b849..f573045a8f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -172,9 +172,6 @@ - - - - - - - - - - - - - + when (actionId) { + EditorInfo.IME_ACTION_DONE -> { + saveNickName(recipient) + return@setOnEditorActionListener true + } + else -> return@setOnEditorActionListener false } - SSKEnvironment.shared.profileManager.setDisplayName(requireContext(), recipient, newNickName) - nameTextView.text = SSKEnvironment.shared.profileManager.getDisplayName(requireContext(), recipient) ?: "Anonymous" } nameTextView.text = SSKEnvironment.shared.profileManager.getDisplayName(requireContext(), recipient) ?: "Anonymous" publicKeyTextView.text = publicKey @@ -68,6 +69,19 @@ public class UserDetailsBottomSheet : BottomSheetDialogFragment() { } } + fun saveNickName(recipient: Recipient) { + nameEditText.clearFocus() + hideSoftKeyboard() + nameTextViewContainer.visibility = View.VISIBLE + nameEditContainer.visibility = View.INVISIBLE + var newNickName: String? = null + if (!nameEditText.text.isEmpty()) { + newNickName = nameEditText.text.toString() + } + SSKEnvironment.shared.profileManager.setDisplayName(requireContext(), recipient, newNickName) + nameTextView.text = SSKEnvironment.shared.profileManager.getDisplayName(requireContext(), recipient) ?: "Anonymous" + } + @SuppressLint("ServiceCast") fun showSoftKeyboard() { val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager From c4cd74fca02e3d079c0fc3666d1037895f3d4924 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 14 May 2021 11:04:28 +1000 Subject: [PATCH 20/28] fix icons in bottom sheets don't follow the theme color --- app/src/main/res/values/themes.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 4239aebee4..ce94e33323 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -145,6 +145,8 @@