Merge branch 'od' into on-2

This commit is contained in:
Andrew 2024-06-25 23:40:34 +09:30
commit 432a2816ab
25 changed files with 240 additions and 273 deletions

View File

@ -1,5 +1,7 @@
package org.thoughtcrime.securesms package org.thoughtcrime.securesms
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import nl.komponents.kovenant.Kovenant import nl.komponents.kovenant.Kovenant
import nl.komponents.kovenant.jvm.asDispatcher import nl.komponents.kovenant.jvm.asDispatcher
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
@ -11,7 +13,7 @@ object AppContext {
fun configureKovenant() { fun configureKovenant() {
Kovenant.context { Kovenant.context {
callbackContext.dispatcher = Executors.newSingleThreadExecutor().asDispatcher() callbackContext.dispatcher = Executors.newSingleThreadExecutor().asDispatcher()
workerContext.dispatcher = ThreadUtils.executorPool.asDispatcher() workerContext.dispatcher = Dispatchers.IO.asExecutor().asDispatcher()
multipleCompletion = { v1, v2 -> multipleCompletion = { v1, v2 ->
Log.d("Loki", "Promise resolved more than once (first with $v1, then with $v2); ignoring $v2.") Log.d("Loki", "Promise resolved more than once (first with $v1, then with $v2); ignoring $v2.")
} }

View File

@ -30,13 +30,15 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
private static final String TAG = BaseActionBarActivity.class.getSimpleName(); private static final String TAG = BaseActionBarActivity.class.getSimpleName();
public ThemeState currentThemeState; public ThemeState currentThemeState;
private Resources.Theme modifiedTheme;
private TextSecurePreferences getPreferences() { private TextSecurePreferences getPreferences() {
ApplicationContext appContext = (ApplicationContext) getApplicationContext(); ApplicationContext appContext = (ApplicationContext) getApplicationContext();
return appContext.textSecurePreferences; return appContext.textSecurePreferences;
} }
@StyleRes @StyleRes
public int getDesiredTheme() { private int getDesiredTheme() {
ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences()); ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences());
int userSelectedTheme = themeState.getTheme(); int userSelectedTheme = themeState.getTheme();
@ -58,7 +60,7 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
} }
@StyleRes @Nullable @StyleRes @Nullable
public Integer getAccentTheme() { private Integer getAccentTheme() {
if (!getPreferences().hasPreference(SELECTED_ACCENT_COLOR)) return null; if (!getPreferences().hasPreference(SELECTED_ACCENT_COLOR)) return null;
ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences()); ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences());
return themeState.getAccentStyle(); return themeState.getAccentStyle();
@ -66,8 +68,12 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
@Override @Override
public Resources.Theme getTheme() { public Resources.Theme getTheme() {
if (modifiedTheme != null) {
return modifiedTheme;
}
// New themes // New themes
Resources.Theme modifiedTheme = super.getTheme(); modifiedTheme = super.getTheme();
modifiedTheme.applyStyle(getDesiredTheme(), true); modifiedTheme.applyStyle(getDesiredTheme(), true);
Integer accentTheme = getAccentTheme(); Integer accentTheme = getAccentTheme();
if (accentTheme != null) { if (accentTheme != null) {

View File

@ -80,6 +80,11 @@ public class AudioSlidePlayer implements SensorEventListener {
} }
} }
@Nullable
public synchronized static AudioSlidePlayer getInstance() {
return playing.orNull();
}
private AudioSlidePlayer(@NonNull Context context, private AudioSlidePlayer(@NonNull Context context,
@NonNull AudioSlide slide, @NonNull AudioSlide slide,
@NonNull Listener listener) @NonNull Listener listener)

View File

@ -21,6 +21,7 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import android.provider.Settings
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityWebrtcBinding import network.loki.messenger.databinding.ActivityWebrtcBinding
@ -100,7 +101,14 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready) super.onCreate(savedInstanceState, ready)
// Only enable auto-rotate if system auto-rotate is enabled
if (isAutoRotateOn()) {
rotationListener.enable() rotationListener.enable()
} else {
rotationListener.disable()
}
binding = ActivityWebrtcBinding.inflate(layoutInflater) binding = ActivityWebrtcBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
@ -185,6 +193,14 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
} }
//Function to check if Android System Auto-rotate is on or off
private fun isAutoRotateOn(): Boolean {
return Settings.System.getInt(
contentResolver,
Settings.System.ACCELEROMETER_ROTATION, 0
) == 1
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
hangupReceiver?.let { receiver -> hangupReceiver?.let { receiver ->

View File

@ -61,7 +61,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityConversationV2Binding import network.loki.messenger.databinding.ActivityConversationV2Binding
import network.loki.messenger.databinding.ViewVisibleMessageBinding
import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.ExpiryMode
import nl.komponents.kovenant.ui.successUi import nl.komponents.kovenant.ui.successUi
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
@ -1555,8 +1554,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
if (indexInAdapter < 0 || indexInAdapter >= adapter.itemCount) { return } if (indexInAdapter < 0 || indexInAdapter >= adapter.itemCount) { return }
val viewHolder = binding?.conversationRecyclerView?.findViewHolderForAdapterPosition(indexInAdapter) as? ConversationAdapter.VisibleMessageViewHolder ?: return val viewHolder = binding?.conversationRecyclerView?.findViewHolderForAdapterPosition(indexInAdapter) as? ConversationAdapter.VisibleMessageViewHolder ?: return
val visibleMessageView = ViewVisibleMessageBinding.bind(viewHolder.view).visibleMessageView viewHolder.view.playVoiceMessage()
visibleMessageView.playVoiceMessage()
} }
override fun sendMessage() { override fun sendMessage() {

View File

@ -5,9 +5,7 @@ import android.content.Intent
import android.database.Cursor import android.database.Cursor
import android.util.SparseArray import android.util.SparseArray
import android.util.SparseBooleanArray import android.util.SparseBooleanArray
import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.core.util.getOrDefault import androidx.core.util.getOrDefault
@ -20,14 +18,11 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ViewVisibleMessageBinding
import org.session.libsession.messaging.contacts.Contact 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.ControlMessageView
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
import org.thoughtcrime.securesms.database.MmsSmsColumns
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
@ -90,7 +85,7 @@ class ConversationAdapter(
} }
} }
class VisibleMessageViewHolder(val view: View) : ViewHolder(view) class VisibleMessageViewHolder(val view: VisibleMessageView) : ViewHolder(view)
class ControlMessageViewHolder(val view: ControlMessageView) : ViewHolder(view) class ControlMessageViewHolder(val view: ControlMessageView) : ViewHolder(view)
override fun getItemViewType(cursor: Cursor): Int { override fun getItemViewType(cursor: Cursor): Int {
@ -103,7 +98,7 @@ class ConversationAdapter(
@Suppress("NAME_SHADOWING") @Suppress("NAME_SHADOWING")
val viewType = ViewType.allValues[viewType] val viewType = ViewType.allValues[viewType]
return when (viewType) { return when (viewType) {
ViewType.Visible -> VisibleMessageViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.view_visible_message, parent, false)) ViewType.Visible -> VisibleMessageViewHolder(VisibleMessageView(context))
ViewType.Control -> ControlMessageViewHolder(ControlMessageView(context)) ViewType.Control -> ControlMessageViewHolder(ControlMessageView(context))
else -> throw IllegalStateException("Unexpected view type: $viewType.") else -> throw IllegalStateException("Unexpected view type: $viewType.")
} }
@ -115,7 +110,7 @@ class ConversationAdapter(
val messageBefore = getMessageBefore(position, cursor) val messageBefore = getMessageBefore(position, cursor)
when (viewHolder) { when (viewHolder) {
is VisibleMessageViewHolder -> { is VisibleMessageViewHolder -> {
val visibleMessageView = ViewVisibleMessageBinding.bind(viewHolder.view).visibleMessageView val visibleMessageView = viewHolder.view
val isSelected = selectedItems.contains(message) val isSelected = selectedItems.contains(message)
visibleMessageView.snIsSelected = isSelected visibleMessageView.snIsSelected = isSelected
visibleMessageView.indexInAdapter = position visibleMessageView.indexInAdapter = position
@ -181,7 +176,7 @@ class ConversationAdapter(
override fun onItemViewRecycled(viewHolder: ViewHolder?) { override fun onItemViewRecycled(viewHolder: ViewHolder?) {
when (viewHolder) { when (viewHolder) {
is VisibleMessageViewHolder -> viewHolder.view.findViewById<VisibleMessageView>(R.id.visibleMessageView).recycle() is VisibleMessageViewHolder -> viewHolder.view.recycle()
is ControlMessageViewHolder -> viewHolder.view.recycle() is ControlMessageViewHolder -> viewHolder.view.recycle()
} }
super.onItemViewRecycled(viewHolder) super.onItemViewRecycled(viewHolder)

View File

@ -27,10 +27,11 @@ import org.session.libsession.utilities.Address
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.database.MmsSmsDatabase import org.thoughtcrime.securesms.audio.AudioSlidePlayer
import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.repository.ConversationRepository
import java.util.UUID import java.util.UUID
@ -103,6 +104,13 @@ class ConversationViewModel(
} }
} }
override fun onCleared() {
super.onCleared()
// Stop all voice message when exiting this page
AudioSlidePlayer.stopAll()
}
fun saveDraft(text: String) { fun saveDraft(text: String) {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
repository.saveDraft(threadId, text) repository.saveDraft(threadId, text)
@ -142,10 +150,20 @@ class ConversationViewModel(
} }
fun deleteLocally(message: MessageRecord) { fun deleteLocally(message: MessageRecord) {
stopPlayingAudioMessage(message)
val recipient = recipient ?: return Log.w("Loki", "Recipient was null for delete locally action") val recipient = recipient ?: return Log.w("Loki", "Recipient was null for delete locally action")
repository.deleteLocally(recipient, message) repository.deleteLocally(recipient, message)
} }
/**
* Stops audio player if its current playing is the one given in the message.
*/
private fun stopPlayingAudioMessage(message: MessageRecord) {
val mmsMessage = message as? MmsMessageRecord ?: return
val audioSlide = mmsMessage.slideDeck.audioSlide ?: return
AudioSlidePlayer.getInstance()?.takeIf { it.audioSlide == audioSlide }?.stop()
}
fun setRecipientApproved() { fun setRecipientApproved() {
val recipient = recipient ?: return Log.w("Loki", "Recipient was null for set approved action") val recipient = recipient ?: return Log.w("Loki", "Recipient was null for set approved action")
repository.setApproved(recipient, true) repository.setApproved(recipient, true)
@ -153,10 +171,12 @@ class ConversationViewModel(
fun deleteForEveryone(message: MessageRecord) = viewModelScope.launch { fun deleteForEveryone(message: MessageRecord) = viewModelScope.launch {
val recipient = recipient ?: return@launch Log.w("Loki", "Recipient was null for delete for everyone - aborting delete operation.") val recipient = recipient ?: return@launch Log.w("Loki", "Recipient was null for delete for everyone - aborting delete operation.")
stopPlayingAudioMessage(message)
repository.deleteForEveryone(threadId, recipient, message) repository.deleteForEveryone(threadId, recipient, message)
.onSuccess { .onSuccess {
Log.d("Loki", "Deleted message ${message.id} ") Log.d("Loki", "Deleted message ${message.id} ")
stopPlayingAudioMessage(message)
} }
.onFailure { .onFailure {
Log.w("Loki", "FAILED TO delete message ${message.id} ") Log.w("Loki", "FAILED TO delete message ${message.id} ")

View File

@ -14,6 +14,7 @@ import android.view.inputmethod.EditorInfo
import android.widget.EditText import android.widget.EditText
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ViewInputBarBinding import network.loki.messenger.databinding.ViewInputBarBinding
@ -122,8 +123,8 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li
// region Updating // region Updating
override fun inputBarEditTextContentChanged(text: CharSequence) { override fun inputBarEditTextContentChanged(text: CharSequence) {
sendButton.isVisible = text.isNotEmpty() microphoneButton.isVisible = text.trim().isEmpty()
microphoneButton.isVisible = text.isEmpty() sendButton.isVisible = microphoneButton.isGone
delegate?.inputBarEditTextContentChanged(text) delegate?.inputBarEditTextContentChanged(text)
} }

View File

@ -25,16 +25,15 @@ class ControlMessageView : LinearLayout {
private val TAG = "ControlMessageView" private val TAG = "ControlMessageView"
private lateinit var binding: ViewControlMessageBinding private val binding = ViewControlMessageBinding.inflate(LayoutInflater.from(context), this, true)
constructor(context: Context) : super(context) { initialize() } constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
@Inject lateinit var disappearingMessages: DisappearingMessages @Inject lateinit var disappearingMessages: DisappearingMessages
private fun initialize() { init {
binding = ViewControlMessageBinding.inflate(LayoutInflater.from(context), this, true)
layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT) layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
} }

View File

@ -72,7 +72,7 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
// Author // Author
val author = contactDb.getContactWithAccountID(authorPublicKey) val author = contactDb.getContactWithAccountID(authorPublicKey)
val localNumber = TextSecurePreferences.getLocalNumber(context) val localNumber = TextSecurePreferences.getLocalNumber(context)
val quoteIsLocalUser = localNumber != null && localNumber == author?.accountID val quoteIsLocalUser = localNumber != null && authorPublicKey == localNumber
val authorDisplayName = val authorDisplayName =
if (quoteIsLocalUser) context.getString(R.string.QuoteView_you) if (quoteIsLocalUser) context.getString(R.string.QuoteView_you)

View File

@ -11,8 +11,10 @@ import android.os.Looper
import android.util.AttributeSet import android.util.AttributeSet
import android.view.Gravity import android.view.Gravity
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
@ -26,17 +28,17 @@ import androidx.core.view.isVisible
import androidx.core.view.marginBottom import androidx.core.view.marginBottom
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ViewEmojiReactionsBinding
import network.loki.messenger.databinding.ViewVisibleMessageBinding import network.loki.messenger.databinding.ViewVisibleMessageBinding
import network.loki.messenger.databinding.ViewstubVisibleMessageMarkerContainerBinding
import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.contacts.Contact.ContactContext import org.session.libsession.messaging.contacts.Contact.ContactContext
import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.ViewUtil import org.session.libsession.utilities.ViewUtil
import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.getColorFromAttr
import org.session.libsession.utilities.modifyLayoutParams import org.session.libsession.utilities.modifyLayoutParams
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.database.LastSentTimestampCache import org.thoughtcrime.securesms.database.LastSentTimestampCache
import org.thoughtcrime.securesms.database.LokiAPIDatabase import org.thoughtcrime.securesms.database.LokiAPIDatabase
@ -65,7 +67,7 @@ import kotlin.math.sqrt
private const val TAG = "VisibleMessageView" private const val TAG = "VisibleMessageView"
@AndroidEntryPoint @AndroidEntryPoint
class VisibleMessageView : LinearLayout { class VisibleMessageView : FrameLayout {
private var replyDisabled: Boolean = false private var replyDisabled: Boolean = false
@Inject lateinit var threadDb: ThreadDatabase @Inject lateinit var threadDb: ThreadDatabase
@Inject lateinit var lokiThreadDb: LokiThreadDatabase @Inject lateinit var lokiThreadDb: LokiThreadDatabase
@ -75,7 +77,16 @@ class VisibleMessageView : LinearLayout {
@Inject lateinit var mmsDb: MmsDatabase @Inject lateinit var mmsDb: MmsDatabase
@Inject lateinit var lastSentTimestampCache: LastSentTimestampCache @Inject lateinit var lastSentTimestampCache: LastSentTimestampCache
private val binding by lazy { ViewVisibleMessageBinding.bind(this) } private val binding = ViewVisibleMessageBinding.inflate(LayoutInflater.from(context), this, true)
private val markerContainerBinding = lazy(LazyThreadSafetyMode.NONE) {
ViewstubVisibleMessageMarkerContainerBinding.bind(binding.unreadMarkerContainerStub.inflate())
}
private val emojiReactionsBinding = lazy(LazyThreadSafetyMode.NONE) {
ViewEmojiReactionsBinding.bind(binding.emojiReactionsView.inflate())
}
private val swipeToReplyIcon = ContextCompat.getDrawable(context, R.drawable.ic_baseline_reply_24)!!.mutate() private val swipeToReplyIcon = ContextCompat.getDrawable(context, R.drawable.ic_baseline_reply_24)!!.mutate()
private val swipeToReplyIconRect = Rect() private val swipeToReplyIconRect = Rect()
private var dx = 0.0f private var dx = 0.0f
@ -94,7 +105,7 @@ class VisibleMessageView : LinearLayout {
var onPress: ((event: MotionEvent) -> Unit)? = null var onPress: ((event: MotionEvent) -> Unit)? = null
var onSwipeToReply: (() -> Unit)? = null var onSwipeToReply: (() -> Unit)? = null
var onLongPress: (() -> Unit)? = null var onLongPress: (() -> Unit)? = null
val messageContentView: VisibleMessageContentView by lazy { binding.messageContentView.root } val messageContentView: VisibleMessageContentView get() = binding.messageContentView.root
companion object { companion object {
const val swipeToReplyThreshold = 64.0f // dp const val swipeToReplyThreshold = 64.0f // dp
@ -108,12 +119,7 @@ class VisibleMessageView : LinearLayout {
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onFinishInflate() { init {
super.onFinishInflate()
initialize()
}
private fun initialize() {
isHapticFeedbackEnabled = true isHapticFeedbackEnabled = true
setWillNotDraw(false) setWillNotDraw(false)
binding.root.disableClipping() binding.root.disableClipping()
@ -121,7 +127,11 @@ class VisibleMessageView : LinearLayout {
binding.messageInnerContainer.disableClipping() binding.messageInnerContainer.disableClipping()
binding.messageInnerLayout.disableClipping() binding.messageInnerLayout.disableClipping()
binding.messageContentView.root.disableClipping() binding.messageContentView.root.disableClipping()
// Default layout params
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
} }
// endregion // endregion
// region Updating // region Updating
@ -203,7 +213,13 @@ class VisibleMessageView : LinearLayout {
binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderAccountID binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderAccountID
// Unread marker // Unread marker
binding.unreadMarkerContainer.isVisible = lastSeen != -1L && message.timestamp > lastSeen && (previous == null || previous.timestamp <= lastSeen) && !message.isOutgoing val shouldShowUnreadMarker = lastSeen != -1L && message.timestamp > lastSeen && (previous == null || previous.timestamp <= lastSeen) && !message.isOutgoing
if (shouldShowUnreadMarker) {
markerContainerBinding.value.root.isVisible = true
} else if (markerContainerBinding.isInitialized()) {
// Only need to hide the binding when the binding is inflated. (default is gone)
markerContainerBinding.value.root.isVisible = false
}
// Date break // Date break
val showDateBreak = isStartOfMessageCluster || snIsSelected val showDateBreak = isStartOfMessageCluster || snIsSelected
@ -214,21 +230,22 @@ class VisibleMessageView : LinearLayout {
showStatusMessage(message) showStatusMessage(message)
// Emoji Reactions // Emoji Reactions
val emojiLayoutParams = binding.emojiReactionsView.root.layoutParams as ConstraintLayout.LayoutParams
emojiLayoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f
binding.emojiReactionsView.root.layoutParams = emojiLayoutParams
if (message.reactions.isNotEmpty()) { if (message.reactions.isNotEmpty()) {
val capabilities = lokiThreadDb.getOpenGroupChat(threadID)?.server?.let { lokiApiDb.getServerCapabilities(it) } val capabilities = lokiThreadDb.getOpenGroupChat(threadID)?.server?.let { lokiApiDb.getServerCapabilities(it) }
if (capabilities.isNullOrEmpty() || capabilities.contains(OpenGroupApi.Capability.REACTIONS.name.lowercase())) { if (capabilities.isNullOrEmpty() || capabilities.contains(OpenGroupApi.Capability.REACTIONS.name.lowercase())) {
binding.emojiReactionsView.root.setReactions(message.id, message.reactions, message.isOutgoing, delegate) emojiReactionsBinding.value.root.let { root ->
binding.emojiReactionsView.root.isVisible = true root.setReactions(message.id, message.reactions, message.isOutgoing, delegate)
} else { root.isVisible = true
binding.emojiReactionsView.root.isVisible = false (root.layoutParams as ConstraintLayout.LayoutParams).apply {
horizontalBias = if (message.isOutgoing) 1f else 0f
} }
} }
else { } else if (emojiReactionsBinding.isInitialized()) {
binding.emojiReactionsView.root.isVisible = false emojiReactionsBinding.value.root.isVisible = false
}
}
else if (emojiReactionsBinding.isInitialized()) {
emojiReactionsBinding.value.root.isVisible = false
} }
// Populate content view // Populate content view

View File

@ -68,7 +68,7 @@ class VoiceMessageView : RelativeLayout, AudioSlidePlayer.Listener {
return return
} }
val player = AudioSlidePlayer.createFor(context, audio, this) val player = AudioSlidePlayer.createFor(context.applicationContext, audio, this)
this.player = player this.player = player
(audio.asAttachment() as? DatabaseAttachment)?.let { attachment -> (audio.asAttachment() as? DatabaseAttachment)?.let { attachment ->

View File

@ -19,11 +19,11 @@ abstract class AppModule {
@Binds @Binds
abstract fun bindConversationRepository(repository: DefaultConversationRepository): ConversationRepository abstract fun bindConversationRepository(repository: DefaultConversationRepository): ConversationRepository
} }
@EntryPoint @EntryPoint
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
interface AppComponent { interface AppComponent {
fun getPrefs(): TextSecurePreferences fun getPrefs(): TextSecurePreferences
} }

View File

@ -7,7 +7,6 @@ import network.loki.messenger.libsession_util.Contacts
import network.loki.messenger.libsession_util.ConversationVolatileConfig import network.loki.messenger.libsession_util.ConversationVolatileConfig
import network.loki.messenger.libsession_util.UserGroupsConfig import network.loki.messenger.libsession_util.UserGroupsConfig
import network.loki.messenger.libsession_util.UserProfile import network.loki.messenger.libsession_util.UserProfile
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigFactoryProtocol
import org.session.libsession.utilities.ConfigFactoryUpdateListener import org.session.libsession.utilities.ConfigFactoryUpdateListener
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
@ -72,7 +71,6 @@ class ConfigFactory(
override val user: UserProfile? override val user: UserProfile?
get() = synchronizedWithLog(userLock) { get() = synchronizedWithLog(userLock) {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
if (_userConfig == null) { if (_userConfig == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return null val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
val userDump = configDatabase.retrieveConfigAndHashes( val userDump = configDatabase.retrieveConfigAndHashes(
@ -92,7 +90,6 @@ class ConfigFactory(
override val contacts: Contacts? override val contacts: Contacts?
get() = synchronizedWithLog(contactsLock) { get() = synchronizedWithLog(contactsLock) {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
if (_contacts == null) { if (_contacts == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return null val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
val contactsDump = configDatabase.retrieveConfigAndHashes( val contactsDump = configDatabase.retrieveConfigAndHashes(
@ -112,7 +109,6 @@ class ConfigFactory(
override val convoVolatile: ConversationVolatileConfig? override val convoVolatile: ConversationVolatileConfig?
get() = synchronizedWithLog(convoVolatileLock) { get() = synchronizedWithLog(convoVolatileLock) {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
if (_convoVolatileConfig == null) { if (_convoVolatileConfig == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return null val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
val convoDump = configDatabase.retrieveConfigAndHashes( val convoDump = configDatabase.retrieveConfigAndHashes(
@ -133,7 +129,6 @@ class ConfigFactory(
override val userGroups: UserGroupsConfig? override val userGroups: UserGroupsConfig?
get() = synchronizedWithLog(userGroupsLock) { get() = synchronizedWithLog(userGroupsLock) {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
if (_userGroups == null) { if (_userGroups == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return null val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
val userGroupsDump = configDatabase.retrieveConfigAndHashes( val userGroupsDump = configDatabase.retrieveConfigAndHashes(
@ -207,8 +202,6 @@ class ConfigFactory(
openGroupId: String?, openGroupId: String?,
visibleOnly: Boolean visibleOnly: Boolean
): Boolean { ): Boolean {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return true
val (_, userPublicKey) = maybeGetUserInfo() ?: return true val (_, userPublicKey) = maybeGetUserInfo() ?: return true
if (openGroupId != null) { if (openGroupId != null) {
@ -241,8 +234,6 @@ class ConfigFactory(
} }
override fun canPerformChange(variant: String, publicKey: String, changeTimestampMs: Long): Boolean { override fun canPerformChange(variant: String, publicKey: String, changeTimestampMs: Long): Boolean {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return true
val lastUpdateTimestampMs = configDatabase.retrieveConfigLastUpdateTimestamp(variant, publicKey) val lastUpdateTimestampMs = configDatabase.retrieveConfigLastUpdateTimestamp(variant, publicKey)
// Ensure the change occurred after the last config message was handled (minus the buffer period) // Ensure the change occurred after the last config message was handled (minus the buffer period)

View File

@ -48,7 +48,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityHomeBinding import network.loki.messenger.databinding.ActivityHomeBinding
import network.loki.messenger.libsession_util.ConfigBase
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
@ -375,8 +374,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
} }
private fun updateLegacyConfigView() { private fun updateLegacyConfigView() {
binding.configOutdatedView.isVisible = ConfigBase.isNewConfigEnabled(textSecurePreferences.hasForcedNewConfig(), SnodeAPI.nowWithOffset) binding.configOutdatedView.isVisible = textSecurePreferences.getHasLegacyConfig()
&& textSecurePreferences.getHasLegacyConfig()
} }
override fun onResume() { override fun onResume() {

View File

@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.home
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context
import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.asFlow import androidx.lifecycle.asFlow
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@ -22,7 +21,6 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
@ -60,12 +58,10 @@ class HomeViewModel @Inject constructor(
observeTypingStatus(), observeTypingStatus(),
messageRequests(), messageRequests(),
::Data ::Data
) ).stateIn(viewModelScope, SharingStarted.Eagerly, null)
.stateIn(viewModelScope, SharingStarted.Eagerly, null)
private fun hasHiddenMessageRequests() = TextSecurePreferences.events private fun hasHiddenMessageRequests() = TextSecurePreferences.events
.filter { it == TextSecurePreferences.HAS_HIDDEN_MESSAGE_REQUESTS } .filter { it == TextSecurePreferences.HAS_HIDDEN_MESSAGE_REQUESTS }
.flowOn(Dispatchers.IO)
.map { prefs.hasHiddenMessageRequests() } .map { prefs.hasHiddenMessageRequests() }
.onStart { emit(prefs.hasHiddenMessageRequests()) } .onStart { emit(prefs.hasHiddenMessageRequests()) }
@ -81,7 +77,7 @@ class HomeViewModel @Inject constructor(
hasHiddenMessageRequests(), hasHiddenMessageRequests(),
latestUnapprovedConversationTimestamp(), latestUnapprovedConversationTimestamp(),
::createMessageRequests ::createMessageRequests
) ).flowOn(Dispatchers.IO)
private fun unapprovedConversationCount() = reloadTriggersAndContentChanges() private fun unapprovedConversationCount() = reloadTriggersAndContentChanges()
.map { threadDb.unapprovedConversationCount } .map { threadDb.unapprovedConversationCount }
@ -96,13 +92,13 @@ class HomeViewModel @Inject constructor(
threadDb.readerFor(openCursor).run { generateSequence { next }.toList() } threadDb.readerFor(openCursor).run { generateSequence { next }.toList() }
} }
} }
.flowOn(Dispatchers.IO)
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
private fun reloadTriggersAndContentChanges() = merge( private fun reloadTriggersAndContentChanges() = merge(
manualReloadTrigger, manualReloadTrigger,
contentResolver.observeChanges(DatabaseContentProviders.ConversationList.CONTENT_URI) contentResolver.observeChanges(DatabaseContentProviders.ConversationList.CONTENT_URI)
) )
.flowOn(Dispatchers.IO)
.debounce(CHANGE_NOTIFICATION_DEBOUNCE_MILLS) .debounce(CHANGE_NOTIFICATION_DEBOUNCE_MILLS)
.onStart { emit(Unit) } .onStart { emit(Unit) }
@ -114,7 +110,7 @@ class HomeViewModel @Inject constructor(
val messageRequests: MessageRequests? = null val messageRequests: MessageRequests? = null
) )
fun createMessageRequests( private fun createMessageRequests(
count: Int, count: Int,
hidden: Boolean, hidden: Boolean,
timestamp: Long timestamp: Long

View File

@ -17,8 +17,6 @@ import org.session.libsession.messaging.jobs.ConfigurationSyncJob
import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
@ -55,62 +53,17 @@ object ConfigurationMessageUtilities {
fun syncConfigurationIfNeeded(context: Context) { fun syncConfigurationIfNeeded(context: Context) {
// add if check here to schedule new config job process and return early // add if check here to schedule new config job process and return early
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
val forcedConfig = TextSecurePreferences.hasForcedNewConfig(context)
val currentTime = SnodeAPI.nowWithOffset
if (ConfigBase.isNewConfigEnabled(forcedConfig, currentTime)) {
scheduleConfigSync(userPublicKey) scheduleConfigSync(userPublicKey)
return
}
val lastSyncTime = TextSecurePreferences.getLastConfigurationSyncTime(context)
val now = System.currentTimeMillis()
if (now - lastSyncTime < 7 * 24 * 60 * 60 * 1000) return
val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
!recipient.name.isNullOrEmpty() && !recipient.isLocalNumber && recipient.address.serialize().isNotEmpty()
}.map { recipient ->
ConfigurationMessage.Contact(
publicKey = recipient.address.serialize(),
name = recipient.name!!,
profilePicture = recipient.profileAvatar,
profileKey = recipient.profileKey,
isApproved = recipient.isApproved,
isBlocked = recipient.isBlocked,
didApproveMe = recipient.hasApprovedMe()
)
}
val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return
MessageSender.send(configurationMessage, Address.fromSerialized(userPublicKey))
TextSecurePreferences.setLastConfigurationSyncTime(context, now)
} }
fun forceSyncConfigurationNowIfNeeded(context: Context): Promise<Unit, Exception> { fun forceSyncConfigurationNowIfNeeded(context: Context): Promise<Unit, Exception> {
// add if check here to schedule new config job process and return early // add if check here to schedule new config job process and return early
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return Promise.ofFail(NullPointerException("User Public Key is null")) val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return Promise.ofFail(NullPointerException("User Public Key is null"))
val forcedConfig = TextSecurePreferences.hasForcedNewConfig(context)
val currentTime = SnodeAPI.nowWithOffset
if (ConfigBase.isNewConfigEnabled(forcedConfig, currentTime)) {
// schedule job if none exist // schedule job if none exist
// don't schedule job if we already have one // don't schedule job if we already have one
scheduleConfigSync(userPublicKey) scheduleConfigSync(userPublicKey)
return Promise.ofSuccess(Unit) return Promise.ofSuccess(Unit)
} }
val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
!recipient.isGroupRecipient && !recipient.name.isNullOrEmpty() && !recipient.isLocalNumber && recipient.address.serialize().isNotEmpty()
}.map { recipient ->
ConfigurationMessage.Contact(
publicKey = recipient.address.serialize(),
name = recipient.name!!,
profilePicture = recipient.profileAvatar,
profileKey = recipient.profileKey,
isApproved = recipient.isApproved,
isBlocked = recipient.isBlocked,
didApproveMe = recipient.hasApprovedMe()
)
}
val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return Promise.ofSuccess(Unit)
val promise = MessageSender.send(configurationMessage, Destination.from(Address.fromSerialized(userPublicKey)), isSyncMessage = true)
TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis())
return promise
}
private fun maybeUserSecretKey() = MessagingModuleConfiguration.shared.getUserED25519KeyPair()?.secretKey?.asBytes private fun maybeUserSecretKey() = MessagingModuleConfiguration.shared.getUserED25519KeyPair()?.secretKey?.asBytes

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
@ -8,45 +8,11 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout <ViewStub
android:id="@+id/unreadMarkerContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/small_spacing" android:id="@+id/unreadMarkerContainerStub"
android:visibility="gone" android:layout="@layout/viewstub_visible_message_marker_container" />
tools:visibility="visible">
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="@dimen/medium_spacing"
android:layout_marginEnd="@dimen/small_spacing"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/unreadMarker"
android:background="?android:colorAccent" />
<TextView
android:id="@+id/unreadMarker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/unread_marker"
android:gravity="center"
android:textColor="?android:colorAccent"
android:textSize="@dimen/small_font_size"
android:textStyle="bold" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="@dimen/small_spacing"
android:layout_marginEnd="@dimen/medium_spacing"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/unreadMarker"
app:layout_constraintEnd_toEndOf="parent"
android:background="?android:colorAccent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView <TextView
android:id="@+id/dateBreakTextView" android:id="@+id/dateBreakTextView"
@ -129,8 +95,10 @@
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
<include layout="@layout/view_emoji_reactions" <ViewStub
android:layout="@layout/view_emoji_reactions"
android:id="@+id/emojiReactionsView" android:id="@+id/emojiReactionsView"
android:inflatedId="@+id/emojiReactionsView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone" android:visibility="gone"
@ -176,4 +144,4 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView> </LinearLayout>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/unreadMarkerContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/small_spacing"
android:gravity="center"
android:orientation="horizontal"
android:visibility="gone"
tools:visibility="visible">
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="@dimen/medium_spacing"
android:layout_marginEnd="@dimen/small_spacing"
android:layout_weight="1"
android:background="?android:colorAccent" />
<TextView
android:id="@+id/unreadMarker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/unread_marker"
android:textColor="?android:colorAccent"
android:textSize="@dimen/small_font_size"
android:textStyle="bold" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="@dimen/small_spacing"
android:layout_marginEnd="@dimen/medium_spacing"
android:layout_weight="1"
android:background="?android:colorAccent" />
</LinearLayout>

View File

@ -27,12 +27,6 @@ sealed class ConfigBase(protected val /* yucky */ pointer: Long) {
is UserGroupsConfig -> Kind.GROUPS is UserGroupsConfig -> Kind.GROUPS
} }
// TODO: time in future to activate (hardcoded to 1st jan 2024 for testing, change before release)
private const val ACTIVATE_TIME = 1690761600000
fun isNewConfigEnabled(forced: Boolean, currentTime: Long) =
forced || currentTime >= ACTIVATE_TIME
const val PRIORITY_HIDDEN = -1 const val PRIORITY_HIDDEN = -1
const val PRIORITY_VISIBLE = 0 const val PRIORITY_VISIBLE = 0
const val PRIORITY_PINNED = 1 const val PRIORITY_PINNED = 1

View File

@ -37,6 +37,7 @@ class BackgroundGroupAddJob(val joinUrl: String): Job {
delegate?.handleJobFailed(this, dispatcherName, DuplicateGroupException()) delegate?.handleJobFailed(this, dispatcherName, DuplicateGroupException())
return return
} }
storage.addOpenGroup(openGroup.joinUrl()) storage.addOpenGroup(openGroup.joinUrl())
storage.onOpenGroupAdded(openGroup.server, openGroup.room) storage.onOpenGroupAdded(openGroup.server, openGroup.room)
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -1,6 +1,5 @@
package org.session.libsession.messaging.jobs package org.session.libsession.messaging.jobs
import network.loki.messenger.libsession_util.ConfigBase
import network.loki.messenger.libsession_util.ConfigBase.Companion.protoKindFor import network.loki.messenger.libsession_util.ConfigBase.Companion.protoKindFor
import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.bind
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
@ -10,7 +9,6 @@ import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.Data import org.session.libsession.messaging.utilities.Data
import org.session.libsession.snode.RawResponse import org.session.libsession.snode.RawResponse
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -26,14 +24,10 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
override suspend fun execute(dispatcherName: String) { override suspend fun execute(dispatcherName: String) {
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val forcedConfig = TextSecurePreferences.hasForcedNewConfig(MessagingModuleConfiguration.shared.context)
val currentTime = SnodeAPI.nowWithOffset
val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair() val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair()
val userPublicKey = storage.getUserPublicKey() val userPublicKey = storage.getUserPublicKey()
val delegate = delegate val delegate = delegate
if (destination is Destination.ClosedGroup // TODO: closed group configs will be handled in closed group feature if (destination is Destination.ClosedGroup
// if we haven't enabled the new configs don't run
|| !ConfigBase.isNewConfigEnabled(forcedConfig, currentTime)
// if we don't have a user ed key pair for signing updates // if we don't have a user ed key pair for signing updates
|| userEdKeyPair == null || userEdKeyPair == null
// this will be useful to not handle null delegate cases // this will be useful to not handle null delegate cases

View File

@ -273,7 +273,6 @@ object OpenGroupApi {
val queryParameters: Map<String, String> = mapOf(), val queryParameters: Map<String, String> = mapOf(),
val parameters: Any? = null, val parameters: Any? = null,
val headers: Map<String, String> = mapOf(), val headers: Map<String, String> = mapOf(),
val isAuthRequired: Boolean = true,
val body: ByteArray? = null, val body: ByteArray? = null,
/** /**
* Always `true` under normal circumstances. You might want to disable * Always `true` under normal circumstances. You might want to disable
@ -319,7 +318,7 @@ object OpenGroupApi {
?: return Promise.ofFail(Error.NoEd25519KeyPair) ?: return Promise.ofFail(Error.NoEd25519KeyPair)
val urlRequest = urlBuilder.toString() val urlRequest = urlBuilder.toString()
val headers = request.headers.toMutableMap() val headers = request.headers.toMutableMap()
if (request.isAuthRequired) {
val nonce = sodium.nonce(16) val nonce = sodium.nonce(16)
val timestamp = TimeUnit.MILLISECONDS.toSeconds(SnodeAPI.nowWithOffset) val timestamp = TimeUnit.MILLISECONDS.toSeconds(SnodeAPI.nowWithOffset)
var pubKey = "" var pubKey = ""
@ -385,7 +384,6 @@ object OpenGroupApi {
headers["X-SOGS-Timestamp"] = "$timestamp" headers["X-SOGS-Timestamp"] = "$timestamp"
headers["X-SOGS-Pubkey"] = pubKey headers["X-SOGS-Pubkey"] = pubKey
headers["X-SOGS-Signature"] = encodeBytes(signature) headers["X-SOGS-Signature"] = encodeBytes(signature)
}
val requestBuilder = okhttp3.Request.Builder() val requestBuilder = okhttp3.Request.Builder()
.url(urlRequest) .url(urlRequest)
@ -927,7 +925,7 @@ object OpenGroupApi {
} }
fun getCapabilities(server: String): Promise<Capabilities, Exception> { fun getCapabilities(server: String): Promise<Capabilities, Exception> {
val request = Request(verb = GET, room = null, server = server, endpoint = Endpoint.Capabilities, isAuthRequired = false) val request = Request(verb = GET, room = null, server = server, endpoint = Endpoint.Capabilities)
return getResponseBody(request).map { response -> return getResponseBody(request).map { response ->
JsonUtil.fromJson(response, Capabilities::class.java) JsonUtil.fromJson(response, Capabilities::class.java)
} }

View File

@ -203,12 +203,10 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) {
TextSecurePreferences.setConfigurationMessageSynced(context, true) TextSecurePreferences.setConfigurationMessageSynced(context, true)
TextSecurePreferences.setLastProfileUpdateTime(context, message.sentTimestamp!!) TextSecurePreferences.setLastProfileUpdateTime(context, message.sentTimestamp!!)
val isForceSync = TextSecurePreferences.hasForcedNewConfig(context)
val currentTime = SnodeAPI.nowWithOffset
if (ConfigBase.isNewConfigEnabled(isForceSync, currentTime)) {
TextSecurePreferences.setHasLegacyConfig(context, true) TextSecurePreferences.setHasLegacyConfig(context, true)
if (!firstTimeSync) return if (!firstTimeSync) return
}
val allClosedGroupPublicKeys = storage.getAllClosedGroupPublicKeys() val allClosedGroupPublicKeys = storage.getAllClosedGroupPublicKeys()
for (closedGroup in message.closedGroups) { for (closedGroup in message.closedGroups) {
if (allClosedGroupPublicKeys.contains(closedGroup.publicKey)) { if (allClosedGroupPublicKeys.contains(closedGroup.publicKey)) {
@ -260,7 +258,7 @@ fun MessageReceiver.handleUnsendRequest(message: UnsendRequest): Long? {
SnodeAPI.deleteMessage(author, listOf(serverHash)) SnodeAPI.deleteMessage(author, listOf(serverHash))
} }
val deletedMessageId = messageDataProvider.updateMessageAsDeleted(timestamp, author) val deletedMessageId = messageDataProvider.updateMessageAsDeleted(timestamp, author)
if (!messageDataProvider.isOutgoingMessage(messageIdToDelete)) { if (!messageDataProvider.isOutgoingMessage(timestamp)) {
SSKEnvironment.shared.notificationManager.updateNotification(context) SSKEnvironment.shared.notificationManager.updateNotification(context)
} }

View File

@ -1,11 +1,13 @@
package org.session.libsignal.utilities package org.session.libsignal.utilities
import android.os.Process import android.os.Process
import kotlinx.coroutines.Dispatchers
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.SynchronousQueue import java.util.concurrent.SynchronousQueue
import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.coroutines.EmptyCoroutineContext
object ThreadUtils { object ThreadUtils {
@ -13,39 +15,16 @@ object ThreadUtils {
const val PRIORITY_IMPORTANT_BACKGROUND_THREAD = Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE const val PRIORITY_IMPORTANT_BACKGROUND_THREAD = Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE
// Paraphrased from: https://www.baeldung.com/kotlin/create-thread-pool
// "A cached thread pool such as one created via:
// `val executorPool: ExecutorService = Executors.newCachedThreadPool()`
// will utilize resources according to the requirements of submitted tasks. It will try to reuse
// existing threads for submitted tasks but will create as many threads as it needs if new tasks
// keep pouring in (with a memory usage of at least 1MB per created thread). These threads will
// live for up to 60 seconds of idle time before terminating by default. As such, it presents a
// very sharp tool that doesn't include any backpressure mechanism - and a sudden peak in load
// can bring the system down with an OutOfMemory error. We can achieve a similar effect but with
// better control by creating a ThreadPoolExecutor manually."
private val corePoolSize = Runtime.getRuntime().availableProcessors() // Default thread pool size is our CPU core count
private val maxPoolSize = corePoolSize * 4 // Allow a maximum pool size of up to 4 threads per core
private val keepAliveTimeSecs = 100L // How long to keep idle threads in the pool before they are terminated
private val workQueue = SynchronousQueue<Runnable>()
val executorPool: ExecutorService = ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTimeSecs, TimeUnit.SECONDS, workQueue)
// Note: To see how many threads are running in our app at any given time we can use: // Note: To see how many threads are running in our app at any given time we can use:
// val threadCount = getAllStackTraces().size // val threadCount = getAllStackTraces().size
@JvmStatic @JvmStatic
fun queue(target: Runnable) { fun queue(target: Runnable) {
executorPool.execute { queue(target::run)
try {
target.run()
} catch (e: Exception) {
Log.e(TAG, e)
}
}
} }
fun queue(target: () -> Unit) { fun queue(target: () -> Unit) {
executorPool.execute { Dispatchers.IO.dispatch(EmptyCoroutineContext) {
try { try {
target() target()
} catch (e: Exception) { } catch (e: Exception) {