Bumped dependencies & improved gradle files + new bullseye based clean docker image using JDK 17

This commit is contained in:
Sylvain Berfini 2023-04-14 10:43:10 +02:00
parent b80f2fc9a2
commit 33b4c09ffd
158 changed files with 3285 additions and 996 deletions

View file

@ -2,15 +2,15 @@ job-android:
stage: build stage: build
tags: [ "docker-android" ] tags: [ "docker-android" ]
image: gitlab.linphone.org:4567/bc/public/linphone-android/bc-dev-android:20220609_android_33 image: gitlab.linphone.org:4567/bc/public/linphone-android/bc-dev-android:20230414_bullseye_jdk_17_cleaned
before_script: before_script:
- if ! [ -z ${SCP_PRIVATE_KEY+x} ]; then eval $(ssh-agent -s); fi - if ! [ -z ${SCP_PRIVATE_KEY+x} ]; then eval $(ssh-agent -s); fi
- if ! [ -z ${SCP_PRIVATE_KEY+x} ]; then echo "$SCP_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null; fi - if ! [ -z ${SCP_PRIVATE_KEY+x} ]; then echo "$SCP_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null; fi
- echo "$ANDROID_SETTINGS_GRADLE" > settings.gradle - if ! [ -z ${ANDROID_SETTINGS_GRADLE+x} ]; then echo "$ANDROID_SETTINGS_GRADLE" > settings.gradle; fi
- git config --global --add safe.directory /builds/BC/public/linphone-android
script: script:
- sdkmanager
- scp -oStrictHostKeyChecking=no $DEPLOY_SERVER:$ANDROID_KEYSTORE_PATH app/ - scp -oStrictHostKeyChecking=no $DEPLOY_SERVER:$ANDROID_KEYSTORE_PATH app/
- scp -oStrictHostKeyChecking=no $DEPLOY_SERVER:$ANDROID_GOOGLE_SERVICES_PATH app/ - scp -oStrictHostKeyChecking=no $DEPLOY_SERVER:$ANDROID_GOOGLE_SERVICES_PATH app/
- echo storePassword=$ANDROID_KEYSTORE_PASSWORD > keystore.properties - echo storePassword=$ANDROID_KEYSTORE_PASSWORD > keystore.properties

View file

@ -2,7 +2,7 @@ plugins {
id 'com.android.application' id 'com.android.application'
id 'kotlin-android' id 'kotlin-android'
id 'kotlin-kapt' id 'kotlin-kapt'
id 'org.jlleitschuh.gradle.ktlint' id 'org.jlleitschuh.gradle.ktlint' version '11.3.1'
id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.android'
} }
@ -78,8 +78,12 @@ project.tasks['preBuild'].dependsOn 'getGitVersion'
project.tasks['preBuild'].dependsOn 'linphoneSdkSource' project.tasks['preBuild'].dependsOn 'linphoneSdkSource'
android { android {
compileOptions {
sourceCompatibility = 17
targetCompatibility = 17
}
compileSdkVersion 33 compileSdkVersion 33
buildToolsVersion '33.0.0'
defaultConfig { defaultConfig {
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 33 targetSdkVersion 33
@ -228,7 +232,7 @@ dependencies {
// https://github.com/Baseflow/PhotoView/blob/master/LICENSE Apache v2.0 // https://github.com/Baseflow/PhotoView/blob/master/LICENSE Apache v2.0
implementation 'com.github.chrisbanes:PhotoView:2.3.0' implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation platform('com.google.firebase:firebase-bom:31.2.3') implementation platform('com.google.firebase:firebase-bom:31.5.0')
if (crashlyticsAvailable) { if (crashlyticsAvailable) {
debugImplementation 'com.google.firebase:firebase-crashlytics-ndk' debugImplementation 'com.google.firebase:firebase-crashlytics-ndk'
releaseWithCrashlyticsImplementation 'com.google.firebase:firebase-crashlytics-ndk' releaseWithCrashlyticsImplementation 'com.google.firebase:firebase-crashlytics-ndk'

View file

@ -22,3 +22,20 @@
-keep public class * extends androidx.fragment.app.Fragment { *; } -keep public class * extends androidx.fragment.app.Fragment { *; }
-dontwarn com.google.errorprone.annotations.Immutable -dontwarn com.google.errorprone.annotations.Immutable
# To prevent following errors:
#ERROR: Missing classes detected while running R8. Please add the missing classes or apply additional keep rules that are generated in /builds/BC/public/linphone-android/app/build/outputs/mapping/release/missing_rules.txt.
#ERROR: R8: Missing class org.bouncycastle.jsse.BCSSLParameters (referenced from: void okhttp3.internal.platform.BouncyCastlePlatform.configureTlsExtensions(javax.net.ssl.SSLSocket, java.lang.String, java.util.List) and 1 other context)
#Missing class org.bouncycastle.jsse.BCSSLSocket (referenced from: void okhttp3.internal.platform.BouncyCastlePlatform.configureTlsExtensions(javax.net.ssl.SSLSocket, java.lang.String, java.util.List) and 5 other contexts)
#Missing class org.bouncycastle.jsse.provider.BouncyCastleJsseProvider (referenced from: void okhttp3.internal.platform.BouncyCastlePlatform.<init>())
#Missing class org.conscrypt.Conscrypt$Version (referenced from: boolean okhttp3.internal.platform.ConscryptPlatform$Companion.atLeastVersion(int, int, int))
#Missing class org.conscrypt.Conscrypt (referenced from: boolean okhttp3.internal.platform.ConscryptPlatform$Companion.atLeastVersion(int, int, int) and 4 other contexts)
#Missing class org.conscrypt.ConscryptHostnameVerifier (referenced from: okhttp3.internal.platform.ConscryptPlatform$DisabledHostnameVerifier)
#Missing class org.openjsse.javax.net.ssl.SSLParameters (referenced from: void okhttp3.internal.platform.OpenJSSEPlatform.configureTlsExtensions(javax.net.ssl.SSLSocket, java.lang.String, java.util.List))
#Missing class org.openjsse.javax.net.ssl.SSLSocket (referenced from: void okhttp3.internal.platform.OpenJSSEPlatform.configureTlsExtensions(javax.net.ssl.SSLSocket, java.lang.String, java.util.List) and 1 other context)
#Missing class org.openjsse.net.ssl.OpenJSSE (referenced from: void okhttp3.internal.platform.OpenJSSEPlatform.<init>())
#> Task :app:lintVitalAnalyzeRelease
#FAILURE: Build failed with an exception.
-dontwarn org.conscrypt.**
-dontwarn org.bouncycastle.**
-dontwarn org.openjsse.**

View file

@ -38,6 +38,7 @@ class LinphoneApplication : Application(), ImageLoaderFactory {
companion object { companion object {
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
lateinit var corePreferences: CorePreferences lateinit var corePreferences: CorePreferences
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
lateinit var coreContext: CoreContext lateinit var coreContext: CoreContext
@ -59,7 +60,10 @@ class LinphoneApplication : Application(), ImageLoaderFactory {
CoreContext.activateVFS() CoreContext.activateVFS()
} }
val config = Factory.instance().createConfigWithFactory(corePreferences.configPath, corePreferences.factoryConfigPath) val config = Factory.instance().createConfigWithFactory(
corePreferences.configPath,
corePreferences.factoryConfigPath
)
corePreferences.config = config corePreferences.config = config
val appName = context.getString(R.string.app_name) val appName = context.getString(R.string.app_name)
@ -83,8 +87,15 @@ class LinphoneApplication : Application(), ImageLoaderFactory {
return false return false
} }
Log.i("[Application] Core context is being created ${if (pushReceived) "from push" else ""}") Log.i(
coreContext = CoreContext(context, corePreferences.config, service, useAutoStartDescription) "[Application] Core context is being created ${if (pushReceived) "from push" else ""}"
)
coreContext = CoreContext(
context,
corePreferences.config,
service,
useAutoStartDescription
)
coreContext.start() coreContext.start()
return true return true
} }

View file

@ -74,7 +74,9 @@ abstract class GenericFragment<T : ViewDataBinding> : Fragment() {
if (!navController.popBackStack()) { if (!navController.popBackStack()) {
Log.d("[Generic Fragment] ${getFragmentRealClassName()} couldn't pop") Log.d("[Generic Fragment] ${getFragmentRealClassName()} couldn't pop")
if (!navController.navigateUp()) { if (!navController.navigateUp()) {
Log.d("[Generic Fragment] ${getFragmentRealClassName()} couldn't navigate up") Log.d(
"[Generic Fragment] ${getFragmentRealClassName()} couldn't navigate up"
)
// Disable this callback & start a new back press event // Disable this callback & start a new back press event
isEnabled = false isEnabled = false
goBack() goBack()
@ -98,7 +100,9 @@ abstract class GenericFragment<T : ViewDataBinding> : Fragment() {
} }
sharedViewModel.isSlidingPaneSlideable.observe(viewLifecycleOwner) { sharedViewModel.isSlidingPaneSlideable.observe(viewLifecycleOwner) {
Log.d("[Generic Fragment] ${getFragmentRealClassName()} shared main VM sliding pane has changed") Log.d(
"[Generic Fragment] ${getFragmentRealClassName()} shared main VM sliding pane has changed"
)
onBackPressedCallback.isEnabled = backPressedCallBackEnabled() onBackPressedCallback.isEnabled = backPressedCallBackEnabled()
} }
@ -151,7 +155,10 @@ abstract class GenericFragment<T : ViewDataBinding> : Fragment() {
onBackPressedCallback.isEnabled = false onBackPressedCallback.isEnabled = false
} }
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, onBackPressedCallback) requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
onBackPressedCallback
)
} }
private fun backPressedCallBackEnabled(): Boolean { private fun backPressedCallBackEnabled(): Boolean {
@ -161,9 +168,13 @@ abstract class GenericFragment<T : ViewDataBinding> : Fragment() {
if (findNavController().graph.id == R.id.main_nav_graph_xml) return false if (findNavController().graph.id == R.id.main_nav_graph_xml) return false
val isSlidingPaneFlat = sharedViewModel.isSlidingPaneSlideable.value == false val isSlidingPaneFlat = sharedViewModel.isSlidingPaneSlideable.value == false
Log.d("[Generic Fragment] ${getFragmentRealClassName()} isSlidingPaneFlat ? $isSlidingPaneFlat") Log.d(
"[Generic Fragment] ${getFragmentRealClassName()} isSlidingPaneFlat ? $isSlidingPaneFlat"
)
val isPreviousFragmentEmpty = findNavController().previousBackStackEntry?.destination?.id in emptyFragmentsIds val isPreviousFragmentEmpty = findNavController().previousBackStackEntry?.destination?.id in emptyFragmentsIds
Log.d("[Generic Fragment] ${getFragmentRealClassName()} isPreviousFragmentEmpty ? $isPreviousFragmentEmpty") Log.d(
"[Generic Fragment] ${getFragmentRealClassName()} isPreviousFragmentEmpty ? $isPreviousFragmentEmpty"
)
val popBackStack = isSlidingPaneFlat || !isPreviousFragmentEmpty val popBackStack = isSlidingPaneFlat || !isPreviousFragmentEmpty
Log.d("[Generic Fragment] ${getFragmentRealClassName()} popBackStack ? $popBackStack") Log.d("[Generic Fragment] ${getFragmentRealClassName()} popBackStack ? $popBackStack")
return popBackStack return popBackStack

View file

@ -34,7 +34,9 @@ abstract class ProximitySensorActivity : GenericActivity() {
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
if (!powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { if (!powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
Log.w("[Proximity Sensor Activity] PROXIMITY_SCREEN_OFF_WAKE_LOCK isn't supported on this device!") Log.w(
"[Proximity Sensor Activity] PROXIMITY_SCREEN_OFF_WAKE_LOCK isn't supported on this device!"
)
} }
proximityWakeLock = powerManager.newWakeLock( proximityWakeLock = powerManager.newWakeLock(
@ -58,7 +60,9 @@ abstract class ProximitySensorActivity : GenericActivity() {
protected fun enableProximitySensor(enable: Boolean) { protected fun enableProximitySensor(enable: Boolean) {
if (enable) { if (enable) {
if (!proximitySensorEnabled) { if (!proximitySensorEnabled) {
Log.i("[Proximity Sensor Activity] Enabling proximity sensor (turning screen OFF when wake lock is acquired)") Log.i(
"[Proximity Sensor Activity] Enabling proximity sensor (turning screen OFF when wake lock is acquired)"
)
if (!proximityWakeLock.isHeld) { if (!proximityWakeLock.isHeld) {
Log.i("[Proximity Sensor Activity] Acquiring PROXIMITY_SCREEN_OFF_WAKE_LOCK") Log.i("[Proximity Sensor Activity] Acquiring PROXIMITY_SCREEN_OFF_WAKE_LOCK")
proximityWakeLock.acquire() proximityWakeLock.acquire()
@ -67,9 +71,13 @@ abstract class ProximitySensorActivity : GenericActivity() {
} }
} else { } else {
if (proximitySensorEnabled) { if (proximitySensorEnabled) {
Log.i("[Proximity Sensor Activity] Disabling proximity sensor (turning screen ON when wake lock is released)") Log.i(
"[Proximity Sensor Activity] Disabling proximity sensor (turning screen ON when wake lock is released)"
)
if (proximityWakeLock.isHeld) { if (proximityWakeLock.isHeld) {
Log.i("[Proximity Sensor Activity] Asking to release PROXIMITY_SCREEN_OFF_WAKE_LOCK next time sensor detects no proximity") Log.i(
"[Proximity Sensor Activity] Asking to release PROXIMITY_SCREEN_OFF_WAKE_LOCK next time sensor detects no proximity"
)
proximityWakeLock.release(RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY) proximityWakeLock.release(RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY)
} }
proximitySensorEnabled = false proximitySensorEnabled = false

View file

@ -41,7 +41,11 @@ class CountryPickerAdapter : BaseAdapter(), Filterable {
} }
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view: View = convertView ?: LayoutInflater.from(parent.context).inflate(R.layout.assistant_country_picker_cell, parent, false) val view: View = convertView ?: LayoutInflater.from(parent.context).inflate(
R.layout.assistant_country_picker_cell,
parent,
false
)
val dialPlan: DialPlan = countries[position] val dialPlan: DialPlan = countries[position]
val name = view.findViewById<TextView>(R.id.country_name) val name = view.findViewById<TextView>(R.id.country_name)

View file

@ -58,7 +58,10 @@ abstract class AbstractPhoneFragment<T : ViewDataBinding> : GenericFragment<T>()
if (!resources.getBoolean(R.bool.isTablet)) { if (!resources.getBoolean(R.bool.isTablet)) {
if (!PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()) { if (!PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()) {
Log.i("[Assistant] Asking for READ_PHONE_STATE/READ_PHONE_NUMBERS permission") Log.i("[Assistant] Asking for READ_PHONE_STATE/READ_PHONE_NUMBERS permission")
Compatibility.requestReadPhoneStateOrNumbersPermission(this, READ_PHONE_STATE_PERMISSION_REQUEST_CODE) Compatibility.requestReadPhoneStateOrNumbersPermission(
this,
READ_PHONE_STATE_PERMISSION_REQUEST_CODE
)
} else { } else {
updateFromDeviceInfo() updateFromDeviceInfo()
} }

View file

@ -57,7 +57,10 @@ class EchoCancellerCalibrationFragment : GenericFragment<AssistantEchoCancellerC
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")
requestPermissions(arrayOf(android.Manifest.permission.RECORD_AUDIO), RECORD_AUDIO_PERMISSION_REQUEST_CODE) requestPermissions(
arrayOf(android.Manifest.permission.RECORD_AUDIO),
RECORD_AUDIO_PERMISSION_REQUEST_CODE
)
} else { } else {
viewModel.startEchoCancellerCalibration() viewModel.startEchoCancellerCalibration()
} }

View file

@ -46,7 +46,10 @@ class EmailAccountCreationFragment : GenericFragment<AssistantEmailAccountCreati
ViewModelProvider(this)[SharedAssistantViewModel::class.java] ViewModelProvider(this)[SharedAssistantViewModel::class.java]
} }
viewModel = ViewModelProvider(this, EmailAccountCreationViewModelFactory(sharedAssistantViewModel.getAccountCreator()))[EmailAccountCreationViewModel::class.java] viewModel = ViewModelProvider(
this,
EmailAccountCreationViewModelFactory(sharedAssistantViewModel.getAccountCreator())
)[EmailAccountCreationViewModel::class.java]
binding.viewModel = viewModel binding.viewModel = viewModel
viewModel.goToEmailValidationEvent.observe( viewModel.goToEmailValidationEvent.observe(

View file

@ -46,7 +46,10 @@ class EmailAccountValidationFragment : GenericFragment<AssistantEmailAccountVali
ViewModelProvider(this)[SharedAssistantViewModel::class.java] ViewModelProvider(this)[SharedAssistantViewModel::class.java]
} }
viewModel = ViewModelProvider(this, EmailAccountValidationViewModelFactory(sharedAssistantViewModel.getAccountCreator()))[EmailAccountValidationViewModel::class.java] viewModel = ViewModelProvider(
this,
EmailAccountValidationViewModelFactory(sharedAssistantViewModel.getAccountCreator())
)[EmailAccountValidationViewModel::class.java]
binding.viewModel = viewModel binding.viewModel = viewModel
viewModel.leaveAssistantEvent.observe( viewModel.leaveAssistantEvent.observe(

View file

@ -51,7 +51,10 @@ class GenericAccountLoginFragment : GenericFragment<AssistantGenericAccountLogin
ViewModelProvider(this)[SharedAssistantViewModel::class.java] ViewModelProvider(this)[SharedAssistantViewModel::class.java]
} }
viewModel = ViewModelProvider(this, GenericLoginViewModelFactory(sharedAssistantViewModel.getAccountCreator(true)))[GenericLoginViewModel::class.java] viewModel = ViewModelProvider(
this,
GenericLoginViewModelFactory(sharedAssistantViewModel.getAccountCreator(true))
)[GenericLoginViewModel::class.java]
binding.viewModel = viewModel binding.viewModel = viewModel
viewModel.leaveAssistantEvent.observe( viewModel.leaveAssistantEvent.observe(

View file

@ -52,7 +52,10 @@ class PhoneAccountValidationFragment : GenericFragment<AssistantPhoneAccountVali
ViewModelProvider(this)[SharedAssistantViewModel::class.java] ViewModelProvider(this)[SharedAssistantViewModel::class.java]
} }
viewModel = ViewModelProvider(this, PhoneAccountValidationViewModelFactory(sharedAssistantViewModel.getAccountCreator()))[PhoneAccountValidationViewModel::class.java] viewModel = ViewModelProvider(
this,
PhoneAccountValidationViewModelFactory(sharedAssistantViewModel.getAccountCreator())
)[PhoneAccountValidationViewModel::class.java]
binding.viewModel = viewModel binding.viewModel = viewModel
viewModel.phoneNumber.value = arguments?.getString("PhoneNumber") viewModel.phoneNumber.value = arguments?.getString("PhoneNumber")
@ -106,7 +109,9 @@ class PhoneAccountValidationFragment : GenericFragment<AssistantPhoneAccountVali
if (data != null && data.itemCount > 0) { if (data != null && data.itemCount > 0) {
val clip = data.getItemAt(0).text.toString() val clip = data.getItemAt(0).text.toString()
if (clip.length == 4) { if (clip.length == 4) {
Log.i("[Assistant] [Phone Account Validation] Found 4 digits as primary clip in clipboard, using it and clear it") Log.i(
"[Assistant] [Phone Account Validation] Found 4 digits as primary clip in clipboard, using it and clear it"
)
viewModel.code.value = clip viewModel.code.value = clip
clipboard.clearPrimaryClip() clipboard.clearPrimaryClip()
} }

View file

@ -67,7 +67,10 @@ class QrCodeFragment : GenericFragment<AssistantQrCodeFragmentBinding>() {
if (!PermissionHelper.required(requireContext()).hasCameraPermission()) { if (!PermissionHelper.required(requireContext()).hasCameraPermission()) {
Log.i("[QR Code] Asking for CAMERA permission") Log.i("[QR Code] Asking for CAMERA permission")
requestPermissions(arrayOf(android.Manifest.permission.CAMERA), CAMERA_PERMISSION_REQUEST_CODE) requestPermissions(
arrayOf(android.Manifest.permission.CAMERA),
CAMERA_PERMISSION_REQUEST_CODE
)
} }
} }

View file

@ -107,7 +107,12 @@ class WelcomeFragment : GenericFragment<AssistantWelcomeFragmentBinding>() {
} }
} }
} }
spannable.setSpan(clickableSpan, termsMatcher.start(0), termsMatcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) spannable.setSpan(
clickableSpan,
termsMatcher.start(0),
termsMatcher.end(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
} }
val policyMatcher = Pattern.compile(privacy).matcher(label) val policyMatcher = Pattern.compile(privacy).matcher(label)
@ -125,7 +130,12 @@ class WelcomeFragment : GenericFragment<AssistantWelcomeFragmentBinding>() {
} }
} }
} }
spannable.setSpan(clickableSpan, policyMatcher.start(0), policyMatcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) spannable.setSpan(
clickableSpan,
policyMatcher.start(0),
policyMatcher.end(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
} }
binding.termsAndPrivacy.text = spannable binding.termsAndPrivacy.text = spannable

View file

@ -159,7 +159,9 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
if (loginWithUsernamePassword.value == true) { if (loginWithUsernamePassword.value == true) {
val result = accountCreator.setUsername(username.value) val result = accountCreator.setUsername(username.value)
if (result != AccountCreator.UsernameStatus.Ok) { if (result != AccountCreator.UsernameStatus.Ok) {
Log.e("[Assistant] [Account Login] Error [${result.name}] setting the username: ${username.value}") Log.e(
"[Assistant] [Account Login] Error [${result.name}] setting the username: ${username.value}"
)
usernameError.value = result.name usernameError.value = result.name
return return
} }
@ -180,9 +182,13 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
onErrorEvent.value = Event("Error: Failed to create account object") onErrorEvent.value = Event("Error: Failed to create account object")
} }
} else { } else {
val result = AccountCreator.PhoneNumberStatus.fromInt(accountCreator.setPhoneNumber(phoneNumber.value, prefix.value)) val result = AccountCreator.PhoneNumberStatus.fromInt(
accountCreator.setPhoneNumber(phoneNumber.value, prefix.value)
)
if (result != AccountCreator.PhoneNumberStatus.Ok) { if (result != AccountCreator.PhoneNumberStatus.Ok) {
Log.e("[Assistant] [Account Login] Error [$result] setting the phone number: ${phoneNumber.value} with prefix: ${prefix.value}") Log.e(
"[Assistant] [Account Login] Error [$result] setting the phone number: ${phoneNumber.value} with prefix: ${prefix.value}"
)
phoneNumberError.value = result.name phoneNumberError.value = result.name
return return
} }
@ -190,7 +196,9 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
val result2 = accountCreator.setUsername(accountCreator.phoneNumber) val result2 = accountCreator.setUsername(accountCreator.phoneNumber)
if (result2 != AccountCreator.UsernameStatus.Ok) { if (result2 != AccountCreator.UsernameStatus.Ok) {
Log.e("[Assistant] [Account Login] Error [${result2.name}] setting the username: ${accountCreator.phoneNumber}") Log.e(
"[Assistant] [Account Login] Error [${result2.name}] setting the username: ${accountCreator.phoneNumber}"
)
usernameError.value = result2.name usernameError.value = result2.name
return return
} }
@ -229,7 +237,9 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
if (proxyConfig.dialPrefix.isNullOrEmpty()) { if (proxyConfig.dialPrefix.isNullOrEmpty()) {
val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(coreContext.context) val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(coreContext.context)
if (dialPlan != null) { if (dialPlan != null) {
Log.i("[Assistant] [Account Login] Found dial plan country ${dialPlan.country} with international prefix ${dialPlan.countryCallingCode}") Log.i(
"[Assistant] [Account Login] Found dial plan country ${dialPlan.country} with international prefix ${dialPlan.countryCallingCode}"
)
proxyConfig.edit() proxyConfig.edit()
proxyConfig.dialPrefix = dialPlan.countryCallingCode proxyConfig.dialPrefix = dialPlan.countryCallingCode
proxyConfig.done() proxyConfig.done()

View file

@ -74,7 +74,9 @@ class EmailAccountCreationViewModel(val accountCreator: AccountCreator) : ViewMo
when (status) { when (status) {
AccountCreator.Status.AccountExist, AccountCreator.Status.AccountExistWithAlias -> { AccountCreator.Status.AccountExist, AccountCreator.Status.AccountExistWithAlias -> {
waitForServerAnswer.value = false waitForServerAnswer.value = false
usernameError.value = AppUtils.getString(R.string.assistant_error_username_already_exists) usernameError.value = AppUtils.getString(
R.string.assistant_error_username_already_exists
)
} }
AccountCreator.Status.AccountNotExist -> { AccountCreator.Status.AccountNotExist -> {
val createAccountStatus = creator.createAccount() val createAccountStatus = creator.createAccount()

View file

@ -109,9 +109,13 @@ class EmailAccountValidationViewModel(val accountCreator: AccountCreator) : View
proxyConfig.isPushNotificationAllowed = true proxyConfig.isPushNotificationAllowed = true
if (proxyConfig.dialPrefix.isNullOrEmpty()) { if (proxyConfig.dialPrefix.isNullOrEmpty()) {
val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(LinphoneApplication.coreContext.context) val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(
LinphoneApplication.coreContext.context
)
if (dialPlan != null) { if (dialPlan != null) {
Log.i("[Assistant] [Account Validation] Found dial plan country ${dialPlan.country} with international prefix ${dialPlan.countryCallingCode}") Log.i(
"[Assistant] [Account Validation] Found dial plan country ${dialPlan.country} with international prefix ${dialPlan.countryCallingCode}"
)
proxyConfig.edit() proxyConfig.edit()
proxyConfig.dialPrefix = dialPlan.countryCallingCode proxyConfig.dialPrefix = dialPlan.countryCallingCode
proxyConfig.done() proxyConfig.done()

View file

@ -41,7 +41,9 @@ class PhoneAccountCreationViewModelFactory(private val accountCreator: AccountCr
} }
} }
class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPhoneViewModel(accountCreator) { class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPhoneViewModel(
accountCreator
) {
val username = MutableLiveData<String>() val username = MutableLiveData<String>()
val useUsername = MutableLiveData<Boolean>() val useUsername = MutableLiveData<Boolean>()
val usernameError = MutableLiveData<String>() val usernameError = MutableLiveData<String>()
@ -71,9 +73,13 @@ class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPh
AccountCreator.Status.AccountExist, AccountCreator.Status.AccountExistWithAlias -> { AccountCreator.Status.AccountExist, AccountCreator.Status.AccountExistWithAlias -> {
waitForServerAnswer.value = false waitForServerAnswer.value = false
if (useUsername.value == true) { if (useUsername.value == true) {
usernameError.value = AppUtils.getString(R.string.assistant_error_username_already_exists) usernameError.value = AppUtils.getString(
R.string.assistant_error_username_already_exists
)
} else { } else {
phoneNumberError.value = AppUtils.getString(R.string.assistant_error_phone_number_already_exists) phoneNumberError.value = AppUtils.getString(
R.string.assistant_error_phone_number_already_exists
)
} }
} }
AccountCreator.Status.AccountNotExist -> { AccountCreator.Status.AccountNotExist -> {
@ -103,7 +109,9 @@ class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPh
goToSmsValidationEvent.value = Event(true) goToSmsValidationEvent.value = Event(true)
} }
AccountCreator.Status.AccountExistWithAlias -> { AccountCreator.Status.AccountExistWithAlias -> {
phoneNumberError.value = AppUtils.getString(R.string.assistant_error_phone_number_already_exists) phoneNumberError.value = AppUtils.getString(
R.string.assistant_error_phone_number_already_exists
)
} }
else -> { else -> {
onErrorEvent.value = Event("Error: ${status.name}") onErrorEvent.value = Event("Error: ${status.name}")
@ -161,7 +169,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 ||

View file

@ -35,7 +35,9 @@ class PhoneAccountLinkingViewModelFactory(private val accountCreator: AccountCre
} }
} }
class PhoneAccountLinkingViewModel(accountCreator: AccountCreator) : AbstractPhoneViewModel(accountCreator) { class PhoneAccountLinkingViewModel(accountCreator: AccountCreator) : AbstractPhoneViewModel(
accountCreator
) {
val username = MutableLiveData<String>() val username = MutableLiveData<String>()
val allowSkip = MutableLiveData<Boolean>() val allowSkip = MutableLiveData<Boolean>()

View file

@ -125,7 +125,9 @@ class PhoneAccountValidationViewModel(val accountCreator: AccountCreator) : View
fun finish() { fun finish() {
accountCreator.activationCode = code.value.orEmpty() accountCreator.activationCode = code.value.orEmpty()
Log.i("[Assistant] [Phone Account Validation] Phone number is ${accountCreator.phoneNumber} and activation code is ${accountCreator.activationCode}") Log.i(
"[Assistant] [Phone Account Validation] Phone number is ${accountCreator.phoneNumber} and activation code is ${accountCreator.activationCode}"
)
waitForServerAnswer.value = true waitForServerAnswer.value = true
val status = when { val status = when {
@ -145,7 +147,9 @@ class PhoneAccountValidationViewModel(val accountCreator: AccountCreator) : View
val proxyConfig: ProxyConfig? = accountCreator.createProxyConfig() val proxyConfig: ProxyConfig? = accountCreator.createProxyConfig()
if (proxyConfig == null) { if (proxyConfig == null) {
Log.e("[Assistant] [Phone Account Validation] Account creator couldn't create proxy config") Log.e(
"[Assistant] [Phone Account Validation] Account creator couldn't create proxy config"
)
return false return false
} }

View file

@ -77,11 +77,15 @@ class ChatBubbleActivity : GenericActivity() {
var chatRoom: ChatRoom? = null var chatRoom: ChatRoom? = null
if (localSipUri != null && remoteSipUri != null) { if (localSipUri != null && remoteSipUri != null) {
Log.i("[Chat Bubble] Found local [$localSipUri] & remote [$remoteSipUri] addresses in arguments") Log.i(
"[Chat Bubble] Found local [$localSipUri] & remote [$remoteSipUri] addresses in arguments"
)
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, null,
localAddress,
remoteSipAddress,
arrayOfNulls( arrayOfNulls(
0 0
) )
@ -155,7 +159,10 @@ class ChatBubbleActivity : GenericActivity() {
binding.setOpenAppClickListener { binding.setOpenAppClickListener {
coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null
coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom(viewModel.chatRoom, false) coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom(
viewModel.chatRoom,
false
)
val intent = Intent(this, MainActivity::class.java) val intent = Intent(this, MainActivity::class.java)
intent.putExtra("RemoteSipUri", remoteSipUri) intent.putExtra("RemoteSipUri", remoteSipUri)
@ -181,7 +188,10 @@ class ChatBubbleActivity : GenericActivity() {
viewModel.chatRoom.addListener(listener) viewModel.chatRoom.addListener(listener)
// Workaround for the removed notification when a chat room is marked as read // Workaround for the removed notification when a chat room is marked as read
coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom(viewModel.chatRoom, true) coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom(
viewModel.chatRoom,
true
)
viewModel.chatRoom.markAsRead() viewModel.chatRoom.markAsRead()
val peerAddress = viewModel.chatRoom.peerAddress.asStringUriOnly() val peerAddress = viewModel.chatRoom.peerAddress.asStringUriOnly()
@ -199,7 +209,10 @@ class ChatBubbleActivity : GenericActivity() {
viewModel.chatRoom.removeListener(listener) viewModel.chatRoom.removeListener(listener)
coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null
coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom(viewModel.chatRoom, false) coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom(
viewModel.chatRoom,
false
)
super.onPause() super.onPause()
} }

View file

@ -205,7 +205,9 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
binding.rootCoordinatorLayout.addKeyboardInsetListener { keyboardVisible -> binding.rootCoordinatorLayout.addKeyboardInsetListener { keyboardVisible ->
val portraitOrientation = resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE val portraitOrientation = resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE
Log.i("[Main Activity] Keyboard is ${if (keyboardVisible) "visible" else "invisible"}, orientation is ${if (portraitOrientation) "portrait" else "landscape"}") Log.i(
"[Main Activity] Keyboard is ${if (keyboardVisible) "visible" else "invisible"}, orientation is ${if (portraitOrientation) "portrait" else "landscape"}"
)
shouldTabsBeVisibleDueToOrientationAndKeyboard = !portraitOrientation || !keyboardVisible shouldTabsBeVisibleDueToOrientationAndKeyboard = !portraitOrientation || !keyboardVisible
updateTabsFragmentVisibility() updateTabsFragmentVisibility()
} }
@ -255,7 +257,9 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
} }
private fun handleIntentParams(intent: Intent) { private fun handleIntentParams(intent: Intent) {
Log.i("[Main Activity] Handling intent with action [${intent.action}], type [${intent.type}] and data [${intent.data}]") Log.i(
"[Main Activity] Handling intent with action [${intent.action}], type [${intent.type}] and data [${intent.data}]"
)
when (intent.action) { when (intent.action) {
Intent.ACTION_MAIN -> handleMainIntent(intent) Intent.ACTION_MAIN -> handleMainIntent(intent)
@ -291,10 +295,14 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
if (stringUri.startsWith("linphone-config:")) { if (stringUri.startsWith("linphone-config:")) {
val remoteConfigUri = stringUri.substring("linphone-config:".length) val remoteConfigUri = stringUri.substring("linphone-config:".length)
if (corePreferences.autoRemoteProvisioningOnConfigUriHandler) { if (corePreferences.autoRemoteProvisioningOnConfigUriHandler) {
Log.w("[Main Activity] Remote provisioning URL set to [$remoteConfigUri], restarting Core now") Log.w(
"[Main Activity] Remote provisioning URL set to [$remoteConfigUri], restarting Core now"
)
applyRemoteProvisioning(remoteConfigUri) applyRemoteProvisioning(remoteConfigUri)
} else { } else {
Log.i("[Main Activity] Remote provisioning URL found [$remoteConfigUri], asking for user validation") Log.i(
"[Main Activity] Remote provisioning URL found [$remoteConfigUri], asking for user validation"
)
showAcceptRemoteConfigurationDialog(remoteConfigUri) showAcceptRemoteConfigurationDialog(remoteConfigUri)
} }
} else { } else {
@ -339,7 +347,9 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
if (intent.hasExtra("RemoteSipUri") && intent.hasExtra("LocalSipUri")) { if (intent.hasExtra("RemoteSipUri") && intent.hasExtra("LocalSipUri")) {
val peerAddress = intent.getStringExtra("RemoteSipUri") val peerAddress = intent.getStringExtra("RemoteSipUri")
val localAddress = intent.getStringExtra("LocalSipUri") val localAddress = intent.getStringExtra("LocalSipUri")
Log.i("[Main Activity] Found chat room intent extra: local SIP URI=[$localAddress], peer SIP URI=[$peerAddress]") Log.i(
"[Main Activity] Found chat room intent extra: local SIP URI=[$localAddress], peer SIP URI=[$peerAddress]"
)
navigateToChatRoom(localAddress, peerAddress) navigateToChatRoom(localAddress, peerAddress)
} else { } else {
Log.i("[Main Activity] Found chat intent extra, go to chat rooms list") Log.i("[Main Activity] Found chat intent extra, go to chat rooms list")
@ -362,9 +372,16 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
val core = coreContext.core val core = coreContext.core
val call = core.currentCall ?: core.calls.firstOrNull() val call = core.currentCall ?: core.calls.firstOrNull()
if (call != null) { if (call != null) {
Log.i("[Main Activity] Launcher clicked while there is at least one active call, go to CallActivity") Log.i(
val callIntent = Intent(this, org.linphone.activities.voip.CallActivity::class.java) "[Main Activity] Launcher clicked while there is at least one active call, go to CallActivity"
callIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) )
val callIntent = Intent(
this,
org.linphone.activities.voip.CallActivity::class.java
)
callIntent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
)
startActivity(callIntent) startActivity(callIntent)
} }
} }
@ -391,7 +408,10 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
} }
} }
val address = coreContext.core.interpretUrl(addressToCall, LinphoneUtils.applyInternationalPrefix()) val address = coreContext.core.interpretUrl(
addressToCall,
LinphoneUtils.applyInternationalPrefix()
)
if (address != null) { if (address != null) {
addressToCall = address.asStringUriOnly() addressToCall = address.asStringUriOnly()
} }
@ -490,8 +510,13 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
val localAddress = val localAddress =
coreContext.core.defaultAccount?.params?.identityAddress?.asStringUriOnly() coreContext.core.defaultAccount?.params?.identityAddress?.asStringUriOnly()
val peerAddress = coreContext.core.interpretUrl(addressToIM, LinphoneUtils.applyInternationalPrefix())?.asStringUriOnly() val peerAddress = coreContext.core.interpretUrl(
Log.i("[Main Activity] Navigating to chat room with local [$localAddress] and peer [$peerAddress] addresses") addressToIM,
LinphoneUtils.applyInternationalPrefix()
)?.asStringUriOnly()
Log.i(
"[Main Activity] Navigating to chat room with local [$localAddress] and peer [$peerAddress] addresses"
)
navigateToChatRoom(localAddress, peerAddress) navigateToChatRoom(localAddress, peerAddress)
} else { } else {
val shortcutId = intent.getStringExtra("android.intent.extra.shortcut.ID") // Intent.EXTRA_SHORTCUT_ID val shortcutId = intent.getStringExtra("android.intent.extra.shortcut.ID") // Intent.EXTRA_SHORTCUT_ID
@ -510,10 +535,14 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
if (split.size == 2) { if (split.size == 2) {
val localAddress = split[0] val localAddress = split[0]
val peerAddress = split[1] val peerAddress = split[1]
Log.i("[Main Activity] Navigating to chat room with local [$localAddress] and peer [$peerAddress] addresses, computed from shortcut/locus id") Log.i(
"[Main Activity] Navigating to chat room with local [$localAddress] and peer [$peerAddress] addresses, computed from shortcut/locus id"
)
navigateToChatRoom(localAddress, peerAddress) navigateToChatRoom(localAddress, peerAddress)
} else { } else {
Log.e("[Main Activity] Failed to parse shortcut/locus id: $id, going to chat rooms list") Log.e(
"[Main Activity] Failed to parse shortcut/locus id: $id, going to chat rooms list"
)
navigateToChatRooms() navigateToChatRooms()
} }
} }
@ -562,7 +591,10 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
} }
private fun showAcceptRemoteConfigurationDialog(remoteConfigUri: String) { private fun showAcceptRemoteConfigurationDialog(remoteConfigUri: String) {
val dialogViewModel = DialogViewModel(remoteConfigUri, getString(R.string.dialog_apply_remote_provisioning_title)) val dialogViewModel = DialogViewModel(
remoteConfigUri,
getString(R.string.dialog_apply_remote_provisioning_title)
)
val dialog = DialogUtils.getDialog(this, dialogViewModel) val dialog = DialogUtils.getDialog(this, dialogViewModel)
dialogViewModel.showCancelButton { dialogViewModel.showCancelButton {
@ -575,7 +607,9 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
) )
dialogViewModel.showOkButton( dialogViewModel.showOkButton(
{ {
Log.w("[Main Activity] Remote provisioning URL set to [$remoteConfigUri], restarting Core now") Log.w(
"[Main Activity] Remote provisioning URL set to [$remoteConfigUri], restarting Core now"
)
applyRemoteProvisioning(remoteConfigUri) applyRemoteProvisioning(remoteConfigUri)
dialog.dismiss() dialog.dismiss()
}, },

View file

@ -26,6 +26,7 @@ internal abstract class ChatScrollListener(private val mLayoutManager: LinearLay
RecyclerView.OnScrollListener() { RecyclerView.OnScrollListener() {
// The total number of items in the data set after the last load // The total number of items in the data set after the last load
private var previousTotalItemCount = 0 private var previousTotalItemCount = 0
// True if we are still waiting for the last set of data to load. // True if we are still waiting for the last set of data to load.
private var loading = true private var loading = true

View file

@ -55,7 +55,10 @@ import org.linphone.utils.HeaderAdapter
class ChatMessagesListAdapter( class ChatMessagesListAdapter(
selectionVM: ListTopBarViewModel, selectionVM: ListTopBarViewModel,
private val viewLifecycleOwner: LifecycleOwner private val viewLifecycleOwner: LifecycleOwner
) : SelectionListAdapter<EventLogData, RecyclerView.ViewHolder>(selectionVM, ChatMessageDiffCallback()), ) : SelectionListAdapter<EventLogData, RecyclerView.ViewHolder>(
selectionVM,
ChatMessageDiffCallback()
),
HeaderAdapter { HeaderAdapter {
companion object { companion object {
const val MAX_TIME_TO_GROUP_MESSAGES = 60 // 1 minute const val MAX_TIME_TO_GROUP_MESSAGES = 60 // 1 minute
@ -116,7 +119,9 @@ class ChatMessagesListAdapter(
override fun onWebUrlClicked(url: String) { override fun onWebUrlClicked(url: String) {
if (popup?.isShowing == true) { if (popup?.isShowing == true) {
Log.w("[Chat Message Data] Long press that displayed context menu detected, aborting click on URL [$url]") Log.w(
"[Chat Message Data] Long press that displayed context menu detected, aborting click on URL [$url]"
)
return return
} }
urlClickEvent.value = Event(url) urlClickEvent.value = Event(url)
@ -124,7 +129,9 @@ class ChatMessagesListAdapter(
override fun onSipAddressClicked(sipUri: String) { override fun onSipAddressClicked(sipUri: String) {
if (popup?.isShowing == true) { if (popup?.isShowing == true) {
Log.w("[Chat Message Data] Long press that displayed context menu detected, aborting click on SIP URI [$sipUri]") Log.w(
"[Chat Message Data] Long press that displayed context menu detected, aborting click on SIP URI [$sipUri]"
)
return return
} }
sipUriClickedEvent.value = Event(sipUri) sipUriClickedEvent.value = Event(sipUri)
@ -155,7 +162,9 @@ class ChatMessagesListAdapter(
private fun createChatMessageViewHolder(parent: ViewGroup): ChatMessageViewHolder { private fun createChatMessageViewHolder(parent: ViewGroup): ChatMessageViewHolder {
val binding: ChatMessageListCellBinding = DataBindingUtil.inflate( val binding: ChatMessageListCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.chat_message_list_cell, parent, false R.layout.chat_message_list_cell,
parent,
false
) )
return ChatMessageViewHolder(binding) return ChatMessageViewHolder(binding)
} }
@ -163,7 +172,9 @@ class ChatMessagesListAdapter(
private fun createEventViewHolder(parent: ViewGroup): EventViewHolder { private fun createEventViewHolder(parent: ViewGroup): EventViewHolder {
val binding: ChatEventListCellBinding = DataBindingUtil.inflate( val binding: ChatEventListCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.chat_event_list_cell, parent, false R.layout.chat_event_list_cell,
parent,
false
) )
return EventViewHolder(binding) return EventViewHolder(binding)
} }
@ -199,9 +210,14 @@ class ChatMessagesListAdapter(
override fun getHeaderViewForPosition(context: Context, position: Int): View { override fun getHeaderViewForPosition(context: Context, position: Int): View {
val binding: ChatUnreadMessagesListHeaderBinding = DataBindingUtil.inflate( val binding: ChatUnreadMessagesListHeaderBinding = DataBindingUtil.inflate(
LayoutInflater.from(context), LayoutInflater.from(context),
R.layout.chat_unread_messages_list_header, null, false R.layout.chat_unread_messages_list_header,
null,
false
)
binding.title = AppUtils.getStringWithPlural(
R.plurals.chat_room_unread_messages_event,
unreadMessagesCount
) )
binding.title = AppUtils.getStringWithPlural(R.plurals.chat_room_unread_messages_event, unreadMessagesCount)
binding.executePendingBindings() binding.executePendingBindings()
return binding.root return binding.root
} }
@ -281,7 +297,10 @@ class ChatMessagesListAdapter(
val previousItem = getItem(bindingAdapterPosition - 1) val previousItem = getItem(bindingAdapterPosition - 1)
if (previousItem.eventLog.type == EventLog.Type.ConferenceChatMessage) { if (previousItem.eventLog.type == EventLog.Type.ConferenceChatMessage) {
val previousMessage = previousItem.eventLog.chatMessage val previousMessage = previousItem.eventLog.chatMessage
if (previousMessage != null && previousMessage.fromAddress.weakEqual(chatMessage.fromAddress)) { if (previousMessage != null && previousMessage.fromAddress.weakEqual(
chatMessage.fromAddress
)
) {
if (abs(chatMessage.time - previousMessage.time) < MAX_TIME_TO_GROUP_MESSAGES) { if (abs(chatMessage.time - previousMessage.time) < MAX_TIME_TO_GROUP_MESSAGES) {
hasPrevious = true hasPrevious = true
} }
@ -293,7 +312,10 @@ class ChatMessagesListAdapter(
val nextItem = getItem(bindingAdapterPosition + 1) val nextItem = getItem(bindingAdapterPosition + 1)
if (nextItem.eventLog.type == EventLog.Type.ConferenceChatMessage) { if (nextItem.eventLog.type == EventLog.Type.ConferenceChatMessage) {
val nextMessage = nextItem.eventLog.chatMessage val nextMessage = nextItem.eventLog.chatMessage
if (nextMessage != null && nextMessage.fromAddress.weakEqual(chatMessage.fromAddress)) { if (nextMessage != null && nextMessage.fromAddress.weakEqual(
chatMessage.fromAddress
)
) {
if (abs(nextMessage.time - chatMessage.time) < MAX_TIME_TO_GROUP_MESSAGES) { if (abs(nextMessage.time - chatMessage.time) < MAX_TIME_TO_GROUP_MESSAGES) {
hasNext = true hasNext = true
} }
@ -308,12 +330,17 @@ class ChatMessagesListAdapter(
setContextMenuClickListener { setContextMenuClickListener {
val popupView: ChatMessageLongPressMenuBindingImpl = DataBindingUtil.inflate( val popupView: ChatMessageLongPressMenuBindingImpl = DataBindingUtil.inflate(
LayoutInflater.from(root.context), LayoutInflater.from(root.context),
R.layout.chat_message_long_press_menu, null, false R.layout.chat_message_long_press_menu,
null,
false
) )
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()
)
) {
// No message id // No message id
popupView.imdnHidden = true popupView.imdnHidden = true
totalSize -= itemSize totalSize -= itemSize
@ -497,7 +524,9 @@ private class ChatMessageDiffCallback : DiffUtil.ItemCallback<EventLogData>() {
oldData.time.value == newData.time.value && oldData.time.value == newData.time.value &&
oldData.isOutgoing == newData.isOutgoing oldData.isOutgoing == newData.isOutgoing
} else oldItem.notifyId == newItem.notifyId } else {
oldItem.notifyId == newItem.notifyId
}
} }
override fun areContentsTheSame( override fun areContentsTheSame(

View file

@ -48,7 +48,9 @@ class ChatRoomsListAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ChatRoomListCellBinding = DataBindingUtil.inflate( val binding: ChatRoomListCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.chat_room_list_cell, parent, false R.layout.chat_room_list_cell,
parent,
false
) )
return ViewHolder(binding) return ViewHolder(binding)
} }
@ -77,7 +79,9 @@ class ChatRoomsListAdapter(
try { try {
notifyItemChanged(bindingAdapterPosition) notifyItemChanged(bindingAdapterPosition)
} catch (e: Exception) { } catch (e: Exception) {
Log.e("[Chat Rooms Adapter] Can't notify item [$bindingAdapterPosition] has changed: $e") Log.e(
"[Chat Rooms Adapter] Can't notify item [$bindingAdapterPosition] has changed: $e"
)
} }
} }
} }

View file

@ -46,7 +46,9 @@ class GroupInfoParticipantsAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ChatRoomGroupInfoParticipantCellBinding = DataBindingUtil.inflate( val binding: ChatRoomGroupInfoParticipantCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.chat_room_group_info_participant_cell, parent, false R.layout.chat_room_group_info_participant_cell,
parent,
false
) )
return ViewHolder(binding) return ViewHolder(binding)
} }

View file

@ -41,7 +41,9 @@ class ImdnAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ChatRoomImdnParticipantCellBinding = DataBindingUtil.inflate( val binding: ChatRoomImdnParticipantCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.chat_room_imdn_participant_cell, parent, false R.layout.chat_room_imdn_participant_cell,
parent,
false
) )
return ViewHolder(binding) return ViewHolder(binding)
} }
@ -70,14 +72,18 @@ class ImdnAdapter(
val previousPosition = position - 1 val previousPosition = position - 1
return if (previousPosition >= 0) { return if (previousPosition >= 0) {
getItem(previousPosition).imdnState.state != participantImdnState.imdnState.state getItem(previousPosition).imdnState.state != participantImdnState.imdnState.state
} else true } else {
true
}
} }
override fun getHeaderViewForPosition(context: Context, position: Int): View { override fun getHeaderViewForPosition(context: Context, position: Int): View {
val participantImdnState = getItem(position).imdnState val participantImdnState = getItem(position).imdnState
val binding: ImdnListHeaderBinding = DataBindingUtil.inflate( val binding: ImdnListHeaderBinding = DataBindingUtil.inflate(
LayoutInflater.from(context), LayoutInflater.from(context),
R.layout.imdn_list_header, null, false R.layout.imdn_list_header,
null,
false
) )
when (participantImdnState.state) { when (participantImdnState.state) {
ChatMessage.State.Displayed -> { ChatMessage.State.Displayed -> {

View file

@ -48,7 +48,7 @@ import org.linphone.utils.TimestampUtils
class ChatMessageContentData( class ChatMessageContentData(
private val chatMessage: ChatMessage, private val chatMessage: ChatMessage,
private val contentIndex: Int, private val contentIndex: Int
) { ) {
var listener: OnContentClickedListener? = null var listener: OnContentClickedListener? = null
@ -172,7 +172,9 @@ class ChatMessageContentData(
fun download() { fun download() {
if (chatMessage.isFileTransferInProgress) { if (chatMessage.isFileTransferInProgress) {
Log.w("[Content] Another FileTransfer content for this message is currently being downloaded, can't start another one for now") Log.w(
"[Content] Another FileTransfer content for this message is currently being downloaded, can't start another one for now"
)
listener?.onError(R.string.chat_message_download_already_in_progress) listener?.onError(R.string.chat_message_download_already_in_progress)
return return
} }
@ -191,7 +193,9 @@ class ChatMessageContentData(
return return
} }
} else { } else {
Log.w("[Content] File path already set [$filePath] using it (auto download that failed probably)") Log.w(
"[Content] File path already set [$filePath] using it (auto download that failed probably)"
)
} }
if (!chatMessage.downloadContent(content)) { if (!chatMessage.downloadContent(content)) {
@ -221,7 +225,9 @@ class ChatMessageContentData(
val content = getContent() val content = getContent()
isFileEncrypted = content.isFileEncrypted isFileEncrypted = content.isFileEncrypted
Log.i("[Content] Is ${if (content.isFile) "file" else "file transfer"} content encrypted ? $isFileEncrypted") Log.i(
"[Content] Is ${if (content.isFile) "file" else "file transfer"} content encrypted ? $isFileEncrypted"
)
filePath.value = "" filePath.value = ""
fileName.value = if (content.name.isNullOrEmpty() && !content.filePath.isNullOrEmpty()) { fileName.value = if (content.name.isNullOrEmpty() && !content.filePath.isNullOrEmpty()) {
@ -232,7 +238,9 @@ class ChatMessageContentData(
// Display download size and underline text // Display download size and underline text
val fileSize = AppUtils.bytesToDisplayableSize(content.fileSize.toLong()) val fileSize = AppUtils.bytesToDisplayableSize(content.fileSize.toLong())
val spannable = SpannableString("${AppUtils.getString(R.string.chat_message_download_file)} ($fileSize)") val spannable = SpannableString(
"${AppUtils.getString(R.string.chat_message_download_file)} ($fileSize)"
)
spannable.setSpan(UnderlineSpan(), 0, spannable.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) spannable.setSpan(UnderlineSpan(), 0, spannable.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
downloadLabel.value = spannable downloadLabel.value = spannable
@ -247,7 +255,9 @@ class ChatMessageContentData(
if (content.isFile || (content.isFileTransfer && chatMessage.isOutgoing)) { if (content.isFile || (content.isFileTransfer && chatMessage.isOutgoing)) {
val path = if (isFileEncrypted) { val path = if (isFileEncrypted) {
Log.i("[Content] [VFS] Content is encrypted, requesting plain file path for file [${content.filePath}]") Log.i(
"[Content] [VFS] Content is encrypted, requesting plain file path for file [${content.filePath}]"
)
content.exportPlainFile() content.exportPlainFile()
} else { } else {
content.filePath ?: "" content.filePath ?: ""
@ -277,13 +287,19 @@ class ChatMessageContentData(
isConferenceIcs -> "conference invitation" isConferenceIcs -> "conference invitation"
else -> "unknown" else -> "unknown"
} }
Log.i("[Content] Extension for file [$path] is [$extension], deduced type from MIME is [$type]") Log.i(
"[Content] Extension for file [$path] is [$extension], deduced type from MIME is [$type]"
)
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(
Log.i("[Content] Voice recording duration is ${voiceRecordDuration.value} ($duration)") duration
)
Log.i(
"[Content] Voice recording duration is ${voiceRecordDuration.value} ($duration)"
)
} else if (isConferenceIcs) { } else if (isConferenceIcs) {
parseConferenceInvite(content) parseConferenceInvite(content)
} }
@ -291,7 +307,9 @@ class ChatMessageContentData(
Log.i("[Content] Found content with icalendar file") Log.i("[Content] Found content with icalendar file")
parseConferenceInvite(content) parseConferenceInvite(content)
} else { } else {
Log.w("[Content] Found ${if (content.isFile) "file" else "file transfer"} content with empty path...") Log.w(
"[Content] Found ${if (content.isFile) "file" else "file transfer"} content with empty path..."
)
isImage.value = false isImage.value = false
isVideo.value = false isVideo.value = false
isAudio.value = false isAudio.value = false
@ -325,7 +343,9 @@ class ChatMessageContentData(
val conferenceUri = conferenceInfo?.uri?.asStringUriOnly() val conferenceUri = conferenceInfo?.uri?.asStringUriOnly()
if (conferenceInfo != null && conferenceUri != null) { if (conferenceInfo != null && conferenceUri != null) {
conferenceAddress.value = conferenceUri!! conferenceAddress.value = conferenceUri!!
Log.i("[Content] Created conference info from ICS with address ${conferenceAddress.value}") Log.i(
"[Content] Created conference info from ICS with address ${conferenceAddress.value}"
)
conferenceSubject.value = conferenceInfo.subject conferenceSubject.value = conferenceInfo.subject
conferenceDescription.value = conferenceInfo.description conferenceDescription.value = conferenceInfo.description
@ -355,7 +375,10 @@ class ChatMessageContentData(
} }
} }
if (!organizerFound) participantsCount += 1 // +1 for organizer if (!organizerFound) participantsCount += 1 // +1 for organizer
conferenceParticipantCount.value = String.format(AppUtils.getString(R.string.conference_invite_participants_count), participantsCount) conferenceParticipantCount.value = String.format(
AppUtils.getString(R.string.conference_invite_participants_count),
participantsCount
)
} else if (conferenceInfo == null) { } else if (conferenceInfo == null) {
if (content.filePath != null) { if (content.filePath != null) {
try { try {
@ -367,7 +390,9 @@ class ChatMessageContentData(
textBuilder.append('\n') textBuilder.append('\n')
} }
br.close() br.close()
Log.e("[Content] Failed to create conference info from ICS file [${content.filePath}]: $textBuilder") Log.e(
"[Content] Failed to create conference info from ICS file [${content.filePath}]: $textBuilder"
)
} catch (e: Exception) { } catch (e: Exception) {
Log.e("[Content] Failed to read content of ICS file [${content.filePath}]: $e") Log.e("[Content] Failed to read content of ICS file [${content.filePath}]: $e")
} }
@ -375,7 +400,9 @@ class ChatMessageContentData(
Log.e("[Content] Failed to create conference info from ICS: ${content.utf8Text}") Log.e("[Content] Failed to create conference info from ICS: ${content.utf8Text}")
} }
} else if (conferenceInfo.uri == null) { } else if (conferenceInfo.uri == null) {
Log.e("[Content] Failed to find the conference URI in conference info [$conferenceInfo]") Log.e(
"[Content] Failed to find the conference URI in conference info [$conferenceInfo]"
)
} }
} }
@ -398,7 +425,11 @@ class ChatMessageContentData(
} }
if (AppUtils.isMediaVolumeLow(coreContext.context)) { if (AppUtils.isMediaVolumeLow(coreContext.context)) {
Toast.makeText(coreContext.context, R.string.chat_message_voice_recording_playback_low_volume, Toast.LENGTH_LONG).show() Toast.makeText(
coreContext.context,
R.string.chat_message_voice_recording_playback_low_volume,
Toast.LENGTH_LONG
).show()
} }
if (voiceRecordAudioFocusRequest == null) { if (voiceRecordAudioFocusRequest == null) {
@ -440,7 +471,9 @@ class ChatMessageContentData(
private fun initVoiceRecordPlayer() { private fun initVoiceRecordPlayer() {
Log.i("[Voice Recording] Creating player for voice record") Log.i("[Voice Recording] Creating player for voice record")
val playbackSoundCard = AudioRouteUtils.getAudioPlaybackDeviceIdForCallRecordingOrVoiceMessage() val playbackSoundCard = AudioRouteUtils.getAudioPlaybackDeviceIdForCallRecordingOrVoiceMessage()
Log.i("[Voice Recording] Using device $playbackSoundCard to make the voice message playback") Log.i(
"[Voice Recording] Using device $playbackSoundCard to make the voice message playback"
)
val localPlayer = coreContext.core.createLocalPlayer(playbackSoundCard, null, null) val localPlayer = coreContext.core.createLocalPlayer(playbackSoundCard, null, null)
if (localPlayer != null) { if (localPlayer != null) {
@ -454,8 +487,12 @@ class ChatMessageContentData(
val path = filePath.value val path = filePath.value
voiceRecordingPlayer.open(path.orEmpty()) voiceRecordingPlayer.open(path.orEmpty())
voiceRecordDuration.value = voiceRecordingPlayer.duration voiceRecordDuration.value = voiceRecordingPlayer.duration
formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(voiceRecordingPlayer.duration) // is already in milliseconds formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(
Log.i("[Voice Recording] Duration is ${voiceRecordDuration.value} (${voiceRecordingPlayer.duration})") voiceRecordingPlayer.duration
) // is already in milliseconds
Log.i(
"[Voice Recording] Duration is ${voiceRecordDuration.value} (${voiceRecordingPlayer.duration})"
)
} }
private fun stopVoiceRecording() { private fun stopVoiceRecording() {

View file

@ -89,7 +89,9 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
if (chatMessage.isReply) { if (chatMessage.isReply) {
val reply = chatMessage.replyMessage val reply = chatMessage.replyMessage
if (reply != null) { if (reply != null) {
Log.i("[Chat Message Data] Message is a reply of message id [${chatMessage.replyMessageId}] sent by [${chatMessage.replyMessageSenderAddress?.asStringUriOnly()}]") Log.i(
"[Chat Message Data] Message is a reply of message id [${chatMessage.replyMessageId}] sent by [${chatMessage.replyMessageSenderAddress?.asStringUriOnly()}]"
)
replyData.value = ChatMessageData(reply) replyData.value = ChatMessageData(reply)
} }
} }
@ -194,7 +196,9 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
val spannable = Spannable.Factory.getInstance().newSpannable(textContent) val spannable = Spannable.Factory.getInstance().newSpannable(textContent)
text.value = PatternClickableSpan() text.value = PatternClickableSpan()
.add( .add(
Pattern.compile("(?:<?sips?:)?[a-zA-Z0-9+_.\\-]+(?:@([a-zA-Z0-9+_.\\-;=~]+))+(>)?"), Pattern.compile(
"(?:<?sips?:)?[a-zA-Z0-9+_.\\-]+(?:@([a-zA-Z0-9+_.\\-;=~]+))+(>)?"
),
object : PatternClickableSpan.SpannableClickedListener { object : PatternClickableSpan.SpannableClickedListener {
override fun onSpanClicked(text: String) { override fun onSpanClicked(text: String) {
Log.i("[Chat Message Data] Clicked on SIP URI: $text") Log.i("[Chat Message Data] Clicked on SIP URI: $text")
@ -222,7 +226,9 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
).build(spannable) ).build(spannable)
isTextEmoji.value = AppUtils.isTextOnlyContainingEmoji(textContent) isTextEmoji.value = AppUtils.isTextOnlyContainingEmoji(textContent)
} else { } else {
Log.e("[Chat Message Data] Unexpected content with type: ${content.type}/${content.subtype}") Log.e(
"[Chat Message Data] Unexpected content with type: ${content.type}/${content.subtype}"
)
} }
} }
@ -257,7 +263,12 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
val days = seconds / 86400 val days = seconds / 86400
return when { return when {
days >= 1L -> AppUtils.getStringWithPlural(R.plurals.days, days.toInt()) days >= 1L -> AppUtils.getStringWithPlural(R.plurals.days, days.toInt())
else -> String.format("%02d:%02d:%02d", seconds / 3600, (seconds % 3600) / 60, (seconds % 60)) else -> String.format(
"%02d:%02d:%02d",
seconds / 3600,
(seconds % 3600) / 60,
(seconds % 60)
)
} }
} }
} }

View file

@ -158,7 +158,9 @@ class ChatRoomData(val chatRoom: ChatRoom) : ContactDataInterface {
if (participants.isNotEmpty()) { if (participants.isNotEmpty()) {
participants.first().address participants.first().address
} else { } else {
Log.e("[Chat Room] ${chatRoom.peerAddress} doesn't have any participant (state ${chatRoom.state})!") Log.e(
"[Chat Room] ${chatRoom.peerAddress} doesn't have any participant (state ${chatRoom.state})!"
)
null null
} }
} }
@ -213,7 +215,9 @@ class ChatRoomData(val chatRoom: ChatRoom) : ContactDataInterface {
val sender: String = val sender: String =
coreContext.contactsManager.findContactByAddress(msg.fromAddress)?.name coreContext.contactsManager.findContactByAddress(msg.fromAddress)?.name
?: LinphoneUtils.getDisplayName(msg.fromAddress) ?: LinphoneUtils.getDisplayName(msg.fromAddress)
builder.append(coreContext.context.getString(R.string.chat_room_last_message_sender_format, sender)) builder.append(
coreContext.context.getString(R.string.chat_room_last_message_sender_format, sender)
)
builder.append(" ") builder.append(" ")
} }
@ -221,12 +225,22 @@ class ChatRoomData(val chatRoom: ChatRoom) : ContactDataInterface {
if (content.isIcalendar) { if (content.isIcalendar) {
val body = AppUtils.getString(R.string.conference_invitation) val body = AppUtils.getString(R.string.conference_invitation)
builder.append(body) builder.append(body)
builder.setSpan(StyleSpan(Typeface.ITALIC), builder.length - body.length, builder.length, 0) builder.setSpan(
StyleSpan(Typeface.ITALIC),
builder.length - body.length,
builder.length,
0
)
break break
} else if (content.isVoiceRecording) { } else if (content.isVoiceRecording) {
val body = AppUtils.getString(R.string.chat_message_voice_recording) val body = AppUtils.getString(R.string.chat_message_voice_recording)
builder.append(body) builder.append(body)
builder.setSpan(StyleSpan(Typeface.ITALIC), builder.length - body.length, builder.length, 0) builder.setSpan(
StyleSpan(Typeface.ITALIC),
builder.length - body.length,
builder.length,
0
)
break break
} else if (content.isFile || content.isFileTransfer) { } else if (content.isFile || content.isFileTransfer) {
builder.append(content.name + " ") builder.append(content.name + " ")

View file

@ -27,7 +27,9 @@ import org.linphone.core.ChatRoomSecurityLevel
import org.linphone.core.Participant import org.linphone.core.Participant
import org.linphone.utils.LinphoneUtils import org.linphone.utils.LinphoneUtils
class DevicesListGroupData(private val participant: Participant) : GenericContactData(participant.address) { class DevicesListGroupData(private val participant: Participant) : GenericContactData(
participant.address
) {
val securityLevelIcon: Int by lazy { val securityLevelIcon: Int by lazy {
when (participant.securityLevel) { when (participant.securityLevel) {
ChatRoomSecurityLevel.Safe -> R.drawable.security_2_indicator ChatRoomSecurityLevel.Safe -> R.drawable.security_2_indicator

View file

@ -65,28 +65,60 @@ class EventData(private val eventLog: EventLog) : GenericContactData(
val context: Context = coreContext.context val context: Context = coreContext.context
text.value = when (eventLog.type) { text.value = when (eventLog.type) {
EventLog.Type.ConferenceCreated -> context.getString(R.string.chat_event_conference_created) EventLog.Type.ConferenceCreated -> context.getString(
EventLog.Type.ConferenceTerminated -> context.getString(R.string.chat_event_conference_destroyed) R.string.chat_event_conference_created
EventLog.Type.ConferenceParticipantAdded -> context.getString(R.string.chat_event_participant_added).format(getName()) )
EventLog.Type.ConferenceParticipantRemoved -> context.getString(R.string.chat_event_participant_removed).format(getName()) EventLog.Type.ConferenceTerminated -> context.getString(
EventLog.Type.ConferenceSubjectChanged -> context.getString(R.string.chat_event_subject_changed).format(eventLog.subject) R.string.chat_event_conference_destroyed
EventLog.Type.ConferenceParticipantSetAdmin -> context.getString(R.string.chat_event_admin_set).format(getName()) )
EventLog.Type.ConferenceParticipantUnsetAdmin -> context.getString(R.string.chat_event_admin_unset).format(getName()) EventLog.Type.ConferenceParticipantAdded -> context.getString(
EventLog.Type.ConferenceParticipantDeviceAdded -> context.getString(R.string.chat_event_device_added).format(getName()) R.string.chat_event_participant_added
EventLog.Type.ConferenceParticipantDeviceRemoved -> context.getString(R.string.chat_event_device_removed).format(getName()) ).format(getName())
EventLog.Type.ConferenceParticipantRemoved -> context.getString(
R.string.chat_event_participant_removed
).format(getName())
EventLog.Type.ConferenceSubjectChanged -> context.getString(
R.string.chat_event_subject_changed
).format(eventLog.subject)
EventLog.Type.ConferenceParticipantSetAdmin -> context.getString(
R.string.chat_event_admin_set
).format(getName())
EventLog.Type.ConferenceParticipantUnsetAdmin -> context.getString(
R.string.chat_event_admin_unset
).format(getName())
EventLog.Type.ConferenceParticipantDeviceAdded -> context.getString(
R.string.chat_event_device_added
).format(getName())
EventLog.Type.ConferenceParticipantDeviceRemoved -> context.getString(
R.string.chat_event_device_removed
).format(getName())
EventLog.Type.ConferenceSecurityEvent -> { EventLog.Type.ConferenceSecurityEvent -> {
val name = getName() val name = getName()
when (eventLog.securityEventType) { when (eventLog.securityEventType) {
EventLog.SecurityEventType.EncryptionIdentityKeyChanged -> context.getString(R.string.chat_security_event_lime_identity_key_changed).format(name) EventLog.SecurityEventType.EncryptionIdentityKeyChanged -> context.getString(
EventLog.SecurityEventType.ManInTheMiddleDetected -> context.getString(R.string.chat_security_event_man_in_the_middle_detected).format(name) R.string.chat_security_event_lime_identity_key_changed
EventLog.SecurityEventType.SecurityLevelDowngraded -> context.getString(R.string.chat_security_event_security_level_downgraded).format(name) ).format(name)
EventLog.SecurityEventType.ParticipantMaxDeviceCountExceeded -> context.getString(R.string.chat_security_event_participant_max_count_exceeded).format(name) EventLog.SecurityEventType.ManInTheMiddleDetected -> context.getString(
R.string.chat_security_event_man_in_the_middle_detected
).format(name)
EventLog.SecurityEventType.SecurityLevelDowngraded -> context.getString(
R.string.chat_security_event_security_level_downgraded
).format(name)
EventLog.SecurityEventType.ParticipantMaxDeviceCountExceeded -> context.getString(
R.string.chat_security_event_participant_max_count_exceeded
).format(name)
else -> "Unexpected security event for $name: ${eventLog.securityEventType}" else -> "Unexpected security event for $name: ${eventLog.securityEventType}"
} }
} }
EventLog.Type.ConferenceEphemeralMessageDisabled -> context.getString(R.string.chat_event_ephemeral_disabled) EventLog.Type.ConferenceEphemeralMessageDisabled -> context.getString(
EventLog.Type.ConferenceEphemeralMessageEnabled -> context.getString(R.string.chat_event_ephemeral_enabled).format(formatEphemeralExpiration(context, eventLog.ephemeralMessageLifetime)) R.string.chat_event_ephemeral_disabled
EventLog.Type.ConferenceEphemeralMessageLifetimeChanged -> context.getString(R.string.chat_event_ephemeral_lifetime_changed).format(formatEphemeralExpiration(context, eventLog.ephemeralMessageLifetime)) )
EventLog.Type.ConferenceEphemeralMessageEnabled -> context.getString(
R.string.chat_event_ephemeral_enabled
).format(formatEphemeralExpiration(context, eventLog.ephemeralMessageLifetime))
EventLog.Type.ConferenceEphemeralMessageLifetimeChanged -> context.getString(
R.string.chat_event_ephemeral_lifetime_changed
).format(formatEphemeralExpiration(context, eventLog.ephemeralMessageLifetime))
else -> "Unexpected event: ${eventLog.type}" else -> "Unexpected event: ${eventLog.type}"
} }
} }

View file

@ -26,7 +26,9 @@ import org.linphone.contact.GenericContactData
import org.linphone.core.ChatRoomSecurityLevel import org.linphone.core.ChatRoomSecurityLevel
import org.linphone.utils.LinphoneUtils import org.linphone.utils.LinphoneUtils
class GroupInfoParticipantData(val participant: GroupChatRoomMember) : GenericContactData(participant.address) { class GroupInfoParticipantData(val participant: GroupChatRoomMember) : GenericContactData(
participant.address
) {
val sipUri: String get() = LinphoneUtils.getDisplayableAddress(participant.address) val sipUri: String get() = LinphoneUtils.getDisplayableAddress(participant.address)
val isAdmin = MutableLiveData<Boolean>() val isAdmin = MutableLiveData<Boolean>()

View file

@ -23,7 +23,9 @@ import org.linphone.contact.GenericContactData
import org.linphone.core.ParticipantImdnState import org.linphone.core.ParticipantImdnState
import org.linphone.utils.TimestampUtils import org.linphone.utils.TimestampUtils
class ImdnParticipantData(val imdnState: ParticipantImdnState) : GenericContactData(imdnState.participant.address) { class ImdnParticipantData(val imdnState: ParticipantImdnState) : GenericContactData(
imdnState.participant.address
) {
val sipUri: String = imdnState.participant.address.asStringUriOnly() val sipUri: String = imdnState.participant.address.asStringUriOnly()
val time: String = TimestampUtils.toString(imdnState.stateChangeTime) val time: String = TimestampUtils.toString(imdnState.stateChangeTime)

View file

@ -70,7 +70,9 @@ class ChatRoomCreationFragment : SecureFragment<ChatRoomCreationFragmentBinding>
binding.contactsList.layoutManager = layoutManager binding.contactsList.layoutManager = layoutManager
// Divider between items // Divider between items
binding.contactsList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager)) binding.contactsList.addItemDecoration(
AppUtils.getDividerDecoration(requireContext(), layoutManager)
)
binding.back.visibility = if (resources.getBoolean(R.bool.isTablet)) View.INVISIBLE else View.VISIBLE binding.back.visibility = if (resources.getBoolean(R.bool.isTablet)) View.INVISIBLE else View.VISIBLE

View file

@ -85,7 +85,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
if (viewModel.isUserScrollingUp.value == false) { if (viewModel.isUserScrollingUp.value == false) {
scrollToFirstUnreadMessageOrBottom(false) scrollToFirstUnreadMessageOrBottom(false)
} else { } else {
Log.d("[Chat Room] User has scrolled up manually in the messages history, don't scroll to the newly added message at the bottom & don't mark the chat room as read") Log.d(
"[Chat Room] User has scrolled up manually in the messages history, don't scroll to the newly added message at the bottom & don't mark the chat room as read"
)
} }
} }
} }
@ -106,7 +108,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
Log.i("[Chat Room] Messages have been displayed, scrolling to first unread") Log.i("[Chat Room] Messages have been displayed, scrolling to first unread")
val notAllMessagesDisplayed = scrollToFirstUnreadMessageOrBottom(false) val notAllMessagesDisplayed = scrollToFirstUnreadMessageOrBottom(false)
if (notAllMessagesDisplayed) { if (notAllMessagesDisplayed) {
Log.w("[Chat Room] More unread messages than the screen can display, do not mark chat room as read now, wait for user to scroll to bottom") Log.w(
"[Chat Room] More unread messages than the screen can display, do not mark chat room as read now, wait for user to scroll to bottom"
)
} else { } else {
// Consider user as scrolled to the end when marking chat room as read // Consider user as scrolled to the end when marking chat room as read
viewModel.isUserScrollingUp.value = false viewModel.isUserScrollingUp.value = false
@ -138,10 +142,14 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
if (chatRoom != null) { if (chatRoom != null) {
outState.putString("LocalSipUri", chatRoom.localAddress.asStringUriOnly()) outState.putString("LocalSipUri", chatRoom.localAddress.asStringUriOnly())
outState.putString("RemoteSipUri", chatRoom.peerAddress.asStringUriOnly()) outState.putString("RemoteSipUri", chatRoom.peerAddress.asStringUriOnly())
Log.i("[Chat Room] Saving current chat room local & remote addresses in save instance state") Log.i(
"[Chat Room] Saving current chat room local & remote addresses in save instance state"
)
} }
} else { } else {
Log.w("[Chat Room] Can't save instance state, sharedViewModel hasn't been initialized yet") Log.w(
"[Chat Room] Can't save instance state, sharedViewModel hasn't been initialized yet"
)
} }
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
} }
@ -155,8 +163,12 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false
val localSipUri = arguments?.getString("LocalSipUri") ?: savedInstanceState?.getString("LocalSipUri") val localSipUri = arguments?.getString("LocalSipUri") ?: savedInstanceState?.getString(
val remoteSipUri = arguments?.getString("RemoteSipUri") ?: savedInstanceState?.getString("RemoteSipUri") "LocalSipUri"
)
val remoteSipUri = arguments?.getString("RemoteSipUri") ?: savedInstanceState?.getString(
"RemoteSipUri"
)
val textToShare = arguments?.getString("TextToShare") val textToShare = arguments?.getString("TextToShare")
val filesToShare = arguments?.getStringArrayList("FilesToShare") val filesToShare = arguments?.getStringArrayList("FilesToShare")
@ -166,12 +178,16 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
} }
arguments?.clear() arguments?.clear()
if (localSipUri != null && remoteSipUri != null) { if (localSipUri != null && remoteSipUri != null) {
Log.i("[Chat Room] Found local [$localSipUri] & remote [$remoteSipUri] addresses in arguments or saved instance state") Log.i(
"[Chat Room] Found local [$localSipUri] & remote [$remoteSipUri] addresses in arguments or saved instance state"
)
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, null,
localAddress,
remoteSipAddress,
arrayOfNulls( arrayOfNulls(
0 0
) )
@ -192,7 +208,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
binding.root.addKeyboardInsetListener { keyboardVisible -> binding.root.addKeyboardInsetListener { keyboardVisible ->
if (keyboardVisible && chatSendingViewModel.isEmojiPickerOpen.value == true) { if (keyboardVisible && chatSendingViewModel.isEmojiPickerOpen.value == true) {
Log.d("[Chat Room] Emoji picker is opened, closing it because keyboard is now visible") Log.d(
"[Chat Room] Emoji picker is opened, closing it because keyboard is now visible"
)
chatSendingViewModel.isEmojiPickerOpen.value = false chatSendingViewModel.isEmojiPickerOpen.value = false
} }
} }
@ -275,12 +293,18 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
} }
} }
} }
RecyclerViewSwipeUtils(ItemTouchHelper.RIGHT or ItemTouchHelper.LEFT, swipeConfiguration, swipeListener) RecyclerViewSwipeUtils(
ItemTouchHelper.RIGHT or ItemTouchHelper.LEFT,
swipeConfiguration,
swipeListener
)
.attachToRecyclerView(binding.chatMessagesList) .attachToRecyclerView(binding.chatMessagesList)
chatScrollListener = object : ChatScrollListener(layoutManager) { chatScrollListener = object : ChatScrollListener(layoutManager) {
override fun onLoadMore(totalItemsCount: Int) { override fun onLoadMore(totalItemsCount: Int) {
Log.i("[Chat Room] User has scrolled up far enough, load more items from history (currently there are $totalItemsCount messages displayed)") Log.i(
"[Chat Room] User has scrolled up far enough, load more items from history (currently there are $totalItemsCount messages displayed)"
)
listViewModel.loadMoreData(totalItemsCount) listViewModel.loadMoreData(totalItemsCount)
} }
@ -295,7 +319,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
if (viewModel.unreadMessagesCount.value != 0 && if (viewModel.unreadMessagesCount.value != 0 &&
coreContext.notificationsManager.currentlyDisplayedChatRoomAddress == peerAddress coreContext.notificationsManager.currentlyDisplayedChatRoomAddress == peerAddress
) { ) {
Log.i("[Chat Room] User has scrolled to the latest message, mark chat room as read") Log.i(
"[Chat Room] User has scrolled to the latest message, mark chat room as read"
)
viewModel.chatRoom.markAsRead() viewModel.chatRoom.markAsRead()
} }
} }
@ -447,7 +473,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
if (path.isNotEmpty() && !File(path).exists()) { if (path.isNotEmpty() && !File(path).exists()) {
Log.e("[Chat Room] File not found: $path") Log.e("[Chat Room] File not found: $path")
(requireActivity() as MainActivity).showSnackBar(R.string.chat_room_file_not_found) (requireActivity() as MainActivity).showSnackBar(
R.string.chat_room_file_not_found
)
} else { } else {
if (path.isEmpty()) { if (path.isEmpty()) {
val name = content.name val name = content.name
@ -456,14 +484,18 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
FileUtils.writeIntoFile(content.buffer, file) FileUtils.writeIntoFile(content.buffer, file)
path = file.absolutePath path = file.absolutePath
content.filePath = path content.filePath = path
Log.i("[Chat Room] Content file path was empty, created file from buffer at $path") Log.i(
"[Chat Room] Content file path was empty, created file from buffer at $path"
)
} else if (content.isIcalendar) { } else if (content.isIcalendar) {
val name = "conference.ics" val name = "conference.ics"
val file = FileUtils.getFileStoragePath(name) val file = FileUtils.getFileStoragePath(name)
FileUtils.writeIntoFile(content.buffer, file) FileUtils.writeIntoFile(content.buffer, file)
path = file.absolutePath path = file.absolutePath
content.filePath = path content.filePath = path
Log.i("[Chat Room] Content file path was empty, created conference.ics from buffer at $path") Log.i(
"[Chat Room] Content file path was empty, created conference.ics from buffer at $path"
)
} }
} }
@ -494,7 +526,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
) )
else -> { else -> {
if (content.isFileEncrypted) { if (content.isFileEncrypted) {
Log.w("[Chat Room] File is encrypted and can't be opened in one of our viewers...") Log.w(
"[Chat Room] File is encrypted and can't be opened in one of our viewers..."
)
showDialogForUserConsentBeforeExportingFileInThirdPartyApp( showDialogForUserConsentBeforeExportingFileInThirdPartyApp(
content content
) )
@ -570,7 +604,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
val eventLog = events.find { eventLog -> val eventLog = events.find { eventLog ->
if (eventLog.eventLog.type == EventLog.Type.ConferenceChatMessage) { if (eventLog.eventLog.type == EventLog.Type.ConferenceChatMessage) {
(eventLog.data as ChatMessageData).chatMessage.messageId == chatMessage.messageId (eventLog.data as ChatMessageData).chatMessage.messageId == chatMessage.messageId
} else false } else {
false
}
} }
index = events.indexOf(eventLog) index = events.indexOf(eventLog)
if (index == -1) { if (index == -1) {
@ -632,7 +668,7 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
alertDialog.setMessage(message) alertDialog.setMessage(message)
alertDialog.setNeutralButton(R.string.chat_message_context_menu_copy_text) { alertDialog.setNeutralButton(R.string.chat_message_context_menu_copy_text) {
_, _ -> _, _ ->
val clipboard: ClipboardManager = val clipboard: ClipboardManager =
coreContext.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager coreContext.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Chat room info", message) val clip = ClipData.newPlainText("Chat room info", message)
@ -662,17 +698,25 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
if (corePreferences.holdToRecordVoiceMessage) { if (corePreferences.holdToRecordVoiceMessage) {
when (event.action) { when (event.action) {
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
Log.i("[Chat Room] Start recording voice message as long as recording button is held") Log.i(
"[Chat Room] Start recording voice message as long as recording button is held"
)
chatSendingViewModel.startVoiceRecording() chatSendingViewModel.startVoiceRecording()
} }
MotionEvent.ACTION_UP -> { MotionEvent.ACTION_UP -> {
val voiceRecordingDuration = chatSendingViewModel.voiceRecordingDuration.value ?: 0 val voiceRecordingDuration = chatSendingViewModel.voiceRecordingDuration.value ?: 0
if (voiceRecordingDuration < 1000) { if (voiceRecordingDuration < 1000) {
Log.w("[Chat Room] Voice recording button has been held for less than a second, considering miss click") Log.w(
"[Chat Room] Voice recording button has been held for less than a second, considering miss click"
)
chatSendingViewModel.cancelVoiceRecording() chatSendingViewModel.cancelVoiceRecording()
(activity as MainActivity).showSnackBar(R.string.chat_message_voice_recording_hold_to_record) (activity as MainActivity).showSnackBar(
R.string.chat_message_voice_recording_hold_to_record
)
} else { } else {
Log.i("[Chat Room] Voice recording button has been released, stop recording") Log.i(
"[Chat Room] Voice recording button has been released, stop recording"
)
chatSendingViewModel.stopVoiceRecording() chatSendingViewModel.stopVoiceRecording()
} }
view.performClick() view.performClick()
@ -712,7 +756,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
chatSendingViewModel.attachingFileInProgress.value = true chatSendingViewModel.attachingFileInProgress.value = true
for (filePath in filesToShare) { for (filePath in filesToShare) {
val path = FileUtils.copyToLocalStorage(filePath) val path = FileUtils.copyToLocalStorage(filePath)
Log.i("[Chat Room] Found [$filePath] file to share, matching path is [$path]") Log.i(
"[Chat Room] Found [$filePath] file to share, matching path is [$path]"
)
if (path != null) { if (path != null) {
chatSendingViewModel.addAttachment(path) chatSendingViewModel.addAttachment(path)
} }
@ -806,7 +852,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
.viewTreeObserver .viewTreeObserver
.addOnGlobalLayoutListener(globalLayoutLayout) .addOnGlobalLayoutListener(globalLayoutLayout)
} else { } else {
Log.e("[Chat Room] Fragment resuming but viewModel lateinit property isn't initialized!") Log.e(
"[Chat Room] Fragment resuming but viewModel lateinit property isn't initialized!"
)
} }
} }
@ -838,10 +886,10 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
chatSendingViewModel.attachingFileInProgress.value = true chatSendingViewModel.attachingFileInProgress.value = true
for ( for (
fileToUploadPath in FileUtils.getFilesPathFromPickerIntent( fileToUploadPath in FileUtils.getFilesPathFromPickerIntent(
data, data,
chatSendingViewModel.temporaryFileUploadPath chatSendingViewModel.temporaryFileUploadPath
) )
) { ) {
Log.i("[Chat Room] Found [$fileToUploadPath] file from intent") Log.i("[Chat Room] Found [$fileToUploadPath] file from intent")
chatSendingViewModel.addAttachment(fileToUploadPath) chatSendingViewModel.addAttachment(fileToUploadPath)
@ -867,9 +915,13 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
dialog.dismiss() dialog.dismiss()
} }
val okLabel = if (viewModel.oneParticipantOneDevice) getString(R.string.dialog_call) else getString( val okLabel = if (viewModel.oneParticipantOneDevice) {
R.string.dialog_ok getString(R.string.dialog_call)
) } else {
getString(
R.string.dialog_ok
)
}
dialogViewModel.showOkButton( dialogViewModel.showOkButton(
{ doNotAskAgain -> { doNotAskAgain ->
if (doNotAskAgain) corePreferences.limeSecurityPopupEnabled = false if (doNotAskAgain) corePreferences.limeSecurityPopupEnabled = false
@ -921,7 +973,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
} }
private fun showForwardConfirmationDialog(chatMessage: ChatMessage) { private fun showForwardConfirmationDialog(chatMessage: ChatMessage) {
val viewModel = DialogViewModel(getString(R.string.chat_message_forward_confirmation_dialog)) val viewModel = DialogViewModel(
getString(R.string.chat_message_forward_confirmation_dialog)
)
viewModel.iconResource = R.drawable.forward_message_default viewModel.iconResource = R.drawable.forward_message_default
viewModel.showIcon = true viewModel.showIcon = true
val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel) val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel)
@ -946,7 +1000,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
private fun showPopupMenu(chatRoom: ChatRoom) { private fun showPopupMenu(chatRoom: ChatRoom) {
val popupView: ChatRoomMenuBindingImpl = DataBindingUtil.inflate( val popupView: ChatRoomMenuBindingImpl = DataBindingUtil.inflate(
LayoutInflater.from(context), LayoutInflater.from(context),
R.layout.chat_room_menu, null, false R.layout.chat_room_menu,
null,
false
) )
val readOnly = chatRoom.isReadOnly val readOnly = chatRoom.isReadOnly
popupView.ephemeralEnabled = !readOnly popupView.ephemeralEnabled = !readOnly
@ -1014,7 +1070,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
if (viewModel.ephemeralChatRoom) { if (viewModel.ephemeralChatRoom) {
if (chatRoom.currentParams.ephemeralMode == ChatRoomEphemeralMode.AdminManaged) { if (chatRoom.currentParams.ephemeralMode == ChatRoomEphemeralMode.AdminManaged) {
if (chatRoom.me?.isAdmin == false) { if (chatRoom.me?.isAdmin == false) {
Log.w("[Chat Room] Hiding ephemeral menu as mode is admin managed and we aren't admin") Log.w(
"[Chat Room] Hiding ephemeral menu as mode is admin managed and we aren't admin"
)
popupView.ephemeralHidden = true popupView.ephemeralHidden = true
totalSize -= itemSize totalSize -= itemSize
} }
@ -1117,7 +1175,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
R.string.chat_message_removal_info, R.string.chat_message_removal_info,
R.string.chat_message_abort_removal R.string.chat_message_abort_removal
) { ) {
Log.i("[Chat Room] Canceled message/event deletion task: $task for message/event at position $position") Log.i(
"[Chat Room] Canceled message/event deletion task: $task for message/event at position $position"
)
adapter.notifyItemRangeChanged(position, adapter.itemCount - position) adapter.notifyItemRangeChanged(position, adapter.itemCount - position)
task.cancel() task.cancel()
} }
@ -1136,7 +1196,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
adapter.itemCount - 1 adapter.itemCount - 1
} }
Log.i("[Chat Room] Scrolling to position $indexToScrollTo, first unread message is at $firstUnreadMessagePosition") Log.i(
"[Chat Room] Scrolling to position $indexToScrollTo, first unread message is at $firstUnreadMessagePosition"
)
scrollTo(indexToScrollTo, smooth) scrollTo(indexToScrollTo, smooth)
if (firstUnreadMessagePosition == 0) { if (firstUnreadMessagePosition == 0) {
@ -1219,7 +1281,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
{ {
dialog.dismiss() dialog.dismiss()
lifecycleScope.launch { lifecycleScope.launch {
Log.i("[Chat Room] [VFS] Content is encrypted, requesting plain file path for file [${content.filePath}]") Log.i(
"[Chat Room] [VFS] Content is encrypted, requesting plain file path for file [${content.filePath}]"
)
val plainFilePath = content.exportPlainFile() val plainFilePath = content.exportPlainFile()
if (!FileUtils.openFileInThirdPartyApp(requireActivity(), plainFilePath)) { if (!FileUtils.openFileInThirdPartyApp(requireActivity(), plainFilePath)) {
showDialogToSuggestOpeningFileAsText() showDialogToSuggestOpeningFileAsText()
@ -1245,7 +1309,10 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
} }
private fun showGroupCallDialog() { private fun showGroupCallDialog() {
val dialogViewModel = DialogViewModel(getString(R.string.conference_start_group_call_dialog_message), getString(R.string.conference_start_group_call_dialog_title)) val dialogViewModel = DialogViewModel(
getString(R.string.conference_start_group_call_dialog_message),
getString(R.string.conference_start_group_call_dialog_title)
)
val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel)
dialogViewModel.iconResource = R.drawable.icon_video_conf_incoming dialogViewModel.iconResource = R.drawable.icon_video_conf_incoming

View file

@ -76,7 +76,9 @@ class GroupInfoFragment : SecureFragment<ChatRoomGroupInfoFragmentBinding>() {
binding.participants.layoutManager = layoutManager binding.participants.layoutManager = layoutManager
// Divider between items // Divider between items
binding.participants.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager)) binding.participants.addItemDecoration(
AppUtils.getDividerDecoration(requireContext(), layoutManager)
)
viewModel.participants.observe( viewModel.participants.observe(
viewLifecycleOwner viewLifecycleOwner
@ -148,7 +150,9 @@ class GroupInfoFragment : SecureFragment<ChatRoomGroupInfoFragmentBinding>() {
} }
binding.setLeaveClickListener { binding.setLeaveClickListener {
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(
@ -190,7 +194,11 @@ class GroupInfoFragment : SecureFragment<ChatRoomGroupInfoFragmentBinding>() {
} else { } else {
list.add( list.add(
GroupInfoParticipantData( GroupInfoParticipantData(
GroupChatRoomMember(address, false, hasLimeX3DHCapability = viewModel.isEncrypted.value == true) GroupChatRoomMember(
address,
false,
hasLimeX3DHCapability = viewModel.isEncrypted.value == true
)
) )
) )
} }

View file

@ -84,7 +84,9 @@ class ImdnFragment : SecureFragment<ChatRoomImdnFragmentBinding>() {
binding.participantsList.layoutManager = layoutManager binding.participantsList.layoutManager = layoutManager
// Divider between items // Divider between items
binding.participantsList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager)) binding.participantsList.addItemDecoration(
AppUtils.getDividerDecoration(requireContext(), layoutManager)
)
// Displays state header // Displays state header
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter)

View file

@ -133,7 +133,9 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
val navHostFragment = val navHostFragment =
childFragmentManager.findFragmentById(R.id.chat_nav_container) as NavHostFragment childFragmentManager.findFragmentById(R.id.chat_nav_container) as NavHostFragment
if (navHostFragment.navController.currentDestination?.id == R.id.emptyChatFragment) { if (navHostFragment.navController.currentDestination?.id == R.id.emptyChatFragment) {
Log.i("[Chat] Foldable device has been folded, closing side pane with empty fragment") Log.i(
"[Chat] Foldable device has been folded, closing side pane with empty fragment"
)
binding.slidingPane.closePane() binding.slidingPane.closePane()
} }
} }
@ -212,7 +214,9 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
if (!binding.slidingPane.isSlideable && if (!binding.slidingPane.isSlideable &&
deletedChatRoom == sharedViewModel.selectedChatRoom.value deletedChatRoom == sharedViewModel.selectedChatRoom.value
) { ) {
Log.i("[Chat] Currently displayed chat room has been deleted, removing detail fragment") Log.i(
"[Chat] Currently displayed chat room has been deleted, removing detail fragment"
)
clearDisplayedChatRoom() clearDisplayedChatRoom()
} }
dialog.dismiss() dialog.dismiss()
@ -224,11 +228,17 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
} }
} }
} }
RecyclerViewSwipeUtils(ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT, swipeConfiguration, swipeListener) RecyclerViewSwipeUtils(
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT,
swipeConfiguration,
swipeListener
)
.attachToRecyclerView(binding.chatList) .attachToRecyclerView(binding.chatList)
// Divider between items // Divider between items
binding.chatList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager)) binding.chatList.addItemDecoration(
AppUtils.getDividerDecoration(requireContext(), layoutManager)
)
listViewModel.chatRooms.observe( listViewModel.chatRooms.observe(
viewLifecycleOwner viewLifecycleOwner
@ -256,7 +266,9 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
if (!binding.slidingPane.isOpen) { if (!binding.slidingPane.isOpen) {
Log.w("[Chat] Chat room is displayed but sliding pane is closed...") Log.w("[Chat] Chat room is displayed but sliding pane is closed...")
if (!binding.slidingPane.openPane()) { if (!binding.slidingPane.openPane()) {
Log.e("[Chat] Tried to open pane to workaround already displayed chat room issue, failed!") Log.e(
"[Chat] Tried to open pane to workaround already displayed chat room issue, failed!"
)
} }
} else { } else {
Log.w("[Chat] This chat room is already displayed!") Log.w("[Chat] This chat room is already displayed!")
@ -314,11 +326,18 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
val localSipUri = arguments?.getString("LocalSipUri") val localSipUri = arguments?.getString("LocalSipUri")
val remoteSipUri = arguments?.getString("RemoteSipUri") val remoteSipUri = arguments?.getString("RemoteSipUri")
if (localSipUri != null && remoteSipUri != null) { if (localSipUri != null && remoteSipUri != null) {
Log.i("[Chat] Found local [$localSipUri] & remote [$remoteSipUri] addresses in arguments") Log.i(
"[Chat] Found local [$localSipUri] & remote [$remoteSipUri] addresses in arguments"
)
arguments?.clear() arguments?.clear()
val localAddress = Factory.instance().createAddress(localSipUri) val localAddress = Factory.instance().createAddress(localSipUri)
val remoteSipAddress = Factory.instance().createAddress(remoteSipUri) val remoteSipAddress = Factory.instance().createAddress(remoteSipUri)
val chatRoom = coreContext.core.searchChatRoom(null, localAddress, remoteSipAddress, arrayOfNulls(0)) val chatRoom = coreContext.core.searchChatRoom(
null,
localAddress,
remoteSipAddress,
arrayOfNulls(0)
)
if (chatRoom != null) { if (chatRoom != null) {
Log.i("[Chat] Found matching chat room $chatRoom") Log.i("[Chat] Found matching chat room $chatRoom")
adapter.selectedChatRoomEvent.value = Event(chatRoom) adapter.selectedChatRoomEvent.value = Event(chatRoom)

View file

@ -209,7 +209,9 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
attachments.value = list attachments.value = list
val pathToDelete = attachment.path val pathToDelete = attachment.path
Log.i("[Chat Message Sending] Attachment is being removed, delete local copy [$pathToDelete]") Log.i(
"[Chat Message Sending] Attachment is being removed, delete local copy [$pathToDelete]"
)
FileUtils.deleteFile(pathToDelete) FileUtils.deleteFile(pathToDelete)
sendMessageEnabled.value = textToSend.value.orEmpty().trim().isNotEmpty() || list.isNotEmpty() || isPendingVoiceRecord.value == true sendMessageEnabled.value = textToSend.value.orEmpty().trim().isNotEmpty() || list.isNotEmpty() || isPendingVoiceRecord.value == true
@ -227,10 +229,11 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
private fun createChatMessage(): ChatMessage { private fun createChatMessage(): ChatMessage {
val pendingMessageToReplyTo = pendingChatMessageToReplyTo.value val pendingMessageToReplyTo = pendingChatMessageToReplyTo.value
return if (isPendingAnswer.value == true && pendingMessageToReplyTo != null) return if (isPendingAnswer.value == true && pendingMessageToReplyTo != null) {
chatRoom.createReplyMessage(pendingMessageToReplyTo.chatMessage) chatRoom.createReplyMessage(pendingMessageToReplyTo.chatMessage)
else } else {
chatRoom.createEmptyMessage() chatRoom.createEmptyMessage()
}
} }
fun sendMessage() { fun sendMessage() {
@ -249,7 +252,9 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
if (isPendingVoiceRecord.value == true && recorder.file != null) { if (isPendingVoiceRecord.value == true && recorder.file != null) {
val content = recorder.createContent() val content = recorder.createContent()
if (content != null) { if (content != null) {
Log.i("[Chat Message Sending] Voice recording content created, file name is ${content.name} and duration is ${content.fileDuration}") Log.i(
"[Chat Message Sending] Voice recording content created, file name is ${content.name} and duration is ${content.fileDuration}"
)
message.addContent(content) message.addContent(content)
voiceRecord = true voiceRecord = true
} else { } else {
@ -373,7 +378,9 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
} }
val tempFileName = "voice-recording-${System.currentTimeMillis()}.$extension" val tempFileName = "voice-recording-${System.currentTimeMillis()}.$extension"
val file = FileUtils.getFileStoragePath(tempFileName) val file = FileUtils.getFileStoragePath(tempFileName)
Log.w("[Chat Message Sending] Recorder is closed, starting recording in ${file.absoluteFile}") Log.w(
"[Chat Message Sending] Recorder is closed, starting recording in ${file.absoluteFile}"
)
recorder.open(file.absolutePath) recorder.open(file.absolutePath)
recorder.start() recorder.start()
} }
@ -393,10 +400,14 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val duration = recorder.duration val duration = recorder.duration
voiceRecordingDuration.value = recorder.duration % voiceRecordingProgressBarMax voiceRecordingDuration.value = recorder.duration % voiceRecordingProgressBarMax
formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(duration) // duration is in ms formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(
duration
) // duration is in ms
if (duration >= maxVoiceRecordDuration) { if (duration >= maxVoiceRecordDuration) {
Log.w("[Chat Message Sending] Max duration for voice recording exceeded (${maxVoiceRecordDuration}ms), stopping.") Log.w(
"[Chat Message Sending] Max duration for voice recording exceeded (${maxVoiceRecordDuration}ms), stopping."
)
stopVoiceRecording() stopVoiceRecording()
} }
} }
@ -463,7 +474,11 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
} }
if (AppUtils.isMediaVolumeLow(coreContext.context)) { if (AppUtils.isMediaVolumeLow(coreContext.context)) {
Toast.makeText(coreContext.context, R.string.chat_message_voice_recording_playback_low_volume, Toast.LENGTH_LONG).show() Toast.makeText(
coreContext.context,
R.string.chat_message_voice_recording_playback_low_volume,
Toast.LENGTH_LONG
).show()
} }
if (voiceRecordAudioFocusRequest == null) { if (voiceRecordAudioFocusRequest == null) {
@ -508,7 +523,9 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
val recordingAudioDevice = AudioRouteUtils.getAudioRecordingDeviceForVoiceMessage() val recordingAudioDevice = AudioRouteUtils.getAudioRecordingDeviceForVoiceMessage()
recorderParams.audioDevice = recordingAudioDevice recorderParams.audioDevice = recordingAudioDevice
Log.i("[Chat Message Sending] Using device ${recorderParams.audioDevice?.id} to make the voice message recording") Log.i(
"[Chat Message Sending] Using device ${recorderParams.audioDevice?.id} to make the voice message recording"
)
recorder = coreContext.core.createRecorder(recorderParams) recorder = coreContext.core.createRecorder(recorderParams)
} }
@ -517,7 +534,9 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
Log.i("[Chat Message Sending] Creating player for voice record") Log.i("[Chat Message Sending] Creating player for voice record")
val playbackSoundCard = AudioRouteUtils.getAudioPlaybackDeviceIdForCallRecordingOrVoiceMessage() val playbackSoundCard = AudioRouteUtils.getAudioPlaybackDeviceIdForCallRecordingOrVoiceMessage()
Log.i("[Chat Message Sending] Using device $playbackSoundCard to make the voice message playback") Log.i(
"[Chat Message Sending] Using device $playbackSoundCard to make the voice message playback"
)
val localPlayer = coreContext.core.createLocalPlayer(playbackSoundCard, null, null) val localPlayer = coreContext.core.createLocalPlayer(playbackSoundCard, null, null)
if (localPlayer != null) { if (localPlayer != null) {
@ -559,6 +578,10 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
} }
private fun updateChatRoomReadOnlyState() { private fun updateChatRoomReadOnlyState() {
isReadOnly.value = chatRoom.isReadOnly || (chatRoom.hasCapability(ChatRoomCapabilities.Conference.toInt()) && chatRoom.participants.isEmpty()) isReadOnly.value = chatRoom.isReadOnly || (
chatRoom.hasCapability(
ChatRoomCapabilities.Conference.toInt()
) && chatRoom.participants.isEmpty()
)
} }
} }

View file

@ -107,7 +107,9 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
} }
override fun onEphemeralMessageDeleted(chatRoom: ChatRoom, eventLog: EventLog) { override fun onEphemeralMessageDeleted(chatRoom: ChatRoom, eventLog: EventLog) {
Log.i("[Chat Messages] An ephemeral chat message has expired, removing it from event list") Log.i(
"[Chat Messages] An ephemeral chat message has expired, removing it from event list"
)
deleteEvent(eventLog) deleteEvent(eventLog)
} }
@ -163,7 +165,10 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
upperBound = maxSize upperBound = maxSize
} }
val history: Array<EventLog> = chatRoom.getHistoryRangeEvents(totalItemsCount, upperBound) val history: Array<EventLog> = chatRoom.getHistoryRangeEvents(
totalItemsCount,
upperBound
)
val list = arrayListOf<EventLogData>() val list = arrayListOf<EventLogData>()
for (eventLog in history) { for (eventLog in history) {
list.add(EventLogData(eventLog)) list.add(EventLogData(eventLog))
@ -187,7 +192,9 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
val list = arrayListOf<EventLogData>() val list = arrayListOf<EventLogData>()
val unreadCount = chatRoom.unreadMessagesCount val unreadCount = chatRoom.unreadMessagesCount
var loadCount = max(MESSAGES_PER_PAGE, unreadCount) var loadCount = max(MESSAGES_PER_PAGE, unreadCount)
Log.i("[Chat Messages] $unreadCount unread messages in this chat room, loading $loadCount from history") Log.i(
"[Chat Messages] $unreadCount unread messages in this chat room, loading $loadCount from history"
)
val history = chatRoom.getHistoryEvents(loadCount) val history = chatRoom.getHistoryEvents(loadCount)
var messageCount = 0 var messageCount = 0
@ -200,8 +207,13 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
// Load enough events to have at least all unread messages // Load enough events to have at least all unread messages
while (unreadCount > 0 && messageCount < unreadCount) { while (unreadCount > 0 && messageCount < unreadCount) {
Log.w("[Chat Messages] There is only $messageCount messages in the last $loadCount events, loading $MESSAGES_PER_PAGE more") Log.w(
val moreHistory = chatRoom.getHistoryRangeEvents(loadCount, loadCount + MESSAGES_PER_PAGE) "[Chat Messages] There is only $messageCount messages in the last $loadCount events, loading $MESSAGES_PER_PAGE more"
)
val moreHistory = chatRoom.getHistoryRangeEvents(
loadCount,
loadCount + MESSAGES_PER_PAGE
)
loadCount += MESSAGES_PER_PAGE loadCount += MESSAGES_PER_PAGE
for (eventLog in moreHistory) { for (eventLog in moreHistory) {
list.add(EventLogData(eventLog)) list.add(EventLogData(eventLog))
@ -235,14 +247,18 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
data.eventLog.type == EventLog.Type.ConferenceChatMessage && data.eventLog.chatMessage?.messageId == chatMessage.messageId data.eventLog.type == EventLog.Type.ConferenceChatMessage && data.eventLog.chatMessage?.messageId == chatMessage.messageId
} }
if (existingEvent != null) { if (existingEvent != null) {
Log.w("[Chat Messages] Found already present chat message, don't add it it's probably the result of an auto download or an aggregated message received before but notified after the conversation was displayed") Log.w(
"[Chat Messages] Found already present chat message, don't add it it's probably the result of an auto download or an aggregated message received before but notified after the conversation was displayed"
)
return return
} }
if (!PermissionHelper.get().hasWriteExternalStoragePermission()) { if (!PermissionHelper.get().hasWriteExternalStoragePermission()) {
for (content in chatMessage.contents) { for (content in chatMessage.contents) {
if (content.isFileTransfer) { if (content.isFileTransfer) {
Log.i("[Chat Messages] Android < 10 detected and WRITE_EXTERNAL_STORAGE permission isn't granted yet") Log.i(
"[Chat Messages] Android < 10 detected and WRITE_EXTERNAL_STORAGE permission isn't granted yet"
)
requestWriteExternalStoragePermissionEvent.value = Event(true) requestWriteExternalStoragePermissionEvent.value = Event(true)
} }
} }

View file

@ -68,7 +68,9 @@ class ChatRoomCreationViewModel : ContactsSelectionViewModel() {
fun updateEncryption(encrypted: Boolean) { fun updateEncryption(encrypted: Boolean) {
if (!encrypted && secureChatMandatory) { if (!encrypted && secureChatMandatory) {
Log.w("[Chat Room Creation] Something tries to force plain text chat room even if secureChatMandatory is enabled!") Log.w(
"[Chat Room Creation] Something tries to force plain text chat room even if secureChatMandatory is enabled!"
)
return return
} }
isEncrypted.value = encrypted isEncrypted.value = encrypted
@ -79,7 +81,10 @@ class ChatRoomCreationViewModel : ContactsSelectionViewModel() {
val defaultAccount = coreContext.core.defaultAccount val defaultAccount = coreContext.core.defaultAccount
var room: ChatRoom? var room: ChatRoom?
val address = searchResult.address ?: coreContext.core.interpretUrl(searchResult.phoneNumber ?: "", LinphoneUtils.applyInternationalPrefix()) val address = searchResult.address ?: coreContext.core.interpretUrl(
searchResult.phoneNumber ?: "",
LinphoneUtils.applyInternationalPrefix()
)
if (address == null) { if (address == null) {
Log.e("[Chat Room Creation] Can't get a valid address from search result $searchResult") Log.e("[Chat Room Creation] Can't get a valid address from search result $searchResult")
onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack)
@ -94,12 +99,15 @@ class ChatRoomCreationViewModel : ContactsSelectionViewModel() {
if (encrypted) { if (encrypted) {
params.isEncryptionEnabled = true params.isEncryptionEnabled = true
params.backend = ChatRoomBackend.FlexisipChat params.backend = ChatRoomBackend.FlexisipChat
params.ephemeralMode = if (corePreferences.useEphemeralPerDeviceMode) params.ephemeralMode = if (corePreferences.useEphemeralPerDeviceMode) {
ChatRoomEphemeralMode.DeviceManaged ChatRoomEphemeralMode.DeviceManaged
else } else {
ChatRoomEphemeralMode.AdminManaged ChatRoomEphemeralMode.AdminManaged
}
params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
Log.i("[Chat Room Creation] Ephemeral mode is ${params.ephemeralMode}, lifetime is ${params.ephemeralLifetime}") Log.i(
"[Chat Room Creation] Ephemeral mode is ${params.ephemeralMode}, lifetime is ${params.ephemeralLifetime}"
)
params.subject = AppUtils.getString(R.string.chat_room_dummy_subject) params.subject = AppUtils.getString(R.string.chat_room_dummy_subject)
} }
@ -108,7 +116,9 @@ class ChatRoomCreationViewModel : ContactsSelectionViewModel() {
room = coreContext.core.searchChatRoom(params, localAddress, null, participants) room = coreContext.core.searchChatRoom(params, localAddress, null, participants)
if (room == null) { if (room == null) {
Log.w("[Chat Room Creation] Couldn't find existing 1-1 chat room with remote ${address.asStringUriOnly()}, encryption=$encrypted and local identity ${localAddress?.asStringUriOnly()}") Log.w(
"[Chat Room Creation] Couldn't find existing 1-1 chat room with remote ${address.asStringUriOnly()}, encryption=$encrypted and local identity ${localAddress?.asStringUriOnly()}"
)
room = coreContext.core.createChatRoom(params, localAddress, participants) room = coreContext.core.createChatRoom(params, localAddress, participants)
if (room != null) { if (room != null) {
@ -119,7 +129,9 @@ class ChatRoomCreationViewModel : ContactsSelectionViewModel() {
chatRoomCreatedEvent.value = Event(room) chatRoomCreatedEvent.value = Event(room)
waitForChatRoomCreation.value = false waitForChatRoomCreation.value = false
} else { } else {
Log.i("[Chat Room Creation] Chat room creation is pending [$state], waiting for Created state") Log.i(
"[Chat Room Creation] Chat room creation is pending [$state], waiting for Created state"
)
room.addListener(listener) room.addListener(listener)
} }
} else { } else {
@ -127,11 +139,15 @@ class ChatRoomCreationViewModel : ContactsSelectionViewModel() {
waitForChatRoomCreation.value = false waitForChatRoomCreation.value = false
} }
} else { } else {
Log.e("[Chat Room Creation] Couldn't create chat room with remote ${address.asStringUriOnly()} and local identity ${localAddress?.asStringUriOnly()}") Log.e(
"[Chat Room Creation] Couldn't create chat room with remote ${address.asStringUriOnly()} and local identity ${localAddress?.asStringUriOnly()}"
)
waitForChatRoomCreation.value = false waitForChatRoomCreation.value = false
} }
} else { } else {
Log.i("[Chat Room Creation] Found existing 1-1 chat room with remote ${address.asStringUriOnly()}, encryption=$encrypted and local identity ${localAddress?.asStringUriOnly()}") Log.i(
"[Chat Room Creation] Found existing 1-1 chat room with remote ${address.asStringUriOnly()}, encryption=$encrypted and local identity ${localAddress?.asStringUriOnly()}"
)
chatRoomCreatedEvent.value = Event(room) chatRoomCreatedEvent.value = Event(room)
waitForChatRoomCreation.value = false waitForChatRoomCreation.value = false
} }

View file

@ -111,7 +111,10 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
private var addressToCall: Address? = null private var addressToCall: Address? = null
private val bounceAnimator: ValueAnimator by lazy { private val bounceAnimator: ValueAnimator by lazy {
ValueAnimator.ofFloat(AppUtils.getDimension(R.dimen.tabs_fragment_unread_count_bounce_offset), 0f).apply { ValueAnimator.ofFloat(
AppUtils.getDimension(R.dimen.tabs_fragment_unread_count_bounce_offset),
0f
).apply {
addUpdateListener { addUpdateListener {
val value = it.animatedValue as Float val value = it.animatedValue as Float
chatUnreadCountTranslateY.value = value chatUnreadCountTranslateY.value = value
@ -265,11 +268,11 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
val localAddress = chatRoom.localAddress.clone() val localAddress = chatRoom.localAddress.clone()
localAddress.clean() // Remove GRUU localAddress.clean() // Remove GRUU
val addresses = Array(chatRoom.participants.size) { val addresses = Array(chatRoom.participants.size) {
index -> index ->
chatRoom.participants[index].address chatRoom.participants[index].address
} }
val localAccount = coreContext.core.accountList.find { val localAccount = coreContext.core.accountList.find {
account -> account ->
account.params.identityAddress?.weakEqual(localAddress) ?: false account.params.identityAddress?.weakEqual(localAddress) ?: false
} }
@ -298,7 +301,9 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
if (chatRoom.participants.isNotEmpty()) { if (chatRoom.participants.isNotEmpty()) {
chatRoom.participants[0].address chatRoom.participants[0].address
} else { } else {
Log.e("[Chat Room] ${chatRoom.peerAddress} doesn't have any participant (state ${chatRoom.state})!") Log.e(
"[Chat Room] ${chatRoom.peerAddress} doesn't have any participant (state ${chatRoom.state})!"
)
null null
} }
} }
@ -338,11 +343,18 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
} }
TimestampUtils.isYesterday(timestamp) -> { TimestampUtils.isYesterday(timestamp) -> {
val time = TimestampUtils.timeToString(timestamp, timestampInSecs = true) val time = TimestampUtils.timeToString(timestamp, timestampInSecs = true)
val text = AppUtils.getString(R.string.chat_room_presence_last_seen_online_yesterday) val text = AppUtils.getString(
R.string.chat_room_presence_last_seen_online_yesterday
)
"$text $time" "$text $time"
} }
else -> { else -> {
val date = TimestampUtils.toString(timestamp, onlyDate = true, shortDate = false, hideYear = true) val date = TimestampUtils.toString(
timestamp,
onlyDate = true,
shortDate = false,
hideYear = true
)
val text = AppUtils.getString(R.string.chat_room_presence_last_seen_online) val text = AppUtils.getString(R.string.chat_room_presence_last_seen_online)
"$text $date" "$text $date"
} }
@ -390,7 +402,11 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
composing += if (composing.isNotEmpty()) ", " else "" composing += if (composing.isNotEmpty()) ", " else ""
composing += contact?.name ?: LinphoneUtils.getDisplayName(address) composing += contact?.name ?: LinphoneUtils.getDisplayName(address)
} }
composingList.value = AppUtils.getStringWithPlural(R.plurals.chat_room_remote_composing, chatRoom.composingAddresses.size, composing) composingList.value = AppUtils.getStringWithPlural(
R.plurals.chat_room_remote_composing,
chatRoom.composingAddresses.size,
composing
)
} }
private fun updateParticipants() { private fun updateParticipants() {
@ -400,10 +416,11 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
chatRoom.me?.devices?.size == 1 && chatRoom.me?.devices?.size == 1 &&
participants.firstOrNull()?.devices?.size == 1 participants.firstOrNull()?.devices?.size == 1
addressToCall = if (basicChatRoom) addressToCall = if (basicChatRoom) {
chatRoom.peerAddress chatRoom.peerAddress
else } else {
participants.firstOrNull()?.address participants.firstOrNull()?.address
}
onlyParticipantOnlyDeviceAddress = participants.firstOrNull()?.devices?.firstOrNull()?.address onlyParticipantOnlyDeviceAddress = participants.firstOrNull()?.devices?.firstOrNull()?.address
} }
@ -411,7 +428,8 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
private fun updateUnreadMessageCount() { private fun updateUnreadMessageCount() {
val count = chatRoom.unreadMessagesCount val count = chatRoom.unreadMessagesCount
unreadMessagesCount.value = count unreadMessagesCount.value = count
if (count > 0 && corePreferences.enableAnimations) bounceAnimator.start() if (count > 0 && corePreferences.enableAnimations) {
else if (count == 0 && bounceAnimator.isStarted) bounceAnimator.end() bounceAnimator.start()
} else if (count == 0 && bounceAnimator.isStarted) bounceAnimator.end()
} }
} }

View file

@ -48,7 +48,9 @@ class ChatRoomsListViewModel : MessageNotifierViewModel() {
private val chatRoomListener = object : ChatRoomListenerStub() { private val chatRoomListener = object : ChatRoomListenerStub() {
override fun onStateChanged(chatRoom: ChatRoom, newState: ChatRoom.State) { override fun onStateChanged(chatRoom: ChatRoom, newState: ChatRoom.State) {
if (newState == ChatRoom.State.Deleted) { if (newState == ChatRoom.State.Deleted) {
Log.i("[Chat Rooms] Chat room [${LinphoneUtils.getChatRoomId(chatRoom)}] is in Deleted state, removing it from list") Log.i(
"[Chat Rooms] Chat room [${LinphoneUtils.getChatRoomId(chatRoom)}] is in Deleted state, removing it from list"
)
val list = arrayListOf<ChatRoomData>() val list = arrayListOf<ChatRoomData>()
val id = LinphoneUtils.getChatRoomId(chatRoom) val id = LinphoneUtils.getChatRoomId(chatRoom)
for (data in chatRooms.value.orEmpty()) { for (data in chatRooms.value.orEmpty()) {
@ -64,14 +66,18 @@ class ChatRoomsListViewModel : MessageNotifierViewModel() {
private val listener: CoreListenerStub = object : CoreListenerStub() { private val listener: CoreListenerStub = object : CoreListenerStub() {
override fun onChatRoomStateChanged(core: Core, chatRoom: ChatRoom, state: ChatRoom.State) { override fun onChatRoomStateChanged(core: Core, chatRoom: ChatRoom, state: ChatRoom.State) {
if (state == ChatRoom.State.Created) { if (state == ChatRoom.State.Created) {
Log.i("[Chat Rooms] Chat room [${LinphoneUtils.getChatRoomId(chatRoom)}] is in Created state, adding it to list") Log.i(
"[Chat Rooms] Chat room [${LinphoneUtils.getChatRoomId(chatRoom)}] is in Created state, adding it to list"
)
val data = ChatRoomData(chatRoom) val data = ChatRoomData(chatRoom)
val list = arrayListOf<ChatRoomData>() val list = arrayListOf<ChatRoomData>()
list.add(data) list.add(data)
list.addAll(chatRooms.value.orEmpty()) list.addAll(chatRooms.value.orEmpty())
chatRooms.value = list chatRooms.value = list
} else if (state == ChatRoom.State.TerminationFailed) { } else if (state == ChatRoom.State.TerminationFailed) {
Log.e("[Chat Rooms] Group chat room removal for address ${chatRoom.peerAddress.asStringUriOnly()} has failed !") Log.e(
"[Chat Rooms] Group chat room removal for address ${chatRoom.peerAddress.asStringUriOnly()} has failed !"
)
onMessageToNotifyEvent.value = Event(R.string.chat_room_removal_failed_snack) onMessageToNotifyEvent.value = Event(R.string.chat_room_removal_failed_snack)
} }
} }
@ -155,8 +161,11 @@ class ChatRoomsListViewModel : MessageNotifierViewModel() {
fun notifyChatRoomUpdate(chatRoom: ChatRoom) { fun notifyChatRoomUpdate(chatRoom: ChatRoom) {
val index = findChatRoomIndex(chatRoom) val index = findChatRoomIndex(chatRoom)
if (index == -1) updateChatRooms() if (index == -1) {
else chatRoomIndexUpdatedEvent.value = Event(index) updateChatRooms()
} else {
chatRoomIndexUpdatedEvent.value = Event(index)
}
} }
private fun reorderChatRooms() { private fun reorderChatRooms() {

View file

@ -50,7 +50,9 @@ class EphemeralViewModel(private val chatRoom: ChatRoom) : ViewModel() {
} }
init { init {
Log.i("[Ephemeral Messages] Current lifetime is ${chatRoom.ephemeralLifetime}, ephemeral enabled? ${chatRoom.isEphemeralEnabled}") Log.i(
"[Ephemeral Messages] Current lifetime is ${chatRoom.ephemeralLifetime}, ephemeral enabled? ${chatRoom.isEphemeralEnabled}"
)
currentSelectedDuration = if (chatRoom.isEphemeralEnabled) chatRoom.ephemeralLifetime else 0 currentSelectedDuration = if (chatRoom.isEphemeralEnabled) chatRoom.ephemeralLifetime else 0
computeEphemeralDurationValues() computeEphemeralDurationValues()
} }
@ -59,10 +61,14 @@ class EphemeralViewModel(private val chatRoom: ChatRoom) : ViewModel() {
Log.i("[Ephemeral Messages] Selected value is $currentSelectedDuration") Log.i("[Ephemeral Messages] Selected value is $currentSelectedDuration")
if (currentSelectedDuration > 0) { if (currentSelectedDuration > 0) {
if (chatRoom.ephemeralLifetime != currentSelectedDuration) { if (chatRoom.ephemeralLifetime != currentSelectedDuration) {
Log.i("[Ephemeral Messages] Setting new lifetime for ephemeral messages to $currentSelectedDuration") Log.i(
"[Ephemeral Messages] Setting new lifetime for ephemeral messages to $currentSelectedDuration"
)
chatRoom.ephemeralLifetime = currentSelectedDuration chatRoom.ephemeralLifetime = currentSelectedDuration
} else { } else {
Log.i("[Ephemeral Messages] Configured lifetime for ephemeral messages was already $currentSelectedDuration") Log.i(
"[Ephemeral Messages] Configured lifetime for ephemeral messages was already $currentSelectedDuration"
)
} }
if (!chatRoom.isEphemeralEnabled) { if (!chatRoom.isEphemeralEnabled) {
@ -77,12 +83,54 @@ class EphemeralViewModel(private val chatRoom: ChatRoom) : ViewModel() {
private fun computeEphemeralDurationValues() { private fun computeEphemeralDurationValues() {
val list = arrayListOf<EphemeralDurationData>() val list = arrayListOf<EphemeralDurationData>()
list.add(EphemeralDurationData(R.string.chat_room_ephemeral_message_disabled, currentSelectedDuration, 0, listener)) list.add(
list.add(EphemeralDurationData(R.string.chat_room_ephemeral_message_one_minute, currentSelectedDuration, 60, listener)) EphemeralDurationData(
list.add(EphemeralDurationData(R.string.chat_room_ephemeral_message_one_hour, currentSelectedDuration, 3600, listener)) R.string.chat_room_ephemeral_message_disabled,
list.add(EphemeralDurationData(R.string.chat_room_ephemeral_message_one_day, currentSelectedDuration, 86400, listener)) currentSelectedDuration,
list.add(EphemeralDurationData(R.string.chat_room_ephemeral_message_three_days, currentSelectedDuration, 259200, listener)) 0,
list.add(EphemeralDurationData(R.string.chat_room_ephemeral_message_one_week, currentSelectedDuration, 604800, listener)) listener
)
)
list.add(
EphemeralDurationData(
R.string.chat_room_ephemeral_message_one_minute,
currentSelectedDuration,
60,
listener
)
)
list.add(
EphemeralDurationData(
R.string.chat_room_ephemeral_message_one_hour,
currentSelectedDuration,
3600,
listener
)
)
list.add(
EphemeralDurationData(
R.string.chat_room_ephemeral_message_one_day,
currentSelectedDuration,
86400,
listener
)
)
list.add(
EphemeralDurationData(
R.string.chat_room_ephemeral_message_three_days,
currentSelectedDuration,
259200,
listener
)
)
list.add(
EphemeralDurationData(
R.string.chat_room_ephemeral_message_one_week,
currentSelectedDuration,
604800,
listener
)
)
durationsList.value = list durationsList.value = list
} }
} }

View file

@ -99,7 +99,9 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : MessageNotifierViewModel() {
subject.value = chatRoom?.subject subject.value = chatRoom?.subject
isMeAdmin.value = chatRoom == null || (chatRoom.me?.isAdmin == true && !chatRoom.isReadOnly) isMeAdmin.value = chatRoom == null || (chatRoom.me?.isAdmin == true && !chatRoom.isReadOnly)
canLeaveGroup.value = chatRoom != null && !chatRoom.isReadOnly canLeaveGroup.value = chatRoom != null && !chatRoom.isReadOnly
isEncrypted.value = corePreferences.forceEndToEndEncryptedChat || chatRoom?.hasCapability(ChatRoomCapabilities.Encrypted.toInt()) == true isEncrypted.value = corePreferences.forceEndToEndEncryptedChat || chatRoom?.hasCapability(
ChatRoomCapabilities.Encrypted.toInt()
) == true
if (chatRoom != null) updateParticipants() if (chatRoom != null) updateParticipants()
@ -120,13 +122,16 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : MessageNotifierViewModel() {
params.isEncryptionEnabled = corePreferences.forceEndToEndEncryptedChat || isEncrypted.value == true params.isEncryptionEnabled = corePreferences.forceEndToEndEncryptedChat || isEncrypted.value == true
params.isGroupEnabled = true params.isGroupEnabled = true
if (params.isEncryptionEnabled) { if (params.isEncryptionEnabled) {
params.ephemeralMode = if (corePreferences.useEphemeralPerDeviceMode) params.ephemeralMode = if (corePreferences.useEphemeralPerDeviceMode) {
ChatRoomEphemeralMode.DeviceManaged ChatRoomEphemeralMode.DeviceManaged
else } else {
ChatRoomEphemeralMode.AdminManaged ChatRoomEphemeralMode.AdminManaged
}
} }
params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
Log.i("[Chat Room Group Info] Ephemeral mode is ${params.ephemeralMode}, lifetime is ${params.ephemeralLifetime}") Log.i(
"[Chat Room Group Info] Ephemeral mode is ${params.ephemeralMode}, lifetime is ${params.ephemeralLifetime}"
)
params.subject = subject.value params.subject = subject.value
val addresses = arrayOfNulls<Address>(participants.value.orEmpty().size) val addresses = arrayOfNulls<Address>(participants.value.orEmpty().size)
@ -137,7 +142,11 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : MessageNotifierViewModel() {
index += 1 index += 1
} }
val chatRoom: ChatRoom? = coreContext.core.createChatRoom(params, coreContext.core.defaultAccount?.params?.identityAddress, addresses) val chatRoom: ChatRoom? = coreContext.core.createChatRoom(
params,
coreContext.core.defaultAccount?.params?.identityAddress,
addresses
)
chatRoom?.addListener(listener) chatRoom?.addListener(listener)
if (chatRoom == null) { if (chatRoom == null) {
Log.e("[Chat Room Group Info] Couldn't create chat room!") Log.e("[Chat Room Group Info] Couldn't create chat room!")
@ -162,7 +171,9 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : MessageNotifierViewModel() {
participant.address.weakEqual(member.participant.address) participant.address.weakEqual(member.participant.address)
} }
if (member == null) { if (member == null) {
Log.w("[Chat Room Group Info] Participant ${participant.address.asStringUriOnly()} will be removed from group") Log.w(
"[Chat Room Group Info] Participant ${participant.address.asStringUriOnly()} will be removed from group"
)
participantsToRemove.add(participant) participantsToRemove.add(participant)
} }
} }
@ -180,12 +191,19 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : MessageNotifierViewModel() {
// Participant found, check if admin status needs to be updated // Participant found, check if admin status needs to be updated
if (member.participant.isAdmin != participant.isAdmin) { if (member.participant.isAdmin != participant.isAdmin) {
if (chatRoom.me?.isAdmin == true) { if (chatRoom.me?.isAdmin == true) {
Log.i("[Chat Room Group Info] Participant ${member.sipUri} will be admin? ${member.isAdmin}") Log.i(
chatRoom.setParticipantAdminStatus(participant, member.participant.isAdmin) "[Chat Room Group Info] Participant ${member.sipUri} will be admin? ${member.isAdmin}"
)
chatRoom.setParticipantAdminStatus(
participant,
member.participant.isAdmin
)
} }
} }
} else { } else {
Log.i("[Chat Room Group Info] Participant ${member.sipUri} will be added to group") Log.i(
"[Chat Room Group Info] Participant ${member.sipUri} will be added to group"
)
participantsToAdd.add(member.participant.address) participantsToAdd.add(member.participant.address)
} }
} }
@ -223,7 +241,12 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : MessageNotifierViewModel() {
for (participant in chatRoom.participants) { for (participant in chatRoom.participants) {
list.add( list.add(
GroupInfoParticipantData( GroupInfoParticipantData(
GroupChatRoomMember(participant.address, participant.isAdmin, participant.securityLevel, canBeSetAdmin = true) GroupChatRoomMember(
participant.address,
participant.isAdmin,
participant.securityLevel,
canBeSetAdmin = true
)
) )
) )
} }

View file

@ -68,7 +68,9 @@ class ImdnViewModel(private val chatMessage: ChatMessage) : ViewModel() {
for (participant in chatMessage.getParticipantsByImdnState(ChatMessage.State.Displayed)) { for (participant in chatMessage.getParticipantsByImdnState(ChatMessage.State.Displayed)) {
list.add(ImdnParticipantData(participant)) list.add(ImdnParticipantData(participant))
} }
for (participant in chatMessage.getParticipantsByImdnState(ChatMessage.State.DeliveredToUser)) { for (participant in chatMessage.getParticipantsByImdnState(
ChatMessage.State.DeliveredToUser
)) {
list.add(ImdnParticipantData(participant)) list.add(ImdnParticipantData(participant))
} }
for (participant in chatMessage.getParticipantsByImdnState(ChatMessage.State.Delivered)) { for (participant in chatMessage.getParticipantsByImdnState(ChatMessage.State.Delivered)) {

View file

@ -62,7 +62,8 @@ class RichEditText : AppCompatEditText {
private fun initReceiveContentListener() { private fun initReceiveContentListener() {
ViewCompat.setOnReceiveContentListener( ViewCompat.setOnReceiveContentListener(
this, RichContentReceiver.MIME_TYPES, this,
RichContentReceiver.MIME_TYPES,
RichContentReceiver { uri -> RichContentReceiver { uri ->
Log.i("[Rich Edit Text] Received URI: $uri") Log.i("[Rich Edit Text] Received URI: $uri")
val activity = context as Activity val activity = context as Activity

View file

@ -41,7 +41,10 @@ import org.linphone.utils.TimestampUtils
class ScheduledConferencesAdapter( class ScheduledConferencesAdapter(
selectionVM: ListTopBarViewModel, selectionVM: ListTopBarViewModel,
private val viewLifecycleOwner: LifecycleOwner private val viewLifecycleOwner: LifecycleOwner
) : SelectionListAdapter<ScheduledConferenceData, RecyclerView.ViewHolder>(selectionVM, ConferenceInfoDiffCallback()), ) : SelectionListAdapter<ScheduledConferenceData, RecyclerView.ViewHolder>(
selectionVM,
ConferenceInfoDiffCallback()
),
HeaderAdapter { HeaderAdapter {
val copyAddressToClipboardEvent: MutableLiveData<Event<String>> by lazy { val copyAddressToClipboardEvent: MutableLiveData<Event<String>> by lazy {
MutableLiveData<Event<String>>() MutableLiveData<Event<String>>()
@ -62,7 +65,9 @@ class ScheduledConferencesAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ScheduledConferencesAdapter.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ScheduledConferencesAdapter.ViewHolder {
val binding: ConferenceScheduleCellBinding = DataBindingUtil.inflate( val binding: ConferenceScheduleCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.conference_schedule_cell, parent, false R.layout.conference_schedule_cell,
parent,
false
) )
return ViewHolder(binding) return ViewHolder(binding)
} }
@ -77,15 +82,22 @@ class ScheduledConferencesAdapter(
val previousPosition = position - 1 val previousPosition = position - 1
return if (previousPosition >= 0) { return if (previousPosition >= 0) {
val previousItem = getItem(previousPosition) val previousItem = getItem(previousPosition)
!TimestampUtils.isSameDay(previousItem.conferenceInfo.dateTime, conferenceInfo.conferenceInfo.dateTime) !TimestampUtils.isSameDay(
} else true previousItem.conferenceInfo.dateTime,
conferenceInfo.conferenceInfo.dateTime
)
} else {
true
}
} }
override fun getHeaderViewForPosition(context: Context, position: Int): View { override fun getHeaderViewForPosition(context: Context, position: Int): View {
val data = getItem(position) val data = getItem(position)
val binding: ConferenceScheduleListHeaderBinding = DataBindingUtil.inflate( val binding: ConferenceScheduleListHeaderBinding = DataBindingUtil.inflate(
LayoutInflater.from(context), LayoutInflater.from(context),
R.layout.conference_schedule_list_header, null, false R.layout.conference_schedule_list_header,
null,
false
) )
binding.title = formatDate(context, data.conferenceInfo.dateTime) binding.title = formatDate(context, data.conferenceInfo.dateTime)
binding.executePendingBindings() binding.executePendingBindings()
@ -143,7 +155,9 @@ class ScheduledConferencesAdapter(
setJoinConferenceClickListener { setJoinConferenceClickListener {
val address = conferenceData.conferenceInfo.uri val address = conferenceData.conferenceInfo.uri
if (address != null) { if (address != null) {
joinConferenceEvent.value = Event(Pair(address.asStringUriOnly(), conferenceData.conferenceInfo.subject)) joinConferenceEvent.value = Event(
Pair(address.asStringUriOnly(), conferenceData.conferenceInfo.subject)
)
} }
} }

View file

@ -54,7 +54,12 @@ class ScheduledConferenceData(val conferenceInfo: ConferenceInfo, private val is
description.value = conferenceInfo.description description.value = conferenceInfo.description
time.value = TimestampUtils.timeToString(conferenceInfo.dateTime) time.value = TimestampUtils.timeToString(conferenceInfo.dateTime)
date.value = TimestampUtils.toString(conferenceInfo.dateTime, onlyDate = true, shortDate = false, hideYear = false) date.value = TimestampUtils.toString(
conferenceInfo.dateTime,
onlyDate = true,
shortDate = false,
hideYear = false
)
isConferenceCancelled.value = conferenceInfo.state == State.Cancelled isConferenceCancelled.value = conferenceInfo.state == State.Cancelled
val minutes = conferenceInfo.duration val minutes = conferenceInfo.duration
@ -72,13 +77,16 @@ class ScheduledConferenceData(val conferenceInfo: ConferenceInfo, private val is
canEdit.value = localAccount != null canEdit.value = localAccount != null
val contact = coreContext.contactsManager.findContactByAddress(organizerAddress) val contact = coreContext.contactsManager.findContactByAddress(organizerAddress)
organizer.value = if (contact != null) organizer.value = if (contact != null) {
contact.name contact.name
else } else {
LinphoneUtils.getDisplayName(conferenceInfo.organizer) LinphoneUtils.getDisplayName(conferenceInfo.organizer)
}
} else { } else {
canEdit.value = false canEdit.value = false
Log.e("[Scheduled Conference] No organizer SIP URI found for: ${conferenceInfo.uri?.asStringUriOnly()}") Log.e(
"[Scheduled Conference] No organizer SIP URI found for: ${conferenceInfo.uri?.asStringUriOnly()}"
)
} }
computeBackgroundResId() computeBackgroundResId()
@ -88,7 +96,9 @@ class ScheduledConferenceData(val conferenceInfo: ConferenceInfo, private val is
fun destroy() {} fun destroy() {}
fun delete() { fun delete() {
Log.w("[Scheduled Conference] Deleting conference info with URI: ${conferenceInfo.uri?.asStringUriOnly()}") Log.w(
"[Scheduled Conference] Deleting conference info with URI: ${conferenceInfo.uri?.asStringUriOnly()}"
)
coreContext.core.deleteConferenceInformation(conferenceInfo) coreContext.core.deleteConferenceInformation(conferenceInfo)
} }
@ -134,7 +144,13 @@ class ScheduledConferenceData(val conferenceInfo: ConferenceInfo, private val is
for (participant in conferenceInfo.participants) { for (participant in conferenceInfo.participants) {
val contact = coreContext.contactsManager.findContactByAddress(participant) val contact = coreContext.contactsManager.findContactByAddress(participant)
val name = if (contact != null) contact.name else LinphoneUtils.getDisplayName(participant) val name = if (contact != null) {
contact.name
} else {
LinphoneUtils.getDisplayName(
participant
)
}
val address = participant.asStringUriOnly() val address = participant.asStringUriOnly()
participantsListShort += "$name, " participantsListShort += "$name, "
participantsListExpanded += "$name ($address)\n" participantsListExpanded += "$name ($address)\n"

View file

@ -38,7 +38,9 @@ import org.linphone.core.tools.Log
import org.linphone.databinding.ConferenceSchedulingFragmentBinding import org.linphone.databinding.ConferenceSchedulingFragmentBinding
class ConferenceSchedulingFragment : GenericFragment<ConferenceSchedulingFragmentBinding>() { class ConferenceSchedulingFragment : GenericFragment<ConferenceSchedulingFragmentBinding>() {
private val viewModel: ConferenceSchedulingViewModel by navGraphViewModels(R.id.conference_scheduling_nav_graph) private val viewModel: ConferenceSchedulingViewModel by navGraphViewModels(
R.id.conference_scheduling_nav_graph
)
override fun getLayoutId(): Int = R.layout.conference_scheduling_fragment override fun getLayoutId(): Int = R.layout.conference_scheduling_fragment
@ -53,7 +55,9 @@ class ConferenceSchedulingFragment : GenericFragment<ConferenceSchedulingFragmen
viewLifecycleOwner viewLifecycleOwner
) { ) {
it.consume { participants -> it.consume { participants ->
Log.i("[Conference Scheduling] Found participants (${participants.size}) to pre-populate for meeting schedule") Log.i(
"[Conference Scheduling] Found participants (${participants.size}) to pre-populate for meeting schedule"
)
viewModel.prePopulateParticipantsList(participants, true) viewModel.prePopulateParticipantsList(participants, true)
} }
} }
@ -64,12 +68,18 @@ class ConferenceSchedulingFragment : GenericFragment<ConferenceSchedulingFragmen
it.consume { address -> it.consume { address ->
val conferenceAddress = Factory.instance().createAddress(address) val conferenceAddress = Factory.instance().createAddress(address)
if (conferenceAddress != null) { if (conferenceAddress != null) {
Log.i("[Conference Scheduling] Trying to edit conference info using address: $address") Log.i(
val conferenceInfo = coreContext.core.findConferenceInformationFromUri(conferenceAddress) "[Conference Scheduling] Trying to edit conference info using address: $address"
)
val conferenceInfo = coreContext.core.findConferenceInformationFromUri(
conferenceAddress
)
if (conferenceInfo != null) { if (conferenceInfo != null) {
viewModel.populateFromConferenceInfo(conferenceInfo) viewModel.populateFromConferenceInfo(conferenceInfo)
} else { } else {
Log.e("[Conference Scheduling] Failed to find ConferenceInfo matching address: $address") Log.e(
"[Conference Scheduling] Failed to find ConferenceInfo matching address: $address"
)
} }
} else { } else {
Log.e("[Conference Scheduling] Failed to parse conference address: $address") Log.e("[Conference Scheduling] Failed to parse conference address: $address")

View file

@ -37,7 +37,9 @@ import org.linphone.utils.AppUtils
import org.linphone.utils.PermissionHelper import org.linphone.utils.PermissionHelper
class ConferenceSchedulingParticipantsListFragment : GenericFragment<ConferenceSchedulingParticipantsListFragmentBinding>() { class ConferenceSchedulingParticipantsListFragment : GenericFragment<ConferenceSchedulingParticipantsListFragmentBinding>() {
private val viewModel: ConferenceSchedulingViewModel by navGraphViewModels(R.id.conference_scheduling_nav_graph) private val viewModel: ConferenceSchedulingViewModel by navGraphViewModels(
R.id.conference_scheduling_nav_graph
)
private lateinit var adapter: ContactsSelectionAdapter private lateinit var adapter: ContactsSelectionAdapter
override fun getLayoutId(): Int = R.layout.conference_scheduling_participants_list_fragment override fun getLayoutId(): Int = R.layout.conference_scheduling_participants_list_fragment
@ -57,7 +59,9 @@ class ConferenceSchedulingParticipantsListFragment : GenericFragment<ConferenceS
binding.contactsList.layoutManager = layoutManager binding.contactsList.layoutManager = layoutManager
// Divider between items // Divider between items
binding.contactsList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager)) binding.contactsList.addItemDecoration(
AppUtils.getDividerDecoration(requireContext(), layoutManager)
)
binding.setNextClickListener { binding.setNextClickListener {
navigateToSummary() navigateToSummary()

View file

@ -31,7 +31,9 @@ import org.linphone.activities.navigateToScheduledConferences
import org.linphone.databinding.ConferenceSchedulingSummaryFragmentBinding import org.linphone.databinding.ConferenceSchedulingSummaryFragmentBinding
class ConferenceSchedulingSummaryFragment : GenericFragment<ConferenceSchedulingSummaryFragmentBinding>() { class ConferenceSchedulingSummaryFragment : GenericFragment<ConferenceSchedulingSummaryFragmentBinding>() {
private val viewModel: ConferenceSchedulingViewModel by navGraphViewModels(R.id.conference_scheduling_nav_graph) private val viewModel: ConferenceSchedulingViewModel by navGraphViewModels(
R.id.conference_scheduling_nav_graph
)
override fun getLayoutId(): Int = R.layout.conference_scheduling_summary_fragment override fun getLayoutId(): Int = R.layout.conference_scheduling_summary_fragment
@ -47,7 +49,9 @@ class ConferenceSchedulingSummaryFragment : GenericFragment<ConferenceScheduling
) { ) {
it.consume { it.consume {
if (viewModel.scheduleForLater.value == true) { if (viewModel.scheduleForLater.value == true) {
(requireActivity() as MainActivity).showSnackBar(R.string.conference_schedule_info_created) (requireActivity() as MainActivity).showSnackBar(
R.string.conference_schedule_info_created
)
navigateToScheduledConferences() navigateToScheduledConferences()
} else { } else {
navigateToDialer() navigateToDialer()

View file

@ -63,10 +63,14 @@ class ConferenceWaitingRoomFragment : GenericFragment<ConferenceWaitingRoomFragm
call.remoteAddress.asStringUriOnly() == conferenceUri call.remoteAddress.asStringUriOnly() == conferenceUri
} }
if (callToCancel != null) { if (callToCancel != null) {
Log.i("[Conference Waiting Room] Call to conference server with URI [$conferenceUri] was started, terminate it") Log.i(
"[Conference Waiting Room] Call to conference server with URI [$conferenceUri] was started, terminate it"
)
callToCancel.terminate() callToCancel.terminate()
} else { } else {
Log.w("[Conference Waiting Room] Call to conference server with URI [$conferenceUri] wasn't found!") Log.w(
"[Conference Waiting Room] Call to conference server with URI [$conferenceUri] wasn't found!"
)
} }
} }
goBack() goBack()
@ -81,13 +85,19 @@ class ConferenceWaitingRoomFragment : GenericFragment<ConferenceWaitingRoomFragm
if (conferenceUri != null) { if (conferenceUri != null) {
val conferenceAddress = coreContext.core.interpretUrl(conferenceUri, false) val conferenceAddress = coreContext.core.interpretUrl(conferenceUri, false)
if (conferenceAddress != null) { if (conferenceAddress != null) {
Log.i("[Conference Waiting Room] Calling conference SIP URI: ${conferenceAddress.asStringUriOnly()}") Log.i(
"[Conference Waiting Room] Calling conference SIP URI: ${conferenceAddress.asStringUriOnly()}"
)
coreContext.startCall(conferenceAddress, callParams) coreContext.startCall(conferenceAddress, callParams)
} else { } else {
Log.e("[Conference Waiting Room] Failed to parse conference SIP URI: $conferenceUri") Log.e(
"[Conference Waiting Room] Failed to parse conference SIP URI: $conferenceUri"
)
} }
} else { } else {
Log.e("[Conference Waiting Room] Failed to find conference SIP URI in arguments") Log.e(
"[Conference Waiting Room] Failed to find conference SIP URI in arguments"
)
} }
} }
} }
@ -191,7 +201,9 @@ class ConferenceWaitingRoomFragment : GenericFragment<ConferenceWaitingRoomFragm
viewModel.enableVideo() viewModel.enableVideo()
} }
Compatibility.BLUETOOTH_CONNECT -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { Compatibility.BLUETOOTH_CONNECT -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
Log.i("[Conference Waiting Room] BLUETOOTH_CONNECT permission has been granted") Log.i(
"[Conference Waiting Room] BLUETOOTH_CONNECT permission has been granted"
)
} }
} }
} }

View file

@ -82,7 +82,9 @@ class ScheduledConferencesFragment : MasterFragment<ConferencesScheduledFragment
override fun onRightToLeftSwipe(viewHolder: RecyclerView.ViewHolder) { override fun onRightToLeftSwipe(viewHolder: RecyclerView.ViewHolder) {
val index = viewHolder.bindingAdapterPosition val index = viewHolder.bindingAdapterPosition
if (index < 0 || index >= adapter.currentList.size) { if (index < 0 || index >= adapter.currentList.size) {
Log.e("[Scheduled Conferences] Index is out of bound, can't delete conference info") Log.e(
"[Scheduled Conferences] Index is out of bound, can't delete conference info"
)
} else { } else {
val deletedConfInfo = adapter.currentList[index] val deletedConfInfo = adapter.currentList[index]
showConfInfoDeleteConfirmationDialog(deletedConfInfo, index) showConfInfoDeleteConfirmationDialog(deletedConfInfo, index)
@ -111,7 +113,9 @@ class ScheduledConferencesFragment : MasterFragment<ConferencesScheduledFragment
val clip = ClipData.newPlainText("Conference address", address) val clip = ClipData.newPlainText("Conference address", address)
clipboard.setPrimaryClip(clip) clipboard.setPrimaryClip(clip)
(activity as MainActivity).showSnackBar(R.string.conference_schedule_address_copied_to_clipboard) (activity as MainActivity).showSnackBar(
R.string.conference_schedule_address_copied_to_clipboard
)
} }
} }

View file

@ -82,7 +82,9 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
Log.i("[Conference Creation] Conference scheduler state is $state") Log.i("[Conference Creation] Conference scheduler state is $state")
if (state == ConferenceScheduler.State.Ready) { if (state == ConferenceScheduler.State.Ready) {
val conferenceAddress = conferenceScheduler.info?.uri val conferenceAddress = conferenceScheduler.info?.uri
Log.i("[Conference Creation] Conference info created, address will be ${conferenceAddress?.asStringUriOnly()}") Log.i(
"[Conference Creation] Conference info created, address will be ${conferenceAddress?.asStringUriOnly()}"
)
conferenceAddress ?: return conferenceAddress ?: return
address.value = conferenceAddress!! address.value = conferenceAddress!!
@ -110,11 +112,17 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
if (failedInvitations?.isNotEmpty() == true) { if (failedInvitations?.isNotEmpty() == true) {
for (address in failedInvitations) { for (address in failedInvitations) {
Log.e("[Conference Creation] Conference information wasn't sent to participant ${address.asStringUriOnly()}") Log.e(
"[Conference Creation] Conference information wasn't sent to participant ${address.asStringUriOnly()}"
)
} }
onMessageToNotifyEvent.value = Event(R.string.conference_schedule_info_not_sent_to_participant) onMessageToNotifyEvent.value = Event(
R.string.conference_schedule_info_not_sent_to_participant
)
} else { } else {
Log.i("[Conference Creation] Conference information successfully sent to all participants") Log.i(
"[Conference Creation] Conference information successfully sent to all participants"
)
} }
val conferenceAddress = conferenceScheduler.info?.uri val conferenceAddress = conferenceScheduler.info?.uri
@ -313,7 +321,9 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
} }
private fun getConferenceStartTimestamp(): Long { private fun getConferenceStartTimestamp(): Long {
val calendar = Calendar.getInstance(TimeZone.getTimeZone(timeZone.value?.id ?: TimeZone.getDefault().id)) val calendar = Calendar.getInstance(
TimeZone.getTimeZone(timeZone.value?.id ?: TimeZone.getDefault().id)
)
calendar.timeInMillis = dateTimestamp calendar.timeInMillis = dateTimestamp
calendar.set(Calendar.HOUR_OF_DAY, hour) calendar.set(Calendar.HOUR_OF_DAY, hour)
calendar.set(Calendar.MINUTE, minutes) calendar.set(Calendar.MINUTE, minutes)

View file

@ -125,7 +125,9 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
leaveWaitingRoomEvent.value = Event(true) leaveWaitingRoomEvent.value = Event(true)
} }
Call.State.Error -> { Call.State.Error -> {
Log.w("[Conference Waiting Room] Call has failed, leaving waiting room fragment") Log.w(
"[Conference Waiting Room] Call has failed, leaving waiting room fragment"
)
leaveWaitingRoomEvent.value = Event(true) leaveWaitingRoomEvent.value = Event(true)
} }
else -> {} else -> {}
@ -138,7 +140,9 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
state: Conference.State? state: Conference.State?
) { ) {
if (state == Conference.State.Created) { if (state == Conference.State.Created) {
Log.i("[Conference Waiting Room] Conference has been created, leaving waiting room fragment") Log.i(
"[Conference Waiting Room] Conference has been created, leaving waiting room fragment"
)
leaveWaitingRoomEvent.value = Event(true) leaveWaitingRoomEvent.value = Event(true)
} }
} }
@ -156,8 +160,12 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
val core = coreContext.core val core = coreContext.core
core.addListener(listener) core.addListener(listener)
audioRoutesMenuTranslateY.value = AppUtils.getDimension(R.dimen.voip_audio_routes_menu_translate_y) audioRoutesMenuTranslateY.value = AppUtils.getDimension(
conferenceLayoutMenuTranslateY.value = AppUtils.getDimension(R.dimen.voip_audio_routes_menu_translate_y) R.dimen.voip_audio_routes_menu_translate_y
)
conferenceLayoutMenuTranslateY.value = AppUtils.getDimension(
R.dimen.voip_audio_routes_menu_translate_y
)
val reachable = core.isNetworkReachable val reachable = core.isNetworkReachable
networkReachable.value = reachable networkReachable.value = reachable
@ -166,7 +174,9 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
} }
callParams.isMicEnabled = PermissionHelper.get().hasRecordAudioPermission() && coreContext.core.isMicEnabled callParams.isMicEnabled = PermissionHelper.get().hasRecordAudioPermission() && coreContext.core.isMicEnabled
Log.i("[Conference Waiting Room] Microphone will be ${if (callParams.isMicEnabled) "enabled" else "muted"}") Log.i(
"[Conference Waiting Room] Microphone will be ${if (callParams.isMicEnabled) "enabled" else "muted"}"
)
updateMicState() updateMicState()
callParams.isVideoEnabled = isVideoAvailableInCore() callParams.isVideoEnabled = isVideoAvailableInCore()
@ -175,7 +185,9 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
isLowBandwidth.value = false isLowBandwidth.value = false
if (LinphoneUtils.checkIfNetworkHasLowBandwidth(coreContext.context)) { if (LinphoneUtils.checkIfNetworkHasLowBandwidth(coreContext.context)) {
Log.w("[Conference Waiting Room] Enabling low bandwidth mode, forcing audio only layout!") Log.w(
"[Conference Waiting Room] Enabling low bandwidth mode, forcing audio only layout!"
)
callParams.isLowBandwidthEnabled = true callParams.isLowBandwidthEnabled = true
callParams.isVideoEnabled = false callParams.isVideoEnabled = false
callParams.videoDirection = MediaDirection.Inactive callParams.videoDirection = MediaDirection.Inactive
@ -224,7 +236,9 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
} }
callParams.isMicEnabled = !callParams.isMicEnabled callParams.isMicEnabled = !callParams.isMicEnabled
Log.i("[Conference Waiting Room] Microphone will be ${if (callParams.isMicEnabled) "enabled" else "muted"}") Log.i(
"[Conference Waiting Room] Microphone will be ${if (callParams.isMicEnabled) "enabled" else "muted"}"
)
updateMicState() updateMicState()
} }
@ -254,10 +268,14 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
fun setBluetoothAudioRoute() { fun setBluetoothAudioRoute() {
Log.i("[Conference Waiting Room] Set default output audio device to Bluetooth") Log.i("[Conference Waiting Room] Set default output audio device to Bluetooth")
callParams.outputAudioDevice = coreContext.core.audioDevices.find { callParams.outputAudioDevice = coreContext.core.audioDevices.find {
it.type == AudioDevice.Type.Bluetooth && it.hasCapability(AudioDevice.Capabilities.CapabilityPlay) it.type == AudioDevice.Type.Bluetooth && it.hasCapability(
AudioDevice.Capabilities.CapabilityPlay
)
} }
callParams.inputAudioDevice = coreContext.core.audioDevices.find { callParams.inputAudioDevice = coreContext.core.audioDevices.find {
it.type == AudioDevice.Type.Bluetooth && it.hasCapability(AudioDevice.Capabilities.CapabilityRecord) it.type == AudioDevice.Type.Bluetooth && it.hasCapability(
AudioDevice.Capabilities.CapabilityRecord
)
} }
updateAudioRouteState() updateAudioRouteState()
@ -270,10 +288,14 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
fun setSpeakerAudioRoute() { fun setSpeakerAudioRoute() {
Log.i("[Conference Waiting Room] Set default output audio device to Speaker") Log.i("[Conference Waiting Room] Set default output audio device to Speaker")
callParams.outputAudioDevice = coreContext.core.audioDevices.find { callParams.outputAudioDevice = coreContext.core.audioDevices.find {
it.type == AudioDevice.Type.Speaker && it.hasCapability(AudioDevice.Capabilities.CapabilityPlay) it.type == AudioDevice.Type.Speaker && it.hasCapability(
AudioDevice.Capabilities.CapabilityPlay
)
} }
callParams.inputAudioDevice = coreContext.core.audioDevices.find { callParams.inputAudioDevice = coreContext.core.audioDevices.find {
it.type == AudioDevice.Type.Microphone && it.hasCapability(AudioDevice.Capabilities.CapabilityRecord) it.type == AudioDevice.Type.Microphone && it.hasCapability(
AudioDevice.Capabilities.CapabilityRecord
)
} }
updateAudioRouteState() updateAudioRouteState()
@ -286,10 +308,14 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
fun setEarpieceAudioRoute() { fun setEarpieceAudioRoute() {
Log.i("[Conference Waiting Room] Set default output audio device to Earpiece") Log.i("[Conference Waiting Room] Set default output audio device to Earpiece")
callParams.outputAudioDevice = coreContext.core.audioDevices.find { callParams.outputAudioDevice = coreContext.core.audioDevices.find {
it.type == AudioDevice.Type.Earpiece && it.hasCapability(AudioDevice.Capabilities.CapabilityPlay) it.type == AudioDevice.Type.Earpiece && it.hasCapability(
AudioDevice.Capabilities.CapabilityPlay
)
} }
callParams.inputAudioDevice = coreContext.core.audioDevices.find { callParams.inputAudioDevice = coreContext.core.audioDevices.find {
it.type == AudioDevice.Type.Microphone && it.hasCapability(AudioDevice.Capabilities.CapabilityRecord) it.type == AudioDevice.Type.Microphone && it.hasCapability(
AudioDevice.Capabilities.CapabilityRecord
)
} }
updateAudioRouteState() updateAudioRouteState()
@ -376,7 +402,9 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
if (!bluetoothDeviceAvailable) { if (!bluetoothDeviceAvailable) {
audioRoutesSelected.value = false audioRoutesSelected.value = false
Log.w("[Conference Waiting Room] Bluetooth device no longer available, switching back to default microphone & earpiece/speaker") Log.w(
"[Conference Waiting Room] Bluetooth device no longer available, switching back to default microphone & earpiece/speaker"
)
if (isBluetoothHeadsetSelected.value == true) { if (isBluetoothHeadsetSelected.value == true) {
for (audioDevice in coreContext.core.audioDevices) { for (audioDevice in coreContext.core.audioDevices) {
if (isVideoEnabled.value == true) { if (isVideoEnabled.value == true) {
@ -418,7 +446,9 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
private fun updateVideoState() { private fun updateVideoState() {
isVideoAvailable.value = callParams.isVideoEnabled isVideoAvailable.value = callParams.isVideoEnabled
isVideoEnabled.value = callParams.isVideoEnabled && callParams.videoDirection == MediaDirection.SendRecv isVideoEnabled.value = callParams.isVideoEnabled && callParams.videoDirection == MediaDirection.SendRecv
Log.i("[Conference Waiting Room] Video will be ${if (callParams.isVideoEnabled) "enabled" else "disabled"} with direction ${callParams.videoDirection}") Log.i(
"[Conference Waiting Room] Video will be ${if (callParams.isVideoEnabled) "enabled" else "disabled"} with direction ${callParams.videoDirection}"
)
isSwitchCameraAvailable.value = callParams.isVideoEnabled && coreContext.showSwitchCameraButton() isSwitchCameraAvailable.value = callParams.isVideoEnabled && coreContext.showSwitchCameraButton()
coreContext.core.isVideoPreviewEnabled = isVideoEnabled.value == true coreContext.core.isVideoPreviewEnabled = isVideoEnabled.value == true

View file

@ -52,7 +52,9 @@ class ScheduledConferencesViewModel : ViewModel() {
) { ) {
Log.i("[Scheduled Conferences] Conference scheduler state is $state") Log.i("[Scheduled Conferences] Conference scheduler state is $state")
if (state == ConferenceScheduler.State.Ready) { if (state == ConferenceScheduler.State.Ready) {
Log.i("[Scheduled Conferences] Conference ${conferenceScheduler.info?.subject} cancelled") Log.i(
"[Scheduled Conferences] Conference ${conferenceScheduler.info?.subject} cancelled"
)
val chatRoomParams = LinphoneUtils.getConferenceInvitationsChatRoomParams() val chatRoomParams = LinphoneUtils.getConferenceInvitationsChatRoomParams()
conferenceScheduler.sendInvitations(chatRoomParams) // Send cancel ICS conferenceScheduler.sendInvitations(chatRoomParams) // Send cancel ICS
} }
@ -64,10 +66,14 @@ class ScheduledConferencesViewModel : ViewModel() {
) { ) {
if (failedInvitations?.isNotEmpty() == true) { if (failedInvitations?.isNotEmpty() == true) {
for (address in failedInvitations) { for (address in failedInvitations) {
Log.e("[Scheduled Conferences] Conference cancelled ICS wasn't sent to participant ${address.asStringUriOnly()}") Log.e(
"[Scheduled Conferences] Conference cancelled ICS wasn't sent to participant ${address.asStringUriOnly()}"
)
} }
} else { } else {
Log.i("[Scheduled Conferences] Conference cancelled ICS successfully sent to all participants") Log.i(
"[Scheduled Conferences] Conference cancelled ICS successfully sent to all participants"
)
} }
} }
} }
@ -139,7 +145,9 @@ class ScheduledConferencesViewModel : ViewModel() {
} }
} else { } else {
val oneHourAgo = now - 7200 // Show all conferences from 2 hours ago and forward val oneHourAgo = now - 7200 // Show all conferences from 2 hours ago and forward
for (conferenceInfo in coreContext.core.getConferenceInformationListAfterTime(oneHourAgo)) { for (conferenceInfo in coreContext.core.getConferenceInformationListAfterTime(
oneHourAgo
)) {
if (conferenceInfo.duration == 0) continue // This isn't a scheduled conference, don't display it if (conferenceInfo.duration == 0) continue // This isn't a scheduled conference, don't display it
val data = ScheduledConferenceData(conferenceInfo, false) val data = ScheduledConferenceData(conferenceInfo, false)
conferencesList.add(data) conferencesList.add(data)

View file

@ -42,7 +42,11 @@ import org.linphone.utils.HeaderAdapter
class ContactsListAdapter( class ContactsListAdapter(
selectionVM: ListTopBarViewModel, selectionVM: ListTopBarViewModel,
private val viewLifecycleOwner: LifecycleOwner private val viewLifecycleOwner: LifecycleOwner
) : SelectionListAdapter<ContactViewModel, RecyclerView.ViewHolder>(selectionVM, ContactDiffCallback()), HeaderAdapter { ) : SelectionListAdapter<ContactViewModel, RecyclerView.ViewHolder>(
selectionVM,
ContactDiffCallback()
),
HeaderAdapter {
val selectedContactEvent: MutableLiveData<Event<Friend>> by lazy { val selectedContactEvent: MutableLiveData<Event<Friend>> by lazy {
MutableLiveData<Event<Friend>>() MutableLiveData<Event<Friend>>()
} }
@ -50,7 +54,9 @@ class ContactsListAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ContactListCellBinding = DataBindingUtil.inflate( val binding: ContactListCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.contact_list_cell, parent, false R.layout.contact_list_cell,
parent,
false
) )
return ViewHolder(binding) return ViewHolder(binding)
} }
@ -108,7 +114,9 @@ class ContactsListAdapter(
return if (previousPosition >= 0) { return if (previousPosition >= 0) {
val previousItemFirstLetter = getItem(previousPosition).fullName.firstOrNull().toString() val previousItemFirstLetter = getItem(previousPosition).fullName.firstOrNull().toString()
!firstLetter.equals(previousItemFirstLetter, ignoreCase = true) !firstLetter.equals(previousItemFirstLetter, ignoreCase = true)
} else true } else {
true
}
} }
override fun getHeaderViewForPosition(context: Context, position: Int): View { override fun getHeaderViewForPosition(context: Context, position: Int): View {
@ -116,7 +124,9 @@ class ContactsListAdapter(
val firstLetter = AppUtils.getInitials(contact.fullName, 1) val firstLetter = AppUtils.getInitials(contact.fullName, 1)
val binding: GenericListHeaderBinding = DataBindingUtil.inflate( val binding: GenericListHeaderBinding = DataBindingUtil.inflate(
LayoutInflater.from(context), LayoutInflater.from(context),
R.layout.generic_list_header, null, false R.layout.generic_list_header,
null,
false
) )
binding.title = firstLetter binding.title = firstLetter
binding.executePendingBindings() binding.executePendingBindings()

View file

@ -19,7 +19,11 @@ class SyncAccountAdapter : BaseAdapter() {
} }
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view: View = convertView ?: LayoutInflater.from(parent.context).inflate(R.layout.contact_sync_account_picker_cell, parent, false) val view: View = convertView ?: LayoutInflater.from(parent.context).inflate(
R.layout.contact_sync_account_picker_cell,
parent,
false
)
val account = getItem(position) val account = getItem(position)
val icon = view.findViewById<ImageView>(R.id.account_icon) val icon = view.findViewById<ImageView>(R.id.account_icon)

View file

@ -88,7 +88,9 @@ class ContactEditorData(val friend: Friend?) : ContactDataInterface {
Log.w("[Contact Editor] vCard first & last name not filled-in yet, doing it now") Log.w("[Contact Editor] vCard first & last name not filled-in yet, doing it now")
fetchFirstAndLastNames(refKey) fetchFirstAndLastNames(refKey)
} else { } else {
Log.e("[Contact Editor] vCard first & last name not available as contact doesn't have a native ID") Log.e(
"[Contact Editor] vCard first & last name not available as contact doesn't have a native ID"
)
} }
} else { } else {
firstName.value = vCard?.givenName firstName.value = vCard?.givenName
@ -246,10 +248,16 @@ class ContactEditorData(val friend: Friend?) : ContactDataInterface {
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
val linphoneMime = AppUtils.getString(R.string.linphone_address_mime_type) val linphoneMime = AppUtils.getString(R.string.linphone_address_mime_type)
val mime: String? = val mime: String? =
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE)) cursor.getString(
cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE)
)
if (mime == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) { if (mime == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) {
val data1: String? = val data1: String? =
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)) cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Phone.NUMBER
)
)
if (data1 != null) { if (data1 != null) {
phoneNumbers.add(NumberOrAddressEditorData(data1, false)) phoneNumbers.add(NumberOrAddressEditorData(data1, false))
} }
@ -258,7 +266,11 @@ class ContactEditorData(val friend: Friend?) : ContactDataInterface {
mime == linphoneMime mime == linphoneMime
) { ) {
val data1: String? = val data1: String? =
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS)) cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS
)
)
if (data1 != null) { if (data1 != null) {
sipAddresses.add(NumberOrAddressEditorData(data1, true)) sipAddresses.add(NumberOrAddressEditorData(data1, true))
} }
@ -274,7 +286,9 @@ class ContactEditorData(val friend: Friend?) : ContactDataInterface {
} }
if (!fetched) { if (!fetched) {
Log.w("[Contact Editor] Fall-backing to friend info (might be inaccurate and thus edition/removal might fail)") Log.w(
"[Contact Editor] Fall-backing to friend info (might be inaccurate and thus edition/removal might fail)"
)
for (number in friend?.phoneNumbers.orEmpty()) { for (number in friend?.phoneNumbers.orEmpty()) {
phoneNumbers.add(NumberOrAddressEditorData(number, false)) phoneNumbers.add(NumberOrAddressEditorData(number, false))
} }
@ -310,17 +324,27 @@ class ContactEditorData(val friend: Friend?) : ContactDataInterface {
) )
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
val mime: String? = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE)) val mime: String? = cursor.getString(
cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE)
)
if (mime == ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) { if (mime == ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) {
val givenName: String? = val givenName: String? =
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)) cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME
)
)
if (!givenName.isNullOrEmpty()) { if (!givenName.isNullOrEmpty()) {
friend?.vcard?.givenName = givenName friend?.vcard?.givenName = givenName
firstName.value = givenName!! firstName.value = givenName!!
} }
val familyName: String? = val familyName: String? =
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)) cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME
)
)
if (!familyName.isNullOrEmpty()) { if (!familyName.isNullOrEmpty()) {
friend?.vcard?.familyName = familyName friend?.vcard?.familyName = familyName
lastName.value = familyName!! lastName.value = familyName!!

View file

@ -116,7 +116,9 @@ class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), S
Log.i("[Contact Editor] WRITE_CONTACTS permission granted") Log.i("[Contact Editor] WRITE_CONTACTS permission granted")
} else { } else {
Log.w("[Contact Editor] WRITE_CONTACTS permission denied") Log.w("[Contact Editor] WRITE_CONTACTS permission denied")
(activity as MainActivity).showSnackBar(R.string.contact_editor_write_permission_denied) (activity as MainActivity).showSnackBar(
R.string.contact_editor_write_permission_denied
)
goBack() goBack()
} }
} }
@ -126,7 +128,10 @@ class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), S
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
lifecycleScope.launch { lifecycleScope.launch {
val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(intent, temporaryPicturePath) val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(
intent,
temporaryPicturePath
)
if (contactImageFilePath != null) { if (contactImageFilePath != null) {
data.setPictureFromPath(contactImageFilePath) data.setPictureFromPath(contactImageFilePath)
} }
@ -171,7 +176,10 @@ class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), S
val chooserIntent = val chooserIntent =
Intent.createChooser(galleryIntent, getString(R.string.chat_message_pick_file_dialog)) Intent.createChooser(galleryIntent, getString(R.string.chat_message_pick_file_dialog))
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(arrayOf<Parcelable>())) chooserIntent.putExtra(
Intent.EXTRA_INITIAL_INTENTS,
cameraIntents.toArray(arrayOf<Parcelable>())
)
startActivityForResult(chooserIntent, 0) startActivityForResult(chooserIntent, 0)
} }

View file

@ -88,7 +88,9 @@ class DetailContactFragment : GenericFragment<ContactDetailFragmentBinding>() {
) { ) {
it.consume { address -> it.consume { address ->
if (coreContext.core.callsNb > 0) { if (coreContext.core.callsNb > 0) {
Log.i("[Contact] Starting dialer with pre-filled URI ${address.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}") Log.i(
"[Contact] Starting dialer with pre-filled URI ${address.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}"
)
sharedViewModel.updateContactsAnimationsBasedOnDestination.value = sharedViewModel.updateContactsAnimationsBasedOnDestination.value =
Event(R.id.dialerFragment) Event(R.id.dialerFragment)
sharedViewModel.updateDialerAnimationsBasedOnDestination.value = sharedViewModel.updateDialerAnimationsBasedOnDestination.value =
@ -186,7 +188,9 @@ class DetailContactFragment : GenericFragment<ContactDetailFragmentBinding>() {
val smsIntent = Intent(Intent.ACTION_SENDTO) val smsIntent = Intent(Intent.ACTION_SENDTO)
smsIntent.putExtra("address", number) smsIntent.putExtra("address", number)
smsIntent.data = Uri.parse("smsto:$number") smsIntent.data = Uri.parse("smsto:$number")
val text = getString(R.string.contact_send_sms_invite_text).format(getString(R.string.contact_send_sms_invite_download_link)) val text = getString(R.string.contact_send_sms_invite_text).format(
getString(R.string.contact_send_sms_invite_download_link)
)
smsIntent.putExtra("sms_body", text) smsIntent.putExtra("sms_body", text)
startActivity(smsIntent) startActivity(smsIntent)
} }

View file

@ -115,7 +115,9 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
val navHostFragment = val navHostFragment =
childFragmentManager.findFragmentById(R.id.contacts_nav_container) as NavHostFragment childFragmentManager.findFragmentById(R.id.contacts_nav_container) as NavHostFragment
if (navHostFragment.navController.currentDestination?.id == R.id.emptyContactFragment) { if (navHostFragment.navController.currentDestination?.id == R.id.emptyContactFragment) {
Log.i("[Contacts] Foldable device has been folded, closing side pane with empty fragment") Log.i(
"[Contacts] Foldable device has been folded, closing side pane with empty fragment"
)
binding.slidingPane.closePane() binding.slidingPane.closePane()
} }
} }
@ -184,7 +186,9 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
if (!binding.slidingPane.isSlideable && if (!binding.slidingPane.isSlideable &&
deletedContact == sharedViewModel.selectedContact.value deletedContact == sharedViewModel.selectedContact.value
) { ) {
Log.i("[Contacts] Currently displayed contact has been deleted, removing detail fragment") Log.i(
"[Contacts] Currently displayed contact has been deleted, removing detail fragment"
)
clearDisplayedContact() clearDisplayedContact()
} }
} }
@ -204,7 +208,9 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
} }
// Divider between items // Divider between items
binding.contactsList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager)) binding.contactsList.addItemDecoration(
AppUtils.getDividerDecoration(requireContext(), layoutManager)
)
// Displays the first letter header // Displays the first letter header
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter)
@ -255,7 +261,9 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
viewLifecycleOwner viewLifecycleOwner
) { ) {
it.consume { it.consume {
(requireActivity() as SnackBarActivity).showSnackBar(R.string.contacts_ldap_query_more_results_available) (requireActivity() as SnackBarActivity).showSnackBar(
R.string.contacts_ldap_query_more_results_available
)
} }
} }
@ -298,24 +306,32 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
Log.i("[Contacts] Found matching contact [${contact.name}]") Log.i("[Contacts] Found matching contact [${contact.name}]")
adapter.selectedContactEvent.value = Event(contact) adapter.selectedContactEvent.value = Event(contact)
} else { } else {
Log.w("[Contacts] Matching contact not found yet, waiting for contacts updated callback") Log.w(
"[Contacts] Matching contact not found yet, waiting for contacts updated callback"
)
contactIdToDisplay = id contactIdToDisplay = id
} }
} else if (sipUri != null) { } else if (sipUri != null) {
Log.i("[Contacts] Found sipUri parameter in arguments [$sipUri]") Log.i("[Contacts] Found sipUri parameter in arguments [$sipUri]")
sipUriToAdd = sipUri sipUriToAdd = sipUri
(activity as MainActivity).showSnackBar(R.string.contact_choose_existing_or_new_to_add_number) (activity as MainActivity).showSnackBar(
R.string.contact_choose_existing_or_new_to_add_number
)
editOnClick = true editOnClick = true
} else if (addressString != null) { } else if (addressString != null) {
val address = Factory.instance().createAddress(addressString) val address = Factory.instance().createAddress(addressString)
if (address != null) { if (address != null) {
Log.i("[Contacts] Found friend SIP address parameter in arguments [${address.asStringUriOnly()}]") Log.i(
"[Contacts] Found friend SIP address parameter in arguments [${address.asStringUriOnly()}]"
)
val contact = coreContext.contactsManager.findContactByAddress(address) val contact = coreContext.contactsManager.findContactByAddress(address)
if (contact != null) { if (contact != null) {
Log.i("[Contacts] Found matching contact $contact") Log.i("[Contacts] Found matching contact $contact")
adapter.selectedContactEvent.value = Event(contact) adapter.selectedContactEvent.value = Event(contact)
} else { } else {
Log.w("[Contacts] No matching contact found for SIP address [${address.asStringUriOnly()}]") Log.w(
"[Contacts] No matching contact found for SIP address [${address.asStringUriOnly()}]"
)
} }
} }
} }
@ -344,7 +360,9 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
listViewModel.deleteContacts(list) listViewModel.deleteContacts(list)
if (!binding.slidingPane.isSlideable && closeSlidingPane) { if (!binding.slidingPane.isSlideable && closeSlidingPane) {
Log.i("[Contacts] Currently displayed contact has been deleted, removing detail fragment") Log.i(
"[Contacts] Currently displayed contact has been deleted, removing detail fragment"
)
clearDisplayedContact() clearDisplayedContact()
} }
} }

View file

@ -215,10 +215,20 @@ class ContactViewModel(friend: Friend, async: Boolean = false) : MessageNotifier
val presenceModel = friend.getPresenceModelForUriOrTel(value) val presenceModel = friend.getPresenceModelForUriOrTel(value)
val hasPresence = presenceModel?.basicStatus == PresenceBasicStatus.Open val hasPresence = presenceModel?.basicStatus == PresenceBasicStatus.Open
val isMe = coreContext.core.defaultAccount?.params?.identityAddress?.weakEqual(address) ?: false val isMe = coreContext.core.defaultAccount?.params?.identityAddress?.weakEqual(address) ?: false
val hasLimeCapability = corePreferences.allowEndToEndEncryptedChatWithoutPresence || (friend.getPresenceModelForUriOrTel(value)?.hasCapability(FriendCapability.LimeX3Dh) ?: false) val hasLimeCapability = corePreferences.allowEndToEndEncryptedChatWithoutPresence || (
friend.getPresenceModelForUriOrTel(
value
)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
)
val secureChatAllowed = LinphoneUtils.isEndToEndEncryptedChatAvailable() && !isMe && hasLimeCapability val secureChatAllowed = LinphoneUtils.isEndToEndEncryptedChatAvailable() && !isMe && hasLimeCapability
val displayValue = if (coreContext.core.defaultAccount?.params?.domain == address.domain) (address.username ?: value) else value val displayValue = if (coreContext.core.defaultAccount?.params?.domain == address.domain) (address.username ?: value) else value
val noa = ContactNumberOrAddressData(address, hasPresence, displayValue, showSecureChat = secureChatAllowed, listener = listener) val noa = ContactNumberOrAddressData(
address,
hasPresence,
displayValue,
showSecureChat = secureChatAllowed,
listener = listener
)
list.add(noa) list.add(noa)
} }
@ -229,11 +239,32 @@ class ContactViewModel(friend: Friend, async: Boolean = false) : MessageNotifier
val contactAddress = presenceModel?.contact ?: number val contactAddress = presenceModel?.contact ?: number
val address = coreContext.core.interpretUrl(contactAddress, true) val address = coreContext.core.interpretUrl(contactAddress, true)
address?.displayName = displayName.value.orEmpty() address?.displayName = displayName.value.orEmpty()
val isMe = if (address != null) coreContext.core.defaultAccount?.params?.identityAddress?.weakEqual(address) ?: false else false val isMe = if (address != null) {
val hasLimeCapability = corePreferences.allowEndToEndEncryptedChatWithoutPresence || (friend.getPresenceModelForUriOrTel(number)?.hasCapability(FriendCapability.LimeX3Dh) ?: false) coreContext.core.defaultAccount?.params?.identityAddress?.weakEqual(
address
) ?: false
} else {
false
}
val hasLimeCapability = corePreferences.allowEndToEndEncryptedChatWithoutPresence || (
friend.getPresenceModelForUriOrTel(
number
)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
)
val secureChatAllowed = LinphoneUtils.isEndToEndEncryptedChatAvailable() && !isMe && hasLimeCapability val secureChatAllowed = LinphoneUtils.isEndToEndEncryptedChatAvailable() && !isMe && hasLimeCapability
val label = PhoneNumberUtils.vcardParamStringToAddressBookLabel(coreContext.context.resources, phoneNumber.label ?: "") val label = PhoneNumberUtils.vcardParamStringToAddressBookLabel(
val noa = ContactNumberOrAddressData(address, hasPresence, number, isSip = false, showSecureChat = secureChatAllowed, typeLabel = label, listener = listener) coreContext.context.resources,
phoneNumber.label ?: ""
)
val noa = ContactNumberOrAddressData(
address,
hasPresence,
number,
isSip = false,
showSecureChat = secureChatAllowed,
typeLabel = label,
listener = listener
)
list.add(noa) list.add(noa)
} }
numbersAndAddresses.postValue(list) numbersAndAddresses.postValue(list)

View file

@ -123,8 +123,15 @@ class ContactsListViewModel : ViewModel() {
val aggregation = MagicSearchAggregation.Friend val aggregation = MagicSearchAggregation.Friend
searchResultsPending = true searchResultsPending = true
fastFetchJob?.cancel() fastFetchJob?.cancel()
Log.i("[Contacts] Asking Magic search for contacts matching filter [$filterValue], domain [$domain] and in sources [$filter]") Log.i(
coreContext.contactsManager.magicSearch.getContactsListAsync(filterValue, domain, filter, aggregation) "[Contacts] Asking Magic search for contacts matching filter [$filterValue], domain [$domain] and in sources [$filter]"
)
coreContext.contactsManager.magicSearch.getContactsListAsync(
filterValue,
domain,
filter,
aggregation
)
val spinnerDelay = corePreferences.delayBeforeShowingContactsSearchSpinner.toLong() val spinnerDelay = corePreferences.delayBeforeShowingContactsSearchSpinner.toLong()
fastFetchJob = viewModelScope.launch { fastFetchJob = viewModelScope.launch {
@ -154,7 +161,9 @@ class ContactsListViewModel : ViewModel() {
ContactViewModel(friend, true) ContactViewModel(friend, true)
} else { } else {
Log.w("[Contacts] SearchResult [$result] has no Friend!") Log.w("[Contacts] SearchResult [$result] has no Friend!")
val fakeFriend = coreContext.contactsManager.createFriendFromSearchResult(result) val fakeFriend = coreContext.contactsManager.createFriendFromSearchResult(
result
)
ContactViewModel(fakeFriend, true) ContactViewModel(fakeFriend, true)
} }

View file

@ -93,13 +93,19 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
} }
binding.setNewContactClickListener { binding.setNewContactClickListener {
sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(R.id.masterContactsFragment) sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(
sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(R.id.dialerFragment) R.id.masterContactsFragment
)
sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(
R.id.dialerFragment
)
navigateToContacts(viewModel.enteredUri.value) navigateToContacts(viewModel.enteredUri.value)
} }
binding.setNewConferenceClickListener { binding.setNewConferenceClickListener {
sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(R.id.conferenceSchedulingFragment) sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(
R.id.conferenceSchedulingFragment
)
navigateToConferenceScheduling() navigateToConferenceScheduling()
} }
@ -157,7 +163,9 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
} }
if (corePreferences.firstStart) { if (corePreferences.firstStart) {
Log.w("[Dialer] First start detected, wait for assistant to be finished to check for update & request permissions") Log.w(
"[Dialer] First start detected, wait for assistant to be finished to check for update & request permissions"
)
return return
} }
@ -168,10 +176,14 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
if (corePreferences.skipDialerForNewCallAndTransfer) { if (corePreferences.skipDialerForNewCallAndTransfer) {
if (sharedViewModel.pendingCallTransfer) { if (sharedViewModel.pendingCallTransfer) {
Log.i("[Dialer] We were asked to skip dialer so starting new call to [$address] now") Log.i(
"[Dialer] We were asked to skip dialer so starting new call to [$address] now"
)
viewModel.transferCallTo(address) viewModel.transferCallTo(address)
} else { } else {
Log.i("[Dialer] We were asked to skip dialer so starting transfer to [$address] now") Log.i(
"[Dialer] We were asked to skip dialer so starting transfer to [$address] now"
)
viewModel.directCall(address) viewModel.directCall(address)
} }
} else if (corePreferences.callRightAway && !skipAutoCall) { } else if (corePreferences.callRightAway && !skipAutoCall) {
@ -289,7 +301,9 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
if (Compatibility.hasTelecomManagerFeature(requireContext())) { if (Compatibility.hasTelecomManagerFeature(requireContext())) {
TelecomHelper.create(requireContext()) TelecomHelper.create(requireContext())
} else { } else {
Log.e("[Dialer] Telecom Helper can't be created, device doesn't support connection service!") Log.e(
"[Dialer] Telecom Helper can't be created, device doesn't support connection service!"
)
return return
} }
} else { } else {

View file

@ -112,7 +112,9 @@ class DialerViewModel : LogsUploadViewModel() {
if (reachable && address.isNotEmpty()) { if (reachable && address.isNotEmpty()) {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
if (now - timeAtWitchWeTriedToCall > 1000) { if (now - timeAtWitchWeTriedToCall > 1000) {
Log.e("[Dialer] More than 1 second has passed waiting for network, abort auto call to $address") Log.e(
"[Dialer] More than 1 second has passed waiting for network, abort auto call to $address"
)
enteredUri.value = address enteredUri.value = address
} else { } else {
Log.i("[Dialer] Network is available, continue auto call to $address") Log.i("[Dialer] Network is available, continue auto call to $address")
@ -198,7 +200,9 @@ class DialerViewModel : LogsUploadViewModel() {
if (coreContext.core.isNetworkReachable) { if (coreContext.core.isNetworkReachable) {
coreContext.startCall(to) coreContext.startCall(to)
} else { } else {
Log.w("[Dialer] Network isnt't reachable at the time, wait for network to start call (happens mainly when app is cold started)") Log.w(
"[Dialer] Network isnt't reachable at the time, wait for network to start call (happens mainly when app is cold started)"
)
timeAtWitchWeTriedToCall = System.currentTimeMillis() timeAtWitchWeTriedToCall = System.currentTimeMillis()
addressWaitingNetworkToBeCalled = to addressWaitingNetworkToBeCalled = to
} }

View file

@ -29,7 +29,9 @@ import org.linphone.activities.main.files.viewmodels.PdfFileViewModel
class PdfPagesListAdapter(private val pdfViewModel: PdfFileViewModel) : RecyclerView.Adapter<PdfPagesListAdapter.PdfPageViewHolder>() { class PdfPagesListAdapter(private val pdfViewModel: PdfFileViewModel) : RecyclerView.Adapter<PdfPagesListAdapter.PdfPageViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PdfPageViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PdfPageViewHolder {
val view = LayoutInflater.from(parent.context).inflate( val view = LayoutInflater.from(parent.context).inflate(
R.layout.file_pdf_viewer_cell, parent, false R.layout.file_pdf_viewer_cell,
parent,
false
) )
return PdfPageViewHolder(view) return PdfPageViewHolder(view)
} }

View file

@ -98,10 +98,14 @@ class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() {
Compatibility.addImageToMediaStore(requireContext(), content) Compatibility.addImageToMediaStore(requireContext(), content)
} }
if (export.await()) { if (export.await()) {
Log.i("[File Viewer] Successfully exported image [${content.name}] to Media Store: ${content.userData}") Log.i(
"[File Viewer] Successfully exported image [${content.name}] to Media Store: ${content.userData}"
)
mediaStoreFilePath = content.userData.toString() mediaStoreFilePath = content.userData.toString()
} else { } else {
Log.e("[File Viewer] Something went wrong while copying file to Media Store...") Log.e(
"[File Viewer] Something went wrong while copying file to Media Store..."
)
} }
} }
FileUtils.isMimeVideo(mime) -> { FileUtils.isMimeVideo(mime) -> {
@ -109,10 +113,14 @@ class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() {
Compatibility.addVideoToMediaStore(requireContext(), content) Compatibility.addVideoToMediaStore(requireContext(), content)
} }
if (export.await()) { if (export.await()) {
Log.i("[File Viewer] Successfully exported video [${content.name}] to Media Store: ${content.userData}") Log.i(
"[File Viewer] Successfully exported video [${content.name}] to Media Store: ${content.userData}"
)
mediaStoreFilePath = content.userData.toString() mediaStoreFilePath = content.userData.toString()
} else { } else {
Log.e("[File Viewer] Something went wrong while copying file to Media Store...") Log.e(
"[File Viewer] Something went wrong while copying file to Media Store..."
)
} }
} }
FileUtils.isMimeAudio(mime) -> { FileUtils.isMimeAudio(mime) -> {
@ -120,32 +128,46 @@ class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() {
Compatibility.addAudioToMediaStore(requireContext(), content) Compatibility.addAudioToMediaStore(requireContext(), content)
} }
if (export.await()) { if (export.await()) {
Log.i("[File Viewer] Successfully exported audio [${content.name}] to Media Store: ${content.userData}") Log.i(
"[File Viewer] Successfully exported audio [${content.name}] to Media Store: ${content.userData}"
)
mediaStoreFilePath = content.userData.toString() mediaStoreFilePath = content.userData.toString()
} else { } else {
Log.e("[File Viewer] Something went wrong while copying file to Media Store...") Log.e(
"[File Viewer] Something went wrong while copying file to Media Store..."
)
} }
} }
else -> { else -> {
Log.w("[File Viewer] File [${content.name}] isn't either an image, an audio file or a video, can't add it to the Media Store") Log.w(
"[File Viewer] File [${content.name}] isn't either an image, an audio file or a video, can't add it to the Media Store"
)
} }
} }
} else { } else {
Log.w("[File Viewer] Can't export image through Media Store API (requires Android 10 or WRITE_EXTERNAL permission, using fallback method...") Log.w(
"[File Viewer] Can't export image through Media Store API (requires Android 10 or WRITE_EXTERNAL permission, using fallback method..."
)
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
if (mediaStoreFilePath.isEmpty()) { if (mediaStoreFilePath.isEmpty()) {
Log.w("[File Viewer] Media store file path is empty, media store export failed?") Log.w(
"[File Viewer] Media store file path is empty, media store export failed?"
)
val filePath = content.exportPlainFile().orEmpty() val filePath = content.exportPlainFile().orEmpty()
plainFilePath = filePath.ifEmpty { content.filePath.orEmpty() } plainFilePath = filePath.ifEmpty { content.filePath.orEmpty() }
Log.i("[File Viewer] Plain file path is: $plainFilePath") Log.i("[File Viewer] Plain file path is: $plainFilePath")
if (plainFilePath.isNotEmpty()) { if (plainFilePath.isNotEmpty()) {
if (!FileUtils.openFileInThirdPartyApp(requireActivity(), plainFilePath)) { if (!FileUtils.openFileInThirdPartyApp(requireActivity(), plainFilePath)) {
(requireActivity() as SnackBarActivity).showSnackBar(R.string.chat_message_no_app_found_to_handle_file_mime_type) (requireActivity() as SnackBarActivity).showSnackBar(
R.string.chat_message_no_app_found_to_handle_file_mime_type
)
if (plainFilePath != content.filePath.orEmpty()) { if (plainFilePath != content.filePath.orEmpty()) {
Log.i("[File Viewer] No app to open plain file path [$plainFilePath], destroying it") Log.i(
"[File Viewer] No app to open plain file path [$plainFilePath], destroying it"
)
FileUtils.deleteFile(plainFilePath) FileUtils.deleteFile(plainFilePath)
} }
plainFilePath = "" plainFilePath = ""

View file

@ -41,7 +41,11 @@ class AudioFileViewModel(content: Content) : FileViewerViewModel(content), Media
init { init {
mediaPlayer.apply { mediaPlayer.apply {
setAudioAttributes(AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).setUsage(AudioAttributes.USAGE_MEDIA).build()) setAudioAttributes(
AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).setUsage(
AudioAttributes.USAGE_MEDIA
).build()
)
setDataSource(filePath) setDataSource(filePath)
prepare() prepare()
start() start()

View file

@ -33,7 +33,9 @@ open class FileViewerViewModel(val content: Content) : ViewModel() {
init { init {
filePath = if (deleteAfterUse) { filePath = if (deleteAfterUse) {
Log.i("[File Viewer] [VFS] Content is encrypted, requesting plain file path for file [${content.filePath}]") Log.i(
"[File Viewer] [VFS] Content is encrypted, requesting plain file path for file [${content.filePath}]"
)
content.exportPlainFile() content.exportPlainFile()
} else { } else {
content.filePath.orEmpty() content.filePath.orEmpty()

View file

@ -136,14 +136,15 @@ abstract class MasterFragment<T : ViewDataBinding, U : SelectionListAdapter<*, *
abstract fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) abstract fun deleteItems(indexesOfItemToDelete: ArrayList<Int>)
class SlidingPaneBackPressedCallback(private val slidingPaneLayout: SlidingPaneLayout) : class SlidingPaneBackPressedCallback(private val slidingPaneLayout: SlidingPaneLayout) :
OnBackPressedCallback OnBackPressedCallback(
(
slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen
), ),
SlidingPaneLayout.PanelSlideListener { SlidingPaneLayout.PanelSlideListener {
init { init {
Log.d("[Master Fragment] SlidingPane isSlideable = ${slidingPaneLayout.isSlideable}, isOpen = ${slidingPaneLayout.isOpen}") Log.d(
"[Master Fragment] SlidingPane isSlideable = ${slidingPaneLayout.isSlideable}, isOpen = ${slidingPaneLayout.isOpen}"
)
slidingPaneLayout.addPanelSlideListener(this) slidingPaneLayout.addPanelSlideListener(this)
} }

View file

@ -77,7 +77,9 @@ abstract class SecureFragment<T : ViewDataBinding> : GenericFragment<T>() {
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

@ -54,32 +54,48 @@ class TabsFragment : GenericFragment<TabsFragmentBinding>(), NavController.OnDes
binding.setHistoryClickListener { binding.setHistoryClickListener {
when (findNavController().currentDestination?.id) { when (findNavController().currentDestination?.id) {
R.id.masterContactsFragment -> sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(R.id.masterCallLogsFragment) R.id.masterContactsFragment -> sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(
R.id.dialerFragment -> sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(R.id.masterCallLogsFragment) R.id.masterCallLogsFragment
)
R.id.dialerFragment -> sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(
R.id.masterCallLogsFragment
)
} }
navigateToCallHistory() navigateToCallHistory()
} }
binding.setContactsClickListener { binding.setContactsClickListener {
when (findNavController().currentDestination?.id) { when (findNavController().currentDestination?.id) {
R.id.dialerFragment -> sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(R.id.masterContactsFragment) R.id.dialerFragment -> sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(
R.id.masterContactsFragment
)
} }
sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(findNavController().currentDestination?.id ?: -1) sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(
findNavController().currentDestination?.id ?: -1
)
navigateToContacts() navigateToContacts()
} }
binding.setDialerClickListener { binding.setDialerClickListener {
when (findNavController().currentDestination?.id) { when (findNavController().currentDestination?.id) {
R.id.masterContactsFragment -> sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(R.id.dialerFragment) R.id.masterContactsFragment -> sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(
R.id.dialerFragment
)
} }
sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(findNavController().currentDestination?.id ?: -1) sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(
findNavController().currentDestination?.id ?: -1
)
navigateToDialer() navigateToDialer()
} }
binding.setChatClickListener { binding.setChatClickListener {
when (findNavController().currentDestination?.id) { when (findNavController().currentDestination?.id) {
R.id.masterContactsFragment -> sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(R.id.masterChatRoomsFragment) R.id.masterContactsFragment -> sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(
R.id.dialerFragment -> sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(R.id.masterChatRoomsFragment) R.id.masterChatRoomsFragment
)
R.id.dialerFragment -> sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(
R.id.masterChatRoomsFragment
)
} }
navigateToChatRooms() navigateToChatRooms()
} }
@ -102,17 +118,30 @@ class TabsFragment : GenericFragment<TabsFragmentBinding>(), NavController.OnDes
) { ) {
if (corePreferences.enableAnimations) { if (corePreferences.enableAnimations) {
when (destination.id) { when (destination.id) {
R.id.masterCallLogsFragment -> binding.motionLayout.transitionToState(R.id.call_history) R.id.masterCallLogsFragment -> binding.motionLayout.transitionToState(
R.id.call_history
)
R.id.masterContactsFragment -> binding.motionLayout.transitionToState(R.id.contacts) R.id.masterContactsFragment -> binding.motionLayout.transitionToState(R.id.contacts)
R.id.dialerFragment -> binding.motionLayout.transitionToState(R.id.dialer) R.id.dialerFragment -> binding.motionLayout.transitionToState(R.id.dialer)
R.id.masterChatRoomsFragment -> binding.motionLayout.transitionToState(R.id.chat_rooms) R.id.masterChatRoomsFragment -> binding.motionLayout.transitionToState(
R.id.chat_rooms
)
} }
} else { } else {
when (destination.id) { when (destination.id) {
R.id.masterCallLogsFragment -> binding.motionLayout.setTransition(R.id.call_history, R.id.call_history) R.id.masterCallLogsFragment -> binding.motionLayout.setTransition(
R.id.masterContactsFragment -> binding.motionLayout.setTransition(R.id.contacts, R.id.contacts) R.id.call_history,
R.id.call_history
)
R.id.masterContactsFragment -> binding.motionLayout.setTransition(
R.id.contacts,
R.id.contacts
)
R.id.dialerFragment -> binding.motionLayout.setTransition(R.id.dialer, R.id.dialer) R.id.dialerFragment -> binding.motionLayout.setTransition(R.id.dialer, R.id.dialer)
R.id.masterChatRoomsFragment -> binding.motionLayout.setTransition(R.id.chat_rooms, R.id.chat_rooms) R.id.masterChatRoomsFragment -> binding.motionLayout.setTransition(
R.id.chat_rooms,
R.id.chat_rooms
)
} }
} }
} }

View file

@ -39,7 +39,11 @@ import org.linphone.utils.*
class CallLogsListAdapter( class CallLogsListAdapter(
selectionVM: ListTopBarViewModel, selectionVM: ListTopBarViewModel,
private val viewLifecycleOwner: LifecycleOwner private val viewLifecycleOwner: LifecycleOwner
) : SelectionListAdapter<GroupedCallLogData, RecyclerView.ViewHolder>(selectionVM, CallLogDiffCallback()), HeaderAdapter { ) : SelectionListAdapter<GroupedCallLogData, RecyclerView.ViewHolder>(
selectionVM,
CallLogDiffCallback()
),
HeaderAdapter {
val selectedCallLogEvent: MutableLiveData<Event<GroupedCallLogData>> by lazy { val selectedCallLogEvent: MutableLiveData<Event<GroupedCallLogData>> by lazy {
MutableLiveData<Event<GroupedCallLogData>>() MutableLiveData<Event<GroupedCallLogData>>()
} }
@ -51,7 +55,9 @@ class CallLogsListAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding: HistoryListCellBinding = DataBindingUtil.inflate( val binding: HistoryListCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.history_list_cell, parent, false R.layout.history_list_cell,
parent,
false
) )
return ViewHolder(binding) return ViewHolder(binding)
} }
@ -115,7 +121,9 @@ class CallLogsListAdapter(
return if (previousPosition >= 0) { return if (previousPosition >= 0) {
val previousItemDate = getItem(previousPosition).lastCallLogStartTimestamp val previousItemDate = getItem(previousPosition).lastCallLogStartTimestamp
!TimestampUtils.isSameDay(date, previousItemDate) !TimestampUtils.isSameDay(date, previousItemDate)
} else true } else {
true
}
} }
override fun getHeaderViewForPosition(context: Context, position: Int): View { override fun getHeaderViewForPosition(context: Context, position: Int): View {
@ -123,7 +131,9 @@ class CallLogsListAdapter(
val date = formatDate(context, callLog.lastCallLogStartTimestamp) val date = formatDate(context, callLog.lastCallLogStartTimestamp)
val binding: GenericListHeaderBinding = DataBindingUtil.inflate( val binding: GenericListHeaderBinding = DataBindingUtil.inflate(
LayoutInflater.from(context), LayoutInflater.from(context),
R.layout.generic_list_header, null, false R.layout.generic_list_header,
null,
false
) )
binding.title = date binding.title = date
binding.executePendingBindings() binding.executePendingBindings()

View file

@ -62,12 +62,16 @@ class DetailCallLogFragment : GenericFragment<HistoryDetailFragmentBinding>() {
copy.clean() copy.clean()
val address = copy.asStringUriOnly() val address = copy.asStringUriOnly()
Log.i("[History] Creating contact with SIP URI [$address]") Log.i("[History] Creating contact with SIP URI [$address]")
sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(R.id.masterCallLogsFragment) sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(
R.id.masterCallLogsFragment
)
navigateToContacts(address) navigateToContacts(address)
} }
binding.setContactClickListener { binding.setContactClickListener {
sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(R.id.masterCallLogsFragment) sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(
R.id.masterCallLogsFragment
)
val contactId = viewModel.contact.value?.refKey val contactId = viewModel.contact.value?.refKey
if (contactId != null) { if (contactId != null) {
Log.i("[History] Displaying native contact [$contactId]") Log.i("[History] Displaying native contact [$contactId]")
@ -90,7 +94,9 @@ class DetailCallLogFragment : GenericFragment<HistoryDetailFragmentBinding>() {
address.clean() address.clean()
if (coreContext.core.callsNb > 0) { if (coreContext.core.callsNb > 0) {
Log.i("[History] Starting dialer with pre-filled URI [${address.asStringUriOnly()}], is transfer? ${sharedViewModel.pendingCallTransfer}") Log.i(
"[History] Starting dialer with pre-filled URI [${address.asStringUriOnly()}], is transfer? ${sharedViewModel.pendingCallTransfer}"
)
sharedViewModel.updateDialerAnimationsBasedOnDestination.value = sharedViewModel.updateDialerAnimationsBasedOnDestination.value =
Event(R.id.masterCallLogsFragment) Event(R.id.masterCallLogsFragment)
@ -104,7 +110,9 @@ class DetailCallLogFragment : GenericFragment<HistoryDetailFragmentBinding>() {
navigateToDialer(args) navigateToDialer(args)
} else { } else {
val localAddress = callLog.localAddress val localAddress = callLog.localAddress
Log.i("[History] Starting call to ${address.asStringUriOnly()} with local address ${localAddress.asStringUriOnly()}") Log.i(
"[History] Starting call to ${address.asStringUriOnly()} with local address ${localAddress.asStringUriOnly()}"
)
coreContext.startCall(address, localAddress = localAddress) coreContext.startCall(address, localAddress = localAddress)
} }
} }

View file

@ -105,7 +105,9 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
val navHostFragment = val navHostFragment =
childFragmentManager.findFragmentById(R.id.history_nav_container) as NavHostFragment childFragmentManager.findFragmentById(R.id.history_nav_container) as NavHostFragment
if (navHostFragment.navController.currentDestination?.id == R.id.emptyCallHistoryFragment) { if (navHostFragment.navController.currentDestination?.id == R.id.emptyCallHistoryFragment) {
Log.i("[History] Foldable device has been folded, closing side pane with empty fragment") Log.i(
"[History] Foldable device has been folded, closing side pane with empty fragment"
)
binding.slidingPane.closePane() binding.slidingPane.closePane()
} }
} }
@ -162,7 +164,9 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
if (!binding.slidingPane.isSlideable && if (!binding.slidingPane.isSlideable &&
deletedCallGroup.lastCallLogId == sharedViewModel.selectedCallLogGroup.value?.lastCallLogId deletedCallGroup.lastCallLogId == sharedViewModel.selectedCallLogGroup.value?.lastCallLogId
) { ) {
Log.i("[History] Currently displayed history has been deleted, removing detail fragment") Log.i(
"[History] Currently displayed history has been deleted, removing detail fragment"
)
clearDisplayedCallHistory() clearDisplayedCallHistory()
} }
dialog.dismiss() dialog.dismiss()
@ -178,7 +182,9 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
.attachToRecyclerView(binding.callLogsList) .attachToRecyclerView(binding.callLogsList)
// Divider between items // Divider between items
binding.callLogsList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager)) binding.callLogsList.addItemDecoration(
AppUtils.getDividerDecoration(requireContext(), layoutManager)
)
// Displays formatted date header // Displays formatted date header
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter)
@ -212,7 +218,7 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
} }
adapter.startCallToEvent.observe( adapter.startCallToEvent.observe(
viewLifecycleOwner, viewLifecycleOwner
) { ) {
it.consume { callLogGroup -> it.consume { callLogGroup ->
val callLog = callLogGroup.lastCallLog val callLog = callLogGroup.lastCallLog
@ -244,8 +250,12 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
} }
coreContext.core.callsNb > 0 -> { coreContext.core.callsNb > 0 -> {
val cleanAddress = LinphoneUtils.getCleanedAddress(callLog.remoteAddress) val cleanAddress = LinphoneUtils.getCleanedAddress(callLog.remoteAddress)
Log.i("[History] Starting dialer with pre-filled URI ${cleanAddress.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}") Log.i(
sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(R.id.masterCallLogsFragment) "[History] Starting dialer with pre-filled URI ${cleanAddress.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}"
)
sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(
R.id.masterCallLogsFragment
)
val args = Bundle() val args = Bundle()
args.putString("URI", cleanAddress.asStringUriOnly()) args.putString("URI", cleanAddress.asStringUriOnly())
args.putBoolean("Transfer", sharedViewModel.pendingCallTransfer) args.putBoolean("Transfer", sharedViewModel.pendingCallTransfer)
@ -255,7 +265,9 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
else -> { else -> {
val cleanAddress = LinphoneUtils.getCleanedAddress(callLog.remoteAddress) val cleanAddress = LinphoneUtils.getCleanedAddress(callLog.remoteAddress)
val localAddress = callLogGroup.lastCallLog.localAddress val localAddress = callLogGroup.lastCallLog.localAddress
Log.i("[History] Starting call to ${cleanAddress.asStringUriOnly()} with local address ${localAddress.asStringUriOnly()}") Log.i(
"[History] Starting call to ${cleanAddress.asStringUriOnly()} with local address ${localAddress.asStringUriOnly()}"
)
coreContext.startCall(cleanAddress, localAddress = localAddress) coreContext.startCall(cleanAddress, localAddress = localAddress)
} }
} }
@ -289,7 +301,9 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
listViewModel.deleteCallLogGroups(list) listViewModel.deleteCallLogGroups(list)
if (!binding.slidingPane.isSlideable && closeSlidingPane) { if (!binding.slidingPane.isSlideable && closeSlidingPane) {
Log.i("[History] Currently displayed history has been deleted, removing detail fragment") Log.i(
"[History] Currently displayed history has been deleted, removing detail fragment"
)
clearDisplayedCallHistory() clearDisplayedCallHistory()
} }
} }

View file

@ -35,7 +35,9 @@ import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils import org.linphone.utils.LinphoneUtils
import org.linphone.utils.TimestampUtils import org.linphone.utils.TimestampUtils
class CallLogViewModel(val callLog: CallLog, private val isRelated: Boolean = false) : GenericContactViewModel(callLog.remoteAddress) { class CallLogViewModel(val callLog: CallLog, private val isRelated: Boolean = false) : GenericContactViewModel(
callLog.remoteAddress
) {
val peerSipUri: String by lazy { val peerSipUri: String by lazy {
LinphoneUtils.getDisplayableAddress(callLog.remoteAddress) LinphoneUtils.getDisplayableAddress(callLog.remoteAddress)
} }
@ -77,7 +79,10 @@ class CallLogViewModel(val callLog: CallLog, private val isRelated: Boolean = fa
} }
val duration: String by lazy { val duration: String by lazy {
val dateFormat = SimpleDateFormat(if (callLog.duration >= 3600) "HH:mm:ss" else "mm:ss", Locale.getDefault()) val dateFormat = SimpleDateFormat(
if (callLog.duration >= 3600) "HH:mm:ss" else "mm:ss",
Locale.getDefault()
)
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
cal[0, 0, 0, 0, 0] = callLog.duration cal[0, 0, 0, 0, 0] = callLog.duration
dateFormat.format(cal.time) dateFormat.format(cal.time)
@ -101,14 +106,25 @@ class CallLogViewModel(val callLog: CallLog, private val isRelated: Boolean = fa
val hidePlainChat = corePreferences.forceEndToEndEncryptedChat val hidePlainChat = corePreferences.forceEndToEndEncryptedChat
val secureChatAllowed = LinphoneUtils.isEndToEndEncryptedChatAvailable() && (corePreferences.allowEndToEndEncryptedChatWithoutPresence || (contact.value?.getPresenceModelForUriOrTel(peerSipUri)?.hasCapability(FriendCapability.LimeX3Dh) ?: false)) val secureChatAllowed = LinphoneUtils.isEndToEndEncryptedChatAvailable() && (
corePreferences.allowEndToEndEncryptedChatWithoutPresence || (
contact.value?.getPresenceModelForUriOrTel(
peerSipUri
)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
)
)
val relatedCallLogs = MutableLiveData<ArrayList<CallLogViewModel>>() val relatedCallLogs = MutableLiveData<ArrayList<CallLogViewModel>>()
private val listener = object : CoreListenerStub() { private val listener = object : CoreListenerStub() {
override fun onCallLogUpdated(core: Core, log: CallLog) { override fun onCallLogUpdated(core: Core, log: CallLog) {
if (callLog.remoteAddress.weakEqual(log.remoteAddress) && callLog.localAddress.weakEqual(log.localAddress)) { if (callLog.remoteAddress.weakEqual(log.remoteAddress) && callLog.localAddress.weakEqual(
Log.i("[History Detail] New call log for ${callLog.remoteAddress.asStringUriOnly()} with local address ${callLog.localAddress.asStringUriOnly()}") log.localAddress
)
) {
Log.i(
"[History Detail] New call log for ${callLog.remoteAddress.asStringUriOnly()} with local address ${callLog.localAddress.asStringUriOnly()}"
)
addRelatedCallLogs(arrayListOf(log)) addRelatedCallLogs(arrayListOf(log))
} }
} }
@ -161,11 +177,21 @@ class CallLogViewModel(val callLog: CallLog, private val isRelated: Boolean = fa
val organizer = conferenceInfo.organizer val organizer = conferenceInfo.organizer
if (organizer != null) { if (organizer != null) {
organizerParticipantData.value = organizerParticipantData.value =
ConferenceSchedulingParticipantData(organizer, showLimeBadge = false, showDivider = false) ConferenceSchedulingParticipantData(
organizer,
showLimeBadge = false,
showDivider = false
)
} }
val list = arrayListOf<ConferenceSchedulingParticipantData>() val list = arrayListOf<ConferenceSchedulingParticipantData>()
for (participant in conferenceInfo.participants) { for (participant in conferenceInfo.participants) {
list.add(ConferenceSchedulingParticipantData(participant, showLimeBadge = false, showDivider = true)) list.add(
ConferenceSchedulingParticipantData(
participant,
showLimeBadge = false,
showDivider = true
)
)
} }
conferenceParticipantsData.value = list conferenceParticipantsData.value = list
} }
@ -205,7 +231,9 @@ class CallLogViewModel(val callLog: CallLog, private val isRelated: Boolean = fa
} }
} else { } else {
waitForChatRoomCreation.value = false waitForChatRoomCreation.value = false
Log.e("[History Detail] Couldn't create chat room with address ${callLog.remoteAddress}") Log.e(
"[History Detail] Couldn't create chat room with address ${callLog.remoteAddress}"
)
onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack)
} }
} }

View file

@ -153,7 +153,11 @@ class CallLogsListViewModel : ViewModel() {
callLogs.value = when (filter.value) { callLogs.value = when (filter.value) {
CallLogsFilter.MISSED -> computeCallLogs(allCallLogs, missed = true, conference = false) CallLogsFilter.MISSED -> computeCallLogs(allCallLogs, missed = true, conference = false)
CallLogsFilter.CONFERENCE -> computeCallLogs(allCallLogs, missed = false, conference = true) CallLogsFilter.CONFERENCE -> computeCallLogs(
allCallLogs,
missed = false,
conference = true
)
else -> computeCallLogs(allCallLogs, missed = false, conference = false) else -> computeCallLogs(allCallLogs, missed = false, conference = false)
} }
} }

View file

@ -39,7 +39,11 @@ import org.linphone.utils.*
class RecordingsListAdapter( class RecordingsListAdapter(
selectionVM: ListTopBarViewModel, selectionVM: ListTopBarViewModel,
private val viewLifecycleOwner: LifecycleOwner private val viewLifecycleOwner: LifecycleOwner
) : SelectionListAdapter<RecordingData, RecyclerView.ViewHolder>(selectionVM, RecordingDiffCallback()), HeaderAdapter { ) : SelectionListAdapter<RecordingData, RecyclerView.ViewHolder>(
selectionVM,
RecordingDiffCallback()
),
HeaderAdapter {
private lateinit var videoSurface: TextureView private lateinit var videoSurface: TextureView
@ -50,7 +54,9 @@ class RecordingsListAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding: RecordingListCellBinding = DataBindingUtil.inflate( val binding: RecordingListCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.recording_list_cell, parent, false R.layout.recording_list_cell,
parent,
false
) )
return ViewHolder(binding) return ViewHolder(binding)
} }
@ -104,7 +110,9 @@ class RecordingsListAdapter(
return if (previousPosition >= 0) { return if (previousPosition >= 0) {
val previousItemDate = getItem(previousPosition).date val previousItemDate = getItem(previousPosition).date
!TimestampUtils.isSameDay(date, previousItemDate) !TimestampUtils.isSameDay(date, previousItemDate)
} else true } else {
true
}
} }
override fun getHeaderViewForPosition(context: Context, position: Int): View { override fun getHeaderViewForPosition(context: Context, position: Int): View {
@ -112,7 +120,9 @@ class RecordingsListAdapter(
val date = formatDate(context, recording.date.time) val date = formatDate(context, recording.date.time)
val binding: GenericListHeaderBinding = DataBindingUtil.inflate( val binding: GenericListHeaderBinding = DataBindingUtil.inflate(
LayoutInflater.from(context), LayoutInflater.from(context),
R.layout.generic_list_header, null, false R.layout.generic_list_header,
null,
false
) )
binding.title = date binding.title = date
binding.executePendingBindings() binding.executePendingBindings()
@ -126,7 +136,13 @@ class RecordingsListAdapter(
} else if (TimestampUtils.isYesterday(date, false)) { } else if (TimestampUtils.isYesterday(date, false)) {
return context.getString(R.string.yesterday) return context.getString(R.string.yesterday)
} }
return TimestampUtils.toString(date, onlyDate = true, timestampInSecs = false, shortDate = false, hideYear = false) return TimestampUtils.toString(
date,
onlyDate = true,
timestampInSecs = false,
shortDate = false,
hideYear = false
)
} }
} }

View file

@ -174,13 +174,18 @@ class RecordingData(val path: String, private val recordingListener: RecordingLi
Log.i("[Recording] Using device $playbackSoundCard to make the call recording playback") Log.i("[Recording] Using device $playbackSoundCard to make the call recording playback")
val localPlayer = coreContext.core.createLocalPlayer(playbackSoundCard, null, null) val localPlayer = coreContext.core.createLocalPlayer(playbackSoundCard, null, null)
if (localPlayer != null) player = localPlayer if (localPlayer != null) {
else Log.e("[Recording] Couldn't create local player!") player = localPlayer
} else {
Log.e("[Recording] Couldn't create local player!")
}
player.addListener(listener) player.addListener(listener)
player.open(path) player.open(path)
duration.value = player.duration duration.value = player.duration
formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(player.duration) // is already in milliseconds formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(
player.duration
) // is already in milliseconds
formattedDate.value = DateFormat.getTimeInstance(DateFormat.SHORT).format(date) formattedDate.value = DateFormat.getTimeInstance(DateFormat.SHORT).format(date)
} }

View file

@ -66,7 +66,9 @@ class RecordingsFragment : MasterFragment<RecordingsFragmentBinding, RecordingsL
binding.recordingsList.layoutManager = layoutManager binding.recordingsList.layoutManager = layoutManager
// Divider between items // Divider between items
binding.recordingsList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager)) binding.recordingsList.addItemDecoration(
AppUtils.getDividerDecoration(requireContext(), layoutManager)
)
// Displays the first letter header // Displays the first letter header
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter)
@ -90,7 +92,9 @@ class RecordingsFragment : MasterFragment<RecordingsFragmentBinding, RecordingsL
intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.recordings_export)) intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.recordings_export))
try { try {
requireActivity().startActivity(Intent.createChooser(intent, getString(R.string.recordings_export))) requireActivity().startActivity(
Intent.createChooser(intent, getString(R.string.recordings_export))
)
} catch (anfe: ActivityNotFoundException) { } catch (anfe: ActivityNotFoundException) {
Log.e(anfe) Log.e(anfe)
} }
@ -136,7 +140,9 @@ class RecordingsFragment : MasterFragment<RecordingsFragmentBinding, RecordingsL
if (this::viewModel.isInitialized) { if (this::viewModel.isInitialized) {
viewModel.updateRecordingsList() viewModel.updateRecordingsList()
} else { } else {
Log.e("[Recordings] Fragment resuming but viewModel lateinit property isn't initialized!") Log.e(
"[Recordings] Fragment resuming but viewModel lateinit property isn't initialized!"
)
} }
super.onResume() super.onResume()
} }

View file

@ -68,7 +68,9 @@ class AccountSettingsFragment : GenericSettingFragment<SettingsAccountFragmentBi
it.consume { it.consume {
val authInfo = viewModel.account.findAuthInfo() val authInfo = viewModel.account.findAuthInfo()
if (authInfo == null) { if (authInfo == null) {
Log.e("[Account Settings] Failed to find auth info for account ${viewModel.account}") Log.e(
"[Account Settings] Failed to find auth info for account ${viewModel.account}"
)
} else { } else {
val args = Bundle() val args = Bundle()
args.putString("Username", authInfo.username) args.putString("Username", authInfo.username)
@ -109,11 +111,21 @@ class AccountSettingsFragment : GenericSettingFragment<SettingsAccountFragmentBi
) { ) {
it.consume { it.consume {
val defaultDomainAccount = viewModel.account.params.identityAddress?.domain == corePreferences.defaultDomain val defaultDomainAccount = viewModel.account.params.identityAddress?.domain == corePreferences.defaultDomain
Log.i("[Account Settings] User clicked on delete account, showing confirmation dialog for ${if (defaultDomainAccount) "default domain account" else "third party account"}") Log.i(
"[Account Settings] User clicked on delete account, showing confirmation dialog for ${if (defaultDomainAccount) "default domain account" else "third party account"}"
)
val dialogViewModel = if (defaultDomainAccount) { val dialogViewModel = if (defaultDomainAccount) {
DialogViewModel(getString(R.string.account_setting_delete_sip_linphone_org_confirmation_dialog), getString(R.string.account_setting_delete_dialog_title)) DialogViewModel(
getString(
R.string.account_setting_delete_sip_linphone_org_confirmation_dialog
),
getString(R.string.account_setting_delete_dialog_title)
)
} else { } else {
DialogViewModel(getString(R.string.account_setting_delete_generic_confirmation_dialog), getString(R.string.account_setting_delete_dialog_title)) DialogViewModel(
getString(R.string.account_setting_delete_generic_confirmation_dialog),
getString(R.string.account_setting_delete_dialog_title)
)
} }
val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel)

View file

@ -113,7 +113,9 @@ class AdvancedSettingsFragment : GenericSettingFragment<SettingsAdvancedFragment
} }
} }
viewModel.powerManagerSettingsVisibility.value = PowerManagerUtils.getDevicePowerManagerIntent(requireContext()) != null viewModel.powerManagerSettingsVisibility.value = PowerManagerUtils.getDevicePowerManagerIntent(
requireContext()
) != null
viewModel.goToPowerManagerSettingsEvent.observe( viewModel.goToPowerManagerSettingsEvent.observe(
viewLifecycleOwner viewLifecycleOwner
) { ) {

View file

@ -53,7 +53,9 @@ class AudioSettingsFragment : GenericSettingFragment<SettingsAudioFragmentBindin
viewLifecycleOwner viewLifecycleOwner
) { ) {
it.consume { it.consume {
Log.i("[Audio Settings] Asking for RECORD_AUDIO permission for echo canceller calibration") Log.i(
"[Audio Settings] Asking for RECORD_AUDIO permission for echo canceller calibration"
)
requestPermissions(arrayOf(android.Manifest.permission.RECORD_AUDIO), 1) requestPermissions(arrayOf(android.Manifest.permission.RECORD_AUDIO), 1)
} }
} }
@ -97,7 +99,12 @@ class AudioSettingsFragment : GenericSettingFragment<SettingsAudioFragmentBindin
private fun initAudioCodecsList() { private fun initAudioCodecsList() {
val list = arrayListOf<ViewDataBinding>() val list = arrayListOf<ViewDataBinding>()
for (payload in coreContext.core.audioPayloadTypes) { for (payload in coreContext.core.audioPayloadTypes) {
val binding = DataBindingUtil.inflate<ViewDataBinding>(LayoutInflater.from(requireContext()), R.layout.settings_widget_switch, null, false) val binding = DataBindingUtil.inflate<ViewDataBinding>(
LayoutInflater.from(requireContext()),
R.layout.settings_widget_switch,
null,
false
)
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())

View file

@ -99,7 +99,9 @@ class CallSettingsFragment : GenericSettingFragment<SettingsCallFragmentBinding>
updateTelecomManagerAccount() updateTelecomManagerAccount()
} }
} else { } else {
Log.e("[Telecom Helper] Telecom Helper can't be created, device doesn't support connection service!") Log.e(
"[Telecom Helper] Telecom Helper can't be created, device doesn't support connection service!"
)
} }
} }
} }
@ -138,7 +140,9 @@ class CallSettingsFragment : GenericSettingFragment<SettingsCallFragmentBinding>
if (Compatibility.hasTelecomManagerFeature(requireContext())) { if (Compatibility.hasTelecomManagerFeature(requireContext())) {
TelecomHelper.create(requireContext()) TelecomHelper.create(requireContext())
} else { } else {
Log.e("[Telecom Helper] Telecom Helper can't be created, device doesn't support connection service") Log.e(
"[Telecom Helper] Telecom Helper can't be created, device doesn't support connection service"
)
} }
} }
updateTelecomManagerAccount() updateTelecomManagerAccount()
@ -168,7 +172,9 @@ class CallSettingsFragment : GenericSettingFragment<SettingsCallFragmentBinding>
for (index in grantResults.indices) { for (index in grantResults.indices) {
val result = grantResults[index] val result = grantResults[index]
if (result != PackageManager.PERMISSION_GRANTED) { if (result != PackageManager.PERMISSION_GRANTED) {
Log.w("[Call Settings] ${permissions[index]} permission denied but required for telecom manager") Log.w(
"[Call Settings] ${permissions[index]} permission denied but required for telecom manager"
)
viewModel.useTelecomManager.value = false viewModel.useTelecomManager.value = false
corePreferences.useTelecomManager = false corePreferences.useTelecomManager = false
return return
@ -179,7 +185,9 @@ class CallSettingsFragment : GenericSettingFragment<SettingsCallFragmentBinding>
TelecomHelper.create(requireContext()) TelecomHelper.create(requireContext())
updateTelecomManagerAccount() updateTelecomManagerAccount()
} else { } else {
Log.e("[Telecom Helper] Telecom Helper can't be created, device doesn't support connection service") Log.e(
"[Telecom Helper] Telecom Helper can't be created, device doesn't support connection service"
)
} }
} }
} }

View file

@ -67,7 +67,9 @@ class ContactsSettingsFragment : GenericSettingFragment<SettingsContactsFragment
viewLifecycleOwner viewLifecycleOwner
) { ) {
it.consume { it.consume {
Log.i("[Contacts Settings] Asking for WRITE_CONTACTS permission to be able to store presence") Log.i(
"[Contacts Settings] Asking for WRITE_CONTACTS permission to be able to store presence"
)
requestPermissions(arrayOf(android.Manifest.permission.WRITE_CONTACTS), 1) requestPermissions(arrayOf(android.Manifest.permission.WRITE_CONTACTS), 1)
} }
} }

View file

@ -80,7 +80,9 @@ class SettingsFragment : SecureFragment<SettingsFragmentBinding>() {
val navHostFragment = val navHostFragment =
childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment
if (navHostFragment.navController.currentDestination?.id == R.id.emptySettingsFragment) { if (navHostFragment.navController.currentDestination?.id == R.id.emptySettingsFragment) {
Log.i("[Settings] Foldable device has been folded, closing side pane with empty fragment") Log.i(
"[Settings] Foldable device has been folded, closing side pane with empty fragment"
)
binding.slidingPane.closePane() binding.slidingPane.closePane()
} }
} }

View file

@ -76,7 +76,12 @@ class VideoSettingsFragment : GenericSettingFragment<SettingsVideoFragmentBindin
private fun initVideoCodecsList() { private fun initVideoCodecsList() {
val list = arrayListOf<ViewDataBinding>() val list = arrayListOf<ViewDataBinding>()
for (payload in coreContext.core.videoPayloadTypes) { for (payload in coreContext.core.videoPayloadTypes) {
val binding = DataBindingUtil.inflate<ViewDataBinding>(LayoutInflater.from(requireContext()), R.layout.settings_widget_switch_and_text, null, false) val binding = DataBindingUtil.inflate<ViewDataBinding>(
LayoutInflater.from(requireContext()),
R.layout.settings_widget_switch_and_text,
null,
false
)
binding.setVariable(BR.switch_title, payload.mimeType) binding.setVariable(BR.switch_title, payload.mimeType)
binding.setVariable(BR.switch_subtitle, "") binding.setVariable(BR.switch_subtitle, "")
binding.setVariable(BR.text_title, "recv-fmtp") binding.setVariable(BR.text_title, "recv-fmtp")

View file

@ -89,7 +89,9 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
message: String message: String
) { ) {
if (state == RegistrationState.Cleared && account == accountToDelete) { if (state == RegistrationState.Cleared && account == accountToDelete) {
Log.i("[Account Settings] Account to remove ([${account.params.identityAddress?.asStringUriOnly()}]) registration is now cleared, removing it") Log.i(
"[Account Settings] Account to remove ([${account.params.identityAddress?.asStringUriOnly()}]) registration is now cleared, removing it"
)
waitForUnregister.value = false waitForUnregister.value = false
deleteAccount(account) deleteAccount(account)
} else { } else {
@ -121,7 +123,9 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
params.identityAddress = newIdentityAddress params.identityAddress = newIdentityAddress
account.params = params account.params = params
} else { } else {
Log.e("[Account Settings] Failed to create identity address sip:$newValue@$domain") Log.e(
"[Account Settings] Failed to create identity address sip:$newValue@$domain"
)
} }
} }
} }
@ -157,10 +161,19 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
val identity = params.identityAddress val identity = params.identityAddress
if (identity != null && identity.username != null) { if (identity != null && identity.username != null) {
val newAuthInfo = Factory.instance() val newAuthInfo = Factory.instance()
.createAuthInfo(identity.username!!, userId.value, newValue, null, null, identity.domain) .createAuthInfo(
identity.username!!,
userId.value,
newValue,
null,
null,
identity.domain
)
core.addAuthInfo(newAuthInfo) core.addAuthInfo(newAuthInfo)
} else { } else {
Log.e("[Account Settings] Failed to find the user's identity, can't create a new auth info") Log.e(
"[Account Settings] Failed to find the user's identity, can't create a new auth info"
)
} }
} }
} }
@ -394,7 +407,9 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
val conferenceFactoryUriListener = object : SettingListenerStub() { val conferenceFactoryUriListener = object : SettingListenerStub() {
override fun onTextValueChanged(newValue: String) { override fun onTextValueChanged(newValue: String) {
val params = account.params.clone() val params = account.params.clone()
Log.i("[Account Settings] Forcing conference factory on proxy config ${params.identityAddress?.asString()} to value: $newValue") Log.i(
"[Account Settings] Forcing conference factory on proxy config ${params.identityAddress?.asString()} to value: $newValue"
)
params.conferenceFactoryUri = newValue params.conferenceFactoryUri = newValue
account.params = params account.params = params
} }
@ -405,7 +420,9 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
override fun onTextValueChanged(newValue: String) { override fun onTextValueChanged(newValue: String) {
val params = account.params.clone() val params = account.params.clone()
val uri = coreContext.core.interpretUrl(newValue, false) val uri = coreContext.core.interpretUrl(newValue, false)
Log.i("[Account Settings] Forcing audio/video conference factory on proxy config ${params.identityAddress?.asString()} to value: $newValue") Log.i(
"[Account Settings] Forcing audio/video conference factory on proxy config ${params.identityAddress?.asString()} to value: $newValue"
)
params.audioVideoConferenceFactoryAddress = uri params.audioVideoConferenceFactoryAddress = uri
account.params = params account.params = params
} }
@ -510,7 +527,9 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
} }
fun startDeleteAccount() { fun startDeleteAccount() {
Log.i("[Account Settings] Starting to delete account [${account.params.identityAddress?.asStringUriOnly()}]") Log.i(
"[Account Settings] Starting to delete account [${account.params.identityAddress?.asStringUriOnly()}]"
)
accountToDelete = account accountToDelete = account
val registered = account.state == RegistrationState.Ok val registered = account.state == RegistrationState.Ok
@ -521,7 +540,9 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
for (accountIterator in core.accountList) { for (accountIterator in core.accountList) {
if (account != accountIterator) { if (account != accountIterator) {
core.defaultAccount = accountIterator core.defaultAccount = accountIterator
Log.i("[Account Settings] New default account is [${accountIterator.params.identityAddress?.asStringUriOnly()}]") Log.i(
"[Account Settings] New default account is [${accountIterator.params.identityAddress?.asStringUriOnly()}]"
)
break break
} }
} }
@ -532,10 +553,14 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
account.params = params account.params = params
if (!registered) { if (!registered) {
Log.w("[Account Settings] Account isn't registered, don't unregister before removing it") Log.w(
"[Account Settings] Account isn't registered, don't unregister before removing it"
)
deleteAccount(account) deleteAccount(account)
} else { } else {
Log.i("[Account Settings] Waiting for account registration to be cleared before removing it") Log.i(
"[Account Settings] Waiting for account registration to be cleared before removing it"
)
} }
} }
} }

View file

@ -44,7 +44,9 @@ class AudioSettingsViewModel : GenericSettingsViewModel() {
core.isEchoCancellationEnabled = newValue core.isEchoCancellationEnabled = newValue
if (!newValue) { if (!newValue) {
core.resetEchoCancellationCalibration() core.resetEchoCancellationCalibration()
softwareEchoCalibration.value = prefs.getString(R.string.audio_settings_echo_canceller_calibration_summary) softwareEchoCalibration.value = prefs.getString(
R.string.audio_settings_echo_canceller_calibration_summary
)
} }
} }
} }
@ -161,7 +163,9 @@ class AudioSettingsViewModel : GenericSettingsViewModel() {
softwareEchoCanceller.value = core.isEchoCancellationEnabled softwareEchoCanceller.value = core.isEchoCancellationEnabled
adaptiveRateControl.value = core.isAdaptiveRateControlEnabled adaptiveRateControl.value = core.isAdaptiveRateControlEnabled
softwareEchoCalibration.value = if (core.echoCancellationCalibration > 0) { softwareEchoCalibration.value = if (core.echoCancellationCalibration > 0) {
prefs.getString(R.string.audio_settings_echo_cancellation_calibration_value).format(core.echoCancellationCalibration) prefs.getString(R.string.audio_settings_echo_cancellation_calibration_value).format(
core.echoCancellationCalibration
)
} else { } else {
prefs.getString(R.string.audio_settings_echo_canceller_calibration_summary) prefs.getString(R.string.audio_settings_echo_canceller_calibration_summary)
} }
@ -183,7 +187,9 @@ class AudioSettingsViewModel : GenericSettingsViewModel() {
core.addListener(listener) core.addListener(listener)
core.startEchoCancellerCalibration() core.startEchoCancellerCalibration()
softwareEchoCalibration.value = prefs.getString(R.string.audio_settings_echo_cancellation_calibration_started) softwareEchoCalibration.value = prefs.getString(
R.string.audio_settings_echo_cancellation_calibration_started
)
} }
fun echoCancellerCalibrationFinished(status: EcCalibratorStatus, delay: Int) { fun echoCancellerCalibrationFinished(status: EcCalibratorStatus, delay: Int) {
@ -191,31 +197,43 @@ class AudioSettingsViewModel : GenericSettingsViewModel() {
when (status) { when (status) {
EcCalibratorStatus.DoneNoEcho -> { EcCalibratorStatus.DoneNoEcho -> {
softwareEchoCalibration.value = prefs.getString(R.string.audio_settings_echo_cancellation_calibration_no_echo) softwareEchoCalibration.value = prefs.getString(
R.string.audio_settings_echo_cancellation_calibration_no_echo
)
softwareEchoCanceller.value = false softwareEchoCanceller.value = false
} }
EcCalibratorStatus.Done -> { EcCalibratorStatus.Done -> {
softwareEchoCalibration.value = prefs.getString(R.string.audio_settings_echo_cancellation_calibration_value).format(delay) softwareEchoCalibration.value = prefs.getString(
R.string.audio_settings_echo_cancellation_calibration_value
).format(delay)
softwareEchoCanceller.value = true softwareEchoCanceller.value = true
} }
EcCalibratorStatus.Failed -> { EcCalibratorStatus.Failed -> {
softwareEchoCalibration.value = prefs.getString(R.string.audio_settings_echo_cancellation_calibration_failed) softwareEchoCalibration.value = prefs.getString(
R.string.audio_settings_echo_cancellation_calibration_failed
)
} }
EcCalibratorStatus.InProgress -> { // We should never get here but still EcCalibratorStatus.InProgress -> { // We should never get here but still
softwareEchoCalibration.value = prefs.getString(R.string.audio_settings_echo_cancellation_calibration_started) softwareEchoCalibration.value = prefs.getString(
R.string.audio_settings_echo_cancellation_calibration_started
)
} }
} }
} }
fun startEchoTester() { fun startEchoTester() {
echoTesterIsRunning = true echoTesterIsRunning = true
echoTesterStatus.value = prefs.getString(R.string.audio_settings_echo_tester_summary_is_running) echoTesterStatus.value = prefs.getString(
R.string.audio_settings_echo_tester_summary_is_running
)
core.startEchoTester(0) core.startEchoTester(0)
} }
fun stopEchoTester() { fun stopEchoTester() {
echoTesterIsRunning = false echoTesterIsRunning = false
echoTesterStatus.value = prefs.getString(R.string.audio_settings_echo_tester_summary_is_stopped) echoTesterStatus.value = prefs.getString(
R.string.audio_settings_echo_tester_summary_is_stopped
)
core.stopEchoTester() core.stopEchoTester()
} }

View file

@ -291,7 +291,9 @@ class CallSettingsViewModel : GenericSettingsViewModel() {
} }
if (core.mediaEncryptionSupported(MediaEncryption.ZRTP)) { if (core.mediaEncryptionSupported(MediaEncryption.ZRTP)) {
if (core.postQuantumAvailable) { if (core.postQuantumAvailable) {
labels.add(prefs.getString(R.string.call_settings_media_encryption_zrtp_post_quantum)) labels.add(
prefs.getString(R.string.call_settings_media_encryption_zrtp_post_quantum)
)
} else { } else {
labels.add(prefs.getString(R.string.call_settings_media_encryption_zrtp)) labels.add(prefs.getString(R.string.call_settings_media_encryption_zrtp))
} }

View file

@ -123,7 +123,9 @@ class LdapSettingsViewModel(private val ldap: Ldap, val index: String) : Generic
val ldapCertCheckListener = object : SettingListenerStub() { val ldapCertCheckListener = object : SettingListenerStub() {
override fun onListValueChanged(position: Int) { override fun onListValueChanged(position: Int) {
val params = ldap.params.clone() val params = ldap.params.clone()
params.serverCertificatesVerificationMode = LdapCertVerificationMode.fromInt(ldapCertCheckValues[position]) params.serverCertificatesVerificationMode = LdapCertVerificationMode.fromInt(
ldapCertCheckValues[position]
)
ldap.params = params ldap.params = params
ldapCertCheckIndex.value = position ldapCertCheckIndex.value = position
} }
@ -291,6 +293,8 @@ class LdapSettingsViewModel(private val ldap: Ldap, val index: String) : Generic
ldapCertCheckValues.add(LdapCertVerificationMode.Enabled.toInt()) ldapCertCheckValues.add(LdapCertVerificationMode.Enabled.toInt())
ldapCertCheckLabels.value = labels ldapCertCheckLabels.value = labels
ldapCertCheckIndex.value = ldapCertCheckValues.indexOf(ldap.params.serverCertificatesVerificationMode.toInt()) ldapCertCheckIndex.value = ldapCertCheckValues.indexOf(
ldap.params.serverCertificatesVerificationMode.toInt()
)
} }
} }

View file

@ -143,7 +143,9 @@ class VideoSettingsViewModel : GenericSettingsViewModel() {
val index = labels.indexOf(core.videoDevice) val index = labels.indexOf(core.videoDevice)
if (index == -1) { if (index == -1) {
val firstDevice = cameraDeviceLabels.value.orEmpty().firstOrNull() val firstDevice = cameraDeviceLabels.value.orEmpty().firstOrNull()
Log.w("[Video Settings] Device not found in labels list: ${core.videoDevice}, replace it by $firstDevice") Log.w(
"[Video Settings] Device not found in labels list: ${core.videoDevice}, replace it by $firstDevice"
)
if (firstDevice != null) { if (firstDevice != null) {
cameraDeviceIndex.value = 0 cameraDeviceIndex.value = 0
core.videoDevice = firstDevice core.videoDevice = firstDevice

View file

@ -142,7 +142,10 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
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 {
val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(data, temporaryPicturePath) val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(
data,
temporaryPicturePath
)
if (contactImageFilePath != null) { if (contactImageFilePath != null) {
viewModel.setPictureFromPath(contactImageFilePath) viewModel.setPictureFromPath(contactImageFilePath)
} }
@ -176,7 +179,10 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
val chooserIntent = val chooserIntent =
Intent.createChooser(galleryIntent, getString(R.string.chat_message_pick_file_dialog)) Intent.createChooser(galleryIntent, getString(R.string.chat_message_pick_file_dialog))
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(arrayOf<Parcelable>())) chooserIntent.putExtra(
Intent.EXTRA_INITIAL_INTENTS,
cameraIntents.toArray(arrayOf<Parcelable>())
)
startActivityForResult(chooserIntent, 0) startActivityForResult(chooserIntent, 0)
} }
@ -186,11 +192,15 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
goToAccountSettings: Boolean = false, goToAccountSettings: Boolean = false,
accountIdentity: String = "" accountIdentity: String = ""
) { ) {
val dialogViewModel = DialogViewModel(getString(R.string.settings_password_protection_dialog_title)) val dialogViewModel = DialogViewModel(
getString(R.string.settings_password_protection_dialog_title)
)
dialogViewModel.showIcon = true dialogViewModel.showIcon = true
dialogViewModel.iconResource = R.drawable.security_toggle_icon_green dialogViewModel.iconResource = R.drawable.security_toggle_icon_green
dialogViewModel.showPassword = true dialogViewModel.showPassword = true
dialogViewModel.passwordTitle = getString(R.string.settings_password_protection_dialog_input_hint) dialogViewModel.passwordTitle = getString(
R.string.settings_password_protection_dialog_input_hint
)
val dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) val dialog = DialogUtils.getDialog(requireContext(), dialogViewModel)
dialogViewModel.showCancelButton { dialogViewModel.showCancelButton {
@ -206,13 +216,19 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
} else { } else {
val authInfo = defaultAccount.findAuthInfo() val authInfo = defaultAccount.findAuthInfo()
if (authInfo == null) { if (authInfo == null) {
Log.e("[Side Menu] No auth info found for account [${defaultAccount.params.identityAddress?.asString()}], can't check password input!") Log.e(
"[Side Menu] No auth info found for account [${defaultAccount.params.identityAddress?.asString()}], can't check password input!"
)
(requireActivity() as MainActivity).showSnackBar(R.string.error_unexpected) (requireActivity() as MainActivity).showSnackBar(R.string.error_unexpected)
} else { } else {
val expectedHash = authInfo.ha1 val expectedHash = authInfo.ha1
if (expectedHash == null) { if (expectedHash == null) {
Log.e("[Side Menu] No ha1 found in auth info, can't check password input!") Log.e(
(requireActivity() as MainActivity).showSnackBar(R.string.error_unexpected) "[Side Menu] No ha1 found in auth info, can't check password input!"
)
(requireActivity() as MainActivity).showSnackBar(
R.string.error_unexpected
)
} else { } else {
val hashAlgorithm = authInfo.algorithm ?: "MD5" val hashAlgorithm = authInfo.algorithm ?: "MD5"
val userId = (authInfo.userid ?: authInfo.username).orEmpty() val userId = (authInfo.userid ?: authInfo.username).orEmpty()
@ -225,8 +241,12 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
hashAlgorithm hashAlgorithm
) )
if (computedHash != expectedHash) { if (computedHash != expectedHash) {
Log.e("[Side Menu] Computed hash [$computedHash] using userId [$userId], realm [$realm] and algorithm [$hashAlgorithm] doesn't match expected hash!") Log.e(
(requireActivity() as MainActivity).showSnackBar(R.string.settings_password_protection_dialog_invalid_input) "[Side Menu] Computed hash [$computedHash] using userId [$userId], realm [$realm] and algorithm [$hashAlgorithm] doesn't match expected hash!"
)
(requireActivity() as MainActivity).showSnackBar(
R.string.settings_password_protection_dialog_invalid_input
)
} else { } else {
if (goToSettings) { if (goToSettings) {
navigateToSettings() navigateToSettings()

View file

@ -41,7 +41,10 @@ class TabsViewModel : ViewModel() {
val chatUnreadCountTranslateY = MutableLiveData<Float>() val chatUnreadCountTranslateY = MutableLiveData<Float>()
private val bounceAnimator: ValueAnimator by lazy { private val bounceAnimator: ValueAnimator by lazy {
ValueAnimator.ofFloat(AppUtils.getDimension(R.dimen.tabs_fragment_unread_count_bounce_offset), 0f).apply { ValueAnimator.ofFloat(
AppUtils.getDimension(R.dimen.tabs_fragment_unread_count_bounce_offset),
0f
).apply {
addUpdateListener { addUpdateListener {
val value = it.animatedValue as Float val value = it.animatedValue as Float
historyMissedCountTranslateY.value = -value historyMissedCountTranslateY.value = -value

View file

@ -104,14 +104,20 @@ class CallActivity : ProximitySensorActivity() {
controlsViewModel.proximitySensorEnabled.observe( controlsViewModel.proximitySensorEnabled.observe(
this this
) { enabled -> ) { enabled ->
Log.i("[Call Activity] ${if (enabled) "Enabling" else "Disabling"} proximity sensor (if possible)") Log.i(
"[Call Activity] ${if (enabled) "Enabling" else "Disabling"} proximity sensor (if possible)"
)
enableProximitySensor(enabled) enableProximitySensor(enabled)
} }
controlsViewModel.isVideoEnabled.observe( controlsViewModel.isVideoEnabled.observe(
this this
) { enabled -> ) { enabled ->
Compatibility.enableAutoEnterPiP(this, enabled, conferenceViewModel.conferenceExists.value == true) Compatibility.enableAutoEnterPiP(
this,
enabled,
conferenceViewModel.conferenceExists.value == true
)
} }
controlsViewModel.callStatsVisible.observe( controlsViewModel.callStatsVisible.observe(
@ -136,10 +142,14 @@ class CallActivity : ProximitySensorActivity() {
) { callData -> ) { callData ->
val call = callData.call val call = callData.call
if (call.conference == null) { if (call.conference == null) {
Log.i("[Call Activity] Current call isn't linked to a conference, switching to SingleCall fragment") Log.i(
"[Call Activity] Current call isn't linked to a conference, switching to SingleCall fragment"
)
navigateToActiveCall() navigateToActiveCall()
} else { } else {
Log.i("[Call Activity] Current call is linked to a conference, switching to ConferenceCall fragment") Log.i(
"[Call Activity] Current call is linked to a conference, switching to ConferenceCall fragment"
)
navigateToConferenceCall() navigateToConferenceCall()
} }
} }
@ -157,10 +167,14 @@ class CallActivity : ProximitySensorActivity() {
this this
) { exists -> ) { exists ->
if (exists) { if (exists) {
Log.i("[Call Activity] Found active conference, changing switching to ConferenceCall fragment") Log.i(
"[Call Activity] Found active conference, changing switching to ConferenceCall fragment"
)
navigateToConferenceCall() navigateToConferenceCall()
} else if (coreContext.core.callsNb > 0) { } else if (coreContext.core.callsNb > 0) {
Log.i("[Call Activity] Conference no longer exists, switching to SingleCall fragment") Log.i(
"[Call Activity] Conference no longer exists, switching to SingleCall fragment"
)
navigateToActiveCall() navigateToActiveCall()
} }
} }
@ -193,7 +207,9 @@ class CallActivity : ProximitySensorActivity() {
) { ) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
Log.i("[Call Activity] onPictureInPictureModeChanged: is in PiP mode? $isInPictureInPictureMode") Log.i(
"[Call Activity] onPictureInPictureModeChanged: is in PiP mode? $isInPictureInPictureMode"
)
if (::controlsViewModel.isInitialized) { if (::controlsViewModel.isInitialized) {
// To hide UI except for TextureViews // To hide UI except for TextureViews
controlsViewModel.pipMode.value = isInPictureInPictureMode controlsViewModel.pipMode.value = isInPictureInPictureMode
@ -299,7 +315,9 @@ class CallActivity : ProximitySensorActivity() {
Log.i("[Call Activity] BLUETOOTH_CONNECT permission has been granted") Log.i("[Call Activity] BLUETOOTH_CONNECT permission has been granted")
} }
Manifest.permission.WRITE_EXTERNAL_STORAGE -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { Manifest.permission.WRITE_EXTERNAL_STORAGE -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
Log.i("[Call Activity] WRITE_EXTERNAL_STORAGE permission has been granted, taking snapshot") Log.i(
"[Call Activity] WRITE_EXTERNAL_STORAGE permission has been granted, taking snapshot"
)
controlsViewModel.takeSnapshot() controlsViewModel.takeSnapshot()
} }
} }
@ -310,7 +328,9 @@ class CallActivity : ProximitySensorActivity() {
override fun onLayoutChanges(foldingFeature: FoldingFeature?) { override fun onLayoutChanges(foldingFeature: FoldingFeature?) {
foldingFeature ?: return foldingFeature ?: return
Log.i("[Call Activity] Folding feature state changed: ${foldingFeature.state}, orientation is ${foldingFeature.orientation}") Log.i(
"[Call Activity] Folding feature state changed: ${foldingFeature.state}, orientation is ${foldingFeature.orientation}"
)
controlsViewModel.foldingState.value = foldingFeature controlsViewModel.foldingState.value = foldingFeature
} }

View file

@ -101,7 +101,10 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
scope.launch { scope.launch {
if (Compatibility.addImageToMediaStore(coreContext.context, content)) { if (Compatibility.addImageToMediaStore(coreContext.context, content)) {
Log.i("[Call] Added snapshot ${content.name} to Media Store") Log.i("[Call] Added snapshot ${content.name} to Media Store")
val message = String.format(AppUtils.getString(R.string.call_screenshot_taken), content.name) val message = String.format(
AppUtils.getString(R.string.call_screenshot_taken),
content.name
)
Toast.makeText(coreContext.context, message, Toast.LENGTH_SHORT).show() Toast.makeText(coreContext.context, message, Toast.LENGTH_SHORT).show()
} else { } else {
Log.e("[Call] Something went wrong while copying file to Media Store...") Log.e("[Call] Something went wrong while copying file to Media Store...")
@ -188,7 +191,9 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
Call.State.PausedByRemote -> { Call.State.PausedByRemote -> {
val conference = call.conference val conference = call.conference
if (conference != null && conference.me.isFocus) { if (conference != null && conference.me.isFocus) {
Log.w("[Call] State is paused by remote but we are the focus of the conference, so considering call as active") Log.w(
"[Call] State is paused by remote but we are the focus of the conference, so considering call as active"
)
false false
} else { } else {
true true
@ -237,7 +242,9 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
if (conference != null) { if (conference != null) {
Log.d("[Call] Found conference attached to call") Log.d("[Call] Found conference attached to call")
remoteConferenceSubject.value = LinphoneUtils.getConferenceSubject(conference) remoteConferenceSubject.value = LinphoneUtils.getConferenceSubject(conference)
Log.d("[Call] Found conference related to this call with subject [${remoteConferenceSubject.value}]") Log.d(
"[Call] Found conference related to this call with subject [${remoteConferenceSubject.value}]"
)
val participantsList = arrayListOf<ConferenceInfoParticipantData>() val participantsList = arrayListOf<ConferenceInfoParticipantData>()
for (participant in conference.participantList) { for (participant in conference.participantList) {
@ -246,12 +253,23 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
} }
conferenceParticipants.value = participantsList conferenceParticipants.value = participantsList
conferenceParticipantsCountLabel.value = coreContext.context.getString(R.string.conference_participants_title, participantsList.size) conferenceParticipantsCountLabel.value = coreContext.context.getString(
R.string.conference_participants_title,
participantsList.size
)
} else { } else {
val conferenceAddress = LinphoneUtils.getConferenceAddress(call) val conferenceAddress = LinphoneUtils.getConferenceAddress(call)
val conferenceInfo = if (conferenceAddress != null) coreContext.core.findConferenceInformationFromUri(conferenceAddress) else null val conferenceInfo = if (conferenceAddress != null) {
coreContext.core.findConferenceInformationFromUri(
conferenceAddress
)
} else {
null
}
if (conferenceInfo != null) { if (conferenceInfo != null) {
Log.d("[Call] Found matching conference info with subject: ${conferenceInfo.subject}") Log.d(
"[Call] Found matching conference info with subject: ${conferenceInfo.subject}"
)
remoteConferenceSubject.value = conferenceInfo.subject remoteConferenceSubject.value = conferenceInfo.subject
val participantsList = arrayListOf<ConferenceInfoParticipantData>() val participantsList = arrayListOf<ConferenceInfoParticipantData>()
@ -271,7 +289,10 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
} }
conferenceParticipants.value = participantsList conferenceParticipants.value = participantsList
conferenceParticipantsCountLabel.value = coreContext.context.getString(R.string.conference_participants_title, participantsList.size) conferenceParticipantsCountLabel.value = coreContext.context.getString(
R.string.conference_participants_title,
participantsList.size
)
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show more