Added ended call fragment

This commit is contained in:
Sylvain Berfini 2023-10-02 11:10:55 +02:00
parent 464865c091
commit 429e8d2704
9 changed files with 327 additions and 12 deletions

View file

@ -162,6 +162,13 @@ class CallActivity : AppCompatActivity() {
}
}
callViewModel.goTEndedCallEvent.observe(this) {
it.consume {
val action = ActiveCallFragmentDirections.actionGlobalEndedCallFragment()
findNavController(R.id.call_nav_container).navigate(action)
}
}
callsViewModel.showIncomingCallEvent.observe(this) {
it.consume {
val action = IncomingCallFragmentDirections.actionGlobalIncomingCallFragment()
@ -194,7 +201,7 @@ class CallActivity : AppCompatActivity() {
}
}
callsViewModel.noMoreCallEvent.observe(this) {
callsViewModel.noCallFoundEvent.observe(this) {
it.consume {
finish()
}

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.ui.call.fragment
import android.os.Bundle
import android.os.SystemClock
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.linphone.core.tools.Log
import org.linphone.databinding.CallEndedFragmentBinding
import org.linphone.ui.call.viewmodel.CurrentCallViewModel
@UiThread
class EndedCallFragment : GenericCallFragment() {
companion object {
private const val TAG = "[Ended Call Fragment]"
}
private lateinit var binding: CallEndedFragmentBinding
private lateinit var callViewModel: CurrentCallViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = CallEndedFragmentBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
callViewModel = requireActivity().run {
ViewModelProvider(this)[CurrentCallViewModel::class.java]
}
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = callViewModel
Log.i("$TAG Showing ended call fragment")
callViewModel.callDuration.observe(viewLifecycleOwner) { duration ->
binding.chronometer.base = SystemClock.elapsedRealtime() - (1000 * duration)
// Do not start it!
}
}
override fun onResume() {
super.onResume()
lifecycleScope.launch {
withContext(Dispatchers.IO) {
Log.i("$TAG Waiting 2 seconds before finishing activity")
delay(2000)
withContext(Dispatchers.Main) {
Log.i("$TAG Finishing activity")
requireActivity().finish()
}
}
}
}
}

View file

@ -47,15 +47,9 @@ class CallsViewModel @UiThread constructor() : ViewModel() {
val showOutgoingCallEvent = MutableLiveData<Event<Boolean>>()
val noMoreCallEvent = MutableLiveData<Event<Boolean>>()
val noCallFoundEvent = MutableLiveData<Event<Boolean>>()
private val coreListener = object : CoreListenerStub() {
@WorkerThread
override fun onLastCallEnded(core: Core) {
Log.i("$TAG No more call, leaving Call activity")
noMoreCallEvent.postValue(Event(true))
}
@WorkerThread
override fun onCallStateChanged(
core: Core,
@ -158,7 +152,7 @@ class CallsViewModel @UiThread constructor() : ViewModel() {
}
} else {
Log.w("$TAG No call found, leaving Call activity")
noMoreCallEvent.postValue(Event(true))
noCallFoundEvent.postValue(Event(true))
}
}
}

View file

@ -102,6 +102,10 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
MutableLiveData<String>()
}
val goTEndedCallEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
// To synchronize chronometers in UI
val callDuration = MutableLiveData<Int>()
@ -194,8 +198,16 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
)
configureCall(newCurrentCall)
} else {
Log.e("$TAG Failed to get a valid call to display!")
Log.e(
"$TAG Failed to get a valid call to display, go to ended call fragment"
)
goTEndedCallEvent.postValue(Event(true))
}
} else {
Log.i("$TAG Call is ending, go to ended call fragment")
// Show that call was ended for a few seconds, then leave
// TODO FIXME: do not show it when call is being ended due to user terminating the call
goTEndedCallEvent.postValue(Event(true))
}
} else {
val videoEnabled = call.currentParams.isVideoEnabled

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="@dimen/call_main_actions_menu_height"
android:paddingBottom="5dp"
android:background="@color/gray_900">
<ImageView
android:id="@+id/hang_up"
android:layout_width="wrap_content"
android:layout_height="@dimen/call_button_size"
android:layout_marginStart="16dp"
android:paddingStart="30dp"
android:paddingEnd="30dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:src="@drawable/phone_disconnect"
android:background="@drawable/shape_squircle_red_disabled_background"
app:tint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<ImageView
android:id="@+id/toggle_video"
android:layout_width="@dimen/call_button_size"
android:layout_height="@dimen/call_button_size"
android:layout_marginEnd="16dp"
android:padding="@dimen/call_button_icon_padding"
android:enabled="false"
android:src="@drawable/video_camera_slash"
android:background="@drawable/shape_round_in_call_disabled_button_background"
app:tint="@color/gray_500"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/hang_up"
app:layout_constraintEnd_toStartOf="@id/toggle_mute_mic" />
<ImageView
android:id="@+id/toggle_mute_mic"
android:layout_width="@dimen/call_button_size"
android:layout_height="@dimen/call_button_size"
android:layout_marginEnd="16dp"
android:padding="@dimen/call_button_icon_padding"
android:src="@drawable/microphone_slash"
android:background="@drawable/shape_round_in_call_disabled_button_background"
app:tint="@color/gray_500"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/toggle_video"
app:layout_constraintEnd_toStartOf="@id/change_audio_output" />
<ImageView
android:id="@+id/change_audio_output"
android:layout_width="@dimen/call_button_size"
android:layout_height="@dimen/call_button_size"
android:layout_marginEnd="16dp"
android:padding="@dimen/call_button_icon_padding"
android:src="@drawable/speaker_slash"
android:background="@drawable/shape_round_in_call_disabled_button_background"
app:tint="@color/gray_500"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/toggle_mute_mic"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="org.linphone.ui.call.viewmodel.CurrentCallViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/gray_900">
<ImageView
android:id="@+id/call_direction_icon"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="10dp"
android:adjustViewBounds="true"
android:src="@{viewModel.isOutgoing ? @drawable/outgoing_call : @drawable/incoming_call, default=@drawable/outgoing_call}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/call_direction_label"
app:layout_constraintBottom_toBottomOf="@id/call_direction_label"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/call_header_style"
android:id="@+id/call_direction_label"
android:layout_width="wrap_content"
android:layout_height="@dimen/call_top_bar_text_height"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="@string/call_ended"
app:layout_constraintStart_toEndOf="@id/call_direction_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/background"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/call_header_style"
android:id="@+id/separator"
android:layout_width="wrap_content"
android:layout_height="@dimen/call_top_bar_text_height"
android:layout_marginStart="5dp"
android:text="@string/vertical_separator"
app:layout_constraintStart_toEndOf="@id/call_direction_label"
app:layout_constraintTop_toTopOf="@id/call_direction_label"/>
<Chronometer
style="@style/call_header_style"
android:id="@+id/chronometer"
android:layout_width="wrap_content"
android:layout_height="@dimen/call_top_bar_text_height"
android:layout_marginStart="5dp"
android:visibility="@{viewModel.isPaused || viewModel.isPausedByRemote ? View.GONE : View.VISIBLE}"
app:layout_constraintStart_toEndOf="@id/separator"
app:layout_constraintTop_toTopOf="@id/call_direction_label"/>
<ImageView
android:id="@+id/background"
android:src="@drawable/shape_round_in_call_gray_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
app:layout_constraintTop_toBottomOf="@id/call_direction_label"
app:layout_constraintBottom_toTopOf="@id/bottom_bar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<io.getstream.avatarview.AvatarView
android:id="@+id/avatar"
android:layout_width="@dimen/avatar_in_call_size"
android:layout_height="@dimen/avatar_in_call_size"
android:background="@drawable/shape_circle_light_blue_background"
contactAvatar="@{viewModel.contact}"
app:avatarViewInitialsBackgroundColor="@color/gray_main2_200"
app:avatarViewInitialsTextColor="@color/gray_main2_600"
app:avatarViewInitialsTextSize="36sp"
app:avatarViewInitialsTextStyle="bold"
app:avatarViewPlaceholder="@drawable/user_circle"
app:avatarViewShape="circle"
app:avatarViewBorderWidth="0dp"
app:layout_constraintEnd_toEndOf="@id/background"
app:layout_constraintStart_toStartOf="@id/background"
app:layout_constraintTop_toTopOf="@id/background"
app:layout_constraintBottom_toBottomOf="@id/background"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="@{viewModel.displayedName, default=`John Doe`}"
android:textColor="@color/white"
android:textSize="22sp"
app:layout_constraintTop_toBottomOf="@id/avatar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.displayedAddress, default=`sip:johndoe@sip.linphone.org`}"
android:textColor="@color/white"
android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<include
android:id="@+id/bottom_bar"
layout="@layout/call_ended_actions"
android:layout_width="0dp"
android:layout_height="@dimen/call_main_actions_menu_height"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -94,4 +94,14 @@
android:label="CallsListFragment"
tools:layout="@layout/calls_list_fragment" />
<fragment
android:id="@+id/endedCallFragment"
android:name="org.linphone.ui.call.fragment.EndedCallFragment"
android:label="EndedCallFragment"
tools:layout="@layout/call_ended_fragment"/>
<action android:id="@+id/action_global_endedCallFragment"
app:destination="@id/endedCallFragment"
app:launchSingleTop="true"/>
</navigation>

View file

@ -291,6 +291,7 @@
<string name="call_outgoing">Outgoing call</string>
<string name="call_incoming">Incoming call</string>
<string name="call_ended">Call ended</string>
<string name="call_incoming_for_account">Incoming call for %s</string>
<string name="operation_in_progress_overlay">Operation in progress, please wait</string>

View file

@ -9,8 +9,8 @@ buildscript {
}
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '8.1.1' apply false
id 'com.android.library' version '8.1.1' apply false
id 'com.android.application' version '8.1.2' apply false
id 'com.android.library' version '8.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.9.0-RC' apply false
id 'com.google.gms.google-services' version '4.3.15' apply false
}