Ran new version of kotlin auto formatter

This commit is contained in:
Sylvain Berfini 2021-09-03 09:07:52 +02:00
parent 06a3124ee6
commit f2a1869a1a
74 changed files with 1776 additions and 1219 deletions

View file

@ -448,7 +448,8 @@ internal fun ChatRoomCreationFragment.navigateToEmptyChatRoom() {
internal fun GroupInfoFragment.navigateToChatRoomCreation(args: Bundle?) { internal fun GroupInfoFragment.navigateToChatRoomCreation(args: Bundle?) {
if (findNavController().currentDestination?.id == R.id.groupInfoFragment) { if (findNavController().currentDestination?.id == R.id.groupInfoFragment) {
findNavController().navigate(R.id.action_groupInfoFragment_to_chatRoomCreationFragment, findNavController().navigate(
R.id.action_groupInfoFragment_to_chatRoomCreationFragment,
args, args,
getLeftToRightAnimationNavOptions(R.id.chatRoomCreationFragment, true) getLeftToRightAnimationNavOptions(R.id.chatRoomCreationFragment, true)
) )

View file

@ -71,9 +71,9 @@ abstract class AbstractPhoneFragment<T : ViewDataBinding> : GenericFragment<T>()
.setTitle(getString(R.string.assistant_phone_number_info_title)) .setTitle(getString(R.string.assistant_phone_number_info_title))
.setMessage( .setMessage(
getString(R.string.assistant_phone_number_link_info_content) + "\n" + getString(R.string.assistant_phone_number_link_info_content) + "\n" +
getString( getString(
R.string.assistant_phone_number_link_info_content_already_account R.string.assistant_phone_number_link_info_content_already_account
) )
) )
.setNegativeButton(getString(R.string.dialog_ok), null) .setNegativeButton(getString(R.string.dialog_ok), null)
.show() .show()

View file

@ -74,51 +74,66 @@ class AccountLoginFragment : AbstractPhoneFragment<AssistantAccountLoginFragment
startActivity(intent) startActivity(intent)
} }
viewModel.goToSmsValidationEvent.observe(viewLifecycleOwner, { viewModel.goToSmsValidationEvent.observe(
it.consume { viewLifecycleOwner,
val args = Bundle() {
args.putBoolean("IsLogin", true) it.consume {
args.putString("PhoneNumber", viewModel.accountCreator.phoneNumber) val args = Bundle()
navigateToPhoneAccountValidation(args) args.putBoolean("IsLogin", true)
} args.putString("PhoneNumber", viewModel.accountCreator.phoneNumber)
}) navigateToPhoneAccountValidation(args)
viewModel.leaveAssistantEvent.observe(viewLifecycleOwner, {
it.consume {
coreContext.contactsManager.updateLocalContacts()
if (coreContext.core.isEchoCancellerCalibrationRequired) {
navigateToEchoCancellerCalibration()
} else {
requireActivity().finish()
} }
} }
}) )
viewModel.invalidCredentialsEvent.observe(viewLifecycleOwner, { viewModel.leaveAssistantEvent.observe(
it.consume { viewLifecycleOwner,
val dialogViewModel = DialogViewModel(getString(R.string.assistant_error_invalid_credentials)) {
val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) it.consume {
coreContext.contactsManager.updateLocalContacts()
dialogViewModel.showCancelButton { if (coreContext.core.isEchoCancellerCalibrationRequired) {
viewModel.removeInvalidProxyConfig() navigateToEchoCancellerCalibration()
dialog.dismiss() } else {
requireActivity().finish()
}
} }
dialogViewModel.showDeleteButton({
viewModel.continueEvenIfInvalidCredentials()
dialog.dismiss()
}, getString(R.string.assistant_continue_even_if_credentials_invalid))
dialog.show()
} }
}) )
viewModel.onErrorEvent.observe(viewLifecycleOwner, { viewModel.invalidCredentialsEvent.observe(
it.consume { message -> viewLifecycleOwner,
(requireActivity() as AssistantActivity).showSnackBar(message) {
it.consume {
val dialogViewModel = DialogViewModel(getString(R.string.assistant_error_invalid_credentials))
val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel)
dialogViewModel.showCancelButton {
viewModel.removeInvalidProxyConfig()
dialog.dismiss()
}
dialogViewModel.showDeleteButton(
{
viewModel.continueEvenIfInvalidCredentials()
dialog.dismiss()
},
getString(R.string.assistant_continue_even_if_credentials_invalid)
)
dialog.show()
}
} }
}) )
viewModel.onErrorEvent.observe(
viewLifecycleOwner,
{
it.consume { message ->
(requireActivity() as AssistantActivity).showSnackBar(message)
}
}
)
checkPermission() checkPermission()
} }

View file

@ -43,11 +43,14 @@ class EchoCancellerCalibrationFragment : GenericFragment<AssistantEchoCancellerC
viewModel = ViewModelProvider(this).get(EchoCancellerCalibrationViewModel::class.java) viewModel = ViewModelProvider(this).get(EchoCancellerCalibrationViewModel::class.java)
binding.viewModel = viewModel binding.viewModel = viewModel
viewModel.echoCalibrationTerminated.observe(viewLifecycleOwner, { viewModel.echoCalibrationTerminated.observe(
it.consume { viewLifecycleOwner,
requireActivity().finish() {
it.consume {
requireActivity().finish()
}
} }
}) )
if (!PermissionHelper.required(requireContext()).hasRecordAudioPermission()) { if (!PermissionHelper.required(requireContext()).hasRecordAudioPermission()) {
Log.i("[Echo Canceller Calibration] Asking for RECORD_AUDIO permission") Log.i("[Echo Canceller Calibration] Asking for RECORD_AUDIO permission")

View file

@ -49,16 +49,22 @@ class EmailAccountCreationFragment : GenericFragment<AssistantEmailAccountCreati
viewModel = ViewModelProvider(this, EmailAccountCreationViewModelFactory(sharedViewModel.getAccountCreator())).get(EmailAccountCreationViewModel::class.java) viewModel = ViewModelProvider(this, EmailAccountCreationViewModelFactory(sharedViewModel.getAccountCreator())).get(EmailAccountCreationViewModel::class.java)
binding.viewModel = viewModel binding.viewModel = viewModel
viewModel.goToEmailValidationEvent.observe(viewLifecycleOwner, { viewModel.goToEmailValidationEvent.observe(
it.consume { viewLifecycleOwner,
navigateToEmailAccountValidation() {
it.consume {
navigateToEmailAccountValidation()
}
} }
}) )
viewModel.onErrorEvent.observe(viewLifecycleOwner, { viewModel.onErrorEvent.observe(
it.consume { message -> viewLifecycleOwner,
(requireActivity() as AssistantActivity).showSnackBar(message) {
it.consume { message ->
(requireActivity() as AssistantActivity).showSnackBar(message)
}
} }
}) )
} }
} }

View file

@ -48,22 +48,28 @@ class EmailAccountValidationFragment : GenericFragment<AssistantEmailAccountVali
viewModel = ViewModelProvider(this, EmailAccountValidationViewModelFactory(sharedViewModel.getAccountCreator())).get(EmailAccountValidationViewModel::class.java) viewModel = ViewModelProvider(this, EmailAccountValidationViewModelFactory(sharedViewModel.getAccountCreator())).get(EmailAccountValidationViewModel::class.java)
binding.viewModel = viewModel binding.viewModel = viewModel
viewModel.leaveAssistantEvent.observe(viewLifecycleOwner, { viewModel.leaveAssistantEvent.observe(
it.consume { viewLifecycleOwner,
coreContext.contactsManager.updateLocalContacts() {
it.consume {
coreContext.contactsManager.updateLocalContacts()
val args = Bundle() val args = Bundle()
args.putBoolean("AllowSkip", true) args.putBoolean("AllowSkip", true)
args.putString("Username", viewModel.accountCreator.username) args.putString("Username", viewModel.accountCreator.username)
args.putString("Password", viewModel.accountCreator.password) args.putString("Password", viewModel.accountCreator.password)
navigateToAccountLinking(args) navigateToAccountLinking(args)
}
} }
}) )
viewModel.onErrorEvent.observe(viewLifecycleOwner, { viewModel.onErrorEvent.observe(
it.consume { message -> viewLifecycleOwner,
(requireActivity() as AssistantActivity).showSnackBar(message) {
it.consume { message ->
(requireActivity() as AssistantActivity).showSnackBar(message)
}
} }
}) )
} }
} }

View file

@ -53,41 +53,53 @@ class GenericAccountLoginFragment : GenericFragment<AssistantGenericAccountLogin
viewModel = ViewModelProvider(this, GenericLoginViewModelFactory(sharedViewModel.getAccountCreator(true))).get(GenericLoginViewModel::class.java) viewModel = ViewModelProvider(this, GenericLoginViewModelFactory(sharedViewModel.getAccountCreator(true))).get(GenericLoginViewModel::class.java)
binding.viewModel = viewModel binding.viewModel = viewModel
viewModel.leaveAssistantEvent.observe(viewLifecycleOwner, { viewModel.leaveAssistantEvent.observe(
it.consume { viewLifecycleOwner,
coreContext.contactsManager.updateLocalContacts() {
it.consume {
coreContext.contactsManager.updateLocalContacts()
if (coreContext.core.isEchoCancellerCalibrationRequired) { if (coreContext.core.isEchoCancellerCalibrationRequired) {
navigateToEchoCancellerCalibration() navigateToEchoCancellerCalibration()
} else { } else {
requireActivity().finish() requireActivity().finish()
}
} }
} }
}) )
viewModel.invalidCredentialsEvent.observe(viewLifecycleOwner, { viewModel.invalidCredentialsEvent.observe(
it.consume { viewLifecycleOwner,
val dialogViewModel = DialogViewModel(getString(R.string.assistant_error_invalid_credentials)) {
val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) it.consume {
val dialogViewModel = DialogViewModel(getString(R.string.assistant_error_invalid_credentials))
val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel)
dialogViewModel.showCancelButton { dialogViewModel.showCancelButton {
viewModel.removeInvalidProxyConfig() viewModel.removeInvalidProxyConfig()
dialog.dismiss() dialog.dismiss()
}
dialogViewModel.showDeleteButton(
{
viewModel.continueEvenIfInvalidCredentials()
dialog.dismiss()
},
getString(R.string.assistant_continue_even_if_credentials_invalid)
)
dialog.show()
} }
dialogViewModel.showDeleteButton({
viewModel.continueEvenIfInvalidCredentials()
dialog.dismiss()
}, getString(R.string.assistant_continue_even_if_credentials_invalid))
dialog.show()
} }
}) )
viewModel.onErrorEvent.observe(viewLifecycleOwner, { viewModel.onErrorEvent.observe(
it.consume { message -> viewLifecycleOwner,
(requireActivity() as AssistantActivity).showSnackBar(message) {
it.consume { message ->
(requireActivity() as AssistantActivity).showSnackBar(message)
}
} }
}) )
} }
} }

View file

@ -56,20 +56,26 @@ class PhoneAccountCreationFragment : AbstractPhoneFragment<AssistantPhoneAccount
CountryPickerFragment(viewModel).show(childFragmentManager, "CountryPicker") CountryPickerFragment(viewModel).show(childFragmentManager, "CountryPicker")
} }
viewModel.goToSmsValidationEvent.observe(viewLifecycleOwner, { viewModel.goToSmsValidationEvent.observe(
it.consume { viewLifecycleOwner,
val args = Bundle() {
args.putBoolean("IsCreation", true) it.consume {
args.putString("PhoneNumber", viewModel.accountCreator.phoneNumber) val args = Bundle()
navigateToPhoneAccountValidation(args) args.putBoolean("IsCreation", true)
args.putString("PhoneNumber", viewModel.accountCreator.phoneNumber)
navigateToPhoneAccountValidation(args)
}
} }
}) )
viewModel.onErrorEvent.observe(viewLifecycleOwner, { viewModel.onErrorEvent.observe(
it.consume { message -> viewLifecycleOwner,
(requireActivity() as AssistantActivity).showSnackBar(message) {
it.consume { message ->
(requireActivity() as AssistantActivity).showSnackBar(message)
}
} }
}) )
checkPermission() checkPermission()
} }

View file

@ -71,30 +71,39 @@ class PhoneAccountLinkingFragment : AbstractPhoneFragment<AssistantPhoneAccountL
CountryPickerFragment(viewModel).show(childFragmentManager, "CountryPicker") CountryPickerFragment(viewModel).show(childFragmentManager, "CountryPicker")
} }
viewModel.goToSmsValidationEvent.observe(viewLifecycleOwner, { viewModel.goToSmsValidationEvent.observe(
it.consume { viewLifecycleOwner,
val args = Bundle() {
args.putBoolean("IsLinking", true) it.consume {
args.putString("PhoneNumber", viewModel.accountCreator.phoneNumber) val args = Bundle()
navigateToPhoneAccountValidation(args) args.putBoolean("IsLinking", true)
} args.putString("PhoneNumber", viewModel.accountCreator.phoneNumber)
}) navigateToPhoneAccountValidation(args)
viewModel.leaveAssistantEvent.observe(viewLifecycleOwner, {
it.consume {
if (LinphoneApplication.coreContext.core.isEchoCancellerCalibrationRequired) {
navigateToEchoCancellerCalibration()
} else {
requireActivity().finish()
} }
} }
}) )
viewModel.onErrorEvent.observe(viewLifecycleOwner, { viewModel.leaveAssistantEvent.observe(
it.consume { message -> viewLifecycleOwner,
(requireActivity() as AssistantActivity).showSnackBar(message) {
it.consume {
if (LinphoneApplication.coreContext.core.isEchoCancellerCalibrationRequired) {
navigateToEchoCancellerCalibration()
} else {
requireActivity().finish()
}
}
} }
}) )
viewModel.onErrorEvent.observe(
viewLifecycleOwner,
{
it.consume { message ->
(requireActivity() as AssistantActivity).showSnackBar(message)
}
}
)
checkPermission() checkPermission()
} }

View file

@ -58,32 +58,38 @@ class PhoneAccountValidationFragment : GenericFragment<AssistantPhoneAccountVali
viewModel.isCreation.value = arguments?.getBoolean("IsCreation", false) viewModel.isCreation.value = arguments?.getBoolean("IsCreation", false)
viewModel.isLinking.value = arguments?.getBoolean("IsLinking", false) viewModel.isLinking.value = arguments?.getBoolean("IsLinking", false)
viewModel.leaveAssistantEvent.observe(viewLifecycleOwner, { viewModel.leaveAssistantEvent.observe(
it.consume { viewLifecycleOwner,
when { {
viewModel.isLogin.value == true || viewModel.isCreation.value == true -> { it.consume {
coreContext.contactsManager.updateLocalContacts() when {
viewModel.isLogin.value == true || viewModel.isCreation.value == true -> {
coreContext.contactsManager.updateLocalContacts()
if (coreContext.core.isEchoCancellerCalibrationRequired) { if (coreContext.core.isEchoCancellerCalibrationRequired) {
navigateToEchoCancellerCalibration() navigateToEchoCancellerCalibration()
} else { } else {
requireActivity().finish() requireActivity().finish()
}
}
viewModel.isLinking.value == true -> {
val args = Bundle()
args.putString("Identity", "sip:${viewModel.accountCreator.username}@${viewModel.accountCreator.domain}")
navigateToAccountSettings(args)
} }
}
viewModel.isLinking.value == true -> {
val args = Bundle()
args.putString("Identity", "sip:${viewModel.accountCreator.username}@${viewModel.accountCreator.domain}")
navigateToAccountSettings(args)
} }
} }
} }
}) )
viewModel.onErrorEvent.observe(viewLifecycleOwner, { viewModel.onErrorEvent.observe(
it.consume { message -> viewLifecycleOwner,
(requireActivity() as AssistantActivity).showSnackBar(message) {
it.consume { message ->
(requireActivity() as AssistantActivity).showSnackBar(message)
}
} }
}) )
val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
clipboard.addPrimaryClipChangedListener { clipboard.addPrimaryClipChangedListener {

View file

@ -51,12 +51,15 @@ class QrCodeFragment : GenericFragment<AssistantQrCodeFragmentBinding>() {
viewModel = ViewModelProvider(this).get(QrCodeViewModel::class.java) viewModel = ViewModelProvider(this).get(QrCodeViewModel::class.java)
binding.viewModel = viewModel binding.viewModel = viewModel
viewModel.qrCodeFoundEvent.observe(viewLifecycleOwner, { viewModel.qrCodeFoundEvent.observe(
it.consume { url -> viewLifecycleOwner,
sharedViewModel.remoteProvisioningUrl.value = url {
findNavController().navigateUp() it.consume { url ->
sharedViewModel.remoteProvisioningUrl.value = url
findNavController().navigateUp()
}
} }
}) )
viewModel.setBackCamera() viewModel.setBackCamera()
if (!PermissionHelper.required(requireContext()).hasCameraPermission()) { if (!PermissionHelper.required(requireContext()).hasCameraPermission()) {

View file

@ -54,20 +54,23 @@ class RemoteProvisioningFragment : GenericFragment<AssistantRemoteProvisioningFr
navigateToQrCode() navigateToQrCode()
} }
viewModel.fetchSuccessfulEvent.observe(viewLifecycleOwner, { viewModel.fetchSuccessfulEvent.observe(
it.consume { success -> viewLifecycleOwner,
if (success) { {
if (coreContext.core.isEchoCancellerCalibrationRequired) { it.consume { success ->
navigateToEchoCancellerCalibration() if (success) {
if (coreContext.core.isEchoCancellerCalibrationRequired) {
navigateToEchoCancellerCalibration()
} else {
requireActivity().finish()
}
} else { } else {
requireActivity().finish() val activity = requireActivity() as AssistantActivity
activity.showSnackBar(R.string.assistant_remote_provisioning_failure)
} }
} else {
val activity = requireActivity() as AssistantActivity
activity.showSnackBar(R.string.assistant_remote_provisioning_failure)
} }
} }
}) )
viewModel.urlToFetch.value = sharedViewModel.remoteProvisioningUrl.value ?: coreContext.core.provisioningUri viewModel.urlToFetch.value = sharedViewModel.remoteProvisioningUrl.value ?: coreContext.core.provisioningUri
} }

View file

@ -72,9 +72,12 @@ class WelcomeFragment : GenericFragment<AssistantWelcomeFragmentBinding>() {
navigateToRemoteProvisioning() navigateToRemoteProvisioning()
} }
viewModel.termsAndPrivacyAccepted.observe(viewLifecycleOwner, { viewModel.termsAndPrivacyAccepted.observe(
if (it) corePreferences.readAndAgreeTermsAndPrivacy = true viewLifecycleOwner,
}) {
if (it) corePreferences.readAndAgreeTermsAndPrivacy = true
}
)
setUpTermsAndPrivacyLinks() setUpTermsAndPrivacyLinks()
} }

View file

@ -30,7 +30,8 @@ import org.linphone.core.DialPlan
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.PhoneNumberUtils import org.linphone.utils.PhoneNumberUtils
abstract class AbstractPhoneViewModel(val accountCreator: AccountCreator) : ViewModel(), abstract class AbstractPhoneViewModel(val accountCreator: AccountCreator) :
ViewModel(),
CountryPickerFragment.CountryPickedListener { CountryPickerFragment.CountryPickedListener {
val prefix = MutableLiveData<String>() val prefix = MutableLiveData<String>()

View file

@ -84,12 +84,12 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
private var proxyConfigToCheck: ProxyConfig? = null private var proxyConfigToCheck: ProxyConfig? = null
private val coreListener = object : CoreListenerStub() { private val coreListener = object : CoreListenerStub() {
override fun onRegistrationStateChanged( override fun onRegistrationStateChanged(
core: Core, core: Core,
cfg: ProxyConfig, cfg: ProxyConfig,
state: RegistrationState, state: RegistrationState,
message: String message: String
) { ) {
if (cfg == proxyConfigToCheck) { if (cfg == proxyConfigToCheck) {
Log.i("[Assistant] [Account Login] Registration state is $state: $message") Log.i("[Assistant] [Account Login] Registration state is $state: $message")
if (state == RegistrationState.Ok) { if (state == RegistrationState.Ok) {

View file

@ -158,13 +158,13 @@ class EmailAccountCreationViewModel(val accountCreator: AccountCreator) : ViewMo
private fun isCreateButtonEnabled(): Boolean { private fun isCreateButtonEnabled(): Boolean {
return username.value.orEmpty().isNotEmpty() && return username.value.orEmpty().isNotEmpty() &&
email.value.orEmpty().isNotEmpty() && email.value.orEmpty().isNotEmpty() &&
password.value.orEmpty().isNotEmpty() && password.value.orEmpty().isNotEmpty() &&
passwordConfirmation.value.orEmpty().isNotEmpty() && passwordConfirmation.value.orEmpty().isNotEmpty() &&
password.value == passwordConfirmation.value && password.value == passwordConfirmation.value &&
usernameError.value.orEmpty().isEmpty() && usernameError.value.orEmpty().isEmpty() &&
emailError.value.orEmpty().isEmpty() && emailError.value.orEmpty().isEmpty() &&
passwordError.value.orEmpty().isEmpty() && passwordError.value.orEmpty().isEmpty() &&
passwordConfirmationError.value.orEmpty().isEmpty() passwordConfirmationError.value.orEmpty().isEmpty()
} }
} }

View file

@ -160,9 +160,11 @@ class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPh
private fun isCreateButtonEnabled(): Boolean { private fun isCreateButtonEnabled(): Boolean {
val usernameRegexp = corePreferences.config.getString("assistant", "username_regex", "^[a-z0-9+_.\\-]*\$") val usernameRegexp = corePreferences.config.getString("assistant", "username_regex", "^[a-z0-9+_.\\-]*\$")
return isPhoneNumberOk() && usernameRegexp != null && return isPhoneNumberOk() && usernameRegexp != null &&
(useUsername.value == false || (
useUsername.value == false ||
username.value.orEmpty().matches(Regex(usernameRegexp)) && username.value.orEmpty().matches(Regex(usernameRegexp)) &&
username.value.orEmpty().isNotEmpty() && username.value.orEmpty().isNotEmpty() &&
usernameError.value.orEmpty().isEmpty()) usernameError.value.orEmpty().isEmpty()
)
} }
} }

View file

@ -62,21 +62,27 @@ class CallActivity : ProximitySensorActivity() {
sharedViewModel = ViewModelProvider(this).get(SharedCallViewModel::class.java) sharedViewModel = ViewModelProvider(this).get(SharedCallViewModel::class.java)
sharedViewModel.toggleDrawerEvent.observe(this, { sharedViewModel.toggleDrawerEvent.observe(
it.consume { this,
if (binding.statsMenu.isDrawerOpen(Gravity.LEFT)) { {
binding.statsMenu.closeDrawer(binding.sideMenuContent, true) it.consume {
} else { if (binding.statsMenu.isDrawerOpen(Gravity.LEFT)) {
binding.statsMenu.openDrawer(binding.sideMenuContent, true) binding.statsMenu.closeDrawer(binding.sideMenuContent, true)
} else {
binding.statsMenu.openDrawer(binding.sideMenuContent, true)
}
} }
} }
}) )
sharedViewModel.resetHiddenInterfaceTimerInVideoCallEvent.observe(this, { sharedViewModel.resetHiddenInterfaceTimerInVideoCallEvent.observe(
it.consume { this,
viewModel.showMomentarily() {
it.consume {
viewModel.showMomentarily()
}
} }
}) )
coreContext.core.nativeVideoWindowId = binding.remoteVideoSurface coreContext.core.nativeVideoWindowId = binding.remoteVideoSurface
coreContext.core.nativePreviewWindowId = binding.localPreviewVideoSurface coreContext.core.nativePreviewWindowId = binding.localPreviewVideoSurface
@ -104,9 +110,12 @@ class CallActivity : ProximitySensorActivity() {
videoZoomHelper = VideoZoomHelper(this, binding.remoteVideoSurface) videoZoomHelper = VideoZoomHelper(this, binding.remoteVideoSurface)
viewModel.proximitySensorEnabled.observe(this, { viewModel.proximitySensorEnabled.observe(
enableProximitySensor(it) this,
}) {
enableProximitySensor(it)
}
)
} }
override fun onResume() { override fun onResume() {
@ -191,10 +200,10 @@ class CallActivity : ProximitySensorActivity() {
private fun hideSystemUI() { private fun hideSystemUI() {
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_IMMERSIVE or View.SYSTEM_UI_FLAG_IMMERSIVE or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
} }
} }

View file

@ -78,19 +78,25 @@ class IncomingCallActivity : GenericActivity() {
)[IncomingCallViewModel::class.java] )[IncomingCallViewModel::class.java]
binding.viewModel = viewModel binding.viewModel = viewModel
viewModel.callEndedEvent.observe(this, { viewModel.callEndedEvent.observe(
it.consume { this,
Log.i("[Incoming Call Activity] Call ended, finish activity") {
finish() it.consume {
Log.i("[Incoming Call Activity] Call ended, finish activity")
finish()
}
} }
}) )
viewModel.earlyMediaVideoEnabled.observe(this, { viewModel.earlyMediaVideoEnabled.observe(
if (it) { this,
Log.i("[Incoming Call Activity] Early media video being received, set native window id") {
coreContext.core.nativeVideoWindowId = binding.remoteVideoSurface if (it) {
Log.i("[Incoming Call Activity] Early media video being received, set native window id")
coreContext.core.nativeVideoWindowId = binding.remoteVideoSurface
}
} }
}) )
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
val keyguardLocked = keyguardManager.isKeyguardLocked val keyguardLocked = keyguardManager.isKeyguardLocked
@ -169,7 +175,8 @@ class IncomingCallActivity : GenericActivity() {
private fun findIncomingCall(): Call? { private fun findIncomingCall(): Call? {
for (call in coreContext.core.calls) { for (call in coreContext.core.calls) {
if (call.state == Call.State.IncomingReceived || if (call.state == Call.State.IncomingReceived ||
call.state == Call.State.IncomingEarlyMedia) { call.state == Call.State.IncomingEarlyMedia
) {
return call return call
} }
} }

View file

@ -79,41 +79,56 @@ class OutgoingCallActivity : ProximitySensorActivity() {
controlsViewModel = ViewModelProvider(this).get(ControlsViewModel::class.java) controlsViewModel = ViewModelProvider(this).get(ControlsViewModel::class.java)
binding.controlsViewModel = controlsViewModel binding.controlsViewModel = controlsViewModel
viewModel.callEndedEvent.observe(this, { viewModel.callEndedEvent.observe(
it.consume { this,
Log.i("[Outgoing Call Activity] Call ended, finish activity") {
finish() it.consume {
Log.i("[Outgoing Call Activity] Call ended, finish activity")
finish()
}
} }
}) )
viewModel.callConnectedEvent.observe(this, { viewModel.callConnectedEvent.observe(
it.consume { this,
Log.i("[Outgoing Call Activity] Call connected, finish activity") {
finish() it.consume {
Log.i("[Outgoing Call Activity] Call connected, finish activity")
finish()
}
} }
}) )
controlsViewModel.isSpeakerSelected.observe(this, { controlsViewModel.isSpeakerSelected.observe(
enableProximitySensor(!it) this,
}) {
enableProximitySensor(!it)
controlsViewModel.askPermissionEvent.observe(this, {
it.consume { permission ->
requestPermissions(arrayOf(permission), 0)
} }
}) )
controlsViewModel.toggleNumpadEvent.observe(this, { controlsViewModel.askPermissionEvent.observe(
it.consume { open -> this,
if (this::numpadAnimator.isInitialized) { {
if (open) { it.consume { permission ->
numpadAnimator.start() requestPermissions(arrayOf(permission), 0)
} else { }
numpadAnimator.reverse() }
)
controlsViewModel.toggleNumpadEvent.observe(
this,
{
it.consume { open ->
if (this::numpadAnimator.isInitialized) {
if (open) {
numpadAnimator.start()
} else {
numpadAnimator.reverse()
}
} }
} }
} }
}) )
if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) { if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) {
checkPermissions() checkPermissions()
@ -192,7 +207,8 @@ class OutgoingCallActivity : ProximitySensorActivity() {
for (call in coreContext.core.calls) { for (call in coreContext.core.calls) {
if (call.state == Call.State.OutgoingInit || if (call.state == Call.State.OutgoingInit ||
call.state == Call.State.OutgoingProgress || call.state == Call.State.OutgoingProgress ||
call.state == Call.State.OutgoingRinging) { call.state == Call.State.OutgoingRinging
) {
return call return call
} }
} }

View file

@ -39,26 +39,29 @@ class VideoZoomHelper(context: Context, private var videoDisplayView: View) : Ge
init { init {
val gestureDetector = GestureDetector(context, this) val gestureDetector = GestureDetector(context, this)
scaleDetector = ScaleGestureDetector(context, object : scaleDetector = ScaleGestureDetector(
ScaleGestureDetector.SimpleOnScaleGestureListener() { context,
override fun onScale(detector: ScaleGestureDetector): Boolean { object :
zoomFactor *= detector.scaleFactor ScaleGestureDetector.SimpleOnScaleGestureListener() {
// Don't let the object get too small or too large. override fun onScale(detector: ScaleGestureDetector): Boolean {
// Zoom to make the video fill the screen vertically zoomFactor *= detector.scaleFactor
val portraitZoomFactor = videoDisplayView.height.toFloat() / (3 * videoDisplayView.width / 4) // Don't let the object get too small or too large.
// Zoom to make the video fill the screen horizontally // Zoom to make the video fill the screen vertically
val landscapeZoomFactor = videoDisplayView.width.toFloat() / (3 * videoDisplayView.height / 4) val portraitZoomFactor = videoDisplayView.height.toFloat() / (3 * videoDisplayView.width / 4)
zoomFactor = max(0.1f, min(zoomFactor, max(portraitZoomFactor, landscapeZoomFactor))) // Zoom to make the video fill the screen horizontally
val landscapeZoomFactor = videoDisplayView.width.toFloat() / (3 * videoDisplayView.height / 4)
zoomFactor = max(0.1f, min(zoomFactor, max(portraitZoomFactor, landscapeZoomFactor)))
val currentCall: Call? = coreContext.core.currentCall val currentCall: Call? = coreContext.core.currentCall
if (currentCall != null) { if (currentCall != null) {
currentCall.zoom(zoomFactor, zoomCenterX, zoomCenterY) currentCall.zoom(zoomFactor, zoomCenterX, zoomCenterY)
return true return true
}
return false
} }
return false
} }
}) )
videoDisplayView.setOnTouchListener { _, event -> videoDisplayView.setOnTouchListener { _, event ->
val currentZoomFactor = zoomFactor val currentZoomFactor = zoomFactor

View file

@ -80,101 +80,131 @@ class ControlsFragment : GenericFragment<CallControlsFragmentBinding>() {
conferenceViewModel = ViewModelProvider(this).get(ConferenceViewModel::class.java) conferenceViewModel = ViewModelProvider(this).get(ConferenceViewModel::class.java)
binding.conferenceViewModel = conferenceViewModel binding.conferenceViewModel = conferenceViewModel
callsViewModel.currentCallViewModel.observe(viewLifecycleOwner, { callsViewModel.currentCallViewModel.observe(
if (it != null) { viewLifecycleOwner,
binding.activeCallTimer.base = {
SystemClock.elapsedRealtime() - (1000 * it.call.duration) // Linphone timestamps are in seconds if (it != null) {
binding.activeCallTimer.start() binding.activeCallTimer.base =
} SystemClock.elapsedRealtime() - (1000 * it.call.duration) // Linphone timestamps are in seconds
}) binding.activeCallTimer.start()
callsViewModel.noMoreCallEvent.observe(viewLifecycleOwner, {
it.consume {
requireActivity().finish()
}
})
callsViewModel.askWriteExternalStoragePermissionEvent.observe(viewLifecycleOwner, {
it.consume {
if (!PermissionHelper.get().hasWriteExternalStorage()) {
Log.i("[Controls Fragment] Asking for WRITE_EXTERNAL_STORAGE permission")
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
} }
} }
}) )
callsViewModel.callUpdateEvent.observe(viewLifecycleOwner, { callsViewModel.noMoreCallEvent.observe(
it.consume { call -> viewLifecycleOwner,
if (call.state == Call.State.StreamsRunning) { {
dialog?.dismiss() it.consume {
} else if (call.state == Call.State.UpdatedByRemote) { requireActivity().finish()
if (coreContext.core.videoCaptureEnabled() || coreContext.core.videoDisplayEnabled()) { }
if (call.currentParams.videoEnabled() != call.remoteParams?.videoEnabled()) { }
showCallVideoUpdateDialog(call) )
callsViewModel.askWriteExternalStoragePermissionEvent.observe(
viewLifecycleOwner,
{
it.consume {
if (!PermissionHelper.get().hasWriteExternalStorage()) {
Log.i("[Controls Fragment] Asking for WRITE_EXTERNAL_STORAGE permission")
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
}
}
}
)
callsViewModel.callUpdateEvent.observe(
viewLifecycleOwner,
{
it.consume { call ->
if (call.state == Call.State.StreamsRunning) {
dialog?.dismiss()
} else if (call.state == Call.State.UpdatedByRemote) {
if (coreContext.core.videoCaptureEnabled() || coreContext.core.videoDisplayEnabled()) {
if (call.currentParams.videoEnabled() != call.remoteParams?.videoEnabled()) {
showCallVideoUpdateDialog(call)
}
} else {
Log.w("[Controls Fragment] Video display & capture are disabled, don't show video dialog")
} }
} else {
Log.w("[Controls Fragment] Video display & capture are disabled, don't show video dialog")
} }
} }
} }
}) )
controlsViewModel.chatClickedEvent.observe(viewLifecycleOwner, { controlsViewModel.chatClickedEvent.observe(
it.consume { viewLifecycleOwner,
val intent = Intent() {
intent.setClass(requireContext(), MainActivity::class.java) it.consume {
intent.putExtra("Chat", true) val intent = Intent()
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.setClass(requireContext(), MainActivity::class.java)
startActivity(intent) intent.putExtra("Chat", true)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
} }
}) )
controlsViewModel.addCallClickedEvent.observe(viewLifecycleOwner, { controlsViewModel.addCallClickedEvent.observe(
it.consume { viewLifecycleOwner,
val intent = Intent() {
intent.setClass(requireContext(), MainActivity::class.java) it.consume {
intent.putExtra("Dialer", true) val intent = Intent()
intent.putExtra("Transfer", false) intent.setClass(requireContext(), MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.putExtra("Dialer", true)
startActivity(intent) intent.putExtra("Transfer", false)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
} }
}) )
controlsViewModel.transferCallClickedEvent.observe(viewLifecycleOwner, { controlsViewModel.transferCallClickedEvent.observe(
it.consume { viewLifecycleOwner,
val intent = Intent() {
intent.setClass(requireContext(), MainActivity::class.java) it.consume {
intent.putExtra("Dialer", true) val intent = Intent()
intent.putExtra("Transfer", true) intent.setClass(requireContext(), MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.putExtra("Dialer", true)
startActivity(intent) intent.putExtra("Transfer", true)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
} }
}) )
controlsViewModel.askPermissionEvent.observe(viewLifecycleOwner, { controlsViewModel.askPermissionEvent.observe(
it.consume { permission -> viewLifecycleOwner,
Log.i("[Controls Fragment] Asking for $permission permission") {
requestPermissions(arrayOf(permission), 0) it.consume { permission ->
Log.i("[Controls Fragment] Asking for $permission permission")
requestPermissions(arrayOf(permission), 0)
}
} }
}) )
controlsViewModel.toggleNumpadEvent.observe(viewLifecycleOwner, { controlsViewModel.toggleNumpadEvent.observe(
it.consume { open -> viewLifecycleOwner,
if (this::numpadAnimator.isInitialized) { {
if (open) { it.consume { open ->
numpadAnimator.start() if (this::numpadAnimator.isInitialized) {
} else { if (open) {
numpadAnimator.reverse() numpadAnimator.start()
} else {
numpadAnimator.reverse()
}
} }
} }
} }
}) )
controlsViewModel.somethingClickedEvent.observe(viewLifecycleOwner, { controlsViewModel.somethingClickedEvent.observe(
it.consume { viewLifecycleOwner,
sharedViewModel.resetHiddenInterfaceTimerInVideoCallEvent.value = Event(true) {
it.consume {
sharedViewModel.resetHiddenInterfaceTimerInVideoCallEvent.value = Event(true)
}
} }
}) )
if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) { if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) {
checkPermissions() checkPermissions()
@ -245,15 +275,21 @@ class ControlsFragment : GenericFragment<CallControlsFragmentBinding>() {
val viewModel = DialogViewModel(AppUtils.getString(R.string.call_video_update_requested_dialog)) val viewModel = DialogViewModel(AppUtils.getString(R.string.call_video_update_requested_dialog))
dialog = DialogUtils.getDialog(requireContext(), viewModel) dialog = DialogUtils.getDialog(requireContext(), viewModel)
viewModel.showCancelButton({ viewModel.showCancelButton(
callsViewModel.answerCallVideoUpdateRequest(call, false) {
dialog?.dismiss() callsViewModel.answerCallVideoUpdateRequest(call, false)
}, getString(R.string.dialog_decline)) dialog?.dismiss()
},
getString(R.string.dialog_decline)
)
viewModel.showOkButton({ viewModel.showOkButton(
callsViewModel.answerCallVideoUpdateRequest(call, true) {
dialog?.dismiss() callsViewModel.answerCallVideoUpdateRequest(call, true)
}, getString(R.string.dialog_accept)) dialog?.dismiss()
},
getString(R.string.dialog_accept)
)
dialog?.show() dialog?.show()
} }

View file

@ -62,13 +62,16 @@ class StatusFragment : GenericFragment<CallStatusFragmentBinding>() {
viewModel.refreshRegister() viewModel.refreshRegister()
} }
viewModel.showZrtpDialogEvent.observe(viewLifecycleOwner, { viewModel.showZrtpDialogEvent.observe(
it.consume { call -> viewLifecycleOwner,
if (call.state == Call.State.Connected || call.state == Call.State.StreamsRunning) { {
showZrtpDialog(call) it.consume { call ->
if (call.state == Call.State.Connected || call.state == Call.State.StreamsRunning) {
showZrtpDialog(call)
}
} }
} }
}) )
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -117,19 +120,25 @@ class StatusFragment : GenericFragment<CallStatusFragmentBinding>() {
val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel) val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel)
viewModel.showDeleteButton({ viewModel.showDeleteButton(
call.authenticationTokenVerified = false {
this@StatusFragment.viewModel.updateEncryptionInfo(call) call.authenticationTokenVerified = false
dialog.dismiss() this@StatusFragment.viewModel.updateEncryptionInfo(call)
zrtpDialog = null dialog.dismiss()
}, getString(R.string.zrtp_dialog_deny_button_label)) zrtpDialog = null
},
getString(R.string.zrtp_dialog_deny_button_label)
)
viewModel.showOkButton({ viewModel.showOkButton(
call.authenticationTokenVerified = true {
this@StatusFragment.viewModel.updateEncryptionInfo(call) call.authenticationTokenVerified = true
dialog.dismiss() this@StatusFragment.viewModel.updateEncryptionInfo(call)
zrtpDialog = null dialog.dismiss()
}, getString(R.string.zrtp_dialog_ok_button_label)) zrtpDialog = null
},
getString(R.string.zrtp_dialog_ok_button_label)
)
zrtpDialog = dialog zrtpDialog = dialog
dialog.show() dialog.show()

View file

@ -148,15 +148,18 @@ open class CallViewModel(val call: Call) : GenericContactViewModel(call.remoteAd
timer?.cancel() timer?.cancel()
timer = Timer("Call update timeout") timer = Timer("Call update timeout")
timer?.schedule(object : TimerTask() { timer?.schedule(
override fun run() { object : TimerTask() {
// Decline call update override fun run() {
viewModelScope.launch { // Decline call update
withContext(Dispatchers.Main) { viewModelScope.launch {
coreContext.answerCallVideoUpdateRequest(call, false) withContext(Dispatchers.Main) {
coreContext.answerCallVideoUpdateRequest(call, false)
}
} }
} }
} },
}, 30000) 30000
)
} }
} }

View file

@ -123,15 +123,18 @@ class ControlsFadingViewModel : ViewModel() {
timer?.cancel() timer?.cancel()
timer = Timer("Hide UI controls scheduler") timer = Timer("Hide UI controls scheduler")
timer?.schedule(object : TimerTask() { timer?.schedule(
override fun run() { object : TimerTask() {
viewModelScope.launch { override fun run() {
withContext(Dispatchers.Main) { viewModelScope.launch {
val videoEnabled = coreContext.isVideoCallOrConferenceActive() withContext(Dispatchers.Main) {
areControlsHidden.postValue(videoEnabled) val videoEnabled = coreContext.isVideoCallOrConferenceActive()
areControlsHidden.postValue(videoEnabled)
}
} }
} }
} },
}, 3000) 3000
)
} }
} }

View file

@ -445,8 +445,10 @@ class ControlsViewModel : ViewModel() {
val core = coreContext.core val core = coreContext.core
val currentCall = core.currentCall val currentCall = core.currentCall
isVideoAvailable.value = (core.videoCaptureEnabled() || core.videoPreviewEnabled()) && isVideoAvailable.value = (core.videoCaptureEnabled() || core.videoPreviewEnabled()) &&
((currentCall != null && !currentCall.mediaInProgress()) || (
core.conference?.isIn == true) (currentCall != null && !currentCall.mediaInProgress()) ||
core.conference?.isIn == true
)
} }
private fun updateVideoEnabled() { private fun updateVideoEnabled() {

View file

@ -62,8 +62,8 @@ class IncomingCallViewModel(call: Call) : CallViewModel(call) {
screenLocked.value = false screenLocked.value = false
inviteWithVideo.value = call.remoteParams?.videoEnabled() == true && coreContext.core.videoActivationPolicy.automaticallyAccept inviteWithVideo.value = call.remoteParams?.videoEnabled() == true && coreContext.core.videoActivationPolicy.automaticallyAccept
earlyMediaVideoEnabled.value = corePreferences.acceptEarlyMedia && earlyMediaVideoEnabled.value = corePreferences.acceptEarlyMedia &&
call.state == Call.State.IncomingEarlyMedia && call.state == Call.State.IncomingEarlyMedia &&
call.currentParams.videoEnabled() call.currentParams.videoEnabled()
} }
override fun onCleared() { override fun onCleared() {

View file

@ -73,7 +73,8 @@ class ChatBubbleActivity : GenericActivity() {
val localAddress = Factory.instance().createAddress(localSipUri) val localAddress = Factory.instance().createAddress(localSipUri)
val remoteSipAddress = Factory.instance().createAddress(remoteSipUri) val remoteSipAddress = Factory.instance().createAddress(remoteSipUri)
chatRoom = coreContext.core.searchChatRoom( chatRoom = coreContext.core.searchChatRoom(
null, localAddress, remoteSipAddress, arrayOfNulls( null, localAddress, remoteSipAddress,
arrayOfNulls(
0 0
) )
) )
@ -114,27 +115,36 @@ class ChatBubbleActivity : GenericActivity() {
// Disable context menu on each message // Disable context menu on each message
adapter.disableContextMenu() adapter.disableContextMenu()
adapter.openContentEvent.observe(this, { adapter.openContentEvent.observe(
it.consume { content -> this,
if (content.isFileEncrypted) { {
Toast.makeText(this, R.string.chat_bubble_cant_open_enrypted_file, Toast.LENGTH_LONG).show() it.consume { content ->
} else { if (content.isFileEncrypted) {
FileUtils.openFileInThirdPartyApp(this, content.filePath.orEmpty(), true) Toast.makeText(this, R.string.chat_bubble_cant_open_enrypted_file, Toast.LENGTH_LONG).show()
} else {
FileUtils.openFileInThirdPartyApp(this, content.filePath.orEmpty(), true)
}
} }
} }
}) )
val layoutManager = LinearLayoutManager(this) val layoutManager = LinearLayoutManager(this)
layoutManager.stackFromEnd = true layoutManager.stackFromEnd = true
binding.chatMessagesList.layoutManager = layoutManager binding.chatMessagesList.layoutManager = layoutManager
listViewModel.events.observe(this, { events -> listViewModel.events.observe(
adapter.submitList(events) this,
}) { events ->
adapter.submitList(events)
}
)
chatSendingViewModel.textToSend.observe(this, { chatSendingViewModel.textToSend.observe(
chatSendingViewModel.onTextToSendChanged(it) this,
}) {
chatSendingViewModel.onTextToSendChanged(it)
}
)
binding.setOpenAppClickListener { binding.setOpenAppClickListener {
val intent = Intent(this, MainActivity::class.java) val intent = Intent(this, MainActivity::class.java)

View file

@ -121,21 +121,27 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
callOverlayViewModel = ViewModelProvider(this).get(CallOverlayViewModel::class.java) callOverlayViewModel = ViewModelProvider(this).get(CallOverlayViewModel::class.java)
binding.callOverlayViewModel = callOverlayViewModel binding.callOverlayViewModel = callOverlayViewModel
sharedViewModel.toggleDrawerEvent.observe(this, { sharedViewModel.toggleDrawerEvent.observe(
it.consume { this,
if (binding.sideMenu.isDrawerOpen(Gravity.LEFT)) { {
binding.sideMenu.closeDrawer(binding.sideMenuContent, true) it.consume {
} else { if (binding.sideMenu.isDrawerOpen(Gravity.LEFT)) {
binding.sideMenu.openDrawer(binding.sideMenuContent, true) binding.sideMenu.closeDrawer(binding.sideMenuContent, true)
} else {
binding.sideMenu.openDrawer(binding.sideMenuContent, true)
}
} }
} }
}) )
coreContext.callErrorMessageResourceId.observe(this, { coreContext.callErrorMessageResourceId.observe(
it.consume { message -> this,
showSnackBar(message) {
it.consume { message ->
showSnackBar(message)
}
} }
}) )
if (coreContext.core.accountList.isEmpty()) { if (coreContext.core.accountList.isEmpty()) {
if (corePreferences.firstStart) { if (corePreferences.firstStart) {
@ -486,7 +492,8 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
} }
MotionEvent.ACTION_UP -> { MotionEvent.ACTION_UP -> {
if (abs(initPosX - view.x) < CorePreferences.OVERLAY_CLICK_SENSITIVITY && if (abs(initPosX - view.x) < CorePreferences.OVERLAY_CLICK_SENSITIVITY &&
abs(initPosY - view.y) < CorePreferences.OVERLAY_CLICK_SENSITIVITY) { abs(initPosY - view.y) < CorePreferences.OVERLAY_CLICK_SENSITIVITY
) {
view.performClick() view.performClick()
} }
} }

View file

@ -153,9 +153,12 @@ class ChatMessagesListAdapter(
// This is for item selection through ListTopBarFragment // This is for item selection through ListTopBarFragment
selectionListViewModel = selectionViewModel selectionListViewModel = selectionViewModel
selectionViewModel.isEditionEnabled.observe(viewLifecycleOwner, { selectionViewModel.isEditionEnabled.observe(
position = adapterPosition viewLifecycleOwner,
}) {
position = adapterPosition
}
)
setClickListener { setClickListener {
if (selectionViewModel.isEditionEnabled.value == true) { if (selectionViewModel.isEditionEnabled.value == true) {
@ -213,7 +216,8 @@ class ChatMessagesListAdapter(
val itemSize = AppUtils.getDimension(R.dimen.chat_message_popup_item_height).toInt() val itemSize = AppUtils.getDimension(R.dimen.chat_message_popup_item_height).toInt()
var totalSize = itemSize * 7 var totalSize = itemSize * 7
if (chatMessage.chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt()) || if (chatMessage.chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt()) ||
chatMessage.state == ChatMessage.State.NotDelivered) { // No message id chatMessage.state == ChatMessage.State.NotDelivered
) { // No message id
popupView.imdnHidden = true popupView.imdnHidden = true
totalSize -= itemSize totalSize -= itemSize
} }
@ -236,7 +240,8 @@ class ChatMessagesListAdapter(
// When using WRAP_CONTENT instead of real size, fails to place the // When using WRAP_CONTENT instead of real size, fails to place the
// popup window above if not enough space is available below // popup window above if not enough space is available below
val popupWindow = PopupWindow(popupView.root, val popupWindow = PopupWindow(
popupView.root,
AppUtils.getDimension(R.dimen.chat_message_popup_width).toInt(), AppUtils.getDimension(R.dimen.chat_message_popup_width).toInt(),
totalSize, totalSize,
true true
@ -354,9 +359,12 @@ class ChatMessagesListAdapter(
// This is for item selection through ListTopBarFragment // This is for item selection through ListTopBarFragment
selectionListViewModel = selectionViewModel selectionListViewModel = selectionViewModel
selectionViewModel.isEditionEnabled.observe(viewLifecycleOwner, { selectionViewModel.isEditionEnabled.observe(
position = adapterPosition viewLifecycleOwner,
}) {
position = adapterPosition
}
)
binding.setClickListener { binding.setClickListener {
if (selectionViewModel.isEditionEnabled.value == true) { if (selectionViewModel.isEditionEnabled.value == true) {
@ -376,9 +384,10 @@ private class ChatMessageDiffCallback : DiffUtil.ItemCallback<EventLogData>() {
newItem: EventLogData newItem: EventLogData
): Boolean { ): Boolean {
return if (oldItem.eventLog.type == EventLog.Type.ConferenceChatMessage && return if (oldItem.eventLog.type == EventLog.Type.ConferenceChatMessage &&
newItem.eventLog.type == EventLog.Type.ConferenceChatMessage) { newItem.eventLog.type == EventLog.Type.ConferenceChatMessage
) {
oldItem.eventLog.chatMessage?.time == newItem.eventLog.chatMessage?.time && oldItem.eventLog.chatMessage?.time == newItem.eventLog.chatMessage?.time &&
oldItem.eventLog.chatMessage?.isOutgoing == newItem.eventLog.chatMessage?.isOutgoing oldItem.eventLog.chatMessage?.isOutgoing == newItem.eventLog.chatMessage?.isOutgoing
} else oldItem.eventLog.notifyId == newItem.eventLog.notifyId } else oldItem.eventLog.notifyId == newItem.eventLog.notifyId
} }

View file

@ -72,9 +72,12 @@ class ChatRoomsListAdapter(
// This is for item selection through ListTopBarFragment // This is for item selection through ListTopBarFragment
selectionListViewModel = selectionViewModel selectionListViewModel = selectionViewModel
selectionViewModel.isEditionEnabled.observe(viewLifecycleOwner, { selectionViewModel.isEditionEnabled.observe(
position = adapterPosition viewLifecycleOwner,
}) {
position = adapterPosition
}
)
forwardPending = isForwardPending forwardPending = isForwardPending

View file

@ -47,7 +47,7 @@ class ChatMessageContentData(
) { ) {
var listener: OnContentClickedListener? = null var listener: OnContentClickedListener? = null
val isOutgoing = chatMessage.isOutgoing val isOutgoing = chatMessage.isOutgoing
val isImage = MutableLiveData<Boolean>() val isImage = MutableLiveData<Boolean>()
@ -214,7 +214,7 @@ class ChatMessageContentData(
isVoiceRecording.value = isVoiceRecord isVoiceRecording.value = isVoiceRecord
if (isVoiceRecord) { if (isVoiceRecord) {
val duration = content.fileDuration// duration is in ms val duration = content.fileDuration // duration is in ms
voiceRecordDuration.value = duration voiceRecordDuration.value = duration
formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(duration) formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(duration)
Log.i("[Voice Recording] Duration is ${voiceRecordDuration.value} ($duration)") Log.i("[Voice Recording] Duration is ${voiceRecordDuration.value} ($duration)")

View file

@ -35,7 +35,8 @@ class EventData(private val eventLog: EventLog) : GenericContactData(
} else { } else {
eventLog.participantAddress!! eventLog.participantAddress!!
} }
}) { }
) {
val text = MutableLiveData<String>() val text = MutableLiveData<String>()
val isSecurity: Boolean by lazy { val isSecurity: Boolean by lazy {

View file

@ -90,42 +90,63 @@ class ChatRoomCreationFragment : SecureFragment<ChatRoomCreationFragmentBinding>
viewModel.sipContactsSelected.value = true viewModel.sipContactsSelected.value = true
} }
viewModel.contactsList.observe(viewLifecycleOwner, { viewModel.contactsList.observe(
adapter.submitList(it) viewLifecycleOwner,
}) {
adapter.submitList(it)
viewModel.isEncrypted.observe(viewLifecycleOwner, {
adapter.updateSecurity(it)
})
viewModel.sipContactsSelected.observe(viewLifecycleOwner, {
viewModel.updateContactsList()
})
viewModel.selectedAddresses.observe(viewLifecycleOwner, {
adapter.updateSelectedAddresses(it)
})
viewModel.chatRoomCreatedEvent.observe(viewLifecycleOwner, {
it.consume { chatRoom ->
sharedViewModel.selectedChatRoom.value = chatRoom
navigateToChatRoom(AppUtils.createBundleWithSharedTextAndFiles(sharedViewModel))
} }
}) )
viewModel.filter.observe(viewLifecycleOwner, { viewModel.isEncrypted.observe(
viewModel.applyFilter() viewLifecycleOwner,
}) {
adapter.updateSecurity(it)
}
)
adapter.selectedContact.observe(viewLifecycleOwner, { viewModel.sipContactsSelected.observe(
it.consume { searchResult -> viewLifecycleOwner,
if (createGroup) { {
viewModel.toggleSelectionForSearchResult(searchResult) viewModel.updateContactsList()
} else { }
viewModel.createOneToOneChat(searchResult) )
viewModel.selectedAddresses.observe(
viewLifecycleOwner,
{
adapter.updateSelectedAddresses(it)
}
)
viewModel.chatRoomCreatedEvent.observe(
viewLifecycleOwner,
{
it.consume { chatRoom ->
sharedViewModel.selectedChatRoom.value = chatRoom
navigateToChatRoom(AppUtils.createBundleWithSharedTextAndFiles(sharedViewModel))
} }
} }
}) )
viewModel.filter.observe(
viewLifecycleOwner,
{
viewModel.applyFilter()
}
)
adapter.selectedContact.observe(
viewLifecycleOwner,
{
it.consume { searchResult ->
if (createGroup) {
viewModel.toggleSelectionForSearchResult(searchResult)
} else {
viewModel.createOneToOneChat(searchResult)
}
}
}
)
addParticipantsFromSharedViewModel() addParticipantsFromSharedViewModel()
@ -136,11 +157,14 @@ class ChatRoomCreationFragment : SecureFragment<ChatRoomCreationFragmentBinding>
navigateToGroupInfo() navigateToGroupInfo()
} }
viewModel.onErrorEvent.observe(viewLifecycleOwner, { viewModel.onErrorEvent.observe(
it.consume { messageResourceId -> viewLifecycleOwner,
(activity as MainActivity).showSnackBar(messageResourceId) {
it.consume { messageResourceId ->
(activity as MainActivity).showSnackBar(messageResourceId)
}
} }
}) )
if (!PermissionHelper.get().hasReadContactsPermission()) { if (!PermissionHelper.get().hasReadContactsPermission()) {
Log.i("[Chat Room Creation] Asking for READ_CONTACTS permission") Log.i("[Chat Room Creation] Asking for READ_CONTACTS permission")

View file

@ -115,7 +115,8 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
val localAddress = Factory.instance().createAddress(localSipUri) val localAddress = Factory.instance().createAddress(localSipUri)
val remoteSipAddress = Factory.instance().createAddress(remoteSipUri) val remoteSipAddress = Factory.instance().createAddress(remoteSipUri)
sharedViewModel.selectedChatRoom.value = coreContext.core.searchChatRoom( sharedViewModel.selectedChatRoom.value = coreContext.core.searchChatRoom(
null, localAddress, remoteSipAddress, arrayOfNulls( null, localAddress, remoteSipAddress,
arrayOfNulls(
0 0
) )
) )
@ -189,146 +190,185 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
} }
binding.chatMessagesList.addOnScrollListener(chatScrollListener) binding.chatMessagesList.addOnScrollListener(chatScrollListener)
chatSendingViewModel.textToSend.observe(viewLifecycleOwner, { chatSendingViewModel.textToSend.observe(
chatSendingViewModel.onTextToSendChanged(it) viewLifecycleOwner,
}) {
chatSendingViewModel.onTextToSendChanged(it)
chatSendingViewModel.requestRecordAudioPermissionEvent.observe(viewLifecycleOwner, {
it.consume {
Log.i("[Chat Room] Asking for RECORD_AUDIO permission")
requestPermissions(arrayOf(android.Manifest.permission.RECORD_AUDIO), 2)
} }
}) )
listViewModel.events.observe(viewLifecycleOwner, { events -> chatSendingViewModel.requestRecordAudioPermissionEvent.observe(
adapter.submitList(events) viewLifecycleOwner,
}) {
it.consume {
listViewModel.messageUpdatedEvent.observe(viewLifecycleOwner, { Log.i("[Chat Room] Asking for RECORD_AUDIO permission")
it.consume { position -> requestPermissions(arrayOf(android.Manifest.permission.RECORD_AUDIO), 2)
adapter.notifyItemChanged(position)
}
})
listViewModel.requestWriteExternalStoragePermissionEvent.observe(viewLifecycleOwner, {
it.consume {
requestPermissions(arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
}
})
adapter.deleteMessageEvent.observe(viewLifecycleOwner, {
it.consume { chatMessage ->
listViewModel.deleteMessage(chatMessage)
}
})
adapter.resendMessageEvent.observe(viewLifecycleOwner, {
it.consume { chatMessage ->
listViewModel.resendMessage(chatMessage)
}
})
adapter.forwardMessageEvent.observe(viewLifecycleOwner, {
it.consume { chatMessage ->
// Remove observer before setting the message to forward
// as we don't want to forward it in this chat room
sharedViewModel.messageToForwardEvent.removeObservers(viewLifecycleOwner)
sharedViewModel.messageToForwardEvent.value = Event(chatMessage)
sharedViewModel.isPendingMessageForward.value = true
if (sharedViewModel.canSlidingPaneBeClosed.value == true) {
Log.i("[Chat Room] Forwarding message, going to chat rooms list")
sharedViewModel.closeSlidingPaneEvent.value = Event(true)
} }
} }
}) )
adapter.replyMessageEvent.observe(viewLifecycleOwner, { listViewModel.events.observe(
it.consume { chatMessage -> viewLifecycleOwner,
chatSendingViewModel.pendingChatMessageToReplyTo.value?.destroy() { events ->
chatSendingViewModel.pendingChatMessageToReplyTo.value = ChatMessageData(chatMessage) adapter.submitList(events)
chatSendingViewModel.isPendingAnswer.value = true
} }
}) )
adapter.showImdnForMessageEvent.observe(viewLifecycleOwner, { listViewModel.messageUpdatedEvent.observe(
it.consume { chatMessage -> viewLifecycleOwner,
val args = Bundle() {
args.putString("MessageId", chatMessage.messageId) it.consume { position ->
navigateToImdn(args) adapter.notifyItemChanged(position)
}
} }
}) )
adapter.addSipUriToContactEvent.observe(viewLifecycleOwner, { listViewModel.requestWriteExternalStoragePermissionEvent.observe(
it.consume { sipUri -> viewLifecycleOwner,
Log.i("[Chat Room] Going to contacts list with SIP URI to add: $sipUri") {
navigateToContacts(sipUri) it.consume {
requestPermissions(arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
}
} }
}) )
adapter.openContentEvent.observe(viewLifecycleOwner, { adapter.deleteMessageEvent.observe(
it.consume { content -> viewLifecycleOwner,
val path = content.filePath.orEmpty() {
it.consume { chatMessage ->
listViewModel.deleteMessage(chatMessage)
}
}
)
if (!File(path).exists()) { adapter.resendMessageEvent.observe(
(requireActivity() as MainActivity).showSnackBar(R.string.chat_room_file_not_found) viewLifecycleOwner,
} else { {
Log.i("[Chat Message] Opening file: $path") it.consume { chatMessage ->
sharedViewModel.contentToOpen.value = content listViewModel.resendMessage(chatMessage)
}
}
)
if (corePreferences.useInAppFileViewerForNonEncryptedFiles || content.isFileEncrypted) { adapter.forwardMessageEvent.observe(
val preventScreenshots = viewLifecycleOwner,
viewModel.chatRoom.currentParams.encryptionEnabled() {
when { it.consume { chatMessage ->
FileUtils.isExtensionImage(path) -> navigateToImageFileViewer( // Remove observer before setting the message to forward
preventScreenshots // as we don't want to forward it in this chat room
) sharedViewModel.messageToForwardEvent.removeObservers(viewLifecycleOwner)
FileUtils.isExtensionVideo(path) -> navigateToVideoFileViewer( sharedViewModel.messageToForwardEvent.value = Event(chatMessage)
preventScreenshots sharedViewModel.isPendingMessageForward.value = true
)
FileUtils.isExtensionAudio(path) -> navigateToAudioFileViewer( if (sharedViewModel.canSlidingPaneBeClosed.value == true) {
preventScreenshots Log.i("[Chat Room] Forwarding message, going to chat rooms list")
) sharedViewModel.closeSlidingPaneEvent.value = Event(true)
FileUtils.isExtensionPdf(path) -> navigateToPdfFileViewer( }
preventScreenshots }
) }
FileUtils.isPlainTextFile(path) -> navigateToTextFileViewer( )
preventScreenshots
) adapter.replyMessageEvent.observe(
else -> { viewLifecycleOwner,
if (content.isFileEncrypted) { {
Log.w("[Chat Message] File is encrypted and can't be opened in one of our viewers...") it.consume { chatMessage ->
showDialogForUserConsentBeforeExportingFileInThirdPartyApp(content) chatSendingViewModel.pendingChatMessageToReplyTo.value?.destroy()
} else if (!FileUtils.openFileInThirdPartyApp(requireActivity(), path)) { chatSendingViewModel.pendingChatMessageToReplyTo.value = ChatMessageData(chatMessage)
showDialogToSuggestOpeningFileAsText() chatSendingViewModel.isPendingAnswer.value = true
}
}
)
adapter.showImdnForMessageEvent.observe(
viewLifecycleOwner,
{
it.consume { chatMessage ->
val args = Bundle()
args.putString("MessageId", chatMessage.messageId)
navigateToImdn(args)
}
}
)
adapter.addSipUriToContactEvent.observe(
viewLifecycleOwner,
{
it.consume { sipUri ->
Log.i("[Chat Room] Going to contacts list with SIP URI to add: $sipUri")
navigateToContacts(sipUri)
}
}
)
adapter.openContentEvent.observe(
viewLifecycleOwner,
{
it.consume { content ->
val path = content.filePath.orEmpty()
if (!File(path).exists()) {
(requireActivity() as MainActivity).showSnackBar(R.string.chat_room_file_not_found)
} else {
Log.i("[Chat Message] Opening file: $path")
sharedViewModel.contentToOpen.value = content
if (corePreferences.useInAppFileViewerForNonEncryptedFiles || content.isFileEncrypted) {
val preventScreenshots =
viewModel.chatRoom.currentParams.encryptionEnabled()
when {
FileUtils.isExtensionImage(path) -> navigateToImageFileViewer(
preventScreenshots
)
FileUtils.isExtensionVideo(path) -> navigateToVideoFileViewer(
preventScreenshots
)
FileUtils.isExtensionAudio(path) -> navigateToAudioFileViewer(
preventScreenshots
)
FileUtils.isExtensionPdf(path) -> navigateToPdfFileViewer(
preventScreenshots
)
FileUtils.isPlainTextFile(path) -> navigateToTextFileViewer(
preventScreenshots
)
else -> {
if (content.isFileEncrypted) {
Log.w("[Chat Message] File is encrypted and can't be opened in one of our viewers...")
showDialogForUserConsentBeforeExportingFileInThirdPartyApp(content)
} else if (!FileUtils.openFileInThirdPartyApp(requireActivity(), path)) {
showDialogToSuggestOpeningFileAsText()
}
} }
} }
} } else {
} else { if (!FileUtils.openFileInThirdPartyApp(requireActivity(), path)) {
if (!FileUtils.openFileInThirdPartyApp(requireActivity(), path)) { showDialogToSuggestOpeningFileAsText()
showDialogToSuggestOpeningFileAsText() }
} }
} }
} }
} }
}) )
adapter.scrollToChatMessageEvent.observe(viewLifecycleOwner, { adapter.scrollToChatMessageEvent.observe(
it.consume { chatMessage -> viewLifecycleOwner,
val events = listViewModel.events.value.orEmpty() {
val eventLog = events.find { eventLog -> it.consume { chatMessage ->
if (eventLog.eventLog.type == EventLog.Type.ConferenceChatMessage) { val events = listViewModel.events.value.orEmpty()
(eventLog.data as ChatMessageData).chatMessage.messageId == chatMessage.messageId val eventLog = events.find { eventLog ->
} else false if (eventLog.eventLog.type == EventLog.Type.ConferenceChatMessage) {
} (eventLog.data as ChatMessageData).chatMessage.messageId == chatMessage.messageId
val index = events.indexOf(eventLog) } else false
try { }
binding.chatMessagesList.smoothScrollToPosition(index) val index = events.indexOf(eventLog)
} catch (iae: IllegalArgumentException) { try {
Log.e("[Chat Room] Can't scroll to position $index") binding.chatMessagesList.smoothScrollToPosition(index)
} catch (iae: IllegalArgumentException) {
Log.e("[Chat Room] Can't scroll to position $index")
}
} }
} }
}) )
binding.setBackClickListener { binding.setBackClickListener {
goBack() goBack()
@ -336,7 +376,8 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
binding.setTitleClickListener { binding.setTitleClickListener {
binding.sipUri.visibility = if (!viewModel.oneToOneChatRoom || binding.sipUri.visibility = if (!viewModel.oneToOneChatRoom ||
binding.sipUri.visibility == View.VISIBLE) View.GONE else View.VISIBLE binding.sipUri.visibility == View.VISIBLE
) View.GONE else View.VISIBLE
} }
binding.setMenuClickListener { binding.setMenuClickListener {
@ -360,7 +401,8 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
arrayOf( arrayOf(
android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.CAMERA android.Manifest.permission.CAMERA
), 0 ),
0
) )
} }
} }
@ -400,13 +442,16 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
} }
} }
sharedViewModel.messageToForwardEvent.observe(viewLifecycleOwner, { sharedViewModel.messageToForwardEvent.observe(
it.consume { chatMessage -> viewLifecycleOwner,
Log.i("[Chat Room] Found message to transfer") {
showForwardConfirmationDialog(chatMessage) it.consume { chatMessage ->
sharedViewModel.isPendingMessageForward.value = false Log.i("[Chat Room] Found message to transfer")
showForwardConfirmationDialog(chatMessage)
sharedViewModel.isPendingMessageForward.value = false
}
} }
}) )
} }
override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) { override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) {
@ -464,10 +509,12 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
lifecycleScope.launch { lifecycleScope.launch {
for (fileToUploadPath in FileUtils.getFilesPathFromPickerIntent( for (
data, fileToUploadPath in FileUtils.getFilesPathFromPickerIntent(
chatSendingViewModel.temporaryFileUploadPath data,
)) { chatSendingViewModel.temporaryFileUploadPath
)
) {
chatSendingViewModel.addAttachment(fileToUploadPath) chatSendingViewModel.addAttachment(fileToUploadPath)
} }
} }
@ -502,20 +549,23 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
val okLabel = if (viewModel.oneParticipantOneDevice) getString(R.string.dialog_call) else getString( val okLabel = if (viewModel.oneParticipantOneDevice) getString(R.string.dialog_call) else getString(
R.string.dialog_ok R.string.dialog_ok
) )
dialogViewModel.showOkButton({ doNotAskAgain -> dialogViewModel.showOkButton(
if (doNotAskAgain) corePreferences.limeSecurityPopupEnabled = false { doNotAskAgain ->
if (doNotAskAgain) corePreferences.limeSecurityPopupEnabled = false
val address = viewModel.onlyParticipantOnlyDeviceAddress val address = viewModel.onlyParticipantOnlyDeviceAddress
if (viewModel.oneParticipantOneDevice) { if (viewModel.oneParticipantOneDevice) {
if (address != null) { if (address != null) {
coreContext.startCall(address, true) coreContext.startCall(address, true)
}
} else {
navigateToDevices()
} }
} else {
navigateToDevices()
}
dialog.dismiss() dialog.dismiss()
}, okLabel) },
okLabel
)
dialog.show() dialog.show()
} else { } else {
@ -551,11 +601,14 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
dialog.dismiss() dialog.dismiss()
} }
viewModel.showOkButton({ viewModel.showOkButton(
Log.i("[Chat Room] Transfer confirmed") {
chatSendingViewModel.transferMessage(chatMessage) Log.i("[Chat Room] Transfer confirmed")
dialog.dismiss() chatSendingViewModel.transferMessage(chatMessage)
}, getString(R.string.chat_message_context_menu_forward)) dialog.dismiss()
},
getString(R.string.chat_message_context_menu_forward)
)
dialog.show() dialog.show()
} }
@ -679,25 +732,31 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
) )
val dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) val dialog = DialogUtils.getDialog(requireContext(), dialogViewModel)
dialogViewModel.showDeleteButton({ dialogViewModel.showDeleteButton(
dialog.dismiss() {
lifecycleScope.launch { dialog.dismiss()
val plainFilePath = content.plainFilePath lifecycleScope.launch {
Log.i("[Cht Room] Making a copy of [$plainFilePath] to the cache directory before exporting it") val plainFilePath = content.plainFilePath
val cacheCopyPath = FileUtils.copyFileToCache(plainFilePath) Log.i("[Cht Room] Making a copy of [$plainFilePath] to the cache directory before exporting it")
if (cacheCopyPath != null) { val cacheCopyPath = FileUtils.copyFileToCache(plainFilePath)
Log.i("[Cht Room] Cache copy has been made: $cacheCopyPath") if (cacheCopyPath != null) {
if (!FileUtils.openFileInThirdPartyApp(requireActivity(), cacheCopyPath)) { Log.i("[Cht Room] Cache copy has been made: $cacheCopyPath")
showDialogToSuggestOpeningFileAsText() if (!FileUtils.openFileInThirdPartyApp(requireActivity(), cacheCopyPath)) {
showDialogToSuggestOpeningFileAsText()
}
} }
} }
} },
}, getString(R.string.chat_message_cant_open_file_in_app_dialog_export_button)) getString(R.string.chat_message_cant_open_file_in_app_dialog_export_button)
)
dialogViewModel.showOkButton({ dialogViewModel.showOkButton(
dialog.dismiss() {
navigateToTextFileViewer(true) dialog.dismiss()
}, getString(R.string.chat_message_cant_open_file_in_app_dialog_open_as_text_button)) navigateToTextFileViewer(true)
},
getString(R.string.chat_message_cant_open_file_in_app_dialog_open_as_text_button)
)
dialogViewModel.showCancelButton { dialogViewModel.showCancelButton {
dialog.dismiss() dialog.dismiss()

View file

@ -74,7 +74,7 @@ class GroupInfoFragment : SecureFragment<ChatRoomGroupInfoFragmentBinding>() {
adapter = GroupInfoParticipantsAdapter( adapter = GroupInfoParticipantsAdapter(
viewLifecycleOwner, viewLifecycleOwner,
chatRoom?.hasCapability(ChatRoomCapabilities.Encrypted.toInt()) ?: viewModel.isEncrypted.value == true chatRoom?.hasCapability(ChatRoomCapabilities.Encrypted.toInt()) ?: viewModel.isEncrypted.value == true
) )
binding.participants.adapter = adapter binding.participants.adapter = adapter
val layoutManager = LinearLayoutManager(activity) val layoutManager = LinearLayoutManager(activity)
@ -83,25 +83,37 @@ class GroupInfoFragment : SecureFragment<ChatRoomGroupInfoFragmentBinding>() {
// Divider between items // Divider between items
binding.participants.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager)) binding.participants.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager))
viewModel.participants.observe(viewLifecycleOwner, { viewModel.participants.observe(
adapter.submitList(it) viewLifecycleOwner,
}) {
adapter.submitList(it)
viewModel.isMeAdmin.observe(viewLifecycleOwner, { isMeAdmin ->
adapter.showAdminControls(isMeAdmin && chatRoom != null)
})
viewModel.meAdminChangedEvent.observe(viewLifecycleOwner, {
it.consume { isMeAdmin ->
showMeAdminStateChanged(isMeAdmin)
} }
}) )
adapter.participantRemovedEvent.observe(viewLifecycleOwner, { viewModel.isMeAdmin.observe(
it.consume { participant -> viewLifecycleOwner,
viewModel.removeParticipant(participant) { isMeAdmin ->
adapter.showAdminControls(isMeAdmin && chatRoom != null)
} }
}) )
viewModel.meAdminChangedEvent.observe(
viewLifecycleOwner,
{
it.consume { isMeAdmin ->
showMeAdminStateChanged(isMeAdmin)
}
}
)
adapter.participantRemovedEvent.observe(
viewLifecycleOwner,
{
it.consume { participant ->
viewModel.removeParticipant(participant)
}
}
)
addParticipantsFromSharedViewModel() addParticipantsFromSharedViewModel()
@ -109,12 +121,15 @@ class GroupInfoFragment : SecureFragment<ChatRoomGroupInfoFragmentBinding>() {
goBack() goBack()
} }
viewModel.createdChatRoomEvent.observe(viewLifecycleOwner, { viewModel.createdChatRoomEvent.observe(
it.consume { chatRoom -> viewLifecycleOwner,
sharedViewModel.selectedChatRoom.value = chatRoom {
navigateToChatRoom(AppUtils.createBundleWithSharedTextAndFiles(sharedViewModel)) it.consume { chatRoom ->
sharedViewModel.selectedChatRoom.value = chatRoom
navigateToChatRoom(AppUtils.createBundleWithSharedTextAndFiles(sharedViewModel))
}
} }
}) )
binding.setNextClickListener { binding.setNextClickListener {
if (viewModel.chatRoom != null) { if (viewModel.chatRoom != null) {
@ -143,10 +158,13 @@ class GroupInfoFragment : SecureFragment<ChatRoomGroupInfoFragmentBinding>() {
val dialogViewModel = DialogViewModel(getString(R.string.chat_room_group_info_leave_dialog_message)) val dialogViewModel = DialogViewModel(getString(R.string.chat_room_group_info_leave_dialog_message))
val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel)
dialogViewModel.showDeleteButton({ dialogViewModel.showDeleteButton(
viewModel.leaveGroup() {
dialog.dismiss() viewModel.leaveGroup()
}, getString(R.string.chat_room_group_info_leave_dialog_button)) dialog.dismiss()
},
getString(R.string.chat_room_group_info_leave_dialog_button)
)
dialogViewModel.showCancelButton { dialogViewModel.showCancelButton {
dialog.dismiss() dialog.dismiss()
@ -155,11 +173,14 @@ class GroupInfoFragment : SecureFragment<ChatRoomGroupInfoFragmentBinding>() {
dialog.show() dialog.show()
} }
viewModel.onErrorEvent.observe(viewLifecycleOwner, { viewModel.onErrorEvent.observe(
it.consume { messageResourceId -> viewLifecycleOwner,
(activity as MainActivity).showSnackBar(messageResourceId) {
it.consume { messageResourceId ->
(activity as MainActivity).showSnackBar(messageResourceId)
}
} }
}) )
} }
private fun addParticipantsFromSharedViewModel() { private fun addParticipantsFromSharedViewModel() {
@ -175,9 +196,11 @@ class GroupInfoFragment : SecureFragment<ChatRoomGroupInfoFragmentBinding>() {
if (exists != null) { if (exists != null) {
list.add(exists) list.add(exists)
} else { } else {
list.add(GroupInfoParticipantData( list.add(
GroupChatRoomMember(address, false, hasLimeX3DHCapability = viewModel.isEncrypted.value == true) GroupInfoParticipantData(
)) GroupChatRoomMember(address, false, hasLimeX3DHCapability = viewModel.isEncrypted.value == true)
)
)
} }
} }

View file

@ -97,9 +97,12 @@ class ImdnFragment : SecureFragment<ChatRoomImdnFragmentBinding>() {
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter)
binding.participantsList.addItemDecoration(headerItemDecoration) binding.participantsList.addItemDecoration(headerItemDecoration)
viewModel.participants.observe(viewLifecycleOwner, { viewModel.participants.observe(
adapter.submitList(it) viewLifecycleOwner,
}) {
adapter.submitList(it)
}
)
binding.setBackClickListener { binding.setBackClickListener {
goBack() goBack()

View file

@ -92,25 +92,31 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
view.doOnPreDraw { sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable } view.doOnPreDraw { sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable }
sharedViewModel.closeSlidingPaneEvent.observe(viewLifecycleOwner, { sharedViewModel.closeSlidingPaneEvent.observe(
it.consume { viewLifecycleOwner,
if (!binding.slidingPane.closePane()) { {
goBack() it.consume {
} if (!binding.slidingPane.closePane()) {
} goBack()
})
sharedViewModel.layoutChangedEvent.observe(viewLifecycleOwner, {
it.consume {
sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable
if (binding.slidingPane.isSlideable) {
val navHostFragment = childFragmentManager.findFragmentById(R.id.chat_nav_container) as NavHostFragment
if (navHostFragment.navController.currentDestination?.id == R.id.emptyChatFragment) {
Log.i("[Chat] Foldable device has been folded, closing side pane with empty fragment")
binding.slidingPane.closePane()
} }
} }
} }
}) )
sharedViewModel.layoutChangedEvent.observe(
viewLifecycleOwner,
{
it.consume {
sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable
if (binding.slidingPane.isSlideable) {
val navHostFragment = childFragmentManager.findFragmentById(R.id.chat_nav_container) as NavHostFragment
if (navHostFragment.navController.currentDestination?.id == R.id.emptyChatFragment) {
Log.i("[Chat] Foldable device has been folded, closing side pane with empty fragment")
binding.slidingPane.closePane()
}
}
}
}
)
binding.slidingPane.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED binding.slidingPane.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
binding.slidingPane.addPanelSlideListener(object : SlidingPaneLayout.PanelSlideListener { binding.slidingPane.addPanelSlideListener(object : SlidingPaneLayout.PanelSlideListener {
override fun onPanelSlide(panel: View, slideOffset: Float) { } override fun onPanelSlide(panel: View, slideOffset: Float) { }
@ -171,16 +177,20 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
dialog.dismiss() dialog.dismiss()
} }
viewModel.showDeleteButton({ viewModel.showDeleteButton(
val deletedChatRoom = adapter.currentList[viewHolder.adapterPosition].chatRoom {
listViewModel.deleteChatRoom(deletedChatRoom) val deletedChatRoom = adapter.currentList[viewHolder.adapterPosition].chatRoom
if (!binding.slidingPane.isSlideable && listViewModel.deleteChatRoom(deletedChatRoom)
deletedChatRoom == sharedViewModel.selectedChatRoom.value) { if (!binding.slidingPane.isSlideable &&
Log.i("[Chat] Currently displayed chat room has been deleted, removing detail fragment") deletedChatRoom == sharedViewModel.selectedChatRoom.value
clearDisplayedChatRoom() ) {
} Log.i("[Chat] Currently displayed chat room has been deleted, removing detail fragment")
dialog.dismiss() clearDisplayedChatRoom()
}, getString(R.string.dialog_delete)) }
dialog.dismiss()
},
getString(R.string.dialog_delete)
)
dialog.show() dialog.show()
} }
@ -191,28 +201,37 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
// Divider between items // Divider between items
binding.chatList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager)) binding.chatList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager))
listViewModel.chatRooms.observe(viewLifecycleOwner, { chatRooms -> listViewModel.chatRooms.observe(
adapter.submitList(chatRooms) viewLifecycleOwner,
}) { chatRooms ->
adapter.submitList(chatRooms)
listViewModel.contactsUpdatedEvent.observe(viewLifecycleOwner, {
it.consume {
adapter.notifyDataSetChanged()
} }
}) )
adapter.selectedChatRoomEvent.observe(viewLifecycleOwner, { listViewModel.contactsUpdatedEvent.observe(
it.consume { chatRoom -> viewLifecycleOwner,
if ((requireActivity() as GenericActivity).isDestructionPending) { {
Log.w("[Chat] Activity is pending destruction, don't start navigating now!") it.consume {
sharedViewModel.destructionPendingChatRoom = chatRoom adapter.notifyDataSetChanged()
} else {
binding.slidingPane.openPane()
sharedViewModel.selectedChatRoom.value = chatRoom
navigateToChatRoom(AppUtils.createBundleWithSharedTextAndFiles(sharedViewModel))
} }
} }
}) )
adapter.selectedChatRoomEvent.observe(
viewLifecycleOwner,
{
it.consume { chatRoom ->
if ((requireActivity() as GenericActivity).isDestructionPending) {
Log.w("[Chat] Activity is pending destruction, don't start navigating now!")
sharedViewModel.destructionPendingChatRoom = chatRoom
} else {
binding.slidingPane.openPane()
sharedViewModel.selectedChatRoom.value = chatRoom
navigateToChatRoom(AppUtils.createBundleWithSharedTextAndFiles(sharedViewModel))
}
}
}
)
binding.setEditClickListener { binding.setEditClickListener {
listSelectionViewModel.isEditionEnabled.value = true listSelectionViewModel.isEditionEnabled.value = true
@ -268,43 +287,55 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
adapter.selectedChatRoomEvent.value = Event(chatRoom) adapter.selectedChatRoomEvent.value = Event(chatRoom)
} }
} else { } else {
sharedViewModel.textToShare.observe(viewLifecycleOwner, { sharedViewModel.textToShare.observe(
if (it.isNotEmpty()) { viewLifecycleOwner,
Log.i("[Chat] Found text to share") {
// val activity = requireActivity() as MainActivity if (it.isNotEmpty()) {
// activity.showSnackBar(R.string.chat_room_toast_choose_for_sharing) Log.i("[Chat] Found text to share")
listViewModel.textSharingPending.value = true // val activity = requireActivity() as MainActivity
} else { // activity.showSnackBar(R.string.chat_room_toast_choose_for_sharing)
if (sharedViewModel.filesToShare.value.isNullOrEmpty()) { listViewModel.textSharingPending.value = true
listViewModel.textSharingPending.value = false } else {
if (sharedViewModel.filesToShare.value.isNullOrEmpty()) {
listViewModel.textSharingPending.value = false
}
} }
} }
}) )
sharedViewModel.filesToShare.observe(viewLifecycleOwner, { sharedViewModel.filesToShare.observe(
if (it.isNotEmpty()) { viewLifecycleOwner,
Log.i("[Chat] Found ${it.size} files to share") {
// val activity = requireActivity() as MainActivity if (it.isNotEmpty()) {
// activity.showSnackBar(R.string.chat_room_toast_choose_for_sharing) Log.i("[Chat] Found ${it.size} files to share")
listViewModel.fileSharingPending.value = true // val activity = requireActivity() as MainActivity
} else { // activity.showSnackBar(R.string.chat_room_toast_choose_for_sharing)
if (sharedViewModel.textToShare.value.isNullOrEmpty()) { listViewModel.fileSharingPending.value = true
listViewModel.fileSharingPending.value = false } else {
if (sharedViewModel.textToShare.value.isNullOrEmpty()) {
listViewModel.fileSharingPending.value = false
}
} }
} }
}) )
sharedViewModel.isPendingMessageForward.observe(viewLifecycleOwner, { sharedViewModel.isPendingMessageForward.observe(
listViewModel.forwardPending.value = it viewLifecycleOwner,
adapter.forwardPending(it) {
if (it) { listViewModel.forwardPending.value = it
Log.i("[Chat] Found chat message to transfer") adapter.forwardPending(it)
if (it) {
Log.i("[Chat] Found chat message to transfer")
}
} }
}) )
listViewModel.onErrorEvent.observe(viewLifecycleOwner, { listViewModel.onErrorEvent.observe(
it.consume { messageResourceId -> viewLifecycleOwner,
(activity as MainActivity).showSnackBar(messageResourceId) {
it.consume { messageResourceId ->
(activity as MainActivity).showSnackBar(messageResourceId)
}
} }
}) )
} }
} }

View file

@ -149,9 +149,11 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
fun addAttachment(path: String) { fun addAttachment(path: String) {
val list = arrayListOf<ChatMessageAttachmentData>() val list = arrayListOf<ChatMessageAttachmentData>()
list.addAll(attachments.value.orEmpty()) list.addAll(attachments.value.orEmpty())
list.add(ChatMessageAttachmentData(path) { list.add(
removeAttachment(it) ChatMessageAttachmentData(path) {
}) removeAttachment(it)
}
)
attachments.value = list attachments.value = list
sendMessageEnabled.value = textToSend.value.orEmpty().isNotEmpty() || list.isNotEmpty() || isPendingVoiceRecord.value == true sendMessageEnabled.value = textToSend.value.orEmpty().isNotEmpty() || list.isNotEmpty() || isPendingVoiceRecord.value == true

View file

@ -47,7 +47,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
override val displayName: MutableLiveData<String> = MutableLiveData<String>() override val displayName: MutableLiveData<String> = MutableLiveData<String>()
override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>() override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
override val showGroupChatAvatar: Boolean = chatRoom.hasCapability(ChatRoomCapabilities.Conference.toInt()) && override val showGroupChatAvatar: Boolean = chatRoom.hasCapability(ChatRoomCapabilities.Conference.toInt()) &&
!chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt()) !chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())
val subject = MutableLiveData<String>() val subject = MutableLiveData<String>()
@ -310,8 +310,8 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
else chatRoom.peerAddress.asStringUriOnly() else chatRoom.peerAddress.asStringUriOnly()
oneParticipantOneDevice = chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt()) && oneParticipantOneDevice = chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt()) &&
chatRoom.me?.devices?.size == 1 && chatRoom.me?.devices?.size == 1 &&
chatRoom.participants.firstOrNull()?.devices?.size == 1 chatRoom.participants.firstOrNull()?.devices?.size == 1
addressToCall = if (chatRoom.hasCapability(ChatRoomCapabilities.Basic.toInt())) addressToCall = if (chatRoom.hasCapability(ChatRoomCapabilities.Basic.toInt()))
chatRoom.peerAddress chatRoom.peerAddress

View file

@ -211,9 +211,11 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : ErrorReportingViewModel() {
if (chatRoom != null) { if (chatRoom != null) {
for (participant in chatRoom.participants) { for (participant in chatRoom.participants) {
list.add(GroupInfoParticipantData( list.add(
GroupChatRoomMember(participant.address, participant.isAdmin, participant.securityLevel, canBeSetAdmin = true) GroupInfoParticipantData(
)) GroupChatRoomMember(participant.address, participant.isAdmin, participant.securityLevel, canBeSetAdmin = true)
)
)
} }
} }

View file

@ -47,9 +47,11 @@ class MultiLineWrapContentWidthTextView : AppCompatTextView {
if (widthMode == MeasureSpec.AT_MOST) { if (widthMode == MeasureSpec.AT_MOST) {
val layout = layout val layout = layout
if (layout != null) { if (layout != null) {
val maxWidth = (ceil(getMaxLineWidth(layout).toDouble()).toInt() + val maxWidth = (
ceil(getMaxLineWidth(layout).toDouble()).toInt() +
totalPaddingLeft + totalPaddingLeft +
totalPaddingRight) totalPaddingRight
)
wSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST) wSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST)
} }
} }

View file

@ -70,9 +70,12 @@ class ContactsListAdapter(
// This is for item selection through ListTopBarFragment // This is for item selection through ListTopBarFragment
selectionListViewModel = selectionViewModel selectionListViewModel = selectionViewModel
selectionViewModel.isEditionEnabled.observe(viewLifecycleOwner, { selectionViewModel.isEditionEnabled.observe(
position = adapterPosition viewLifecycleOwner,
}) {
position = adapterPosition
}
)
setClickListener { setClickListener {
if (selectionViewModel.isEditionEnabled.value == true) { if (selectionViewModel.isEditionEnabled.value == true) {

View file

@ -78,35 +78,44 @@ class DetailContactFragment : GenericFragment<ContactDetailFragmentBinding>() {
)[ContactViewModel::class.java] )[ContactViewModel::class.java]
binding.viewModel = viewModel binding.viewModel = viewModel
viewModel.sendSmsToEvent.observe(viewLifecycleOwner, { viewModel.sendSmsToEvent.observe(
it.consume { number -> viewLifecycleOwner,
sendSms(number) {
} it.consume { number ->
}) sendSms(number)
viewModel.startCallToEvent.observe(viewLifecycleOwner, {
it.consume { address ->
if (coreContext.core.callsNb > 0) {
Log.i("[Contact] Starting dialer with pre-filled URI ${address.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}")
val args = Bundle()
args.putString("URI", address.asStringUriOnly())
args.putBoolean("Transfer", sharedViewModel.pendingCallTransfer)
args.putBoolean("SkipAutoCallStart", true) // If auto start call setting is enabled, ignore it
navigateToDialer(args)
} else {
coreContext.startCall(address)
} }
} }
}) )
viewModel.chatRoomCreatedEvent.observe(viewLifecycleOwner, { viewModel.startCallToEvent.observe(
it.consume { chatRoom -> viewLifecycleOwner,
val args = Bundle() {
args.putString("LocalSipUri", chatRoom.localAddress.asStringUriOnly()) it.consume { address ->
args.putString("RemoteSipUri", chatRoom.peerAddress.asStringUriOnly()) if (coreContext.core.callsNb > 0) {
navigateToChatRoom(args) Log.i("[Contact] Starting dialer with pre-filled URI ${address.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}")
val args = Bundle()
args.putString("URI", address.asStringUriOnly())
args.putBoolean("Transfer", sharedViewModel.pendingCallTransfer)
args.putBoolean("SkipAutoCallStart", true) // If auto start call setting is enabled, ignore it
navigateToDialer(args)
} else {
coreContext.startCall(address)
}
}
} }
}) )
viewModel.chatRoomCreatedEvent.observe(
viewLifecycleOwner,
{
it.consume { chatRoom ->
val args = Bundle()
args.putString("LocalSipUri", chatRoom.localAddress.asStringUriOnly())
args.putString("RemoteSipUri", chatRoom.peerAddress.asStringUriOnly())
navigateToChatRoom(args)
}
}
)
binding.setBackClickListener { binding.setBackClickListener {
goBack() goBack()
@ -120,11 +129,14 @@ class DetailContactFragment : GenericFragment<ContactDetailFragmentBinding>() {
confirmContactRemoval() confirmContactRemoval()
} }
viewModel.onErrorEvent.observe(viewLifecycleOwner, { viewModel.onErrorEvent.observe(
it.consume { messageResourceId -> viewLifecycleOwner,
(activity as MainActivity).showSnackBar(messageResourceId) {
it.consume { messageResourceId ->
(activity as MainActivity).showSnackBar(messageResourceId)
}
} }
}) )
} }
override fun goBack() { override fun goBack() {
@ -143,11 +155,14 @@ class DetailContactFragment : GenericFragment<ContactDetailFragmentBinding>() {
dialog.dismiss() dialog.dismiss()
} }
dialogViewModel.showDeleteButton({ dialogViewModel.showDeleteButton(
viewModel.deleteContact() {
dialog.dismiss() viewModel.deleteContact()
goBack() dialog.dismiss()
}, getString(R.string.dialog_delete)) goBack()
},
getString(R.string.dialog_delete)
)
dialog.show() dialog.show()
} }

View file

@ -80,25 +80,31 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
view.doOnPreDraw { sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable } view.doOnPreDraw { sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable }
sharedViewModel.closeSlidingPaneEvent.observe(viewLifecycleOwner, { sharedViewModel.closeSlidingPaneEvent.observe(
it.consume { viewLifecycleOwner,
if (!binding.slidingPane.closePane()) { {
goBack() it.consume {
} if (!binding.slidingPane.closePane()) {
} goBack()
})
sharedViewModel.layoutChangedEvent.observe(viewLifecycleOwner, {
it.consume {
sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable
if (binding.slidingPane.isSlideable) {
val navHostFragment = childFragmentManager.findFragmentById(R.id.contacts_nav_container) as NavHostFragment
if (navHostFragment.navController.currentDestination?.id == R.id.emptyContactFragment) {
Log.i("[Contacts] Foldable device has been folded, closing side pane with empty fragment")
binding.slidingPane.closePane()
} }
} }
} }
}) )
sharedViewModel.layoutChangedEvent.observe(
viewLifecycleOwner,
{
it.consume {
sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable
if (binding.slidingPane.isSlideable) {
val navHostFragment = childFragmentManager.findFragmentById(R.id.contacts_nav_container) as NavHostFragment
if (navHostFragment.navController.currentDestination?.id == R.id.emptyContactFragment) {
Log.i("[Contacts] Foldable device has been folded, closing side pane with empty fragment")
binding.slidingPane.closePane()
}
}
}
}
)
binding.slidingPane.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED binding.slidingPane.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
/*binding.slidingPane.addPanelSlideListener(object : SlidingPaneLayout.PanelSlideListener { /*binding.slidingPane.addPanelSlideListener(object : SlidingPaneLayout.PanelSlideListener {
override fun onPanelSlide(panel: View, slideOffset: Float) { } override fun onPanelSlide(panel: View, slideOffset: Float) { }
@ -155,16 +161,20 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
dialog.dismiss() dialog.dismiss()
} }
viewModel.showDeleteButton({ viewModel.showDeleteButton(
val deletedContact = adapter.currentList[viewHolder.adapterPosition].contactInternal {
listViewModel.deleteContact(deletedContact) val deletedContact = adapter.currentList[viewHolder.adapterPosition].contactInternal
if (!binding.slidingPane.isSlideable && listViewModel.deleteContact(deletedContact)
deletedContact == sharedViewModel.selectedContact.value) { if (!binding.slidingPane.isSlideable &&
Log.i("[Contacts] Currently displayed contact has been deleted, removing detail fragment") deletedContact == sharedViewModel.selectedContact.value
clearDisplayedContact() ) {
} Log.i("[Contacts] Currently displayed contact has been deleted, removing detail fragment")
dialog.dismiss() clearDisplayedContact()
}, getString(R.string.dialog_delete)) }
dialog.dismiss()
},
getString(R.string.dialog_delete)
)
dialog.show() dialog.show()
} }
@ -179,35 +189,41 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter)
binding.contactsList.addItemDecoration(headerItemDecoration) binding.contactsList.addItemDecoration(headerItemDecoration)
adapter.selectedContactEvent.observe(viewLifecycleOwner, { adapter.selectedContactEvent.observe(
it.consume { contact -> viewLifecycleOwner,
Log.i("[Contacts] Selected item in list changed: $contact") {
sharedViewModel.selectedContact.value = contact it.consume { contact ->
listViewModel.filter.value = "" Log.i("[Contacts] Selected item in list changed: $contact")
sharedViewModel.selectedContact.value = contact
listViewModel.filter.value = ""
binding.slidingPane.openPane() binding.slidingPane.openPane()
if (editOnClick) { if (editOnClick) {
navigateToContactEditor(sipUriToAdd) navigateToContactEditor(sipUriToAdd)
editOnClick = false editOnClick = false
sipUriToAdd = null sipUriToAdd = null
} else { } else {
navigateToContact() navigateToContact()
}
} }
} }
}) )
listViewModel.contactsList.observe(viewLifecycleOwner, { listViewModel.contactsList.observe(
val id = contactIdToDisplay viewLifecycleOwner,
if (id != null) { {
val contact = coreContext.contactsManager.findContactById(id) val id = contactIdToDisplay
if (contact != null) { if (id != null) {
contactIdToDisplay = null val contact = coreContext.contactsManager.findContactById(id)
Log.i("[Contacts] Found matching contact $contact after callback") if (contact != null) {
adapter.selectedContactEvent.value = Event(contact) contactIdToDisplay = null
Log.i("[Contacts] Found matching contact $contact after callback")
adapter.selectedContactEvent.value = Event(contact)
}
} }
adapter.submitList(it)
} }
adapter.submitList(it) )
})
binding.setAllContactsToggleClickListener { binding.setAllContactsToggleClickListener {
listViewModel.sipContactsSelected.value = false listViewModel.sipContactsSelected.value = false
@ -216,13 +232,19 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
listViewModel.sipContactsSelected.value = true listViewModel.sipContactsSelected.value = true
} }
listViewModel.sipContactsSelected.observe(viewLifecycleOwner, { listViewModel.sipContactsSelected.observe(
listViewModel.updateContactsList() viewLifecycleOwner,
}) {
listViewModel.updateContactsList()
}
)
listViewModel.filter.observe(viewLifecycleOwner, { listViewModel.filter.observe(
listViewModel.updateContactsList() viewLifecycleOwner,
}) {
listViewModel.updateContactsList()
}
)
binding.setNewContactClickListener { binding.setNewContactClickListener {
// Remove any previously selected contact // Remove any previously selected contact

View file

@ -166,12 +166,15 @@ class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactDataInterfac
} }
when (orientation) { when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> image = ExifInterface.ORIENTATION_ROTATE_90 ->
ImageUtils.rotateImage(image, 90f) image =
ExifInterface.ORIENTATION_ROTATE_180 -> image = ImageUtils.rotateImage(image, 90f)
ImageUtils.rotateImage(image, 180f) ExifInterface.ORIENTATION_ROTATE_180 ->
ExifInterface.ORIENTATION_ROTATE_270 -> image = image =
ImageUtils.rotateImage(image, 270f) ImageUtils.rotateImage(image, 180f)
ExifInterface.ORIENTATION_ROTATE_270 ->
image =
ImageUtils.rotateImage(image, 270f)
} }
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()

View file

@ -95,35 +95,44 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
} }
arguments?.clear() arguments?.clear()
viewModel.enteredUri.observe(viewLifecycleOwner, { viewModel.enteredUri.observe(
if (it == corePreferences.debugPopupCode) { viewLifecycleOwner,
displayDebugPopup() {
viewModel.enteredUri.value = "" if (it == corePreferences.debugPopupCode) {
} displayDebugPopup()
}) viewModel.enteredUri.value = ""
viewModel.uploadFinishedEvent.observe(viewLifecycleOwner, {
it.consume { url ->
// To prevent being trigger when using the Send Logs button in About page
if (uploadLogsInitiatedByUs) {
val clipboard =
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Logs url", url)
clipboard.setPrimaryClip(clip)
val activity = requireActivity() as MainActivity
activity.showSnackBar(R.string.logs_url_copied_to_clipboard)
AppUtils.shareUploadedLogsUrl(activity, url)
} }
} }
}) )
viewModel.updateAvailableEvent.observe(viewLifecycleOwner, { viewModel.uploadFinishedEvent.observe(
it.consume { url -> viewLifecycleOwner,
displayNewVersionAvailableDialog(url) {
it.consume { url ->
// To prevent being trigger when using the Send Logs button in About page
if (uploadLogsInitiatedByUs) {
val clipboard =
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Logs url", url)
clipboard.setPrimaryClip(clip)
val activity = requireActivity() as MainActivity
activity.showSnackBar(R.string.logs_url_copied_to_clipboard)
AppUtils.shareUploadedLogsUrl(activity, url)
}
}
} }
}) )
viewModel.updateAvailableEvent.observe(
viewLifecycleOwner,
{
it.consume { url ->
displayNewVersionAvailableDialog(url)
}
}
)
Log.i("[Dialer] Pending call transfer mode = ${sharedViewModel.pendingCallTransfer}") Log.i("[Dialer] Pending call transfer mode = ${sharedViewModel.pendingCallTransfer}")
viewModel.transferVisibility.value = sharedViewModel.pendingCallTransfer viewModel.transferVisibility.value = sharedViewModel.pendingCallTransfer
@ -204,11 +213,14 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
dialog.dismiss() dialog.dismiss()
} }
viewModel.showOkButton({ viewModel.showOkButton(
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) {
startActivity(browserIntent) val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
dialog.dismiss() startActivity(browserIntent)
}, getString(R.string.dialog_ok)) dialog.dismiss()
},
getString(R.string.dialog_ok)
)
dialog.show() dialog.show()
} }

View file

@ -62,42 +62,57 @@ abstract class MasterFragment<T : ViewDataBinding, U : SelectionListAdapter<*, *
// List selection // List selection
listSelectionViewModel = ViewModelProvider(this).get(ListTopBarViewModel::class.java) listSelectionViewModel = ViewModelProvider(this).get(ListTopBarViewModel::class.java)
listSelectionViewModel.isEditionEnabled.observe(viewLifecycleOwner, { listSelectionViewModel.isEditionEnabled.observe(
if (!it) listSelectionViewModel.onUnSelectAll() viewLifecycleOwner,
}) {
if (!it) listSelectionViewModel.onUnSelectAll()
listSelectionViewModel.selectAllEvent.observe(viewLifecycleOwner, {
it.consume {
listSelectionViewModel.onSelectAll(getItemCount() - 1)
} }
}) )
listSelectionViewModel.unSelectAllEvent.observe(viewLifecycleOwner, { listSelectionViewModel.selectAllEvent.observe(
it.consume { viewLifecycleOwner,
listSelectionViewModel.onUnSelectAll() {
} it.consume {
}) listSelectionViewModel.onSelectAll(getItemCount() - 1)
listSelectionViewModel.deleteSelectionEvent.observe(viewLifecycleOwner, {
it.consume {
val confirmationDialog = AppUtils.getStringWithPlural(dialogConfirmationMessageBeforeRemoval, listSelectionViewModel.selectedItems.value.orEmpty().size)
val viewModel = DialogViewModel(confirmationDialog)
val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel)
viewModel.showCancelButton {
dialog.dismiss()
listSelectionViewModel.isEditionEnabled.value = false
} }
viewModel.showDeleteButton({
delete()
dialog.dismiss()
listSelectionViewModel.isEditionEnabled.value = false
}, getString(R.string.dialog_delete))
dialog.show()
} }
}) )
listSelectionViewModel.unSelectAllEvent.observe(
viewLifecycleOwner,
{
it.consume {
listSelectionViewModel.onUnSelectAll()
}
}
)
listSelectionViewModel.deleteSelectionEvent.observe(
viewLifecycleOwner,
{
it.consume {
val confirmationDialog = AppUtils.getStringWithPlural(dialogConfirmationMessageBeforeRemoval, listSelectionViewModel.selectedItems.value.orEmpty().size)
val viewModel = DialogViewModel(confirmationDialog)
val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel)
viewModel.showCancelButton {
dialog.dismiss()
listSelectionViewModel.isEditionEnabled.value = false
}
viewModel.showDeleteButton(
{
delete()
dialog.dismiss()
listSelectionViewModel.isEditionEnabled.value = false
},
getString(R.string.dialog_delete)
)
dialog.show()
}
}
)
} }
private fun delete() { private fun delete() {

View file

@ -75,7 +75,8 @@ abstract class SecureFragment<T : ViewDataBinding> : GenericFragment<T>() {
val flags: Int = window.attributes.flags val flags: Int = window.attributes.flags
if ((enable && flags and WindowManager.LayoutParams.FLAG_SECURE != 0) || if ((enable && flags and WindowManager.LayoutParams.FLAG_SECURE != 0) ||
(!enable && flags and WindowManager.LayoutParams.FLAG_SECURE == 0)) { (!enable && flags and WindowManager.LayoutParams.FLAG_SECURE == 0)
) {
Log.d("[Secure Fragment] Secure flag is already ${if (enable) "enabled" else "disabled"}, skipping...") Log.d("[Secure Fragment] Secure flag is already ${if (enable) "enabled" else "disabled"}, skipping...")
return return
} }

View file

@ -49,13 +49,16 @@ class StatusFragment : GenericFragment<StatusFragmentBinding>() {
ViewModelProvider(this).get(SharedMainViewModel::class.java) ViewModelProvider(this).get(SharedMainViewModel::class.java)
} }
sharedViewModel.accountRemoved.observe(viewLifecycleOwner, { sharedViewModel.accountRemoved.observe(
Log.i("[Status Fragment] An account was removed, update default account state") viewLifecycleOwner,
val defaultAccount = coreContext.core.defaultAccount {
if (defaultAccount != null) { Log.i("[Status Fragment] An account was removed, update default account state")
viewModel.updateDefaultAccountRegistrationStatus(defaultAccount.state) val defaultAccount = coreContext.core.defaultAccount
if (defaultAccount != null) {
viewModel.updateDefaultAccountRegistrationStatus(defaultAccount.state)
}
} }
}) )
binding.setMenuClickListener { binding.setMenuClickListener {
sharedViewModel.toggleDrawerEvent.value = Event(true) sharedViewModel.toggleDrawerEvent.value = Event(true)

View file

@ -72,9 +72,12 @@ class CallLogsListAdapter(
// This is for item selection through ListTopBarFragment // This is for item selection through ListTopBarFragment
selectionListViewModel = selectionViewModel selectionListViewModel = selectionViewModel
selectionViewModel.isEditionEnabled.observe(viewLifecycleOwner, { selectionViewModel.isEditionEnabled.observe(
position = adapterPosition viewLifecycleOwner,
}) {
position = adapterPosition
}
)
setClickListener { setClickListener {
if (selectionViewModel.isEditionEnabled.value == true) { if (selectionViewModel.isEditionEnabled.value == true) {

View file

@ -94,40 +94,49 @@ class DetailCallLogFragment : GenericFragment<HistoryDetailFragmentBinding>() {
} }
} }
viewModel.startCallEvent.observe(viewLifecycleOwner, { viewModel.startCallEvent.observe(
it.consume { callLog -> viewLifecycleOwner,
val address = callLog.remoteAddress {
if (coreContext.core.callsNb > 0) { it.consume { callLog ->
Log.i("[History] Starting dialer with pre-filled URI ${address.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}") val address = callLog.remoteAddress
val args = Bundle() if (coreContext.core.callsNb > 0) {
args.putString("URI", address.asStringUriOnly()) Log.i("[History] Starting dialer with pre-filled URI ${address.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}")
args.putBoolean("Transfer", sharedViewModel.pendingCallTransfer) val args = Bundle()
args.putBoolean( args.putString("URI", address.asStringUriOnly())
"SkipAutoCallStart", args.putBoolean("Transfer", sharedViewModel.pendingCallTransfer)
true args.putBoolean(
) // If auto start call setting is enabled, ignore it "SkipAutoCallStart",
navigateToDialer(args) true
} else { ) // If auto start call setting is enabled, ignore it
val localAddress = callLog.localAddress navigateToDialer(args)
coreContext.startCall(address, localAddress = localAddress) } else {
val localAddress = callLog.localAddress
coreContext.startCall(address, localAddress = localAddress)
}
} }
} }
}) )
viewModel.chatRoomCreatedEvent.observe(viewLifecycleOwner, { viewModel.chatRoomCreatedEvent.observe(
it.consume { chatRoom -> viewLifecycleOwner,
val args = Bundle() {
args.putString("LocalSipUri", chatRoom.localAddress.asStringUriOnly()) it.consume { chatRoom ->
args.putString("RemoteSipUri", chatRoom.peerAddress.asStringUriOnly()) val args = Bundle()
navigateToChatRoom(args) args.putString("LocalSipUri", chatRoom.localAddress.asStringUriOnly())
args.putString("RemoteSipUri", chatRoom.peerAddress.asStringUriOnly())
navigateToChatRoom(args)
}
} }
}) )
viewModel.onErrorEvent.observe(viewLifecycleOwner, { viewModel.onErrorEvent.observe(
it.consume { messageResourceId -> viewLifecycleOwner,
(activity as MainActivity).showSnackBar(messageResourceId) {
it.consume { messageResourceId ->
(activity as MainActivity).showSnackBar(messageResourceId)
}
} }
}) )
} }
override fun goBack() { override fun goBack() {

View file

@ -83,25 +83,31 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
view.doOnPreDraw { sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable } view.doOnPreDraw { sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable }
sharedViewModel.closeSlidingPaneEvent.observe(viewLifecycleOwner, { sharedViewModel.closeSlidingPaneEvent.observe(
it.consume { viewLifecycleOwner,
if (!binding.slidingPane.closePane()) { {
goBack() it.consume {
} if (!binding.slidingPane.closePane()) {
} goBack()
})
sharedViewModel.layoutChangedEvent.observe(viewLifecycleOwner, {
it.consume {
sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable
if (binding.slidingPane.isSlideable) {
val navHostFragment = childFragmentManager.findFragmentById(R.id.history_nav_container) as NavHostFragment
if (navHostFragment.navController.currentDestination?.id == R.id.emptyCallHistoryFragment) {
Log.i("[History] Foldable device has been folded, closing side pane with empty fragment")
binding.slidingPane.closePane()
} }
} }
} }
}) )
sharedViewModel.layoutChangedEvent.observe(
viewLifecycleOwner,
{
it.consume {
sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable
if (binding.slidingPane.isSlideable) {
val navHostFragment = childFragmentManager.findFragmentById(R.id.history_nav_container) as NavHostFragment
if (navHostFragment.navController.currentDestination?.id == R.id.emptyCallHistoryFragment) {
Log.i("[History] Foldable device has been folded, closing side pane with empty fragment")
binding.slidingPane.closePane()
}
}
}
}
)
binding.slidingPane.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED binding.slidingPane.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
/*binding.slidingPane.addPanelSlideListener(object : SlidingPaneLayout.PanelSlideListener { /*binding.slidingPane.addPanelSlideListener(object : SlidingPaneLayout.PanelSlideListener {
override fun onPanelSlide(panel: View, slideOffset: Float) { } override fun onPanelSlide(panel: View, slideOffset: Float) { }
@ -156,16 +162,20 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
dialog.dismiss() dialog.dismiss()
} }
viewModel.showDeleteButton({ viewModel.showDeleteButton(
val deletedCallGroup = adapter.currentList[viewHolder.adapterPosition] {
listViewModel.deleteCallLogGroup(deletedCallGroup) val deletedCallGroup = adapter.currentList[viewHolder.adapterPosition]
if (!binding.slidingPane.isSlideable && listViewModel.deleteCallLogGroup(deletedCallGroup)
deletedCallGroup.lastCallLog.callId == sharedViewModel.selectedCallLogGroup.value?.lastCallLog?.callId) { if (!binding.slidingPane.isSlideable &&
Log.i("[History] Currently displayed history has been deleted, removing detail fragment") deletedCallGroup.lastCallLog.callId == sharedViewModel.selectedCallLogGroup.value?.lastCallLog?.callId
clearDisplayedCallHistory() ) {
} Log.i("[History] Currently displayed history has been deleted, removing detail fragment")
dialog.dismiss() clearDisplayedCallHistory()
}, getString(R.string.dialog_delete)) }
dialog.dismiss()
},
getString(R.string.dialog_delete)
)
dialog.show() dialog.show()
} }
@ -180,56 +190,74 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter)
binding.callLogsList.addItemDecoration(headerItemDecoration) binding.callLogsList.addItemDecoration(headerItemDecoration)
listViewModel.callLogs.observe(viewLifecycleOwner, { callLogs -> listViewModel.callLogs.observe(
if (listViewModel.missedCallLogsSelected.value == false) { viewLifecycleOwner,
adapter.submitList(callLogs) { callLogs ->
} if (listViewModel.missedCallLogsSelected.value == false) {
}) adapter.submitList(callLogs)
listViewModel.missedCallLogs.observe(viewLifecycleOwner, { callLogs ->
if (listViewModel.missedCallLogsSelected.value == true) {
adapter.submitList(callLogs)
}
})
listViewModel.missedCallLogsSelected.observe(viewLifecycleOwner, {
if (it) {
adapter.submitList(listViewModel.missedCallLogs.value)
} else {
adapter.submitList(listViewModel.callLogs.value)
}
})
listViewModel.contactsUpdatedEvent.observe(viewLifecycleOwner, {
it.consume {
adapter.notifyDataSetChanged()
}
})
adapter.selectedCallLogEvent.observe(viewLifecycleOwner, {
it.consume { callLog ->
sharedViewModel.selectedCallLogGroup.value = callLog
binding.slidingPane.openPane()
navigateToCallHistory()
}
})
adapter.startCallToEvent.observe(viewLifecycleOwner, {
it.consume { callLogGroup ->
val remoteAddress = callLogGroup.lastCallLog.remoteAddress
if (coreContext.core.callsNb > 0) {
Log.i("[History] Starting dialer with pre-filled URI ${remoteAddress.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}")
val args = Bundle()
args.putString("URI", remoteAddress.asStringUriOnly())
args.putBoolean("Transfer", sharedViewModel.pendingCallTransfer)
args.putBoolean("SkipAutoCallStart", true) // If auto start call setting is enabled, ignore it
navigateToDialer(args)
} else {
val localAddress = callLogGroup.lastCallLog.localAddress
coreContext.startCall(remoteAddress, localAddress = localAddress)
} }
} }
}) )
listViewModel.missedCallLogs.observe(
viewLifecycleOwner,
{ callLogs ->
if (listViewModel.missedCallLogsSelected.value == true) {
adapter.submitList(callLogs)
}
}
)
listViewModel.missedCallLogsSelected.observe(
viewLifecycleOwner,
{
if (it) {
adapter.submitList(listViewModel.missedCallLogs.value)
} else {
adapter.submitList(listViewModel.callLogs.value)
}
}
)
listViewModel.contactsUpdatedEvent.observe(
viewLifecycleOwner,
{
it.consume {
adapter.notifyDataSetChanged()
}
}
)
adapter.selectedCallLogEvent.observe(
viewLifecycleOwner,
{
it.consume { callLog ->
sharedViewModel.selectedCallLogGroup.value = callLog
binding.slidingPane.openPane()
navigateToCallHistory()
}
}
)
adapter.startCallToEvent.observe(
viewLifecycleOwner,
{
it.consume { callLogGroup ->
val remoteAddress = callLogGroup.lastCallLog.remoteAddress
if (coreContext.core.callsNb > 0) {
Log.i("[History] Starting dialer with pre-filled URI ${remoteAddress.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}")
val args = Bundle()
args.putString("URI", remoteAddress.asStringUriOnly())
args.putBoolean("Transfer", sharedViewModel.pendingCallTransfer)
args.putBoolean("SkipAutoCallStart", true) // If auto start call setting is enabled, ignore it
navigateToDialer(args)
} else {
val localAddress = callLogGroup.lastCallLog.localAddress
coreContext.startCall(remoteAddress, localAddress = localAddress)
}
}
}
)
binding.setAllCallLogsToggleClickListener { binding.setAllCallLogsToggleClickListener {
listViewModel.missedCallLogsSelected.value = false listViewModel.missedCallLogsSelected.value = false

View file

@ -109,7 +109,8 @@ class CallLogsListViewModel : ViewModel() {
if (previousCallLogGroup == null) { if (previousCallLogGroup == null) {
previousCallLogGroup = GroupedCallLogData(callLog) previousCallLogGroup = GroupedCallLogData(callLog)
} else if (previousCallLogGroup.lastCallLog.localAddress.weakEqual(callLog.localAddress) && } else if (previousCallLogGroup.lastCallLog.localAddress.weakEqual(callLog.localAddress) &&
previousCallLogGroup.lastCallLog.remoteAddress.weakEqual(callLog.remoteAddress)) { previousCallLogGroup.lastCallLog.remoteAddress.weakEqual(callLog.remoteAddress)
) {
if (TimestampUtils.isSameDay(previousCallLogGroup.lastCallLog.startDate, callLog.startDate)) { if (TimestampUtils.isSameDay(previousCallLogGroup.lastCallLog.startDate, callLog.startDate)) {
previousCallLogGroup.callLogs.add(callLog) previousCallLogGroup.callLogs.add(callLog)
previousCallLogGroup.lastCallLog = callLog previousCallLogGroup.lastCallLog = callLog
@ -126,7 +127,8 @@ class CallLogsListViewModel : ViewModel() {
if (previousMissedCallLogGroup == null) { if (previousMissedCallLogGroup == null) {
previousMissedCallLogGroup = GroupedCallLogData(callLog) previousMissedCallLogGroup = GroupedCallLogData(callLog)
} else if (previousMissedCallLogGroup.lastCallLog.localAddress.weakEqual(callLog.localAddress) && } else if (previousMissedCallLogGroup.lastCallLog.localAddress.weakEqual(callLog.localAddress) &&
previousMissedCallLogGroup.lastCallLog.remoteAddress.weakEqual(callLog.remoteAddress)) { previousMissedCallLogGroup.lastCallLog.remoteAddress.weakEqual(callLog.remoteAddress)
) {
if (TimestampUtils.isSameDay(previousMissedCallLogGroup.lastCallLog.startDate, callLog.startDate)) { if (TimestampUtils.isSameDay(previousMissedCallLogGroup.lastCallLog.startDate, callLog.startDate)) {
previousMissedCallLogGroup.callLogs.add(callLog) previousMissedCallLogGroup.callLogs.add(callLog)
previousMissedCallLogGroup.lastCallLog = callLog previousMissedCallLogGroup.lastCallLog = callLog

View file

@ -68,9 +68,12 @@ class RecordingsFragment : MasterFragment<RecordingsFragmentBinding, RecordingsL
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter)
binding.recordingsList.addItemDecoration(headerItemDecoration) binding.recordingsList.addItemDecoration(headerItemDecoration)
viewModel.recordingsList.observe(viewLifecycleOwner, { recordings -> viewModel.recordingsList.observe(
adapter.submitList(recordings) viewLifecycleOwner,
}) { recordings ->
adapter.submitList(recordings)
}
)
binding.setBackClickListener { goBack() } binding.setBackClickListener { goBack() }

View file

@ -59,7 +59,8 @@ class AccountSettingsFragment : GenericFragment<SettingsAccountFragmentBinding>(
try { try {
viewModel = ViewModelProvider(this, AccountSettingsViewModelFactory(identity)).get( viewModel = ViewModelProvider(this, AccountSettingsViewModelFactory(identity)).get(
AccountSettingsViewModel::class.java) AccountSettingsViewModel::class.java
)
} catch (nsee: NoSuchElementException) { } catch (nsee: NoSuchElementException) {
Log.e("[Account Settings] Failed to find Account object, aborting!") Log.e("[Account Settings] Failed to find Account object, aborting!")
goBack() goBack()
@ -69,27 +70,33 @@ class AccountSettingsFragment : GenericFragment<SettingsAccountFragmentBinding>(
binding.setBackClickListener { goBack() } binding.setBackClickListener { goBack() }
viewModel.linkPhoneNumberEvent.observe(viewLifecycleOwner, { viewModel.linkPhoneNumberEvent.observe(
it.consume { viewLifecycleOwner,
val authInfo = viewModel.account.findAuthInfo() {
if (authInfo == null) { it.consume {
Log.e("[Account Settings] Failed to find auth info for account ${viewModel.account}") val authInfo = viewModel.account.findAuthInfo()
} else { if (authInfo == null) {
val args = Bundle() Log.e("[Account Settings] Failed to find auth info for account ${viewModel.account}")
args.putString("Username", authInfo.username) } else {
args.putString("Password", authInfo.password) val args = Bundle()
args.putString("HA1", authInfo.ha1) args.putString("Username", authInfo.username)
navigateToPhoneLinking(args) args.putString("Password", authInfo.password)
args.putString("HA1", authInfo.ha1)
navigateToPhoneLinking(args)
}
} }
} }
}) )
viewModel.accountRemovedEvent.observe(viewLifecycleOwner, { viewModel.accountRemovedEvent.observe(
it.consume { viewLifecycleOwner,
sharedViewModel.accountRemoved.value = true {
goBack() it.consume {
sharedViewModel.accountRemoved.value = true
goBack()
}
} }
}) )
} }
override fun goBack() { override fun goBack() {

View file

@ -61,77 +61,104 @@ class AdvancedSettingsFragment : GenericFragment<SettingsAdvancedFragmentBinding
binding.setBackClickListener { goBack() } binding.setBackClickListener { goBack() }
viewModel.uploadFinishedEvent.observe(viewLifecycleOwner, { viewModel.uploadFinishedEvent.observe(
it.consume { url -> viewLifecycleOwner,
val clipboard = {
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager it.consume { url ->
val clip = ClipData.newPlainText("Logs url", url) val clipboard =
clipboard.setPrimaryClip(clip) requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Logs url", url)
clipboard.setPrimaryClip(clip)
val activity = requireActivity() as MainActivity val activity = requireActivity() as MainActivity
activity.showSnackBar(R.string.logs_url_copied_to_clipboard) activity.showSnackBar(R.string.logs_url_copied_to_clipboard)
AppUtils.shareUploadedLogsUrl(activity, url) AppUtils.shareUploadedLogsUrl(activity, url)
}
} }
}) )
viewModel.uploadErrorEvent.observe(viewLifecycleOwner, { viewModel.uploadErrorEvent.observe(
it.consume { viewLifecycleOwner,
val activity = requireActivity() as MainActivity {
activity.showSnackBar(R.string.logs_upload_failure) it.consume {
val activity = requireActivity() as MainActivity
activity.showSnackBar(R.string.logs_upload_failure)
}
} }
}) )
viewModel.resetCompleteEvent.observe(viewLifecycleOwner, { viewModel.resetCompleteEvent.observe(
it.consume { viewLifecycleOwner,
val activity = requireActivity() as MainActivity {
activity.showSnackBar(R.string.logs_reset_complete) it.consume {
val activity = requireActivity() as MainActivity
activity.showSnackBar(R.string.logs_reset_complete)
}
} }
}) )
viewModel.setNightModeEvent.observe(viewLifecycleOwner, { viewModel.setNightModeEvent.observe(
it.consume { value -> viewLifecycleOwner,
AppCompatDelegate.setDefaultNightMode( {
when (value) { it.consume { value ->
0 -> AppCompatDelegate.MODE_NIGHT_NO AppCompatDelegate.setDefaultNightMode(
1 -> AppCompatDelegate.MODE_NIGHT_YES when (value) {
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM 0 -> AppCompatDelegate.MODE_NIGHT_NO
} 1 -> AppCompatDelegate.MODE_NIGHT_YES
) else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
)
}
} }
}) )
viewModel.backgroundModeEnabled.value = !DeviceUtils.isAppUserRestricted(requireContext()) viewModel.backgroundModeEnabled.value = !DeviceUtils.isAppUserRestricted(requireContext())
viewModel.goToBatterySettingsEvent.observe(viewLifecycleOwner, { it.consume { viewModel.goToBatterySettingsEvent.observe(
try { viewLifecycleOwner,
val intent = Intent("android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS") {
startActivity(intent) it.consume {
} catch (anfe: ActivityNotFoundException) { try {
Log.e("[Advanced Settings] ActivityNotFound exception: ", anfe) val intent = Intent("android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS")
} startActivity(intent)
} }) } catch (anfe: ActivityNotFoundException) {
Log.e("[Advanced Settings] ActivityNotFound exception: ", anfe)
viewModel.powerManagerSettingsVisibility.value = PowerManagerUtils.getDevicePowerManagerIntent(requireContext()) != null }
viewModel.goToPowerManagerSettingsEvent.observe(viewLifecycleOwner, { it.consume {
val intent = PowerManagerUtils.getDevicePowerManagerIntent(requireActivity())
if (intent != null) {
try {
startActivity(intent)
} catch (se: SecurityException) {
Log.e("[Advanced Settings] Security exception: ", se)
} }
} }
} }) )
viewModel.goToAndroidSettingsEvent.observe(viewLifecycleOwner, { it.consume { viewModel.powerManagerSettingsVisibility.value = PowerManagerUtils.getDevicePowerManagerIntent(requireContext()) != null
val intent = Intent() viewModel.goToPowerManagerSettingsEvent.observe(
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS viewLifecycleOwner,
intent.addCategory(Intent.CATEGORY_DEFAULT) {
intent.data = Uri.parse("package:${requireContext().packageName}") it.consume {
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) val intent = PowerManagerUtils.getDevicePowerManagerIntent(requireActivity())
ContextCompat.startActivity(requireContext(), intent, null) if (intent != null) {
} }) try {
startActivity(intent)
} catch (se: SecurityException) {
Log.e("[Advanced Settings] Security exception: ", se)
}
}
}
}
)
viewModel.goToAndroidSettingsEvent.observe(
viewLifecycleOwner,
{
it.consume {
val intent = Intent()
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
intent.addCategory(Intent.CATEGORY_DEFAULT)
intent.data = Uri.parse("package:${requireContext().packageName}")
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
ContextCompat.startActivity(requireContext(), intent, null)
}
}
)
} }
override fun goBack() { override fun goBack() {

View file

@ -60,19 +60,25 @@ class AudioSettingsFragment : GenericFragment<SettingsAudioFragmentBinding>() {
binding.setBackClickListener { goBack() } binding.setBackClickListener { goBack() }
viewModel.askAudioRecordPermissionForEchoCancellerCalibrationEvent.observe(viewLifecycleOwner, { viewModel.askAudioRecordPermissionForEchoCancellerCalibrationEvent.observe(
it.consume { viewLifecycleOwner,
Log.i("[Audio Settings] Asking for RECORD_AUDIO permission for echo canceller calibration") {
requestPermissions(arrayOf(android.Manifest.permission.RECORD_AUDIO), 1) it.consume {
Log.i("[Audio Settings] Asking for RECORD_AUDIO permission for echo canceller calibration")
requestPermissions(arrayOf(android.Manifest.permission.RECORD_AUDIO), 1)
}
} }
}) )
viewModel.askAudioRecordPermissionForEchoTesterEvent.observe(viewLifecycleOwner, { viewModel.askAudioRecordPermissionForEchoTesterEvent.observe(
it.consume { viewLifecycleOwner,
Log.i("[Audio Settings] Asking for RECORD_AUDIO permission for echo tester") {
requestPermissions(arrayOf(android.Manifest.permission.RECORD_AUDIO), 2) it.consume {
Log.i("[Audio Settings] Asking for RECORD_AUDIO permission for echo tester")
requestPermissions(arrayOf(android.Manifest.permission.RECORD_AUDIO), 2)
}
} }
}) )
initAudioCodecsList() initAudioCodecsList()
@ -107,11 +113,14 @@ class AudioSettingsFragment : GenericFragment<SettingsAudioFragmentBinding>() {
binding.setVariable(BR.title, payload.mimeType) binding.setVariable(BR.title, payload.mimeType)
binding.setVariable(BR.subtitle, "${payload.clockRate} Hz") binding.setVariable(BR.subtitle, "${payload.clockRate} Hz")
binding.setVariable(BR.checked, payload.enabled()) binding.setVariable(BR.checked, payload.enabled())
binding.setVariable(BR.listener, object : SettingListenerStub() { binding.setVariable(
override fun onBoolValueChanged(newValue: Boolean) { BR.listener,
payload.enable(newValue) object : SettingListenerStub() {
override fun onBoolValueChanged(newValue: Boolean) {
payload.enable(newValue)
}
} }
}) )
binding.lifecycleOwner = this binding.lifecycleOwner = this
list.add(binding) list.add(binding)
} }

View file

@ -57,31 +57,39 @@ class CallSettingsFragment : GenericFragment<SettingsCallFragmentBinding>() {
binding.setBackClickListener { goBack() } binding.setBackClickListener { goBack() }
viewModel.systemWideOverlayEnabledEvent.observe(viewLifecycleOwner, { viewModel.systemWideOverlayEnabledEvent.observe(
it.consume { viewLifecycleOwner,
if (!Compatibility.canDrawOverlay(requireContext())) { {
val intent = Intent("android.settings.action.MANAGE_OVERLAY_PERMISSION", Uri.parse("package:${requireContext().packageName}")) it.consume {
startActivityForResult(intent, 0) if (!Compatibility.canDrawOverlay(requireContext())) {
val intent = Intent("android.settings.action.MANAGE_OVERLAY_PERMISSION", Uri.parse("package:${requireContext().packageName}"))
startActivityForResult(intent, 0)
}
} }
} }
}) )
viewModel.goToAndroidNotificationSettingsEvent.observe(viewLifecycleOwner, { it.consume { viewModel.goToAndroidNotificationSettingsEvent.observe(
if (Build.VERSION.SDK_INT >= Version.API26_O_80) { viewLifecycleOwner,
val i = Intent() {
i.action = Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS it.consume {
i.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName) if (Build.VERSION.SDK_INT >= Version.API26_O_80) {
i.putExtra( val i = Intent()
Settings.EXTRA_CHANNEL_ID, i.action = Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS
getString(R.string.notification_channel_service_id) i.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
) i.putExtra(
i.addCategory(Intent.CATEGORY_DEFAULT) Settings.EXTRA_CHANNEL_ID,
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) getString(R.string.notification_channel_service_id)
i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) )
i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) i.addCategory(Intent.CATEGORY_DEFAULT)
startActivity(i) i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
startActivity(i)
}
}
} }
} }) )
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

View file

@ -56,32 +56,40 @@ class ChatSettingsFragment : GenericFragment<SettingsChatFragmentBinding>() {
binding.setBackClickListener { goBack() } binding.setBackClickListener { goBack() }
viewModel.launcherShortcutsEvent.observe(viewLifecycleOwner, { viewModel.launcherShortcutsEvent.observe(
it.consume { newValue -> viewLifecycleOwner,
if (newValue) { {
Compatibility.createShortcutsToChatRooms(requireContext()) it.consume { newValue ->
} else { if (newValue) {
Compatibility.removeShortcuts(requireContext()) Compatibility.createShortcutsToChatRooms(requireContext())
} else {
Compatibility.removeShortcuts(requireContext())
}
} }
} }
}) )
viewModel.goToAndroidNotificationSettingsEvent.observe(viewLifecycleOwner, { it.consume { viewModel.goToAndroidNotificationSettingsEvent.observe(
if (Build.VERSION.SDK_INT >= Version.API26_O_80) { viewLifecycleOwner,
val i = Intent() {
i.action = Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS it.consume {
i.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName) if (Build.VERSION.SDK_INT >= Version.API26_O_80) {
i.putExtra( val i = Intent()
Settings.EXTRA_CHANNEL_ID, i.action = Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS
getString(R.string.notification_channel_chat_id) i.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
) i.putExtra(
i.addCategory(Intent.CATEGORY_DEFAULT) Settings.EXTRA_CHANNEL_ID,
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) getString(R.string.notification_channel_chat_id)
i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) )
i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) i.addCategory(Intent.CATEGORY_DEFAULT)
startActivity(i) i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
startActivity(i)
}
}
} }
} }) )
} }
override fun goBack() { override fun goBack() {

View file

@ -57,25 +57,31 @@ class ContactsSettingsFragment : GenericFragment<SettingsContactsFragmentBinding
binding.setBackClickListener { goBack() } binding.setBackClickListener { goBack() }
viewModel.launcherShortcutsEvent.observe(viewLifecycleOwner, { viewModel.launcherShortcutsEvent.observe(
it.consume { newValue -> viewLifecycleOwner,
if (newValue) { {
Compatibility.createShortcutsToContacts(requireContext()) it.consume { newValue ->
} else { if (newValue) {
Compatibility.removeShortcuts(requireContext()) Compatibility.createShortcutsToContacts(requireContext())
if (corePreferences.chatRoomShortcuts) { } else {
Compatibility.createShortcutsToChatRooms(requireContext()) Compatibility.removeShortcuts(requireContext())
if (corePreferences.chatRoomShortcuts) {
Compatibility.createShortcutsToChatRooms(requireContext())
}
} }
} }
} }
}) )
viewModel.askWriteContactsPermissionForPresenceStorageEvent.observe(viewLifecycleOwner, { viewModel.askWriteContactsPermissionForPresenceStorageEvent.observe(
it.consume { viewLifecycleOwner,
Log.i("[Contacts Settings] Asking for WRITE_CONTACTS permission to be able to store presence") {
requestPermissions(arrayOf(android.Manifest.permission.WRITE_CONTACTS), 1) it.consume {
Log.i("[Contacts Settings] Asking for WRITE_CONTACTS permission to be able to store presence")
requestPermissions(arrayOf(android.Manifest.permission.WRITE_CONTACTS), 1)
}
} }
}) )
if (!PermissionHelper.required(requireContext()).hasReadContactsPermission()) { if (!PermissionHelper.required(requireContext()).hasReadContactsPermission()) {
Log.i("[Contacts Settings] Asking for READ_CONTACTS permission") Log.i("[Contacts Settings] Asking for READ_CONTACTS permission")

View file

@ -57,25 +57,31 @@ class SettingsFragment : SecureFragment<SettingsFragmentBinding>() {
view.doOnPreDraw { sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable } view.doOnPreDraw { sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable }
sharedViewModel.closeSlidingPaneEvent.observe(viewLifecycleOwner, { sharedViewModel.closeSlidingPaneEvent.observe(
it.consume { viewLifecycleOwner,
if (!binding.slidingPane.closePane()) { {
goBack() it.consume {
} if (!binding.slidingPane.closePane()) {
} goBack()
})
sharedViewModel.layoutChangedEvent.observe(viewLifecycleOwner, {
it.consume {
sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable
if (binding.slidingPane.isSlideable) {
val navHostFragment = childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment
if (navHostFragment.navController.currentDestination?.id == R.id.emptySettingsFragment) {
Log.i("[Settings] Foldable device has been folded, closing side pane with empty fragment")
binding.slidingPane.closePane()
} }
} }
} }
}) )
sharedViewModel.layoutChangedEvent.observe(
viewLifecycleOwner,
{
it.consume {
sharedViewModel.canSlidingPaneBeClosed.value = binding.slidingPane.isSlideable
if (binding.slidingPane.isSlideable) {
val navHostFragment = childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment
if (navHostFragment.navController.currentDestination?.id == R.id.emptySettingsFragment) {
Log.i("[Settings] Foldable device has been folded, closing side pane with empty fragment")
binding.slidingPane.closePane()
}
}
}
}
)
binding.slidingPane.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED binding.slidingPane.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
/* End of shared view model & sliding pane related */ /* End of shared view model & sliding pane related */
@ -85,10 +91,13 @@ class SettingsFragment : SecureFragment<SettingsFragmentBinding>() {
binding.setBackClickListener { goBack() } binding.setBackClickListener { goBack() }
sharedViewModel.accountRemoved.observe(viewLifecycleOwner, { sharedViewModel.accountRemoved.observe(
Log.i("[Settings] Account removed, update accounts list") viewLifecycleOwner,
viewModel.updateAccountsList() {
}) Log.i("[Settings] Account removed, update accounts list")
viewModel.updateAccountsList()
}
)
val identity = arguments?.getString("Identity") val identity = arguments?.getString("Identity")
if (identity != null) { if (identity != null) {

View file

@ -93,15 +93,18 @@ class VideoSettingsFragment : GenericFragment<SettingsVideoFragmentBinding>() {
binding.setVariable(BR.text_subtitle, "") binding.setVariable(BR.text_subtitle, "")
binding.setVariable(BR.defaultValue, payload.recvFmtp) binding.setVariable(BR.defaultValue, payload.recvFmtp)
binding.setVariable(BR.checked, payload.enabled()) binding.setVariable(BR.checked, payload.enabled())
binding.setVariable(BR.listener, object : SettingListenerStub() { binding.setVariable(
override fun onBoolValueChanged(newValue: Boolean) { BR.listener,
payload.enable(newValue) object : SettingListenerStub() {
} override fun onBoolValueChanged(newValue: Boolean) {
payload.enable(newValue)
}
override fun onTextValueChanged(newValue: String) { override fun onTextValueChanged(newValue: String) {
payload.recvFmtp = newValue payload.recvFmtp = newValue
}
} }
}) )
binding.lifecycleOwner = this binding.lifecycleOwner = this
list.add(binding) list.add(binding)
} }

View file

@ -64,8 +64,8 @@ class CallSettingsViewModel : GenericSettingsViewModel() {
val fullScreenListener = object : SettingListenerStub() { val fullScreenListener = object : SettingListenerStub() {
override fun onBoolValueChanged(newValue: Boolean) { override fun onBoolValueChanged(newValue: Boolean) {
prefs.fullScreenCallUI = newValue prefs.fullScreenCallUI = newValue
} }
} }
val fullScreen = MutableLiveData<Boolean>() val fullScreen = MutableLiveData<Boolean>()

View file

@ -67,10 +67,13 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
ViewModelProvider(this).get(SharedMainViewModel::class.java) ViewModelProvider(this).get(SharedMainViewModel::class.java)
} }
sharedViewModel.accountRemoved.observe(viewLifecycleOwner, { sharedViewModel.accountRemoved.observe(
Log.i("[Side Menu] Account removed, update accounts list") viewLifecycleOwner,
viewModel.updateAccountsList() {
}) Log.i("[Side Menu] Account removed, update accounts list")
viewModel.updateAccountsList()
}
)
viewModel.accountsSettingsListener = object : SettingListenerStub() { viewModel.accountsSettingsListener = object : SettingListenerStub() {
override fun onAccountClicked(identity: String) { override fun onAccountClicked(identity: String) {

View file

@ -49,8 +49,8 @@ class CallOverlayViewModel : ViewModel() {
init { init {
displayCallOverlay.value = corePreferences.showCallOverlay && displayCallOverlay.value = corePreferences.showCallOverlay &&
!corePreferences.systemWideCallOverlay && !corePreferences.systemWideCallOverlay &&
coreContext.core.callsNb > 0 coreContext.core.callsNb > 0
coreContext.core.addListener(listener) coreContext.core.addListener(listener)
} }

View file

@ -315,7 +315,8 @@ class ContactsManager(private val context: Context) {
val accounts = accountManager.getAccountsByType(context.getString(R.string.sync_account_type)) val accounts = accountManager.getAccountsByType(context.getString(R.string.sync_account_type))
if (accounts.isEmpty()) { if (accounts.isEmpty()) {
val newAccount = Account( val newAccount = Account(
context.getString(R.string.sync_account_name), context.getString( context.getString(R.string.sync_account_name),
context.getString(
R.string.sync_account_type R.string.sync_account_type
) )
) )

View file

@ -178,10 +178,13 @@ class CoreContext(val context: Context, coreConfig: Config) {
} else { } else {
Log.i("[Context] Scheduling auto answering in $autoAnswerDelay milliseconds") Log.i("[Context] Scheduling auto answering in $autoAnswerDelay milliseconds")
val mainThreadHandler = Handler(Looper.getMainLooper()) val mainThreadHandler = Handler(Looper.getMainLooper())
mainThreadHandler.postDelayed({ mainThreadHandler.postDelayed(
Log.w("[Context] Auto answering call") {
answerCall(call) Log.w("[Context] Auto answering call")
}, autoAnswerDelay.toLong()) answerCall(call)
},
autoAnswerDelay.toLong()
)
} }
} }
} else if (state == Call.State.OutgoingInit) { } else if (state == Call.State.OutgoingInit) {
@ -214,7 +217,8 @@ class CoreContext(val context: Context, coreConfig: Config) {
// Do not turn speaker on when video is enabled if headset or bluetooth is used // Do not turn speaker on when video is enabled if headset or bluetooth is used
if (!AudioRouteUtils.isHeadsetAudioRouteAvailable() && !AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed( if (!AudioRouteUtils.isHeadsetAudioRouteAvailable() && !AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed(
call call
)) { )
) {
Log.i("[Context] Video enabled and no wired headset not bluetooth in use, routing audio to speaker") Log.i("[Context] Video enabled and no wired headset not bluetooth in use, routing audio to speaker")
AudioRouteUtils.routeAudioToSpeaker(call) AudioRouteUtils.routeAudioToSpeaker(call)
} }
@ -237,8 +241,9 @@ class CoreContext(val context: Context, coreConfig: Config) {
} }
callErrorMessageResourceId.value = Event(message) callErrorMessageResourceId.value = Event(message)
} else if (state == Call.State.End && } else if (state == Call.State.End &&
call.dir == Call.Dir.Outgoing && call.dir == Call.Dir.Outgoing &&
call.errorInfo.reason == Reason.Declined) { call.errorInfo.reason == Reason.Declined
) {
Log.i("[Context] Call has been declined") Log.i("[Context] Call has been declined")
val message = context.getString(R.string.call_error_declined) val message = context.getString(R.string.call_error_declined)
callErrorMessageResourceId.value = Event(message) callErrorMessageResourceId.value = Event(message)
@ -597,7 +602,8 @@ class CoreContext(val context: Context, coreConfig: Config) {
} }
MotionEvent.ACTION_UP -> { MotionEvent.ACTION_UP -> {
if (abs(overlayX - params.x) < CorePreferences.OVERLAY_CLICK_SENSITIVITY && if (abs(overlayX - params.x) < CorePreferences.OVERLAY_CLICK_SENSITIVITY &&
abs(overlayY - params.y) < CorePreferences.OVERLAY_CLICK_SENSITIVITY) { abs(overlayY - params.y) < CorePreferences.OVERLAY_CLICK_SENSITIVITY
) {
view.performClick() view.performClick()
} }
overlayX = params.x.toFloat() overlayX = params.x.toFloat()

View file

@ -151,8 +151,9 @@ class NotificationsManager(private val context: Context) {
} }
if (message.contents.find { content -> if (message.contents.find { content ->
content.isFile or content.isFileTransfer or content.isText content.isFile or content.isFileTransfer or content.isText
} == null) { } == null
) {
Log.w("[Notifications Manager] Received message with neither text or attachment, do not notify") Log.w("[Notifications Manager] Received message with neither text or attachment, do not notify")
return return
} }
@ -291,7 +292,7 @@ class NotificationsManager(private val context: Context) {
currentForegroundServiceNotificationId = notificationId currentForegroundServiceNotificationId = notificationId
service?.startForeground(currentForegroundServiceNotificationId, callNotification) service?.startForeground(currentForegroundServiceNotificationId, callNotification)
} }
} }
private fun stopForegroundNotification() { private fun stopForegroundNotification() {
if (service != null) { if (service != null) {
@ -462,7 +463,8 @@ class NotificationsManager(private val context: Context) {
.createPendingIntent() .createPendingIntent()
val builder = NotificationCompat.Builder( val builder = NotificationCompat.Builder(
context, context.getString(R.string.notification_channel_missed_call_id)) context, context.getString(R.string.notification_channel_missed_call_id)
)
.setContentTitle(context.getString(R.string.missed_call_notification_title)) .setContentTitle(context.getString(R.string.missed_call_notification_title))
.setContentText(body) .setContentText(body)
.setSmallIcon(R.drawable.topbar_missed_call_notification) .setSmallIcon(R.drawable.topbar_missed_call_notification)
@ -546,7 +548,8 @@ class NotificationsManager(private val context: Context) {
val pendingIntent = PendingIntent.getActivity(context, 0, callNotificationIntent, PendingIntent.FLAG_UPDATE_CURRENT) val pendingIntent = PendingIntent.getActivity(context, 0, callNotificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val builder = NotificationCompat.Builder( val builder = NotificationCompat.Builder(
context, channelToUse) context, channelToUse
)
.setContentTitle(contact?.fullName ?: displayName) .setContentTitle(contact?.fullName ?: displayName)
.setContentText(context.getString(stringResourceId)) .setContentText(context.getString(stringResourceId))
.setSmallIcon(iconResourceId) .setSmallIcon(iconResourceId)

View file

@ -106,7 +106,8 @@ class AudioRouteUtils {
fun isBluetoothAudioRouteAvailable(): Boolean { fun isBluetoothAudioRouteAvailable(): Boolean {
for (audioDevice in coreContext.core.audioDevices) { for (audioDevice in coreContext.core.audioDevices) {
if (audioDevice.type == AudioDevice.Type.Bluetooth && if (audioDevice.type == AudioDevice.Type.Bluetooth &&
audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) { audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityPlay)
) {
Log.i("[Audio Route Helper] Found bluetooth audio device [${audioDevice.deviceName}]") Log.i("[Audio Route Helper] Found bluetooth audio device [${audioDevice.deviceName}]")
return true return true
} }
@ -117,7 +118,8 @@ class AudioRouteUtils {
fun isHeadsetAudioRouteAvailable(): Boolean { fun isHeadsetAudioRouteAvailable(): Boolean {
for (audioDevice in coreContext.core.audioDevices) { for (audioDevice in coreContext.core.audioDevices) {
if ((audioDevice.type == AudioDevice.Type.Headset || audioDevice.type == AudioDevice.Type.Headphones) && if ((audioDevice.type == AudioDevice.Type.Headset || audioDevice.type == AudioDevice.Type.Headphones) &&
audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) { audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityPlay)
) {
Log.i("[Audio Route Helper] Found headset/headphones audio device [${audioDevice.deviceName}]") Log.i("[Audio Route Helper] Found headset/headphones audio device [${audioDevice.deviceName}]")
return true return true
} }

View file

@ -265,9 +265,9 @@ class FileUtils {
context.contentResolver.openInputStream(uri) context.contentResolver.openInputStream(uri)
Log.i( Log.i(
"[File Utils] Trying to copy file from " + "[File Utils] Trying to copy file from " +
uri.toString() + uri.toString() +
" to local file " + " to local file " +
localFile.absolutePath localFile.absolutePath
) )
coroutineScope { coroutineScope {
val deferred = async { copyToFile(remoteFile, localFile) } val deferred = async { copyToFile(remoteFile, localFile) }
@ -417,7 +417,8 @@ class FileUtils {
} catch (e: Exception) { } catch (e: Exception) {
Log.e( Log.e(
"[Chat Message] Couldn't get URI for file $file using file provider ${context.getString( "[Chat Message] Couldn't get URI for file $file using file provider ${context.getString(
R.string.file_provider)}" R.string.file_provider
)}"
) )
Uri.parse(path) Uri.parse(path)
} }

View file

@ -67,8 +67,8 @@ class LinphoneUtils {
fun isLimeAvailable(): Boolean { fun isLimeAvailable(): Boolean {
val core = coreContext.core val core = coreContext.core
return core.limeX3DhAvailable() && core.limeX3DhEnabled() && return core.limeX3DhAvailable() && core.limeX3DhEnabled() &&
core.limeX3DhServerUrl != null && core.limeX3DhServerUrl != null &&
core.defaultAccount?.params?.conferenceFactoryUri != null core.defaultAccount?.params?.conferenceFactoryUri != null
} }
fun isGroupChatAvailable(): Boolean { fun isGroupChatAvailable(): Boolean {
@ -143,10 +143,14 @@ class LinphoneUtils {
} }
fun isCallLogMissed(callLog: CallLog): Boolean { fun isCallLogMissed(callLog: CallLog): Boolean {
return (callLog.dir == Call.Dir.Incoming && return (
(callLog.status == Call.Status.Missed || callLog.dir == Call.Dir.Incoming &&
callLog.status == Call.Status.Aborted || (
callLog.status == Call.Status.EarlyAborted)) callLog.status == Call.Status.Missed ||
callLog.status == Call.Status.Aborted ||
callLog.status == Call.Status.EarlyAborted
)
)
} }
fun getChatRoomId(localAddress: String, remoteAddress: String): String { fun getChatRoomId(localAddress: String, remoteAddress: String): String {

View file

@ -93,8 +93,8 @@ class TimestampUtils {
cal2: Calendar cal2: Calendar
): Boolean { ): Boolean {
return cal1[Calendar.ERA] == cal2[Calendar.ERA] && return cal1[Calendar.ERA] == cal2[Calendar.ERA] &&
cal1[Calendar.YEAR] == cal2[Calendar.YEAR] && cal1[Calendar.YEAR] == cal2[Calendar.YEAR] &&
cal1[Calendar.DAY_OF_YEAR] == cal2[Calendar.DAY_OF_YEAR] cal1[Calendar.DAY_OF_YEAR] == cal2[Calendar.DAY_OF_YEAR]
} }
private fun isSameYear( private fun isSameYear(
@ -102,7 +102,7 @@ class TimestampUtils {
cal2: Calendar cal2: Calendar
): Boolean { ): Boolean {
return cal1[Calendar.ERA] == cal2[Calendar.ERA] && return cal1[Calendar.ERA] == cal2[Calendar.ERA] &&
cal1[Calendar.YEAR] == cal2[Calendar.YEAR] cal1[Calendar.YEAR] == cal2[Calendar.YEAR]
} }
} }
} }

View file

@ -63,7 +63,8 @@ class VoiceRecordProgressBar : View {
context.theme.obtainStyledAttributes( context.theme.obtainStyledAttributes(
attrs, attrs,
R.styleable.VoiceRecordProgressBar, R.styleable.VoiceRecordProgressBar,
0, 0).apply { 0, 0
).apply {
try { try {
val drawable = getDrawable(R.styleable.VoiceRecordProgressBar_progressDrawable) val drawable = getDrawable(R.styleable.VoiceRecordProgressBar_progressDrawable)