Merge branch 'dev' into fix-dialog-button

This commit is contained in:
Andrew 2024-04-17 15:59:43 +09:30
commit c28eba313a
12 changed files with 126 additions and 28 deletions

View File

@ -39,15 +39,20 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
public int getDesiredTheme() { public int getDesiredTheme() {
ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences()); ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences());
int userSelectedTheme = themeState.getTheme(); int userSelectedTheme = themeState.getTheme();
// If the user has configured Session to follow the system light/dark theme mode then do so..
if (themeState.getFollowSystem()) { if (themeState.getFollowSystem()) {
// do light or dark based on the selected theme
// Use light or dark versions of the user's theme based on light-mode / dark-mode settings
boolean isDayUi = UiModeUtilities.isDayUiMode(this); boolean isDayUi = UiModeUtilities.isDayUiMode(this);
if (userSelectedTheme == R.style.Ocean_Dark || userSelectedTheme == R.style.Ocean_Light) { if (userSelectedTheme == R.style.Ocean_Dark || userSelectedTheme == R.style.Ocean_Light) {
return isDayUi ? R.style.Ocean_Light : R.style.Ocean_Dark; return isDayUi ? R.style.Ocean_Light : R.style.Ocean_Dark;
} else { } else {
return isDayUi ? R.style.Classic_Light : R.style.Classic_Dark; return isDayUi ? R.style.Classic_Light : R.style.Classic_Dark;
} }
} else { }
else // ..otherwise just return their selected theme.
{
return userSelectedTheme; return userSelectedTheme;
} }
} }

View File

@ -8,13 +8,14 @@ import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.Button import android.widget.Button
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.LinearLayout.VERTICAL import android.widget.LinearLayout.VERTICAL
import android.widget.Space
import android.widget.TextView import android.widget.TextView
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.annotation.StyleRes import androidx.annotation.StyleRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.setPadding import androidx.core.view.setMargins
import androidx.core.view.updateMargins import androidx.core.view.updateMargins
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import network.loki.messenger.R import network.loki.messenger.R
@ -36,7 +37,9 @@ class SessionDialogBuilder(val context: Context) {
private var dialog: AlertDialog? = null private var dialog: AlertDialog? = null
private fun dismiss() = dialog?.dismiss() private fun dismiss() = dialog?.dismiss()
private val topView = LinearLayout(context).apply { orientation = VERTICAL } private val topView = LinearLayout(context)
.apply { setPadding(0, dp20, 0, 0) }
.apply { orientation = VERTICAL }
.also(dialogBuilder::setCustomTitle) .also(dialogBuilder::setCustomTitle)
private val contentView = LinearLayout(context).apply { orientation = VERTICAL } private val contentView = LinearLayout(context).apply { orientation = VERTICAL }
private val buttonLayout = LinearLayout(context) private val buttonLayout = LinearLayout(context)
@ -52,14 +55,14 @@ class SessionDialogBuilder(val context: Context) {
fun title(text: CharSequence?) = title(text?.toString()) fun title(text: CharSequence?) = title(text?.toString())
fun title(text: String?) { fun title(text: String?) {
text(text, R.style.TextAppearance_AppCompat_Title) { setPadding(dp20) } text(text, R.style.TextAppearance_AppCompat_Title) { setPadding(dp20, 0, dp20, 0) }
} }
fun text(@StringRes id: Int, style: Int = 0) = text(context.getString(id), style) fun text(@StringRes id: Int, style: Int = 0) = text(context.getString(id), style)
fun text(text: CharSequence?, @StyleRes style: Int = 0) { fun text(text: CharSequence?, @StyleRes style: Int = 0) {
text(text, style) { text(text, style) {
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
.apply { updateMargins(dp40, 0, dp40, dp20) } .apply { updateMargins(dp40, 0, dp40, 0) }
} }
} }
@ -71,6 +74,10 @@ class SessionDialogBuilder(val context: Context) {
textAlignment = View.TEXT_ALIGNMENT_CENTER textAlignment = View.TEXT_ALIGNMENT_CENTER
modify() modify()
}.let(topView::addView) }.let(topView::addView)
Space(context).apply {
layoutParams = LinearLayout.LayoutParams(0, dp20)
}.let(topView::addView)
} }
fun view(view: View) = contentView.addView(view) fun view(view: View) = contentView.addView(view)
@ -123,7 +130,8 @@ class SessionDialogBuilder(val context: Context) {
) = Button(context, null, 0, style).apply { ) = Button(context, null, 0, style).apply {
setText(text) setText(text)
contentDescription = resources.getString(contentDescriptionRes) contentDescription = resources.getString(contentDescriptionRes)
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, dp60, 1f) layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, dp60, 1f)
.apply { setMargins(dp20) }
setOnClickListener { setOnClickListener {
listener.invoke() listener.invoke()
if (dismiss) dismiss() if (dismiss) dismiss()

View File

@ -17,6 +17,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.utilities.getColorFromAttr
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
@ -84,7 +85,7 @@ class ContextMenuList(recyclerView: RecyclerView, onItemClick: () -> Unit) {
context.theme.resolveAttribute(item.iconRes, typedValue, true) context.theme.resolveAttribute(item.iconRes, typedValue, true)
icon.setImageDrawable(ContextCompat.getDrawable(context, typedValue.resourceId)) icon.setImageDrawable(ContextCompat.getDrawable(context, typedValue.resourceId))
icon.imageTintList = color?.let(ColorStateList::valueOf) icon.imageTintList = ColorStateList.valueOf(color ?: context.getColorFromAttr(android.R.attr.textColor))
} }
item.contentDescription?.let(context.resources::getString)?.let { itemView.contentDescription = it } item.contentDescription?.let(context.resources::getString)?.let { itemView.contentDescription = it }
title.setText(item.title) title.setText(item.title)

View File

@ -140,6 +140,8 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li
} }
fun draftQuote(thread: Recipient, message: MessageRecord, glide: GlideRequests) { fun draftQuote(thread: Recipient, message: MessageRecord, glide: GlideRequests) {
quoteView?.let(binding.inputBarAdditionalContentContainer::removeView)
quote = message quote = message
// If we already have a link preview View then clear the 'additional content' layout so that // If we already have a link preview View then clear the 'additional content' layout so that

View File

@ -4,18 +4,24 @@ import android.content.Context
import android.graphics.Typeface import android.graphics.Typeface
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString import android.text.SpannableString
import android.text.style.BackgroundColorSpan
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan import android.text.style.StyleSpan
import android.util.Range import android.util.Range
import androidx.appcompat.widget.ThemeUtils
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import network.loki.messenger.R import network.loki.messenger.R
import nl.komponents.kovenant.combine.Tuple2 import nl.komponents.kovenant.combine.Tuple2
import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.ThemeUtil
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.util.UiModeUtilities import org.thoughtcrime.securesms.util.UiModeUtilities
import org.thoughtcrime.securesms.util.getAccentColor import org.thoughtcrime.securesms.util.getAccentColor
import org.thoughtcrime.securesms.util.getColorResourceIdFromAttr
import org.thoughtcrime.securesms.util.getMessageTextColourAttr
import java.util.regex.Pattern import java.util.regex.Pattern
object MentionUtilities { object MentionUtilities {
@ -58,15 +64,37 @@ object MentionUtilities {
} }
} }
val result = SpannableString(text) val result = SpannableString(text)
val isLightMode = UiModeUtilities.isDayUiMode(context)
val color = if (isOutgoingMessage) { var mentionTextColour: Int? = null
ResourcesCompat.getColor(context.resources, if (isLightMode) R.color.white else R.color.black, context.theme) // In dark themes..
} else { if (ThemeUtil.isDarkTheme(context)) {
context.getAccentColor() // ..we use the standard outgoing message colour for outgoing messages..
if (isOutgoingMessage) {
val mentionTextColourAttributeId = getMessageTextColourAttr(true)
val mentionTextColourResourceId = getColorResourceIdFromAttr(context, mentionTextColourAttributeId)
mentionTextColour = ResourcesCompat.getColor(context.resources, mentionTextColourResourceId, context.theme)
}
else // ..but we use the accent colour for incoming messages (i.e., someone mentioning us)..
{
mentionTextColour = context.getAccentColor()
}
} }
else // ..while in light themes we always just use the incoming or outgoing message text colour for mentions.
{
val mentionTextColourAttributeId = getMessageTextColourAttr(isOutgoingMessage)
val mentionTextColourResourceId = getColorResourceIdFromAttr(context, mentionTextColourAttributeId)
mentionTextColour = ResourcesCompat.getColor(context.resources, mentionTextColourResourceId, context.theme)
}
for (mention in mentions) { for (mention in mentions) {
result.setSpan(ForegroundColorSpan(color), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) result.setSpan(ForegroundColorSpan(mentionTextColour), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
result.setSpan(StyleSpan(Typeface.BOLD), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) result.setSpan(StyleSpan(Typeface.BOLD), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
// If we're using a light theme then we change the background colour of the mention to be the accent colour
if (ThemeUtil.isLightTheme(context)) {
val backgroundColour = context.getAccentColor();
result.setSpan(BackgroundColorSpan(backgroundColour), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
} }
return result return result
} }

View File

@ -320,6 +320,19 @@ public class MmsSmsDatabase extends Database {
return -1; return -1;
} }
public long getLastMessageTimestamp(long threadId) {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
try (Cursor cursor = queryTables(PROJECTION, selection, order, "1")) {
if (cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_SENT));
}
}
return -1;
}
public Cursor getUnread() { public Cursor getUnread() {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " ASC"; String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " ASC";
String selection = "(" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1) AND " + MmsSmsColumns.NOTIFIED + " = 0"; String selection = "(" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1) AND " + MmsSmsColumns.NOTIFIED + " = 0";

View File

@ -934,7 +934,17 @@ public class ThreadDatabase extends Database {
readReceiptCount = 0; readReceiptCount = 0;
} }
return new ThreadRecord(body, snippetUri, recipient, date, count, MessageRecord lastMessage = null;
if (count > 0) {
MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase();
long messageTimestamp = mmsSmsDatabase.getLastMessageTimestamp(threadId);
if (messageTimestamp > 0) {
lastMessage = mmsSmsDatabase.getMessageForTimestamp(messageTimestamp);
}
}
return new ThreadRecord(body, snippetUri, lastMessage, recipient, date, count,
unreadCount, unreadMentionCount, threadId, deliveryReceiptCount, status, type, unreadCount, unreadMentionCount, threadId, deliveryReceiptCount, status, type,
distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned); distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned);
} }

View File

@ -43,6 +43,7 @@ import network.loki.messenger.R;
public class ThreadRecord extends DisplayRecord { public class ThreadRecord extends DisplayRecord {
private @Nullable final Uri snippetUri; private @Nullable final Uri snippetUri;
public @Nullable final MessageRecord lastMessage;
private final long count; private final long count;
private final int unreadCount; private final int unreadCount;
private final int unreadMentionCount; private final int unreadMentionCount;
@ -54,13 +55,14 @@ public class ThreadRecord extends DisplayRecord {
private final int initialRecipientHash; private final int initialRecipientHash;
public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri, public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri,
@NonNull Recipient recipient, long date, long count, int unreadCount, @Nullable MessageRecord lastMessage, @NonNull Recipient recipient, long date, long count, int unreadCount,
int unreadMentionCount, long threadId, int deliveryReceiptCount, int status, int unreadMentionCount, long threadId, int deliveryReceiptCount, int status,
long snippetType, int distributionType, boolean archived, long expiresIn, long snippetType, int distributionType, boolean archived, long expiresIn,
long lastSeen, int readReceiptCount, boolean pinned) long lastSeen, int readReceiptCount, boolean pinned)
{ {
super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount); super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount);
this.snippetUri = snippetUri; this.snippetUri = snippetUri;
this.lastMessage = lastMessage;
this.count = count; this.count = count;
this.unreadCount = unreadCount; this.unreadCount = unreadCount;
this.unreadMentionCount = unreadMentionCount; this.unreadMentionCount = unreadMentionCount;

View File

@ -4,6 +4,8 @@ import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.text.SpannableString
import android.text.TextUtils
import android.util.AttributeSet import android.util.AttributeSet
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
@ -89,7 +91,7 @@ class ConversationView : LinearLayout {
|| (configFactory.convoVolatile?.getConversationUnread(thread) == true) || (configFactory.convoVolatile?.getConversationUnread(thread) == true)
binding.unreadMentionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize) binding.unreadMentionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
binding.unreadMentionIndicator.isVisible = (thread.unreadMentionCount != 0 && thread.recipient.address.isGroup) binding.unreadMentionIndicator.isVisible = (thread.unreadMentionCount != 0 && thread.recipient.address.isGroup)
val senderDisplayName = getUserDisplayName(thread.recipient) val senderDisplayName = getTitle(thread.recipient)
?: thread.recipient.address.toString() ?: thread.recipient.address.toString()
binding.conversationViewDisplayNameTextView.text = senderDisplayName binding.conversationViewDisplayNameTextView.text = senderDisplayName
binding.timestampTextView.text = thread.date.takeIf { it != 0L }?.let { DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), it) } binding.timestampTextView.text = thread.date.takeIf { it != 0L }?.let { DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), it) }
@ -101,9 +103,7 @@ class ConversationView : LinearLayout {
R.drawable.ic_notifications_mentions R.drawable.ic_notifications_mentions
} }
binding.muteIndicatorImageView.setImageResource(drawableRes) binding.muteIndicatorImageView.setImageResource(drawableRes)
val rawSnippet = thread.getDisplayBody(context) binding.snippetTextView.text = highlightMentions(thread.getSnippet(), thread.threadId, context)
val snippet = highlightMentions(rawSnippet, thread.threadId, context)
binding.snippetTextView.text = snippet
binding.snippetTextView.typeface = if (unreadCount > 0 && !thread.isRead) Typeface.DEFAULT_BOLD else Typeface.DEFAULT binding.snippetTextView.typeface = if (unreadCount > 0 && !thread.isRead) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
binding.snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE binding.snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE
if (isTyping) { if (isTyping) {
@ -131,12 +131,21 @@ class ConversationView : LinearLayout {
binding.profilePictureView.recycle() binding.profilePictureView.recycle()
} }
private fun getUserDisplayName(recipient: Recipient): String? { private fun getTitle(recipient: Recipient): String? = when {
return if (recipient.isLocalNumber) { recipient.isLocalNumber -> context.getString(R.string.note_to_self)
context.getString(R.string.note_to_self) else -> recipient.toShortString() // Internally uses the Contact API
} else { }
recipient.toShortString() // Internally uses the Contact API
} private fun ThreadRecord.getSnippet(): CharSequence =
concatSnippet(getSnippetPrefix(), getDisplayBody(context))
private fun concatSnippet(prefix: CharSequence?, body: CharSequence): CharSequence =
prefix?.let { TextUtils.concat(it, ": ", body) } ?: body
private fun ThreadRecord.getSnippetPrefix(): CharSequence? = when {
recipient.isLocalNumber || lastMessage?.isControlMessage == true -> null
lastMessage?.isOutgoing == true -> resources.getString(R.string.MessageRecord_you)
else -> lastMessage?.individualRecipient?.toShortString()
} }
// endregion // endregion
} }

View File

@ -9,12 +9,15 @@ import android.graphics.Bitmap
import android.graphics.PointF import android.graphics.PointF
import android.graphics.Rect import android.graphics.Rect
import android.util.Size import android.util.Size
import android.util.TypedValue
import android.view.View import android.view.View
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.DimenRes import androidx.annotation.DimenRes
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.getColorFromAttr
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.annotation.AttrRes
import androidx.annotation.ColorRes
import androidx.core.graphics.applyCanvas import androidx.core.graphics.applyCanvas
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -32,6 +35,20 @@ val View.hitRect: Rect
@ColorInt @ColorInt
fun Context.getAccentColor() = getColorFromAttr(R.attr.colorAccent) fun Context.getAccentColor() = getColorFromAttr(R.attr.colorAccent)
// Method to grab the appropriate attribute for a message colour.
// Note: This is an attribute, NOT a resource Id - see `getColorResourceIdFromAttr` for that.
@AttrRes
fun getMessageTextColourAttr(messageIsOutgoing: Boolean): Int =
if (messageIsOutgoing) R.attr.message_sent_text_color else R.attr.message_received_text_color
// Method to get an actual R.id.<SOME_COLOUR> resource Id from an attribute such as R.attr.message_sent_text_color etc.
@ColorRes
fun getColorResourceIdFromAttr(context: Context, attr: Int): Int {
val typedValue = TypedValue()
context.theme.resolveAttribute(attr, typedValue, true)
return typedValue.resourceId
}
fun View.animateSizeChange(@DimenRes startSizeID: Int, @DimenRes endSizeID: Int, animationDuration: Long = 250) { fun View.animateSizeChange(@DimenRes startSizeID: Int, @DimenRes endSizeID: Int, animationDuration: Long = 250) {
val startSize = resources.getDimension(startSizeID) val startSize = resources.getDimension(startSizeID)
val endSize = resources.getDimension(endSizeID) val endSize = resources.getDimension(endSizeID)
@ -70,7 +87,6 @@ fun View.hideKeyboard() {
imm.hideSoftInputFromWindow(this.windowToken, 0) imm.hideSoftInputFromWindow(this.windowToken, 0)
} }
fun View.drawToBitmap(config: Bitmap.Config = Bitmap.Config.ARGB_8888, longestWidth: Int = 2000): Bitmap { fun View.drawToBitmap(config: Bitmap.Config = Bitmap.Config.ARGB_8888, longestWidth: Int = 2000): Bitmap {
val size = Size(measuredWidth, measuredHeight).coerceAtMost(longestWidth) val size = Size(measuredWidth, measuredHeight).coerceAtMost(longestWidth)
val scale = size.width / measuredWidth.toFloat() val scale = size.width / measuredWidth.toFloat()

View File

@ -2,7 +2,7 @@ package org.session.libsession.messaging.mentions
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact
import java.util.* import java.util.Locale
object MentionsManager { object MentionsManager {
var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys

View File

@ -27,6 +27,10 @@ public class ThemeUtil {
return getAttributeText(context, R.attr.theme_type, "light").equals("dark"); return getAttributeText(context, R.attr.theme_type, "light").equals("dark");
} }
public static boolean isLightTheme(@NonNull Context context) {
return getAttributeText(context, R.attr.theme_type, "light").equals("light");
}
public static boolean getThemedBoolean(@NonNull Context context, @AttrRes int attr) { public static boolean getThemedBoolean(@NonNull Context context, @AttrRes int attr) {
TypedValue typedValue = new TypedValue(); TypedValue typedValue = new TypedValue();
Resources.Theme theme = context.getTheme(); Resources.Theme theme = context.getTheme();