Add call tab info screen.

This commit is contained in:
Alex Hart
2023-03-17 12:52:04 -03:00
committed by Greyson Parrelli
parent 49a814abef
commit 545f1fa5a4
12 changed files with 203 additions and 25 deletions

View File

@@ -70,7 +70,7 @@ class CallLogContextMenu(
iconRes = R.drawable.symbol_info_24,
title = fragment.getString(R.string.CallContextMenu__info)
) {
val intent = ConversationSettingsActivity.forCall(fragment.requireContext(), call.peer)
val intent = ConversationSettingsActivity.forCall(fragment.requireContext(), call.peer, longArrayOf(call.call.messageId))
fragment.startActivity(intent)
}
}

View File

@@ -67,7 +67,7 @@ class ConversationSettingsActivity : DSLSettingsActivity(), ConversationSettings
@JvmStatic
fun forGroup(context: Context, groupId: GroupId): Intent {
val startBundle = ConversationSettingsFragmentArgs.Builder(null, ParcelableGroupId.from(groupId))
val startBundle = ConversationSettingsFragmentArgs.Builder(null, ParcelableGroupId.from(groupId), null)
.build()
.toBundle()
@@ -77,7 +77,7 @@ class ConversationSettingsActivity : DSLSettingsActivity(), ConversationSettings
@JvmStatic
fun forRecipient(context: Context, recipientId: RecipientId): Intent {
val startBundle = ConversationSettingsFragmentArgs.Builder(recipientId, null)
val startBundle = ConversationSettingsFragmentArgs.Builder(recipientId, null, null)
.build()
.toBundle()
@@ -86,17 +86,14 @@ class ConversationSettingsActivity : DSLSettingsActivity(), ConversationSettings
}
@JvmStatic
fun forCall(context: Context, callPeer: Recipient): Intent {
fun forCall(context: Context, callPeer: Recipient, callMessageIds: LongArray): Intent {
val startBundleBuilder = if (callPeer.isGroup) {
ConversationSettingsFragmentArgs.Builder(null, ParcelableGroupId.from(callPeer.requireGroupId()))
ConversationSettingsFragmentArgs.Builder(null, ParcelableGroupId.from(callPeer.requireGroupId()), callMessageIds)
} else {
ConversationSettingsFragmentArgs.Builder(callPeer.id, null)
ConversationSettingsFragmentArgs.Builder(callPeer.id, null, callMessageIds)
}
val startBundle = startBundleBuilder
.setIsCallInfo(true)
.build()
.toBundle()
val startBundle = startBundleBuilder.build().toBundle()
return getIntent(context)
.putExtra(ARG_START_BUNDLE, startBundle)

View File

@@ -19,6 +19,7 @@ import androidx.core.content.ContextCompat
import androidx.core.view.doOnPreDraw
import androidx.fragment.app.viewModels
import androidx.navigation.Navigation
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import app.cash.exhaustive.Exhaustive
@@ -49,6 +50,7 @@ import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.components.settings.conversation.preferences.AvatarPreference
import org.thoughtcrime.securesms.components.settings.conversation.preferences.BioTextPreference
import org.thoughtcrime.securesms.components.settings.conversation.preferences.ButtonStripPreference
import org.thoughtcrime.securesms.components.settings.conversation.preferences.CallPreference
import org.thoughtcrime.securesms.components.settings.conversation.preferences.GroupDescriptionPreference
import org.thoughtcrime.securesms.components.settings.conversation.preferences.InternalPreference
import org.thoughtcrime.securesms.components.settings.conversation.preferences.LargeIconClickPreference
@@ -83,6 +85,7 @@ import org.thoughtcrime.securesms.stories.viewer.AddToGroupStoryDelegate
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.ContextUtil
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.ExpirationUtil
import org.thoughtcrime.securesms.util.LifecycleDisposable
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
@@ -92,6 +95,7 @@ import org.thoughtcrime.securesms.util.navigation.safeNavigate
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperActivity
import java.util.Locale
private const val REQUEST_CODE_VIEW_CONTACT = 1
private const val REQUEST_CODE_ADD_CONTACT = 2
@@ -103,6 +107,7 @@ class ConversationSettingsFragment : DSLSettingsFragment(
menuId = R.menu.conversation_settings
) {
private val args: ConversationSettingsFragmentArgs by navArgs()
private val alertTint by lazy { ContextCompat.getColor(requireContext(), R.color.signal_alert_primary) }
private val blockIcon by lazy {
ContextUtil.requireDrawable(requireContext(), R.drawable.ic_block_tinted_24).apply {
@@ -122,13 +127,12 @@ class ConversationSettingsFragment : DSLSettingsFragment(
private val viewModel by viewModels<ConversationSettingsViewModel>(
factoryProducer = {
val args = ConversationSettingsFragmentArgs.fromBundle(requireArguments())
val groupId = args.groupId as? ParcelableGroupId
ConversationSettingsViewModel.Factory(
recipientId = args.recipientId,
groupId = ParcelableGroupId.get(groupId),
isCallInfo = args.isCallInfo,
callMessageIds = args.callMessageIds ?: longArrayOf(),
repository = ConversationSettingsRepository(requireContext())
)
}
@@ -221,6 +225,7 @@ class ConversationSettingsFragment : DSLSettingsFragment(
InternalPreference.register(adapter)
GroupDescriptionPreference.register(adapter)
LegacyGroupPreference.register(adapter)
CallPreference.register(adapter)
val recipientId = args.recipientId
if (recipientId != null) {
@@ -437,6 +442,17 @@ class ConversationSettingsFragment : DSLSettingsFragment(
dividerPref()
if (state.calls.isNotEmpty()) {
val firstCall = state.calls.first()
sectionHeaderPref(DSLSettingsText.from(DateUtils.formatDate(Locale.getDefault(), firstCall.record.timestamp)))
for (call in state.calls) {
customPref(call)
}
dividerPref()
}
val summary = DSLSettingsText.from(formatDisappearingMessagesLifespan(state.disappearingMessagesLifespan))
val icon = if (state.disappearingMessagesLifespan <= 0 || state.recipient.isBlocked) {
R.drawable.ic_update_timer_disabled_16

View File

@@ -5,6 +5,7 @@ import android.database.Cursor
import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.logging.Log
@@ -15,6 +16,7 @@ import org.thoughtcrime.securesms.database.MediaTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.database.model.IdentityRecord
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.StoryViewState
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.groups.GroupId
@@ -37,6 +39,16 @@ class ConversationSettingsRepository(
private val groupManagementRepository: GroupManagementRepository = GroupManagementRepository(context)
) {
fun getCallEvents(callMessageIds: LongArray): Single<List<MessageRecord>> {
return if (callMessageIds.isEmpty()) {
Single.just(emptyList())
} else {
Single.fromCallable {
SignalDatabase.messages.getMessages(callMessageIds.toList()).iterator().asSequence().toList()
}
}
}
@WorkerThread
fun getThreadMedia(threadId: Long): Optional<Cursor> {
return if (threadId <= 0) {

View File

@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.settings.conversation
import android.database.Cursor
import org.thoughtcrime.securesms.components.settings.conversation.preferences.ButtonStripPreference
import org.thoughtcrime.securesms.components.settings.conversation.preferences.CallPreference
import org.thoughtcrime.securesms.components.settings.conversation.preferences.LegacyGroupPreference
import org.thoughtcrime.securesms.database.model.IdentityRecord
import org.thoughtcrime.securesms.database.model.StoryViewState
@@ -19,6 +20,7 @@ data class ConversationSettingsState(
val sharedMedia: Cursor? = null,
val sharedMediaIds: List<Long> = listOf(),
val displayInternalRecipientDetails: Boolean = false,
val calls: List<CallPreference.Model> = emptyList(),
private val sharedMediaLoaded: Boolean = false,
private val specificSettingsState: SpecificSettingsState
) {

View File

@@ -16,6 +16,7 @@ import org.signal.core.util.CursorUtil
import org.signal.core.util.ThreadUtil
import org.signal.core.util.concurrent.SignalExecutors
import org.thoughtcrime.securesms.components.settings.conversation.preferences.ButtonStripPreference
import org.thoughtcrime.securesms.components.settings.conversation.preferences.CallPreference
import org.thoughtcrime.securesms.components.settings.conversation.preferences.LegacyGroupPreference
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.RecipientTable
@@ -33,6 +34,7 @@ import org.thoughtcrime.securesms.util.livedata.Store
import java.util.Optional
sealed class ConversationSettingsViewModel(
private val callMessageIds: LongArray,
private val repository: ConversationSettingsRepository,
specificSettingsState: SpecificSettingsState
) : ViewModel() {
@@ -64,6 +66,10 @@ sealed class ConversationSettingsViewModel(
repository.getThreadMedia(tId)
}
store.update(repository.getCallEvents(callMessageIds).toObservable()) { callRecords, state ->
state.copy(calls = callRecords.map { CallPreference.Model(it) })
}
store.update(sharedMedia) { cursor, state ->
if (!cleared) {
if (cursor.isPresent) {
@@ -128,9 +134,10 @@ sealed class ConversationSettingsViewModel(
private class RecipientSettingsViewModel(
private val recipientId: RecipientId,
private val isCallInfo: Boolean,
private val callMessageIds: LongArray,
private val repository: ConversationSettingsRepository
) : ConversationSettingsViewModel(
callMessageIds,
repository,
SpecificSettingsState.RecipientSettingsState()
) {
@@ -152,13 +159,13 @@ sealed class ConversationSettingsViewModel(
state.copy(
recipient = recipient,
buttonStripState = ButtonStripPreference.State(
isMessageAvailable = isCallInfo,
isMessageAvailable = callMessageIds.isNotEmpty(),
isVideoAvailable = recipient.registered == RecipientTable.RegisteredState.REGISTERED && !recipient.isSelf && !recipient.isBlocked && !recipient.isReleaseNotes,
isAudioAvailable = isAudioAvailable,
isAudioSecure = recipient.registered == RecipientTable.RegisteredState.REGISTERED,
isMuted = recipient.isMuted,
isMuteAvailable = !recipient.isSelf,
isSearchAvailable = !isCallInfo
isSearchAvailable = callMessageIds.isEmpty()
),
disappearingMessagesLifespan = recipient.expiresInSeconds,
canModifyBlockedState = !recipient.isSelf && RecipientUtil.isBlockable(recipient),
@@ -258,9 +265,9 @@ sealed class ConversationSettingsViewModel(
private class GroupSettingsViewModel(
private val groupId: GroupId,
private val isCallInfo: Boolean,
private val callMessageIds: LongArray,
private val repository: ConversationSettingsRepository
) : ConversationSettingsViewModel(repository, SpecificSettingsState.GroupSettingsState(groupId)) {
) : ConversationSettingsViewModel(callMessageIds, repository, SpecificSettingsState.GroupSettingsState(groupId)) {
private val liveGroup = LiveGroup(groupId)
@@ -274,13 +281,13 @@ sealed class ConversationSettingsViewModel(
state.copy(
recipient = recipient,
buttonStripState = ButtonStripPreference.State(
isMessageAvailable = isCallInfo,
isMessageAvailable = callMessageIds.isNotEmpty(),
isVideoAvailable = recipient.isPushV2Group && !recipient.isBlocked && isActive,
isAudioAvailable = false,
isAudioSecure = recipient.isPushV2Group,
isMuted = recipient.isMuted,
isMuteAvailable = true,
isSearchAvailable = !isCallInfo,
isSearchAvailable = callMessageIds.isEmpty(),
isAddToStoryAvailable = recipient.isPushV2Group && !recipient.isBlocked && isActive && !SignalStore.storyValues().isFeatureDisabled
),
canModifyBlockedState = RecipientUtil.isBlockable(recipient),
@@ -483,7 +490,7 @@ sealed class ConversationSettingsViewModel(
class Factory(
private val recipientId: RecipientId? = null,
private val groupId: GroupId? = null,
private val isCallInfo: Boolean,
private val callMessageIds: LongArray,
private val repository: ConversationSettingsRepository
) : ViewModelProvider.Factory {
@@ -491,8 +498,8 @@ sealed class ConversationSettingsViewModel(
return requireNotNull(
modelClass.cast(
when {
recipientId != null -> RecipientSettingsViewModel(recipientId, isCallInfo, repository)
groupId != null -> GroupSettingsViewModel(groupId, isCallInfo, repository)
recipientId != null -> RecipientSettingsViewModel(recipientId, callMessageIds, repository)
groupId != null -> GroupSettingsViewModel(groupId, callMessageIds, repository)
else -> error("One of RecipientId or GroupId required.")
}
)

View File

@@ -0,0 +1,71 @@
package org.thoughtcrime.securesms.components.settings.conversation.preferences
import androidx.annotation.DrawableRes
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.database.MessageTypes
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.databinding.ConversationSettingsCallPreferenceItemBinding
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.adapter.mapping.BindingFactory
import org.thoughtcrime.securesms.util.adapter.mapping.BindingViewHolder
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
import java.util.Locale
/**
* Renders a single call preference row when displaying call info.
*/
object CallPreference {
fun register(mappingAdapter: MappingAdapter) {
mappingAdapter.registerFactory(Model::class.java, BindingFactory(::ViewHolder, ConversationSettingsCallPreferenceItemBinding::inflate))
}
class Model(
val record: MessageRecord
) : MappingModel<Model> {
override fun areItemsTheSame(newItem: Model): Boolean = record.id == newItem.record.id
override fun areContentsTheSame(newItem: Model): Boolean {
return record.type == newItem.record.type &&
record.isOutgoing == newItem.record.isOutgoing &&
record.timestamp == newItem.record.timestamp &&
record.id == newItem.record.id
}
}
private class ViewHolder(binding: ConversationSettingsCallPreferenceItemBinding) : BindingViewHolder<Model, ConversationSettingsCallPreferenceItemBinding>(binding) {
override fun bind(model: Model) {
binding.callIcon.setImageResource(getCallIcon(model.record))
binding.callType.text = getCallType(model.record)
binding.callTime.text = getCallTime(model.record)
}
@DrawableRes
private fun getCallIcon(messageRecord: MessageRecord): Int {
return when (messageRecord.type) {
MessageTypes.MISSED_VIDEO_CALL_TYPE, MessageTypes.MISSED_AUDIO_CALL_TYPE -> R.drawable.symbol_missed_incoming_24
MessageTypes.INCOMING_AUDIO_CALL_TYPE, MessageTypes.INCOMING_VIDEO_CALL_TYPE -> R.drawable.symbol_arrow_downleft_24
MessageTypes.OUTGOING_AUDIO_CALL_TYPE, MessageTypes.OUTGOING_VIDEO_CALL_TYPE -> R.drawable.symbol_arrow_upright_24
else -> error("Unexpected type ${messageRecord.type}")
}
}
private fun getCallType(messageRecord: MessageRecord): String {
val id = when (messageRecord.type) {
MessageTypes.MISSED_VIDEO_CALL_TYPE -> R.string.MessageRecord_missed_voice_call
MessageTypes.MISSED_AUDIO_CALL_TYPE -> R.string.MessageRecord_missed_video_call
MessageTypes.INCOMING_AUDIO_CALL_TYPE -> R.string.MessageRecord_incoming_voice_call
MessageTypes.INCOMING_VIDEO_CALL_TYPE -> R.string.MessageRecord_incoming_video_call
MessageTypes.OUTGOING_AUDIO_CALL_TYPE -> R.string.MessageRecord_outgoing_voice_call
MessageTypes.OUTGOING_VIDEO_CALL_TYPE -> R.string.MessageRecord_outgoing_video_call
else -> error("Unexpected type ${messageRecord.type}")
}
return context.getString(id)
}
private fun getCallTime(messageRecord: MessageRecord): String {
return DateUtils.getOnlyTimeString(context, Locale.getDefault(), messageRecord.timestamp)
}
}
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M18.37 6.87l-8.25 8.25-1.5 1.26h7.63c0.48 0 0.88 0.39 0.88 0.87s-0.4 0.88-0.88 0.88h-9.5c-0.48 0-0.88-0.4-0.88-0.88v-9.5c0-0.48 0.4-0.88 0.88-0.88s0.88 0.4 0.88 0.88v7.64l1.25-1.5 8.25-8.26c0.34-0.34 0.9-0.34 1.24 0 0.34 0.34 0.34 0.9 0 1.24Z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M16.38 8.61v7.64c0 0.48 0.39 0.88 0.87 0.88s0.88-0.4 0.88-0.88v-9.5c0-0.48-0.4-0.88-0.88-0.88h-9.5c-0.48 0-0.88 0.4-0.88 0.88s0.4 0.88 0.88 0.88h7.64l-1.5 1.25-8.26 8.25c-0.34 0.34-0.34 0.9 0 1.24 0.34 0.34 0.9 0.34 1.24 0l8.25-8.25 1.26-1.5Z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M4.13 9.61v6.14c0 0.48-0.4 0.88-0.88 0.88s-0.88-0.4-0.88-0.88v-8c0-0.48 0.4-0.88 0.88-0.88h8c0.48 0 0.88 0.4 0.88 0.88s-0.4 0.88-0.88 0.88H5.11l1.5 1.25 6.14 6.13 8.63-8.63c0.34-0.34 0.9-0.34 1.24 0 0.34 0.34 0.34 0.9 0 1.24l-8.72 8.72c-0.64 0.63-1.66 0.63-2.3 0l-6.22-6.22-1.25-1.5Z"/>
</vector>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="72dp"
android:paddingHorizontal="@dimen/dsl_settings_gutter"
android:paddingVertical="14dp">
<ImageView
android:id="@+id/call_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:importantForAccessibility="no"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_update_video_call_missed_16" />
<TextView
android:id="@+id/call_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:textAppearance="@style/Signal.Text.BodyLarge"
app:layout_constraintBottom_toTopOf="@id/call_time"
app:layout_constraintStart_toEndOf="@id/call_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Incoming video call" />
<TextView
android:id="@+id/call_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:textAppearance="@style/Signal.Text.BodyMedium"
android:textColor="@color/signal_colorOnSurfaceVariant"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/call_icon"
app:layout_constraintTop_toBottomOf="@id/call_type"
tools:text="10:00 AM" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -48,9 +48,9 @@
app:nullable="true" />
<argument
android:name="is_call_info"
app:argType="boolean"
android:defaultValue="false" />
android:name="call_message_ids"
app:argType="long[]"
app:nullable="true" />
<action
android:id="@+id/action_conversationSettingsFragment_to_soundsAndNotificationsSettingsFragment"