srctree

Robin Linden parent 9ae23612 f71e0415
Display an ongoing call banner in the chat view

inlinesplit
atox/src/main/kotlin/ui/chat/ChatFragment.kt added: 87, removed: 13, total 74
@@ -11,6 +11,7 @@ import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.ContextMenu
import android.view.MenuItem
@@ -50,6 +51,7 @@ import ltd.evilcorp.core.vo.FileTransfer
import ltd.evilcorp.core.vo.Message
import ltd.evilcorp.core.vo.MessageType
import ltd.evilcorp.core.vo.isComplete
import ltd.evilcorp.domain.feature.CallState
import ltd.evilcorp.domain.tox.PublicKey
 
const val CONTACT_PUBLIC_KEY = "publicKey"
@@ -156,11 +158,7 @@ class ChatFragment : BaseFragment<FragmentChatBinding>(FragmentChatBinding::infl
true
}
R.id.call -> {
WindowInsetsControllerCompat(requireActivity().window, view).hide(WindowInsetsCompat.Type.ime())
findNavController().navigate(
R.id.action_chatFragment_to_callFragment,
bundleOf(CONTACT_PUBLIC_KEY to contactPubKey)
)
navigateToCallScreen()
true
}
else -> super.onOptionsItemSelected(item)
@@ -179,6 +177,7 @@ class ChatFragment : BaseFragment<FragmentChatBinding>(FragmentChatBinding::infl
it.name = it.name.ifEmpty { getString(R.string.contact_default_name) }
 
contactName = it.name
ongoingCallInfo.text = getString(R.string.in_call_with, contactName)
viewModel.contactOnline = it.connectionStatus != ConnectionStatus.None
 
title.text = contactName
@@ -218,6 +217,28 @@ class ChatFragment : BaseFragment<FragmentChatBinding>(FragmentChatBinding::infl
}
}
 
viewModel.ongoingCall.observe(viewLifecycleOwner) {
if (it is CallState.InCall && it.publicKey.string() == contactPubKey) {
ongoingCallContainer.visibility = View.VISIBLE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
ongoingCallDuration.visibility = View.VISIBLE
ongoingCallDuration.base = it.startTime
ongoingCallDuration.isCountDown = false
ongoingCallDuration.start()
} else {
ongoingCallDuration.visibility = View.GONE
}
} else {
ongoingCallContainer.visibility = View.GONE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
ongoingCallDuration.stop()
}
}
}
 
ongoingCallEnd.setOnClickListener { viewModel.onEndCall() }
ongoingCallInfo.setOnClickListener { navigateToCallScreen() }
 
val adapter = ChatAdapter(layoutInflater, resources)
messages.adapter = adapter
registerForContextMenu(messages)
@@ -384,4 +405,12 @@ class ChatFragment : BaseFragment<FragmentChatBinding>(FragmentChatBinding::infl
)
)
}
 
private fun navigateToCallScreen() {
view?.let { WindowInsetsControllerCompat(requireActivity().window, it).hide(WindowInsetsCompat.Type.ime()) }
findNavController().navigate(
R.id.action_chatFragment_to_callFragment,
bundleOf(CONTACT_PUBLIC_KEY to contactPubKey)
)
}
}
 
atox/src/main/kotlin/ui/chat/ChatViewModel.kt added: 87, removed: 13, total 74
@@ -62,6 +62,8 @@ class ChatViewModel @Inject constructor(
val messages: LiveData<List<Message>> by lazy { chatManager.messagesFor(publicKey).asLiveData() }
val fileTransfers: LiveData<List<FileTransfer>> by lazy { fileTransferManager.transfersFor(publicKey).asLiveData() }
 
val ongoingCall = callManager.inCall.asLiveData()
 
val callState get() = contactManager.get(publicKey)
.transform { emit(it.connectionStatus != ConnectionStatus.None) }
.combine(callManager.inCall) { contactOnline, callState ->
@@ -151,4 +153,9 @@ class ChatViewModel @Inject constructor(
 
fun setDraft(draft: String) = contactManager.setDraft(publicKey, draft)
fun clearDraft() = setDraft("")
 
fun onEndCall() {
callManager.endCall(publicKey)
notificationHelper.dismissCallNotification(publicKey)
}
}
 
atox/src/main/res/layout/fragment_chat.xml added: 87, removed: 13, total 74
@@ -39,6 +39,43 @@
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
 
<androidx.cardview.widget.CardView android:id="@+id/ongoingCallContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/toolbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Chronometer android:id="@+id/ongoingCallDuration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView android:id="@+id/ongoingCallInfo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:padding="8dp"
android:textAlignment="center"
app:layout_constraintLeft_toRightOf="@id/ongoingCallDuration"
app:layout_constraintRight_toLeftOf="@id/ongoingCallEnd"
app:layout_constraintTop_toTopOf="parent"
tools:text="In call with subject"/>
<Button android:id="@+id/ongoingCallEnd"
style="@style/Widget.AppCompat.ActionButton"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:text="@string/end_call"
android:textColor="@android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/ongoingCallInfo"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
 
<ListView android:id="@+id/messages"
android:layout_width="0dp"
android:layout_height="0dp"
@@ -51,7 +88,7 @@
app:layout_constraintBottom_toTopOf="@+id/bottomBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar"/>
app:layout_constraintTop_toBottomOf="@id/ongoingCallContainer"/>
 
<LinearLayout android:id="@+id/bottomBar"
android:layout_width="match_parent"
 
domain/src/main/kotlin/feature/CallManager.kt added: 87, removed: 13, total 74
@@ -6,6 +6,7 @@ package ltd.evilcorp.domain.feature
 
import android.content.Context
import android.media.AudioManager
import android.os.SystemClock
import android.util.Log
import androidx.core.content.ContextCompat
import im.tox.tox4j.av.exceptions.ToxavCallControlException
@@ -22,7 +23,7 @@ import ltd.evilcorp.domain.tox.Tox
 
sealed class CallState {
object NotInCall : CallState()
data class InCall(val publicKey: PublicKey) : CallState()
data class InCall(val publicKey: PublicKey, val startTime: Long) : CallState()
}
 
private const val TAG = "CallManager"
@@ -47,13 +48,13 @@ class CallManager @Inject constructor(
 
fun startCall(publicKey: PublicKey) {
tox.startCall(publicKey)
_inCall.value = CallState.InCall(publicKey)
_inCall.value = CallState.InCall(publicKey, SystemClock.elapsedRealtime())
audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION
}
 
fun answerCall(publicKey: PublicKey) {
tox.answerCall(publicKey)
_inCall.value = CallState.InCall(publicKey)
_inCall.value = CallState.InCall(publicKey, SystemClock.elapsedRealtime())
audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION
}