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() {
ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences());
int userSelectedTheme = themeState.getTheme();
// If the user has configured Session to follow the system light/dark theme mode then do so..
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);
if (userSelectedTheme == R.style.Ocean_Dark || userSelectedTheme == R.style.Ocean_Light) {
return isDayUi ? R.style.Ocean_Light : R.style.Ocean_Dark;
} else {
return isDayUi ? R.style.Classic_Light : R.style.Classic_Dark;
}
} else {
}
else // ..otherwise just return their selected theme.
{
return userSelectedTheme;
}
}

View File

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

View File

@ -17,6 +17,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
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.MappingAdapter
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)
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 }
title.setText(item.title)

View File

@ -140,6 +140,8 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li
}
fun draftQuote(thread: Recipient, message: MessageRecord, glide: GlideRequests) {
quoteView?.let(binding.inputBarAdditionalContentContainer::removeView)
quote = message
// 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.text.Spannable
import android.text.SpannableString
import android.text.style.BackgroundColorSpan
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import android.util.Range
import androidx.appcompat.widget.ThemeUtils
import androidx.core.content.res.ResourcesCompat
import network.loki.messenger.R
import nl.komponents.kovenant.combine.Tuple2
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.utilities.SodiumUtilities
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.util.UiModeUtilities
import org.thoughtcrime.securesms.util.getAccentColor
import org.thoughtcrime.securesms.util.getColorResourceIdFromAttr
import org.thoughtcrime.securesms.util.getMessageTextColourAttr
import java.util.regex.Pattern
object MentionUtilities {
@ -58,15 +64,37 @@ object MentionUtilities {
}
}
val result = SpannableString(text)
val isLightMode = UiModeUtilities.isDayUiMode(context)
val color = if (isOutgoingMessage) {
ResourcesCompat.getColor(context.resources, if (isLightMode) R.color.white else R.color.black, context.theme)
} else {
context.getAccentColor()
var mentionTextColour: Int? = null
// In dark themes..
if (ThemeUtil.isDarkTheme(context)) {
// ..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) {
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)
// 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
}

View File

@ -320,6 +320,19 @@ public class MmsSmsDatabase extends Database {
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() {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " ASC";
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;
}
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,
distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned);
}

View File

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

View File

@ -4,6 +4,8 @@ import android.content.Context
import android.content.res.Resources
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.text.SpannableString
import android.text.TextUtils
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
@ -89,7 +91,7 @@ class ConversationView : LinearLayout {
|| (configFactory.convoVolatile?.getConversationUnread(thread) == true)
binding.unreadMentionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
binding.unreadMentionIndicator.isVisible = (thread.unreadMentionCount != 0 && thread.recipient.address.isGroup)
val senderDisplayName = getUserDisplayName(thread.recipient)
val senderDisplayName = getTitle(thread.recipient)
?: thread.recipient.address.toString()
binding.conversationViewDisplayNameTextView.text = senderDisplayName
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
}
binding.muteIndicatorImageView.setImageResource(drawableRes)
val rawSnippet = thread.getDisplayBody(context)
val snippet = highlightMentions(rawSnippet, thread.threadId, context)
binding.snippetTextView.text = snippet
binding.snippetTextView.text = highlightMentions(thread.getSnippet(), thread.threadId, context)
binding.snippetTextView.typeface = if (unreadCount > 0 && !thread.isRead) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
binding.snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE
if (isTyping) {
@ -131,12 +131,21 @@ class ConversationView : LinearLayout {
binding.profilePictureView.recycle()
}
private fun getUserDisplayName(recipient: Recipient): String? {
return if (recipient.isLocalNumber) {
context.getString(R.string.note_to_self)
} else {
recipient.toShortString() // Internally uses the Contact API
private fun getTitle(recipient: Recipient): String? = when {
recipient.isLocalNumber -> context.getString(R.string.note_to_self)
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
}

View File

@ -9,12 +9,15 @@ import android.graphics.Bitmap
import android.graphics.PointF
import android.graphics.Rect
import android.util.Size
import android.util.TypedValue
import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.DimenRes
import network.loki.messenger.R
import org.session.libsession.utilities.getColorFromAttr
import android.view.inputmethod.InputMethodManager
import androidx.annotation.AttrRes
import androidx.annotation.ColorRes
import androidx.core.graphics.applyCanvas
import kotlin.math.roundToInt
@ -32,6 +35,20 @@ val View.hitRect: Rect
@ColorInt
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) {
val startSize = resources.getDimension(startSizeID)
val endSize = resources.getDimension(endSizeID)
@ -70,7 +87,6 @@ fun View.hideKeyboard() {
imm.hideSoftInputFromWindow(this.windowToken, 0)
}
fun View.drawToBitmap(config: Bitmap.Config = Bitmap.Config.ARGB_8888, longestWidth: Int = 2000): Bitmap {
val size = Size(measuredWidth, measuredHeight).coerceAtMost(longestWidth)
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.contacts.Contact
import java.util.*
import java.util.Locale
object MentionsManager {
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");
}
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) {
TypedValue typedValue = new TypedValue();
Resources.Theme theme = context.getTheme();