feat: add a first call missed control message and info popup with link to privacy settings

This commit is contained in:
Harris 2022-04-06 16:38:55 +10:00
parent e7b0707377
commit 34863c5cab
9 changed files with 61 additions and 9 deletions

View File

@ -1,10 +1,13 @@
package org.thoughtcrime.securesms.conversation.v2 package org.thoughtcrime.securesms.conversation.v2
import android.app.AlertDialog
import android.content.Context import android.content.Context
import android.content.Intent
import android.database.Cursor import android.database.Cursor
import android.view.MotionEvent import android.view.MotionEvent
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.recyclerview.widget.RecyclerView.ViewHolder
import network.loki.messenger.R
import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentViewDelegate import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentViewDelegate
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
@ -12,6 +15,7 @@ import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
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
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPress: (MessageRecord, Int, VisibleMessageView, MotionEvent) -> Unit, class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPress: (MessageRecord, Int, VisibleMessageView, MotionEvent) -> Unit,
private val onItemSwipeToReply: (MessageRecord, Int) -> Unit, private val onItemLongPress: (MessageRecord, Int) -> Unit, private val onItemSwipeToReply: (MessageRecord, Int) -> Unit, private val onItemLongPress: (MessageRecord, Int) -> Unit,
@ -76,7 +80,26 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr
} }
view.contentViewDelegate = visibleMessageContentViewDelegate view.contentViewDelegate = visibleMessageContentViewDelegate
} }
is ControlMessageViewHolder -> viewHolder.view.bind(message, messageBefore) is ControlMessageViewHolder -> {
viewHolder.view.bind(message, messageBefore)
if (message.isCallLog && message.isFirstMissedCall) {
viewHolder.view.setOnClickListener {
AlertDialog.Builder(context)
.setTitle(R.string.CallNotificationBuilder_first_call_title)
.setMessage(R.string.CallNotificationBuilder_first_call_message)
.setPositiveButton(R.string.activity_settings_title) { _, _ ->
val intent = Intent(context, PrivacySettingsActivity::class.java)
context.startActivity(intent)
}
.setNeutralButton(R.string.cancel) { d, _ ->
d.dismiss()
}
.show()
}
} else {
viewHolder.view.setOnClickListener(null)
}
}
} }
} }

View File

@ -1,12 +1,10 @@
package org.thoughtcrime.securesms.conversation.v2.messages package org.thoughtcrime.securesms.conversation.v2.messages
import android.content.Context import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import network.loki.messenger.R import network.loki.messenger.R
@ -53,6 +51,7 @@ class ControlMessageView : LinearLayout {
val drawable = when { val drawable = when {
message.isIncomingCall -> R.drawable.ic_incoming_call message.isIncomingCall -> R.drawable.ic_incoming_call
message.isOutgoingCall -> R.drawable.ic_outgoing_call message.isOutgoingCall -> R.drawable.ic_outgoing_call
message.isFirstMissedCall -> R.drawable.ic_info_outline_light
else -> R.drawable.ic_missed_call else -> R.drawable.ic_missed_call
} }
binding.iconImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, drawable, context.theme)) binding.iconImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, drawable, context.theme))

View File

@ -32,6 +32,7 @@ public interface MmsSmsColumns {
protected static final long OUTGOING_CALL_TYPE = 2; protected static final long OUTGOING_CALL_TYPE = 2;
protected static final long MISSED_CALL_TYPE = 3; protected static final long MISSED_CALL_TYPE = 3;
protected static final long JOINED_TYPE = 4; protected static final long JOINED_TYPE = 4;
protected static final long FIRST_MISSED_CALL_TYPE = 5;
protected static final long BASE_INBOX_TYPE = 20; protected static final long BASE_INBOX_TYPE = 20;
protected static final long BASE_OUTBOX_TYPE = 21; protected static final long BASE_OUTBOX_TYPE = 21;
@ -208,7 +209,7 @@ public interface MmsSmsColumns {
public static boolean isCallLog(long type) { public static boolean isCallLog(long type) {
long baseType = type & BASE_TYPE_MASK; long baseType = type & BASE_TYPE_MASK;
return baseType == INCOMING_CALL_TYPE || baseType == OUTGOING_CALL_TYPE || baseType == MISSED_CALL_TYPE; return baseType == INCOMING_CALL_TYPE || baseType == OUTGOING_CALL_TYPE || baseType == MISSED_CALL_TYPE || baseType == FIRST_MISSED_CALL_TYPE;
} }
public static boolean isExpirationTimerUpdate(long type) { public static boolean isExpirationTimerUpdate(long type) {
@ -239,6 +240,11 @@ public interface MmsSmsColumns {
return (type & BASE_TYPE_MASK) == MISSED_CALL_TYPE; return (type & BASE_TYPE_MASK) == MISSED_CALL_TYPE;
} }
public static boolean isFirstMissedCall(long type) {
return (type & BASE_TYPE_MASK) == FIRST_MISSED_CALL_TYPE;
}
public static boolean isGroupUpdate(long type) { public static boolean isGroupUpdate(long type) {
return (type & GROUP_UPDATE_BIT) != 0; return (type & GROUP_UPDATE_BIT) != 0;
} }

View File

@ -386,6 +386,9 @@ public class SmsDatabase extends MessagingDatabase {
case CALL_MISSED: case CALL_MISSED:
type |= Types.MISSED_CALL_TYPE; type |= Types.MISSED_CALL_TYPE;
break; break;
case CALL_FIRST_MISSED:
type |= Types.FIRST_MISSED_CALL_TYPE;
break;
} }
} }

View File

@ -117,6 +117,9 @@ public abstract class DisplayRecord {
public boolean isMissedCall() { public boolean isMissedCall() {
return SmsDatabase.Types.isMissedCall(type); return SmsDatabase.Types.isMissedCall(type);
} }
public boolean isFirstMissedCall() {
return SmsDatabase.Types.isFirstMissedCall(type);
}
public boolean isDeleted() { return MmsSmsColumns.Types.isDeletedMessage(type); } public boolean isDeleted() { return MmsSmsColumns.Types.isDeletedMessage(type); }
public boolean isMessageRequestResponse() { return MmsSmsColumns.Types.isMessageRequestResponse(type); } public boolean isMessageRequestResponse() { return MmsSmsColumns.Types.isMessageRequestResponse(type); }

View File

@ -114,7 +114,16 @@ public abstract class MessageRecord extends DisplayRecord {
if (isScreenshotNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT, getIndividualRecipient().getAddress().serialize()))); if (isScreenshotNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT, getIndividualRecipient().getAddress().serialize())));
else if (isMediaSavedNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED, getIndividualRecipient().getAddress().serialize()))); else if (isMediaSavedNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED, getIndividualRecipient().getAddress().serialize())));
} else if (isCallLog()) { } else if (isCallLog()) {
CallMessageType callType = isIncomingCall() ? CallMessageType.CALL_INCOMING : isOutgoingCall() ? CallMessageType.CALL_OUTGOING : CallMessageType.CALL_MISSED; CallMessageType callType;
if (isIncomingCall()) {
callType = CallMessageType.CALL_INCOMING;
} else if (isOutgoingCall()) {
callType = CallMessageType.CALL_OUTGOING;
} else if (isMissedCall()) {
callType = CallMessageType.CALL_MISSED;
} else {
callType = CallMessageType.CALL_FIRST_MISSED;
}
return new SpannableString(UpdateMessageBuilder.INSTANCE.buildCallMessage(context, callType, getIndividualRecipient().getAddress().serialize())); return new SpannableString(UpdateMessageBuilder.INSTANCE.buildCallMessage(context, callType, getIndividualRecipient().getAddress().serialize()));
} }

View File

@ -47,8 +47,10 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
// first time call notification encountered // first time call notification encountered
val notification = CallNotificationBuilder.getFirstCallNotification(context) val notification = CallNotificationBuilder.getFirstCallNotification(context)
context.getSystemService(NotificationManager::class.java).notify(CallNotificationBuilder.WEBRTC_NOTIFICATION, notification) context.getSystemService(NotificationManager::class.java).notify(CallNotificationBuilder.WEBRTC_NOTIFICATION, notification)
insertMissedCall(sender, sentTimestamp, isFirstCall = true)
} else {
insertMissedCall(sender, sentTimestamp)
} }
insertMissedCall(sender, sentTimestamp)
continue continue
} }
when (nextMessage.type) { when (nextMessage.type) {
@ -63,10 +65,14 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
} }
} }
private fun insertMissedCall(sender: String, sentTimestamp: Long) { private fun insertMissedCall(sender: String, sentTimestamp: Long, isFirstCall: Boolean = false) {
val currentUserPublicKey = storage.getUserPublicKey() val currentUserPublicKey = storage.getUserPublicKey()
if (sender == currentUserPublicKey) return // don't insert a "missed" due to call notifications disabled if it's our own sender if (sender == currentUserPublicKey) return // don't insert a "missed" due to call notifications disabled if it's our own sender
storage.insertCallMessage(sender, CallMessageType.CALL_MISSED, sentTimestamp) if (isFirstCall) {
storage.insertCallMessage(sender, CallMessageType.CALL_FIRST_MISSED, sentTimestamp)
} else {
storage.insertCallMessage(sender, CallMessageType.CALL_MISSED, sentTimestamp)
}
} }
private fun incomingHangup(callMessage: CallMessage) { private fun incomingHangup(callMessage: CallMessage) {

View File

@ -3,5 +3,6 @@ package org.session.libsession.messaging.calls
enum class CallMessageType { enum class CallMessageType {
CALL_MISSED, CALL_MISSED,
CALL_INCOMING, CALL_INCOMING,
CALL_OUTGOING CALL_OUTGOING,
CALL_FIRST_MISSED,
} }

View File

@ -114,6 +114,8 @@ object UpdateMessageBuilder {
context.getString(R.string.MessageRecord_s_called_you, senderName) context.getString(R.string.MessageRecord_s_called_you, senderName)
CallMessageType.CALL_OUTGOING -> CallMessageType.CALL_OUTGOING ->
context.getString(R.string.MessageRecord_called_s, senderName) context.getString(R.string.MessageRecord_called_s, senderName)
CallMessageType.CALL_FIRST_MISSED ->
context.getString(R.string.MessageRecord_missed_call_from, senderName)
} }
} }
} }