mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-24 18:45:19 +00:00
Merge branch 'od' into on-2
This commit is contained in:
commit
432a2816ab
@ -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.")
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
rotationListener.enable()
|
|
||||||
|
// Only enable auto-rotate if system auto-rotate is enabled
|
||||||
|
if (isAutoRotateOn()) {
|
||||||
|
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 ->
|
||||||
|
@ -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() {
|
||||||
|
@ -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)
|
||||||
|
@ -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} ")
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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 if (emojiReactionsBinding.isInitialized()) {
|
||||||
|
emojiReactionsBinding.value.root.isVisible = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else if (emojiReactionsBinding.isInitialized()) {
|
||||||
binding.emojiReactionsView.root.isVisible = false
|
emojiReactionsBinding.value.root.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate content view
|
// Populate content view
|
||||||
|
@ -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 ->
|
||||||
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
@ -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)
|
||||||
|
@ -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() {
|
||||||
|
@ -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
|
||||||
|
@ -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,61 +53,16 @@ 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)
|
scheduleConfigSync(userPublicKey)
|
||||||
val currentTime = SnodeAPI.nowWithOffset
|
|
||||||
if (ConfigBase.isNewConfigEnabled(forcedConfig, currentTime)) {
|
|
||||||
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)
|
// schedule job if none exist
|
||||||
val currentTime = SnodeAPI.nowWithOffset
|
// don't schedule job if we already have one
|
||||||
if (ConfigBase.isNewConfigEnabled(forcedConfig, currentTime)) {
|
scheduleConfigSync(userPublicKey)
|
||||||
// schedule job if none exist
|
return Promise.ofSuccess(Unit)
|
||||||
// don't schedule job if we already have one
|
|
||||||
scheduleConfigSync(userPublicKey)
|
|
||||||
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
|
||||||
|
@ -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>
|
@ -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>
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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,73 +318,72 @@ 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 timestamp = TimeUnit.MILLISECONDS.toSeconds(SnodeAPI.nowWithOffset)
|
|
||||||
var pubKey = ""
|
|
||||||
var signature = ByteArray(Sign.BYTES)
|
|
||||||
var bodyHash = ByteArray(0)
|
|
||||||
if (request.parameters != null) {
|
|
||||||
val parameterBytes = JsonUtil.toJson(request.parameters).toByteArray()
|
|
||||||
val parameterHash = ByteArray(GenericHash.BYTES_MAX)
|
|
||||||
if (sodium.cryptoGenericHash(
|
|
||||||
parameterHash,
|
|
||||||
parameterHash.size,
|
|
||||||
parameterBytes,
|
|
||||||
parameterBytes.size.toLong()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
bodyHash = parameterHash
|
|
||||||
}
|
|
||||||
} else if (request.body != null) {
|
|
||||||
val byteHash = ByteArray(GenericHash.BYTES_MAX)
|
|
||||||
if (sodium.cryptoGenericHash(
|
|
||||||
byteHash,
|
|
||||||
byteHash.size,
|
|
||||||
request.body,
|
|
||||||
request.body.size.toLong()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
bodyHash = byteHash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val messageBytes = Hex.fromStringCondensed(publicKey)
|
|
||||||
.plus(nonce)
|
|
||||||
.plus("$timestamp".toByteArray(Charsets.US_ASCII))
|
|
||||||
.plus(request.verb.rawValue.toByteArray())
|
|
||||||
.plus("/${request.endpoint.value}".toByteArray())
|
|
||||||
.plus(bodyHash)
|
|
||||||
if (serverCapabilities.isEmpty() || serverCapabilities.contains(Capability.BLIND.name.lowercase())) {
|
|
||||||
SodiumUtilities.blindedKeyPair(publicKey, ed25519KeyPair)?.let { keyPair ->
|
|
||||||
pubKey = AccountId(
|
|
||||||
IdPrefix.BLINDED,
|
|
||||||
keyPair.publicKey.asBytes
|
|
||||||
).hexString
|
|
||||||
|
|
||||||
signature = SodiumUtilities.sogsSignature(
|
val nonce = sodium.nonce(16)
|
||||||
messageBytes,
|
val timestamp = TimeUnit.MILLISECONDS.toSeconds(SnodeAPI.nowWithOffset)
|
||||||
ed25519KeyPair.secretKey.asBytes,
|
var pubKey = ""
|
||||||
keyPair.secretKey.asBytes,
|
var signature = ByteArray(Sign.BYTES)
|
||||||
keyPair.publicKey.asBytes
|
var bodyHash = ByteArray(0)
|
||||||
) ?: return Promise.ofFail(Error.SigningFailed)
|
if (request.parameters != null) {
|
||||||
} ?: return Promise.ofFail(Error.SigningFailed)
|
val parameterBytes = JsonUtil.toJson(request.parameters).toByteArray()
|
||||||
} else {
|
val parameterHash = ByteArray(GenericHash.BYTES_MAX)
|
||||||
pubKey = AccountId(
|
if (sodium.cryptoGenericHash(
|
||||||
IdPrefix.UN_BLINDED,
|
parameterHash,
|
||||||
ed25519KeyPair.publicKey.asBytes
|
parameterHash.size,
|
||||||
).hexString
|
parameterBytes,
|
||||||
sodium.cryptoSignDetached(
|
parameterBytes.size.toLong()
|
||||||
signature,
|
|
||||||
messageBytes,
|
|
||||||
messageBytes.size.toLong(),
|
|
||||||
ed25519KeyPair.secretKey.asBytes
|
|
||||||
)
|
)
|
||||||
|
) {
|
||||||
|
bodyHash = parameterHash
|
||||||
|
}
|
||||||
|
} else if (request.body != null) {
|
||||||
|
val byteHash = ByteArray(GenericHash.BYTES_MAX)
|
||||||
|
if (sodium.cryptoGenericHash(
|
||||||
|
byteHash,
|
||||||
|
byteHash.size,
|
||||||
|
request.body,
|
||||||
|
request.body.size.toLong()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
bodyHash = byteHash
|
||||||
}
|
}
|
||||||
headers["X-SOGS-Nonce"] = encodeBytes(nonce)
|
|
||||||
headers["X-SOGS-Timestamp"] = "$timestamp"
|
|
||||||
headers["X-SOGS-Pubkey"] = pubKey
|
|
||||||
headers["X-SOGS-Signature"] = encodeBytes(signature)
|
|
||||||
}
|
}
|
||||||
|
val messageBytes = Hex.fromStringCondensed(publicKey)
|
||||||
|
.plus(nonce)
|
||||||
|
.plus("$timestamp".toByteArray(Charsets.US_ASCII))
|
||||||
|
.plus(request.verb.rawValue.toByteArray())
|
||||||
|
.plus("/${request.endpoint.value}".toByteArray())
|
||||||
|
.plus(bodyHash)
|
||||||
|
if (serverCapabilities.isEmpty() || serverCapabilities.contains(Capability.BLIND.name.lowercase())) {
|
||||||
|
SodiumUtilities.blindedKeyPair(publicKey, ed25519KeyPair)?.let { keyPair ->
|
||||||
|
pubKey = AccountId(
|
||||||
|
IdPrefix.BLINDED,
|
||||||
|
keyPair.publicKey.asBytes
|
||||||
|
).hexString
|
||||||
|
|
||||||
|
signature = SodiumUtilities.sogsSignature(
|
||||||
|
messageBytes,
|
||||||
|
ed25519KeyPair.secretKey.asBytes,
|
||||||
|
keyPair.secretKey.asBytes,
|
||||||
|
keyPair.publicKey.asBytes
|
||||||
|
) ?: return Promise.ofFail(Error.SigningFailed)
|
||||||
|
} ?: return Promise.ofFail(Error.SigningFailed)
|
||||||
|
} else {
|
||||||
|
pubKey = AccountId(
|
||||||
|
IdPrefix.UN_BLINDED,
|
||||||
|
ed25519KeyPair.publicKey.asBytes
|
||||||
|
).hexString
|
||||||
|
sodium.cryptoSignDetached(
|
||||||
|
signature,
|
||||||
|
messageBytes,
|
||||||
|
messageBytes.size.toLong(),
|
||||||
|
ed25519KeyPair.secretKey.asBytes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
headers["X-SOGS-Nonce"] = encodeBytes(nonce)
|
||||||
|
headers["X-SOGS-Timestamp"] = "$timestamp"
|
||||||
|
headers["X-SOGS-Pubkey"] = pubKey
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
TextSecurePreferences.setHasLegacyConfig(context, true)
|
||||||
if (ConfigBase.isNewConfigEnabled(isForceSync, currentTime)) {
|
if (!firstTimeSync) return
|
||||||
TextSecurePreferences.setHasLegacyConfig(context, true)
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user