Compare commits

...

3 Commits

Author SHA1 Message Date
Al Lansley 0febb0456e
SES-212 - Always show delivery status of last sent message - FINAL! (#1418)
* Fixes #1408

* Addressed PR feedback

* Cleanup

* PR adjustments

* Further PR adjustments

* Updated libsession-util

* Added fix for crash when no messages

* Ignoring dirty submodules so they don't show up in git

* Re-fixed display of delivery status on last sent message (got broken by disappearing messages)

* Removed ignore dirty modules line in .gitmodules as it all seems to be playing nice now

---------

Co-authored-by: AL-Session <160798022+AL-Session@users.noreply.github.com>
Co-authored-by: Al Lansley <al@oxen.io>
2 months ago
Al Lansley 54d6c025b1
SES-1352 - User and group names allowing multi-line strings (#1395)
* Fix WIP

* Resolved issue - pushing before cleanup & PR tomorrow morning

* Enforced single line for new closed group names

* Fixes #1394

* Final cleanup prior to PR

* Added code to restore a previous contact nickname if an empty one is given

* Added initial limits to nicknames and group names, both creation and display

* Minor adjustments

* Adjusted max nickname and group name to 35 chars as per Kee's instructions

* Fixed closed group edit text able to get too wide and cut off buttons

---------

Co-authored-by: = <=>
Co-authored-by: AL-Session <160798022+AL-Session@users.noreply.github.com>
Co-authored-by: Al Lansley <al@oxen.io>
2 months ago
Al Lansley 9e62e1eab4
SES-789 - Scroll to bottom of long new message(s) (#1426)
* WIP

* Working - push before cleanup

* Fixes #1316

* Cleanup

* PR review adjustments

* Fixed scrolling when receiving an image based message while keyboard is up

* Prevent auto-scroll to last seen item pos in conversation view if <= 3

* Put back <=3 check to scroll

* Forced scrolling to bottom of long messages (both sent and received) when already at the bottom of the RecyclerView

* Fixes #1364

---------

Co-authored-by: = <=>
Co-authored-by: AL-Session <160798022+AL-Session@users.noreply.github.com>
2 months ago

@ -2162,7 +2162,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun onChanged() {
super.onChanged()
if (recyclerView.isScrolledToWithin30dpOfBottom) {
recyclerView.scrollToPosition(adapter.itemCount-1)
// Note: The adapter itemCount is zero based - so calling this with the itemCount in
// a non-zero based manner scrolls us to the bottom of the last message (including
// to the bottom of long messages as required by Jira SES-789 / GitHub 1364).
recyclerView.scrollToPosition(adapter.itemCount)
}
}
}

@ -22,10 +22,12 @@ import kotlinx.coroutines.launch
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewVisibleMessageBinding
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
import org.thoughtcrime.securesms.database.MmsSmsColumns
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.mms.GlideRequests
@ -57,6 +59,7 @@ class ConversationAdapter(
private val contactCache = SparseArray<Contact>(100)
private val contactLoadedCache = SparseBooleanArray(100)
private val lastSeen = AtomicLong(originalLastSeen)
private var lastSentMessageId: Long = -1L
init {
lifecycleCoroutineScope.launch(IO) {
@ -136,7 +139,8 @@ class ConversationAdapter(
senderId,
lastSeen.get(),
visibleMessageViewDelegate,
onAttachmentNeedsDownload
onAttachmentNeedsDownload,
lastSentMessageId
)
if (!message.isDeleted) {
@ -205,8 +209,23 @@ class ConversationAdapter(
return messageDB.readerFor(cursor).current
}
private fun getLastSentMessageId(cursor: Cursor): Long {
// If we don't move to first (or at least step backwards) we can step off the end of the
// cursor and any query will return an "Index = -1" error.
val cursorHasContent = cursor.moveToFirst()
if (cursorHasContent) {
val thisThreadId = cursor.getLong(4) // Column index 4 is "thread_id"
if (thisThreadId != -1L) {
val thisUsersSessionId = TextSecurePreferences.getLocalNumber(context)
return messageDB.getLastSentMessageFromSender(thisThreadId, thisUsersSessionId)
}
}
return -1L
}
override fun changeCursor(cursor: Cursor?) {
super.changeCursor(cursor)
val toRemove = mutableSetOf<MessageRecord>()
val toDeselect = mutableSetOf<Pair<Int, MessageRecord>>()
for (selected in selectedItems) {
@ -224,6 +243,11 @@ class ConversationAdapter(
toDeselect.iterator().forEach { (pos, record) ->
onDeselect(record, pos)
}
// This value gets updated here ONLY when the cursor changes, and the value is then passed
// through to `VisibleMessageView.bind` each time we bind via `onBindItemViewHolder`, above.
// If there are no messages then lastSentMessageId is assigned the value -1L.
if (cursor != null) { lastSentMessageId = getLastSentMessageId(cursor) }
}
fun findLastSeenItemPosition(lastSeenTimestamp: Long): Int? {

@ -32,6 +32,7 @@ import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.contacts.Contact.ContactContext
import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.ViewUtil
import org.session.libsession.utilities.getColorFromAttr
import org.session.libsession.utilities.modifyLayoutParams
@ -131,7 +132,8 @@ class VisibleMessageView : LinearLayout {
senderSessionID: String,
lastSeen: Long,
delegate: VisibleMessageViewDelegate? = null,
onAttachmentNeedsDownload: (Long, Long) -> Unit
onAttachmentNeedsDownload: (Long, Long) -> Unit,
lastSentMessageId: Long
) {
val threadID = message.threadId
val thread = threadDb.getRecipientForThreadId(threadID) ?: return
@ -195,14 +197,18 @@ class VisibleMessageView : LinearLayout {
val contactContext =
if (thread.isOpenGroupRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR
binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderSessionID
// Unread marker
binding.unreadMarkerContainer.isVisible = lastSeen != -1L && message.timestamp > lastSeen && (previous == null || previous.timestamp <= lastSeen) && !message.isOutgoing
// Date break
val showDateBreak = isStartOfMessageCluster || snIsSelected
binding.dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else null
binding.dateBreakTextView.isVisible = showDateBreak
// Message status indicator
showStatusMessage(message)
// Emoji Reactions
val emojiLayoutParams = binding.emojiReactionsView.root.layoutParams as ConstraintLayout.LayoutParams
emojiLayoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f
@ -238,7 +244,8 @@ class VisibleMessageView : LinearLayout {
}
private fun showStatusMessage(message: MessageRecord) {
val disappearing = message.expiresIn > 0
val scheduledToDisappear = message.expiresIn > 0
binding.messageInnerLayout.modifyLayoutParams<FrameLayout.LayoutParams> {
gravity = if (message.isOutgoing) Gravity.END else Gravity.START
@ -250,21 +257,22 @@ class VisibleMessageView : LinearLayout {
binding.expirationTimerView.isGone = true
if (message.isOutgoing || disappearing) {
if (message.isOutgoing || scheduledToDisappear) {
val (iconID, iconColor, textId) = getMessageStatusImage(message)
textId?.let(binding.messageStatusTextView::setText)
iconColor?.let(binding.messageStatusTextView::setTextColor)
iconID?.let { ContextCompat.getDrawable(context, it) }
?.run { iconColor?.let { mutate().apply { setTint(it) } } ?: this }
?.let(binding.messageStatusImageView::setImageDrawable)
val lastMessageID = mmsSmsDb.getLastMessageID(message.threadId)
val isLastMessage = message.id == lastMessageID
binding.messageStatusTextView.isVisible =
textId != null && (!message.isSent || isLastMessage || disappearing)
val showTimer = disappearing && !message.isPending
binding.messageStatusImageView.isVisible =
iconID != null && !showTimer && (!message.isSent || isLastMessage)
// Always show the delivery status of the last sent message
val thisUsersSessionId = TextSecurePreferences.getLocalNumber(context)
val lastSentMessageId = mmsSmsDb.getLastSentMessageFromSender(message.threadId, thisUsersSessionId)
val isLastSentMessage = lastSentMessageId == message.id
binding.messageStatusTextView.isVisible = textId != null && (isLastSentMessage || scheduledToDisappear)
val showTimer = scheduledToDisappear && !message.isPending
binding.messageStatusImageView.isVisible = iconID != null && !showTimer && (!message.isSent || isLastSentMessage)
binding.messageStatusImageView.bringToFront()
binding.expirationTimerView.bringToFront()

@ -209,6 +209,24 @@ public class MmsSmsDatabase extends Database {
}
}
public long getLastSentMessageFromSender(long threadId, String serializedAuthor) {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
boolean isOwnNumber = Util.isOwnNumber(context, serializedAuthor);
// Try everything with resources so that they auto-close on end of scope
try (Cursor cursor = queryTables(PROJECTION, selection, order, null)) {
try (MmsSmsDatabase.Reader reader = readerFor(cursor)) {
MessageRecord messageRecord;
while ((messageRecord = reader.getNext()) != null) {
if (isOwnNumber && messageRecord.isOutgoing()) { return messageRecord.id; }
}
}
}
return -1;
}
public Cursor getUnread() {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " ASC";
String selection = "(" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1) AND " + MmsSmsColumns.NOTIFIED + " = 0";

@ -78,7 +78,7 @@ class CreateGroupFragment : Fragment() {
if (name.isEmpty()) {
return@setOnClickListener Toast.makeText(context, R.string.activity_create_closed_group_group_name_missing_error, Toast.LENGTH_LONG).show()
}
if (name.length >= 30) {
if (name.length > resources.getInteger(R.integer.max_group_and_community_name_length_chars)) {
return@setOnClickListener Toast.makeText(context, R.string.activity_create_closed_group_group_name_too_long_error, Toast.LENGTH_LONG).show()
}
val selectedMembers = adapter.selectedMembers

@ -25,7 +25,6 @@ import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.IdPrefix
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.mms.GlideApp
import javax.inject.Inject
@AndroidEntryPoint
@ -34,6 +33,9 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() {
@Inject lateinit var threadDb: ThreadDatabase
private lateinit var binding: FragmentUserDetailsBottomSheetBinding
private var previousContactNickname: String = ""
companion object {
const val ARGUMENT_PUBLIC_KEY = "publicKey"
const val ARGUMENT_THREAD_ID = "threadId"
@ -130,9 +132,10 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() {
nameTextViewContainer.visibility = View.VISIBLE
nameEditTextContainer.visibility = View.INVISIBLE
var newNickName: String? = null
if (nicknameEditText.text.isNotEmpty()) {
if (nicknameEditText.text.isNotEmpty() && nicknameEditText.text.trim().length != 0) {
newNickName = nicknameEditText.text.toString()
}
else { newNickName = previousContactNickname }
val publicKey = recipient.address.serialize()
val storage = MessagingModuleConfiguration.shared.storage
val contact = storage.getContactWithSessionID(publicKey) ?: Contact(publicKey)
@ -145,6 +148,9 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() {
fun showSoftKeyboard() {
val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
imm?.showSoftInput(binding.nicknameEditText, 0)
// Keep track of the original nickname to re-use if an empty / blank nickname is entered
previousContactNickname = binding.nameTextView.text.toString()
}
fun hideSoftKeyboard() {

@ -17,6 +17,7 @@ import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.core.view.isVisible
@ -203,6 +204,21 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
binding.displayNameEditText.selectAll()
binding.displayNameEditText.requestFocus()
inputMethodManager.showSoftInput(binding.displayNameEditText, 0)
// Save the updated display name when the user presses enter on the soft keyboard
binding.displayNameEditText.setOnEditorActionListener { v, actionId, event ->
when (actionId) {
// Note: IME_ACTION_DONE is how we've configured the soft keyboard to respond,
// while IME_ACTION_UNSPECIFIED is what triggers when we hit enter on a
// physical keyboard.
EditorInfo.IME_ACTION_DONE, EditorInfo.IME_ACTION_UNSPECIFIED -> {
saveDisplayName()
displayNameEditActionMode?.finish()
true
}
else -> false
}
}
} else {
inputMethodManager.hideSoftInputFromWindow(binding.displayNameEditText.windowToken, 0)
}

@ -43,6 +43,8 @@
android:paddingBottom="0dp"
android:gravity="center_vertical"
android:inputType="textCapWords"
android:maxLength="@integer/max_user_nickname_length_chars"
android:maxLines="1"
android:hint="@string/activity_display_name_edit_text_hint" />
<View

@ -43,6 +43,8 @@
android:paddingBottom="0dp"
android:gravity="center_vertical"
android:inputType="textCapWords"
android:maxLength="@integer/max_user_nickname_length_chars"
android:maxLines="1"
android:hint="@string/activity_display_name_edit_text_hint" />
<View

@ -32,6 +32,7 @@
android:id="@+id/btnCancelGroupNameEdit"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginLeft="@dimen/medium_spacing"
android:contentDescription="@string/AccessibilityId_cancel_name_change"
android:src="@drawable/ic_baseline_clear_24"/>
@ -49,6 +50,7 @@
android:inputType="text"
android:singleLine="true"
android:imeOptions="actionDone"
android:maxLength="@integer/max_group_and_community_name_length_chars"
android:contentDescription="@string/AccessibilityId_group_name"
android:hint="@string/activity_edit_closed_group_edit_text_hint" />
@ -57,6 +59,7 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:layout_marginRight="@dimen/medium_spacing"
android:contentDescription="@string/AccessibilityId_accept_name_change"
android:src="@drawable/ic_baseline_done_24"/>

@ -47,7 +47,11 @@
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:visibility="invisible"
android:hint="@string/activity_settings_display_name_edit_text_hint" />
android:hint="@string/activity_settings_display_name_edit_text_hint"
android:imeOptions="actionDone"
android:inputType="textCapWords"
android:maxLength="@integer/max_user_nickname_length_chars"
android:maxLines="1" />
<TextView
android:id="@+id/btnGroupNameDisplay"
@ -57,6 +61,7 @@
android:contentDescription="@string/AccessibilityId_username"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/very_large_font_size"
android:maxLength="@integer/max_user_nickname_length_chars"
android:textStyle="bold" />
</RelativeLayout>

@ -62,10 +62,14 @@
android:layout_marginBottom="@dimen/medium_spacing"
android:contentDescription="@string/AccessibilityId_group_name_input"
android:hint="@string/activity_create_closed_group_edit_text_hint"
android:maxLength="30"
android:imeOptions="actionDone"
android:inputType="textCapWords"
android:maxLength="@integer/max_group_and_community_name_length_chars"
android:maxLines="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleText" />
app:layout_constraintTop_toBottomOf="@id/titleText"
tools:ignore="ContentDescription" />
<org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView
android:id="@+id/contactSearch"

@ -29,6 +29,8 @@
android:id="@+id/nameTextViewContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/medium_spacing"
android:paddingEnd="@dimen/medium_spacing"
android:gravity="center"
android:orientation="horizontal"
android:layout_centerInParent="true"
@ -42,6 +44,7 @@
android:id="@+id/nameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/small_spacing"
android:layout_marginEnd="@dimen/small_spacing"
@ -57,6 +60,7 @@
android:layout_height="22dp"
android:contentDescription="@string/AccessibilityId_edit_user_nickname"
android:paddingTop="2dp"
android:layout_marginEnd="20dp"
android:src="@drawable/ic_baseline_edit_24" />
</LinearLayout>
@ -73,6 +77,7 @@
android:id="@+id/cancelNicknameEditingButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginLeft="@dimen/large_spacing"
android:contentDescription="@string/AccessibilityId_cancel"
android:src="@drawable/ic_baseline_clear_24" />
@ -82,12 +87,12 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginHorizontal="@dimen/small_spacing"
android:contentDescription="@string/AccessibilityId_username"
android:textAlignment="center"
android:paddingVertical="12dp"
android:inputType="text"
android:singleLine="true"
android:maxLength="@integer/max_user_nickname_length_chars"
android:maxLines="1"
android:imeOptions="actionDone"
android:textColorHint="?android:textColorSecondary"
android:hint="@string/fragment_user_details_bottom_sheet_edit_text_hint" />
@ -96,6 +101,7 @@
android:id="@+id/saveNicknameButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginRight="@dimen/large_spacing"
android:contentDescription="@string/AccessibilityId_apply"
android:src="@drawable/ic_baseline_done_24" />

@ -25,6 +25,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/medium_spacing"
android:maxLength="@integer/max_user_nickname_length_chars"
android:maxLines="1"
android:textAlignment="viewStart"
android:ellipsize="end"

@ -165,7 +165,7 @@
android:maxLines="1"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/medium_font_size"
tools:text="Sorry, gotta go fight crime again" />
tools:text="Sorry, gotta go fight crime again - and more text to make it ellipsize" />
<include layout="@layout/view_typing_indicator"
android:id="@+id/typingIndicatorView"

@ -6,4 +6,7 @@
<integer name="reaction_scrubber_reveal_offset">100</integer>
<integer name="reaction_scrubber_hide_duration">150</integer>
<integer name="reaction_scrubber_emoji_reveal_duration_start_delay_factor">10</integer>
<integer name="max_user_nickname_length_chars">35</integer>
<integer name="max_group_and_community_name_length_chars">35</integer>
</resources>
Loading…
Cancel
Save