srctree

Robin Linden parent 7cf99d2a 54312ba5
Indicate being in a call on the call button

inlinesplit
atox/src/main/kotlin/ui/call/CallFragment.kt added: 59, removed: 18, total 41
@@ -17,6 +17,7 @@ import ltd.evilcorp.atox.ui.BaseFragment
import ltd.evilcorp.atox.ui.chat.CONTACT_PUBLIC_KEY
import ltd.evilcorp.atox.ui.setAvatarFromContact
import ltd.evilcorp.atox.vmFactory
import ltd.evilcorp.domain.feature.CallState
import ltd.evilcorp.domain.tox.PublicKey
 
private val PERMISSIONS = arrayOf(Manifest.permission.RECORD_AUDIO)
@@ -42,7 +43,7 @@ class CallFragment : BaseFragment<FragmentCallBinding>(FragmentCallBinding::infl
findNavController().popBackStack()
}
 
if (vm.inCall.value) {
if (vm.inCall.value is CallState.InCall) {
return
}
 
@@ -73,7 +74,7 @@ class CallFragment : BaseFragment<FragmentCallBinding>(FragmentCallBinding::infl
private fun startCall() {
vm.startCall()
vm.inCall.asLiveData().observe(viewLifecycleOwner) { inCall ->
if (!inCall) {
if (inCall == CallState.NotInCall) {
findNavController().popBackStack()
}
}
 
atox/src/main/kotlin/ui/chat/ChatFragment.kt added: 59, removed: 18, total 41
@@ -23,7 +23,6 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.viewModels
import androidx.lifecycle.asLiveData
import androidx.navigation.fragment.findNavController
import java.io.File
import java.net.URLConnection
@@ -125,13 +124,25 @@ class ChatFragment : BaseFragment<FragmentChatBinding>(FragmentChatBinding::infl
viewModel.clearDraft()
}
 
toolbar.menu.findItem(R.id.call).isEnabled = viewModel.contactOnline && !viewModel.inCall.value
 
updateActions()
}
 
viewModel.inCall.asLiveData().observe(viewLifecycleOwner) { inCall ->
toolbar.menu.findItem(R.id.call).isEnabled = viewModel.contactOnline && !inCall
viewModel.callState.observe(viewLifecycleOwner) { state ->
when (state) {
CallAvailability.Unavailable -> {
toolbar.menu.findItem(R.id.call).title = getString(R.string.call)
toolbar.menu.findItem(R.id.call).isEnabled = false
}
CallAvailability.Available -> {
toolbar.menu.findItem(R.id.call).title = getString(R.string.call)
toolbar.menu.findItem(R.id.call).isEnabled = true
}
CallAvailability.Active -> {
toolbar.menu.findItem(R.id.call).title = "Ongoing call"
toolbar.menu.findItem(R.id.call).isEnabled = true
}
null -> {}
}
}
 
val adapter = ChatAdapter(layoutInflater, resources)
 
atox/src/main/kotlin/ui/chat/ChatViewModel.kt added: 59, removed: 18, total 41
@@ -15,16 +15,20 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import ltd.evilcorp.atox.R
import ltd.evilcorp.atox.ui.NotificationHelper
import ltd.evilcorp.core.vo.ConnectionStatus
import ltd.evilcorp.core.vo.Contact
import ltd.evilcorp.core.vo.FileTransfer
import ltd.evilcorp.core.vo.Message
import ltd.evilcorp.core.vo.MessageType
import ltd.evilcorp.domain.feature.CallManager
import ltd.evilcorp.domain.feature.CallState
import ltd.evilcorp.domain.feature.ChatManager
import ltd.evilcorp.domain.feature.ContactManager
import ltd.evilcorp.domain.feature.FileTransferManager
@@ -32,6 +36,12 @@ import ltd.evilcorp.domain.tox.PublicKey
 
private const val TAG = "ChatViewModel"
 
enum class CallAvailability {
Unavailable,
Available,
Active,
}
 
class ChatViewModel @Inject constructor(
private val callManager: CallManager,
private val chatManager: ChatManager,
@@ -48,6 +58,22 @@ 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 callState get() = contactManager.get(publicKey)
.transform { emit(it.connectionStatus != ConnectionStatus.None) }
.combine(callManager.inCall) { contactOnline, callState ->
if (!contactOnline) return@combine CallAvailability.Unavailable
when (callState) {
CallState.NotInCall -> CallAvailability.Available
is CallState.InCall -> {
if (callState.publicKey == publicKey) {
CallAvailability.Active
} else {
CallAvailability.Unavailable
}
}
}
}.asLiveData()
 
var contactOnline = false
 
fun send(message: String, type: MessageType) = chatManager.sendMessage(publicKey, message, type)
@@ -121,6 +147,4 @@ class ChatViewModel @Inject constructor(
 
fun setDraft(draft: String) = contactManager.setDraft(publicKey, draft)
fun clearDraft() = setDraft("")
 
val inCall = callManager.inCall
}
 
domain/src/main/kotlin/feature/CallManager.kt added: 59, removed: 18, total 41
@@ -14,14 +14,19 @@ import ltd.evilcorp.domain.av.AudioCapture
import ltd.evilcorp.domain.tox.PublicKey
import ltd.evilcorp.domain.tox.Tox
 
sealed class CallState {
object NotInCall : CallState()
data class InCall(val publicKey: PublicKey) : CallState()
}
 
private const val TAG = "CallManager"
 
@Singleton
class CallManager @Inject constructor(
private val tox: Tox,
) : CoroutineScope by GlobalScope {
private val _inCall = MutableStateFlow(false)
val inCall: StateFlow<Boolean> get() = _inCall
private val _inCall = MutableStateFlow<CallState>(CallState.NotInCall)
val inCall: StateFlow<CallState> get() = _inCall
 
fun startCall(publicKey: PublicKey): Boolean {
val recorder = AudioCapture(48_000, 1)
@@ -30,11 +35,11 @@ class CallManager @Inject constructor(
}
 
tox.startCall(publicKey)
_inCall.value = true
_inCall.value = CallState.InCall(publicKey)
 
launch {
recorder.start()
while (inCall.value) {
while (inCall.value is CallState.InCall) {
val start = System.currentTimeMillis()
val audioFrame = recorder.read()
try {
@@ -54,7 +59,7 @@ class CallManager @Inject constructor(
}
 
fun endCall(publicKey: PublicKey) {
_inCall.value = false
_inCall.value = CallState.NotInCall
try {
tox.endCall(publicKey)
} catch (e: ToxavCallControlException) {