Bumped dependencies & improved gradle files + new bullseye based clean docker image using JDK 17
This commit is contained in:
parent
b80f2fc9a2
commit
33b4c09ffd
158 changed files with 3285 additions and 996 deletions
|
@ -2,15 +2,15 @@ job-android:
|
|||
|
||||
stage: build
|
||||
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:
|
||||
- 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
|
||||
- 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:
|
||||
- sdkmanager
|
||||
- scp -oStrictHostKeyChecking=no $DEPLOY_SERVER:$ANDROID_KEYSTORE_PATH app/
|
||||
- scp -oStrictHostKeyChecking=no $DEPLOY_SERVER:$ANDROID_GOOGLE_SERVICES_PATH app/
|
||||
- echo storePassword=$ANDROID_KEYSTORE_PASSWORD > keystore.properties
|
||||
|
|
|
@ -2,7 +2,7 @@ plugins {
|
|||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-kapt'
|
||||
id 'org.jlleitschuh.gradle.ktlint'
|
||||
id 'org.jlleitschuh.gradle.ktlint' version '11.3.1'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
|
@ -78,8 +78,12 @@ project.tasks['preBuild'].dependsOn 'getGitVersion'
|
|||
project.tasks['preBuild'].dependsOn 'linphoneSdkSource'
|
||||
|
||||
android {
|
||||
compileOptions {
|
||||
sourceCompatibility = 17
|
||||
targetCompatibility = 17
|
||||
}
|
||||
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion '33.0.0'
|
||||
defaultConfig {
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 33
|
||||
|
@ -228,7 +232,7 @@ dependencies {
|
|||
// https://github.com/Baseflow/PhotoView/blob/master/LICENSE Apache v2.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) {
|
||||
debugImplementation 'com.google.firebase:firebase-crashlytics-ndk'
|
||||
releaseWithCrashlyticsImplementation 'com.google.firebase:firebase-crashlytics-ndk'
|
||||
|
|
17
app/proguard-rules.pro
vendored
17
app/proguard-rules.pro
vendored
|
@ -22,3 +22,20 @@
|
|||
|
||||
-keep public class * extends androidx.fragment.app.Fragment { *; }
|
||||
-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.**
|
|
@ -38,6 +38,7 @@ class LinphoneApplication : Application(), ImageLoaderFactory {
|
|||
companion object {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
lateinit var corePreferences: CorePreferences
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
lateinit var coreContext: CoreContext
|
||||
|
||||
|
@ -59,7 +60,10 @@ class LinphoneApplication : Application(), ImageLoaderFactory {
|
|||
CoreContext.activateVFS()
|
||||
}
|
||||
|
||||
val config = Factory.instance().createConfigWithFactory(corePreferences.configPath, corePreferences.factoryConfigPath)
|
||||
val config = Factory.instance().createConfigWithFactory(
|
||||
corePreferences.configPath,
|
||||
corePreferences.factoryConfigPath
|
||||
)
|
||||
corePreferences.config = config
|
||||
|
||||
val appName = context.getString(R.string.app_name)
|
||||
|
@ -83,8 +87,15 @@ class LinphoneApplication : Application(), ImageLoaderFactory {
|
|||
return false
|
||||
}
|
||||
|
||||
Log.i("[Application] Core context is being created ${if (pushReceived) "from push" else ""}")
|
||||
coreContext = CoreContext(context, corePreferences.config, service, useAutoStartDescription)
|
||||
Log.i(
|
||||
"[Application] Core context is being created ${if (pushReceived) "from push" else ""}"
|
||||
)
|
||||
coreContext = CoreContext(
|
||||
context,
|
||||
corePreferences.config,
|
||||
service,
|
||||
useAutoStartDescription
|
||||
)
|
||||
coreContext.start()
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -74,7 +74,9 @@ abstract class GenericFragment<T : ViewDataBinding> : Fragment() {
|
|||
if (!navController.popBackStack()) {
|
||||
Log.d("[Generic Fragment] ${getFragmentRealClassName()} couldn't pop")
|
||||
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
|
||||
isEnabled = false
|
||||
goBack()
|
||||
|
@ -98,7 +100,9 @@ abstract class GenericFragment<T : ViewDataBinding> : Fragment() {
|
|||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
@ -151,7 +155,10 @@ abstract class GenericFragment<T : ViewDataBinding> : Fragment() {
|
|||
onBackPressedCallback.isEnabled = false
|
||||
}
|
||||
|
||||
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, onBackPressedCallback)
|
||||
requireActivity().onBackPressedDispatcher.addCallback(
|
||||
viewLifecycleOwner,
|
||||
onBackPressedCallback
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
Log.d("[Generic Fragment] ${getFragmentRealClassName()} isPreviousFragmentEmpty ? $isPreviousFragmentEmpty")
|
||||
Log.d(
|
||||
"[Generic Fragment] ${getFragmentRealClassName()} isPreviousFragmentEmpty ? $isPreviousFragmentEmpty"
|
||||
)
|
||||
val popBackStack = isSlidingPaneFlat || !isPreviousFragmentEmpty
|
||||
Log.d("[Generic Fragment] ${getFragmentRealClassName()} popBackStack ? $popBackStack")
|
||||
return popBackStack
|
||||
|
|
|
@ -34,7 +34,9 @@ abstract class ProximitySensorActivity : GenericActivity() {
|
|||
|
||||
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
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(
|
||||
|
@ -58,7 +60,9 @@ abstract class ProximitySensorActivity : GenericActivity() {
|
|||
protected fun enableProximitySensor(enable: Boolean) {
|
||||
if (enable) {
|
||||
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) {
|
||||
Log.i("[Proximity Sensor Activity] Acquiring PROXIMITY_SCREEN_OFF_WAKE_LOCK")
|
||||
proximityWakeLock.acquire()
|
||||
|
@ -67,9 +71,13 @@ abstract class ProximitySensorActivity : GenericActivity() {
|
|||
}
|
||||
} else {
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
proximitySensorEnabled = false
|
||||
|
|
|
@ -41,7 +41,11 @@ class CountryPickerAdapter : BaseAdapter(), Filterable {
|
|||
}
|
||||
|
||||
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 name = view.findViewById<TextView>(R.id.country_name)
|
||||
|
|
|
@ -58,7 +58,10 @@ abstract class AbstractPhoneFragment<T : ViewDataBinding> : GenericFragment<T>()
|
|||
if (!resources.getBoolean(R.bool.isTablet)) {
|
||||
if (!PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()) {
|
||||
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 {
|
||||
updateFromDeviceInfo()
|
||||
}
|
||||
|
|
|
@ -57,7 +57,10 @@ class EchoCancellerCalibrationFragment : GenericFragment<AssistantEchoCancellerC
|
|||
|
||||
if (!PermissionHelper.required(requireContext()).hasRecordAudioPermission()) {
|
||||
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 {
|
||||
viewModel.startEchoCancellerCalibration()
|
||||
}
|
||||
|
|
|
@ -46,7 +46,10 @@ class EmailAccountCreationFragment : GenericFragment<AssistantEmailAccountCreati
|
|||
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
|
||||
|
||||
viewModel.goToEmailValidationEvent.observe(
|
||||
|
|
|
@ -46,7 +46,10 @@ class EmailAccountValidationFragment : GenericFragment<AssistantEmailAccountVali
|
|||
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
|
||||
|
||||
viewModel.leaveAssistantEvent.observe(
|
||||
|
|
|
@ -51,7 +51,10 @@ class GenericAccountLoginFragment : GenericFragment<AssistantGenericAccountLogin
|
|||
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
|
||||
|
||||
viewModel.leaveAssistantEvent.observe(
|
||||
|
|
|
@ -52,7 +52,10 @@ class PhoneAccountValidationFragment : GenericFragment<AssistantPhoneAccountVali
|
|||
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
|
||||
|
||||
viewModel.phoneNumber.value = arguments?.getString("PhoneNumber")
|
||||
|
@ -106,7 +109,9 @@ class PhoneAccountValidationFragment : GenericFragment<AssistantPhoneAccountVali
|
|||
if (data != null && data.itemCount > 0) {
|
||||
val clip = data.getItemAt(0).text.toString()
|
||||
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
|
||||
clipboard.clearPrimaryClip()
|
||||
}
|
||||
|
|
|
@ -67,7 +67,10 @@ class QrCodeFragment : GenericFragment<AssistantQrCodeFragmentBinding>() {
|
|||
|
||||
if (!PermissionHelper.required(requireContext()).hasCameraPermission()) {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
@ -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
|
||||
|
|
|
@ -159,7 +159,9 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
|
|||
if (loginWithUsernamePassword.value == true) {
|
||||
val result = accountCreator.setUsername(username.value)
|
||||
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
|
||||
return
|
||||
}
|
||||
|
@ -180,9 +182,13 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
|
|||
onErrorEvent.value = Event("Error: Failed to create account object")
|
||||
}
|
||||
} 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) {
|
||||
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
|
||||
return
|
||||
}
|
||||
|
@ -190,7 +196,9 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
|
|||
|
||||
val result2 = accountCreator.setUsername(accountCreator.phoneNumber)
|
||||
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
|
||||
return
|
||||
}
|
||||
|
@ -229,7 +237,9 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
|
|||
if (proxyConfig.dialPrefix.isNullOrEmpty()) {
|
||||
val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(coreContext.context)
|
||||
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.dialPrefix = dialPlan.countryCallingCode
|
||||
proxyConfig.done()
|
||||
|
|
|
@ -74,7 +74,9 @@ class EmailAccountCreationViewModel(val accountCreator: AccountCreator) : ViewMo
|
|||
when (status) {
|
||||
AccountCreator.Status.AccountExist, AccountCreator.Status.AccountExistWithAlias -> {
|
||||
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 -> {
|
||||
val createAccountStatus = creator.createAccount()
|
||||
|
|
|
@ -109,9 +109,13 @@ class EmailAccountValidationViewModel(val accountCreator: AccountCreator) : View
|
|||
proxyConfig.isPushNotificationAllowed = true
|
||||
|
||||
if (proxyConfig.dialPrefix.isNullOrEmpty()) {
|
||||
val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(LinphoneApplication.coreContext.context)
|
||||
val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(
|
||||
LinphoneApplication.coreContext.context
|
||||
)
|
||||
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.dialPrefix = dialPlan.countryCallingCode
|
||||
proxyConfig.done()
|
||||
|
|
|
@ -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 useUsername = MutableLiveData<Boolean>()
|
||||
val usernameError = MutableLiveData<String>()
|
||||
|
@ -71,9 +73,13 @@ class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPh
|
|||
AccountCreator.Status.AccountExist, AccountCreator.Status.AccountExistWithAlias -> {
|
||||
waitForServerAnswer.value = false
|
||||
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 {
|
||||
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 -> {
|
||||
|
@ -103,7 +109,9 @@ class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPh
|
|||
goToSmsValidationEvent.value = Event(true)
|
||||
}
|
||||
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 -> {
|
||||
onErrorEvent.value = Event("Error: ${status.name}")
|
||||
|
@ -161,7 +169,11 @@ class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPh
|
|||
}
|
||||
|
||||
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 &&
|
||||
(
|
||||
useUsername.value == false ||
|
||||
|
|
|
@ -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 allowSkip = MutableLiveData<Boolean>()
|
||||
|
|
|
@ -125,7 +125,9 @@ class PhoneAccountValidationViewModel(val accountCreator: AccountCreator) : View
|
|||
|
||||
fun finish() {
|
||||
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
|
||||
|
||||
val status = when {
|
||||
|
@ -145,7 +147,9 @@ class PhoneAccountValidationViewModel(val accountCreator: AccountCreator) : View
|
|||
val proxyConfig: ProxyConfig? = accountCreator.createProxyConfig()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -77,11 +77,15 @@ class ChatBubbleActivity : GenericActivity() {
|
|||
var chatRoom: ChatRoom? = 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 remoteSipAddress = Factory.instance().createAddress(remoteSipUri)
|
||||
chatRoom = coreContext.core.searchChatRoom(
|
||||
null, localAddress, remoteSipAddress,
|
||||
null,
|
||||
localAddress,
|
||||
remoteSipAddress,
|
||||
arrayOfNulls(
|
||||
0
|
||||
)
|
||||
|
@ -155,7 +159,10 @@ class ChatBubbleActivity : GenericActivity() {
|
|||
|
||||
binding.setOpenAppClickListener {
|
||||
coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null
|
||||
coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom(viewModel.chatRoom, false)
|
||||
coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom(
|
||||
viewModel.chatRoom,
|
||||
false
|
||||
)
|
||||
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
intent.putExtra("RemoteSipUri", remoteSipUri)
|
||||
|
@ -181,7 +188,10 @@ class ChatBubbleActivity : GenericActivity() {
|
|||
viewModel.chatRoom.addListener(listener)
|
||||
|
||||
// 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()
|
||||
|
||||
val peerAddress = viewModel.chatRoom.peerAddress.asStringUriOnly()
|
||||
|
@ -199,7 +209,10 @@ class ChatBubbleActivity : GenericActivity() {
|
|||
viewModel.chatRoom.removeListener(listener)
|
||||
|
||||
coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null
|
||||
coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom(viewModel.chatRoom, false)
|
||||
coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom(
|
||||
viewModel.chatRoom,
|
||||
false
|
||||
)
|
||||
|
||||
super.onPause()
|
||||
}
|
||||
|
|
|
@ -205,7 +205,9 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
|
|||
|
||||
binding.rootCoordinatorLayout.addKeyboardInsetListener { keyboardVisible ->
|
||||
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
|
||||
updateTabsFragmentVisibility()
|
||||
}
|
||||
|
@ -255,7 +257,9 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
|
|||
}
|
||||
|
||||
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) {
|
||||
Intent.ACTION_MAIN -> handleMainIntent(intent)
|
||||
|
@ -291,10 +295,14 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
|
|||
if (stringUri.startsWith("linphone-config:")) {
|
||||
val remoteConfigUri = stringUri.substring("linphone-config:".length)
|
||||
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)
|
||||
} 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)
|
||||
}
|
||||
} else {
|
||||
|
@ -339,7 +347,9 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
|
|||
if (intent.hasExtra("RemoteSipUri") && intent.hasExtra("LocalSipUri")) {
|
||||
val peerAddress = intent.getStringExtra("RemoteSipUri")
|
||||
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)
|
||||
} else {
|
||||
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 call = core.currentCall ?: core.calls.firstOrNull()
|
||||
if (call != null) {
|
||||
Log.i("[Main Activity] Launcher clicked while there is at least one active call, go to CallActivity")
|
||||
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)
|
||||
Log.i(
|
||||
"[Main Activity] Launcher clicked while there is at least one active call, go to CallActivity"
|
||||
)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
addressToCall = address.asStringUriOnly()
|
||||
}
|
||||
|
@ -490,8 +510,13 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
|
|||
|
||||
val localAddress =
|
||||
coreContext.core.defaultAccount?.params?.identityAddress?.asStringUriOnly()
|
||||
val peerAddress = coreContext.core.interpretUrl(addressToIM, LinphoneUtils.applyInternationalPrefix())?.asStringUriOnly()
|
||||
Log.i("[Main Activity] Navigating to chat room with local [$localAddress] and peer [$peerAddress] addresses")
|
||||
val peerAddress = coreContext.core.interpretUrl(
|
||||
addressToIM,
|
||||
LinphoneUtils.applyInternationalPrefix()
|
||||
)?.asStringUriOnly()
|
||||
Log.i(
|
||||
"[Main Activity] Navigating to chat room with local [$localAddress] and peer [$peerAddress] addresses"
|
||||
)
|
||||
navigateToChatRoom(localAddress, peerAddress)
|
||||
} else {
|
||||
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) {
|
||||
val localAddress = split[0]
|
||||
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)
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
|
@ -562,7 +591,10 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
|
|||
}
|
||||
|
||||
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)
|
||||
|
||||
dialogViewModel.showCancelButton {
|
||||
|
@ -575,7 +607,9 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
|
|||
)
|
||||
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)
|
||||
dialog.dismiss()
|
||||
},
|
||||
|
|
|
@ -26,6 +26,7 @@ internal abstract class ChatScrollListener(private val mLayoutManager: LinearLay
|
|||
RecyclerView.OnScrollListener() {
|
||||
// The total number of items in the data set after the last load
|
||||
private var previousTotalItemCount = 0
|
||||
|
||||
// True if we are still waiting for the last set of data to load.
|
||||
private var loading = true
|
||||
|
||||
|
|
|
@ -55,7 +55,10 @@ import org.linphone.utils.HeaderAdapter
|
|||
class ChatMessagesListAdapter(
|
||||
selectionVM: ListTopBarViewModel,
|
||||
private val viewLifecycleOwner: LifecycleOwner
|
||||
) : SelectionListAdapter<EventLogData, RecyclerView.ViewHolder>(selectionVM, ChatMessageDiffCallback()),
|
||||
) : SelectionListAdapter<EventLogData, RecyclerView.ViewHolder>(
|
||||
selectionVM,
|
||||
ChatMessageDiffCallback()
|
||||
),
|
||||
HeaderAdapter {
|
||||
companion object {
|
||||
const val MAX_TIME_TO_GROUP_MESSAGES = 60 // 1 minute
|
||||
|
@ -116,7 +119,9 @@ class ChatMessagesListAdapter(
|
|||
|
||||
override fun onWebUrlClicked(url: String) {
|
||||
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
|
||||
}
|
||||
urlClickEvent.value = Event(url)
|
||||
|
@ -124,7 +129,9 @@ class ChatMessagesListAdapter(
|
|||
|
||||
override fun onSipAddressClicked(sipUri: String) {
|
||||
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
|
||||
}
|
||||
sipUriClickedEvent.value = Event(sipUri)
|
||||
|
@ -155,7 +162,9 @@ class ChatMessagesListAdapter(
|
|||
private fun createChatMessageViewHolder(parent: ViewGroup): ChatMessageViewHolder {
|
||||
val binding: ChatMessageListCellBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
R.layout.chat_message_list_cell, parent, false
|
||||
R.layout.chat_message_list_cell,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return ChatMessageViewHolder(binding)
|
||||
}
|
||||
|
@ -163,7 +172,9 @@ class ChatMessagesListAdapter(
|
|||
private fun createEventViewHolder(parent: ViewGroup): EventViewHolder {
|
||||
val binding: ChatEventListCellBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
R.layout.chat_event_list_cell, parent, false
|
||||
R.layout.chat_event_list_cell,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return EventViewHolder(binding)
|
||||
}
|
||||
|
@ -199,9 +210,14 @@ class ChatMessagesListAdapter(
|
|||
override fun getHeaderViewForPosition(context: Context, position: Int): View {
|
||||
val binding: ChatUnreadMessagesListHeaderBinding = DataBindingUtil.inflate(
|
||||
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()
|
||||
return binding.root
|
||||
}
|
||||
|
@ -281,7 +297,10 @@ class ChatMessagesListAdapter(
|
|||
val previousItem = getItem(bindingAdapterPosition - 1)
|
||||
if (previousItem.eventLog.type == EventLog.Type.ConferenceChatMessage) {
|
||||
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) {
|
||||
hasPrevious = true
|
||||
}
|
||||
|
@ -293,7 +312,10 @@ class ChatMessagesListAdapter(
|
|||
val nextItem = getItem(bindingAdapterPosition + 1)
|
||||
if (nextItem.eventLog.type == EventLog.Type.ConferenceChatMessage) {
|
||||
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) {
|
||||
hasNext = true
|
||||
}
|
||||
|
@ -308,12 +330,17 @@ class ChatMessagesListAdapter(
|
|||
setContextMenuClickListener {
|
||||
val popupView: ChatMessageLongPressMenuBindingImpl = DataBindingUtil.inflate(
|
||||
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()
|
||||
var totalSize = itemSize * 7
|
||||
if (chatMessage.chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())) {
|
||||
if (chatMessage.chatRoom.hasCapability(
|
||||
ChatRoomCapabilities.OneToOne.toInt()
|
||||
)
|
||||
) {
|
||||
// No message id
|
||||
popupView.imdnHidden = true
|
||||
totalSize -= itemSize
|
||||
|
@ -497,7 +524,9 @@ private class ChatMessageDiffCallback : DiffUtil.ItemCallback<EventLogData>() {
|
|||
|
||||
oldData.time.value == newData.time.value &&
|
||||
oldData.isOutgoing == newData.isOutgoing
|
||||
} else oldItem.notifyId == newItem.notifyId
|
||||
} else {
|
||||
oldItem.notifyId == newItem.notifyId
|
||||
}
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(
|
||||
|
|
|
@ -48,7 +48,9 @@ class ChatRoomsListAdapter(
|
|||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding: ChatRoomListCellBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
R.layout.chat_room_list_cell, parent, false
|
||||
R.layout.chat_room_list_cell,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
@ -77,7 +79,9 @@ class ChatRoomsListAdapter(
|
|||
try {
|
||||
notifyItemChanged(bindingAdapterPosition)
|
||||
} 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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,9 @@ class GroupInfoParticipantsAdapter(
|
|||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding: ChatRoomGroupInfoParticipantCellBinding = DataBindingUtil.inflate(
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -41,7 +41,9 @@ class ImdnAdapter(
|
|||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding: ChatRoomImdnParticipantCellBinding = DataBindingUtil.inflate(
|
||||
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)
|
||||
}
|
||||
|
@ -70,14 +72,18 @@ class ImdnAdapter(
|
|||
val previousPosition = position - 1
|
||||
return if (previousPosition >= 0) {
|
||||
getItem(previousPosition).imdnState.state != participantImdnState.imdnState.state
|
||||
} else true
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun getHeaderViewForPosition(context: Context, position: Int): View {
|
||||
val participantImdnState = getItem(position).imdnState
|
||||
val binding: ImdnListHeaderBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(context),
|
||||
R.layout.imdn_list_header, null, false
|
||||
R.layout.imdn_list_header,
|
||||
null,
|
||||
false
|
||||
)
|
||||
when (participantImdnState.state) {
|
||||
ChatMessage.State.Displayed -> {
|
||||
|
|
|
@ -48,7 +48,7 @@ import org.linphone.utils.TimestampUtils
|
|||
|
||||
class ChatMessageContentData(
|
||||
private val chatMessage: ChatMessage,
|
||||
private val contentIndex: Int,
|
||||
private val contentIndex: Int
|
||||
) {
|
||||
var listener: OnContentClickedListener? = null
|
||||
|
||||
|
@ -172,7 +172,9 @@ class ChatMessageContentData(
|
|||
|
||||
fun download() {
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
@ -191,7 +193,9 @@ class ChatMessageContentData(
|
|||
return
|
||||
}
|
||||
} 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)) {
|
||||
|
@ -221,7 +225,9 @@ class ChatMessageContentData(
|
|||
|
||||
val content = getContent()
|
||||
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 = ""
|
||||
fileName.value = if (content.name.isNullOrEmpty() && !content.filePath.isNullOrEmpty()) {
|
||||
|
@ -232,7 +238,9 @@ class ChatMessageContentData(
|
|||
|
||||
// Display download size and underline text
|
||||
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)
|
||||
downloadLabel.value = spannable
|
||||
|
||||
|
@ -247,7 +255,9 @@ class ChatMessageContentData(
|
|||
|
||||
if (content.isFile || (content.isFileTransfer && chatMessage.isOutgoing)) {
|
||||
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()
|
||||
} else {
|
||||
content.filePath ?: ""
|
||||
|
@ -277,13 +287,19 @@ class ChatMessageContentData(
|
|||
isConferenceIcs -> "conference invitation"
|
||||
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) {
|
||||
val duration = content.fileDuration // duration is in ms
|
||||
voiceRecordDuration.value = duration
|
||||
formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(duration)
|
||||
Log.i("[Content] Voice recording duration is ${voiceRecordDuration.value} ($duration)")
|
||||
formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(
|
||||
duration
|
||||
)
|
||||
Log.i(
|
||||
"[Content] Voice recording duration is ${voiceRecordDuration.value} ($duration)"
|
||||
)
|
||||
} else if (isConferenceIcs) {
|
||||
parseConferenceInvite(content)
|
||||
}
|
||||
|
@ -291,7 +307,9 @@ class ChatMessageContentData(
|
|||
Log.i("[Content] Found content with icalendar file")
|
||||
parseConferenceInvite(content)
|
||||
} 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
|
||||
isVideo.value = false
|
||||
isAudio.value = false
|
||||
|
@ -325,7 +343,9 @@ class ChatMessageContentData(
|
|||
val conferenceUri = conferenceInfo?.uri?.asStringUriOnly()
|
||||
if (conferenceInfo != null && conferenceUri != null) {
|
||||
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
|
||||
conferenceDescription.value = conferenceInfo.description
|
||||
|
||||
|
@ -355,7 +375,10 @@ class ChatMessageContentData(
|
|||
}
|
||||
}
|
||||
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) {
|
||||
if (content.filePath != null) {
|
||||
try {
|
||||
|
@ -367,7 +390,9 @@ class ChatMessageContentData(
|
|||
textBuilder.append('\n')
|
||||
}
|
||||
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) {
|
||||
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}")
|
||||
}
|
||||
} 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)) {
|
||||
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) {
|
||||
|
@ -440,7 +471,9 @@ class ChatMessageContentData(
|
|||
private fun initVoiceRecordPlayer() {
|
||||
Log.i("[Voice Recording] Creating player for voice record")
|
||||
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)
|
||||
if (localPlayer != null) {
|
||||
|
@ -454,8 +487,12 @@ class ChatMessageContentData(
|
|||
val path = filePath.value
|
||||
voiceRecordingPlayer.open(path.orEmpty())
|
||||
voiceRecordDuration.value = voiceRecordingPlayer.duration
|
||||
formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(voiceRecordingPlayer.duration) // is already in milliseconds
|
||||
Log.i("[Voice Recording] Duration is ${voiceRecordDuration.value} (${voiceRecordingPlayer.duration})")
|
||||
formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(
|
||||
voiceRecordingPlayer.duration
|
||||
) // is already in milliseconds
|
||||
Log.i(
|
||||
"[Voice Recording] Duration is ${voiceRecordDuration.value} (${voiceRecordingPlayer.duration})"
|
||||
)
|
||||
}
|
||||
|
||||
private fun stopVoiceRecording() {
|
||||
|
|
|
@ -89,7 +89,9 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
|||
if (chatMessage.isReply) {
|
||||
val reply = chatMessage.replyMessage
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -194,7 +196,9 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
|||
val spannable = Spannable.Factory.getInstance().newSpannable(textContent)
|
||||
text.value = PatternClickableSpan()
|
||||
.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 {
|
||||
override fun onSpanClicked(text: String) {
|
||||
Log.i("[Chat Message Data] Clicked on SIP URI: $text")
|
||||
|
@ -222,7 +226,9 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
|||
).build(spannable)
|
||||
isTextEmoji.value = AppUtils.isTextOnlyContainingEmoji(textContent)
|
||||
} 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
|
||||
return when {
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,7 +158,9 @@ class ChatRoomData(val chatRoom: ChatRoom) : ContactDataInterface {
|
|||
if (participants.isNotEmpty()) {
|
||||
participants.first().address
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
@ -213,7 +215,9 @@ class ChatRoomData(val chatRoom: ChatRoom) : ContactDataInterface {
|
|||
val sender: String =
|
||||
coreContext.contactsManager.findContactByAddress(msg.fromAddress)?.name
|
||||
?: 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(" ")
|
||||
}
|
||||
|
||||
|
@ -221,12 +225,22 @@ class ChatRoomData(val chatRoom: ChatRoom) : ContactDataInterface {
|
|||
if (content.isIcalendar) {
|
||||
val body = AppUtils.getString(R.string.conference_invitation)
|
||||
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
|
||||
} else if (content.isVoiceRecording) {
|
||||
val body = AppUtils.getString(R.string.chat_message_voice_recording)
|
||||
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
|
||||
} else if (content.isFile || content.isFileTransfer) {
|
||||
builder.append(content.name + " ")
|
||||
|
|
|
@ -27,7 +27,9 @@ import org.linphone.core.ChatRoomSecurityLevel
|
|||
import org.linphone.core.Participant
|
||||
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 {
|
||||
when (participant.securityLevel) {
|
||||
ChatRoomSecurityLevel.Safe -> R.drawable.security_2_indicator
|
||||
|
|
|
@ -65,28 +65,60 @@ class EventData(private val eventLog: EventLog) : GenericContactData(
|
|||
val context: Context = coreContext.context
|
||||
|
||||
text.value = when (eventLog.type) {
|
||||
EventLog.Type.ConferenceCreated -> context.getString(R.string.chat_event_conference_created)
|
||||
EventLog.Type.ConferenceTerminated -> context.getString(R.string.chat_event_conference_destroyed)
|
||||
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.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.ConferenceCreated -> context.getString(
|
||||
R.string.chat_event_conference_created
|
||||
)
|
||||
EventLog.Type.ConferenceTerminated -> context.getString(
|
||||
R.string.chat_event_conference_destroyed
|
||||
)
|
||||
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.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 -> {
|
||||
val name = getName()
|
||||
when (eventLog.securityEventType) {
|
||||
EventLog.SecurityEventType.EncryptionIdentityKeyChanged -> context.getString(R.string.chat_security_event_lime_identity_key_changed).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)
|
||||
EventLog.SecurityEventType.EncryptionIdentityKeyChanged -> context.getString(
|
||||
R.string.chat_security_event_lime_identity_key_changed
|
||||
).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}"
|
||||
}
|
||||
}
|
||||
EventLog.Type.ConferenceEphemeralMessageDisabled -> context.getString(R.string.chat_event_ephemeral_disabled)
|
||||
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))
|
||||
EventLog.Type.ConferenceEphemeralMessageDisabled -> context.getString(
|
||||
R.string.chat_event_ephemeral_disabled
|
||||
)
|
||||
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}"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,9 @@ import org.linphone.contact.GenericContactData
|
|||
import org.linphone.core.ChatRoomSecurityLevel
|
||||
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 isAdmin = MutableLiveData<Boolean>()
|
||||
|
|
|
@ -23,7 +23,9 @@ import org.linphone.contact.GenericContactData
|
|||
import org.linphone.core.ParticipantImdnState
|
||||
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 time: String = TimestampUtils.toString(imdnState.stateChangeTime)
|
||||
|
|
|
@ -70,7 +70,9 @@ class ChatRoomCreationFragment : SecureFragment<ChatRoomCreationFragmentBinding>
|
|||
binding.contactsList.layoutManager = layoutManager
|
||||
|
||||
// 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
|
||||
|
||||
|
|
|
@ -85,7 +85,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
if (viewModel.isUserScrollingUp.value == false) {
|
||||
scrollToFirstUnreadMessageOrBottom(false)
|
||||
} 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")
|
||||
val notAllMessagesDisplayed = scrollToFirstUnreadMessageOrBottom(false)
|
||||
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 {
|
||||
// Consider user as scrolled to the end when marking chat room as read
|
||||
viewModel.isUserScrollingUp.value = false
|
||||
|
@ -138,10 +142,14 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
if (chatRoom != null) {
|
||||
outState.putString("LocalSipUri", chatRoom.localAddress.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 {
|
||||
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)
|
||||
}
|
||||
|
@ -155,8 +163,12 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
|
||||
useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false
|
||||
|
||||
val localSipUri = arguments?.getString("LocalSipUri") ?: savedInstanceState?.getString("LocalSipUri")
|
||||
val remoteSipUri = arguments?.getString("RemoteSipUri") ?: savedInstanceState?.getString("RemoteSipUri")
|
||||
val localSipUri = arguments?.getString("LocalSipUri") ?: savedInstanceState?.getString(
|
||||
"LocalSipUri"
|
||||
)
|
||||
val remoteSipUri = arguments?.getString("RemoteSipUri") ?: savedInstanceState?.getString(
|
||||
"RemoteSipUri"
|
||||
)
|
||||
|
||||
val textToShare = arguments?.getString("TextToShare")
|
||||
val filesToShare = arguments?.getStringArrayList("FilesToShare")
|
||||
|
@ -166,12 +178,16 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
}
|
||||
arguments?.clear()
|
||||
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 remoteSipAddress = Factory.instance().createAddress(remoteSipUri)
|
||||
sharedViewModel.selectedChatRoom.value = coreContext.core.searchChatRoom(
|
||||
null, localAddress, remoteSipAddress,
|
||||
null,
|
||||
localAddress,
|
||||
remoteSipAddress,
|
||||
arrayOfNulls(
|
||||
0
|
||||
)
|
||||
|
@ -192,7 +208,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
|
||||
binding.root.addKeyboardInsetListener { keyboardVisible ->
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
chatScrollListener = object : ChatScrollListener(layoutManager) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -295,7 +319,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
if (viewModel.unreadMessagesCount.value != 0 &&
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -447,7 +473,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
|
||||
if (path.isNotEmpty() && !File(path).exists()) {
|
||||
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 {
|
||||
if (path.isEmpty()) {
|
||||
val name = content.name
|
||||
|
@ -456,14 +484,18 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
FileUtils.writeIntoFile(content.buffer, file)
|
||||
path = file.absolutePath
|
||||
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) {
|
||||
val name = "conference.ics"
|
||||
val file = FileUtils.getFileStoragePath(name)
|
||||
FileUtils.writeIntoFile(content.buffer, file)
|
||||
path = file.absolutePath
|
||||
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 -> {
|
||||
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(
|
||||
content
|
||||
)
|
||||
|
@ -570,7 +604,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
val eventLog = events.find { eventLog ->
|
||||
if (eventLog.eventLog.type == EventLog.Type.ConferenceChatMessage) {
|
||||
(eventLog.data as ChatMessageData).chatMessage.messageId == chatMessage.messageId
|
||||
} else false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
index = events.indexOf(eventLog)
|
||||
if (index == -1) {
|
||||
|
@ -632,7 +668,7 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
alertDialog.setMessage(message)
|
||||
|
||||
alertDialog.setNeutralButton(R.string.chat_message_context_menu_copy_text) {
|
||||
_, _ ->
|
||||
_, _ ->
|
||||
val clipboard: ClipboardManager =
|
||||
coreContext.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("Chat room info", message)
|
||||
|
@ -662,17 +698,25 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
if (corePreferences.holdToRecordVoiceMessage) {
|
||||
when (event.action) {
|
||||
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()
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
val voiceRecordingDuration = chatSendingViewModel.voiceRecordingDuration.value ?: 0
|
||||
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()
|
||||
(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 {
|
||||
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()
|
||||
}
|
||||
view.performClick()
|
||||
|
@ -712,7 +756,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
chatSendingViewModel.attachingFileInProgress.value = true
|
||||
for (filePath in filesToShare) {
|
||||
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) {
|
||||
chatSendingViewModel.addAttachment(path)
|
||||
}
|
||||
|
@ -806,7 +852,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
.viewTreeObserver
|
||||
.addOnGlobalLayoutListener(globalLayoutLayout)
|
||||
} 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) {
|
||||
chatSendingViewModel.attachingFileInProgress.value = true
|
||||
for (
|
||||
fileToUploadPath in FileUtils.getFilesPathFromPickerIntent(
|
||||
data,
|
||||
chatSendingViewModel.temporaryFileUploadPath
|
||||
)
|
||||
fileToUploadPath in FileUtils.getFilesPathFromPickerIntent(
|
||||
data,
|
||||
chatSendingViewModel.temporaryFileUploadPath
|
||||
)
|
||||
) {
|
||||
Log.i("[Chat Room] Found [$fileToUploadPath] file from intent")
|
||||
chatSendingViewModel.addAttachment(fileToUploadPath)
|
||||
|
@ -867,9 +915,13 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
dialog.dismiss()
|
||||
}
|
||||
|
||||
val okLabel = if (viewModel.oneParticipantOneDevice) getString(R.string.dialog_call) else getString(
|
||||
R.string.dialog_ok
|
||||
)
|
||||
val okLabel = if (viewModel.oneParticipantOneDevice) {
|
||||
getString(R.string.dialog_call)
|
||||
} else {
|
||||
getString(
|
||||
R.string.dialog_ok
|
||||
)
|
||||
}
|
||||
dialogViewModel.showOkButton(
|
||||
{ doNotAskAgain ->
|
||||
if (doNotAskAgain) corePreferences.limeSecurityPopupEnabled = false
|
||||
|
@ -921,7 +973,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
}
|
||||
|
||||
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.showIcon = true
|
||||
val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel)
|
||||
|
@ -946,7 +1000,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
private fun showPopupMenu(chatRoom: ChatRoom) {
|
||||
val popupView: ChatRoomMenuBindingImpl = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(context),
|
||||
R.layout.chat_room_menu, null, false
|
||||
R.layout.chat_room_menu,
|
||||
null,
|
||||
false
|
||||
)
|
||||
val readOnly = chatRoom.isReadOnly
|
||||
popupView.ephemeralEnabled = !readOnly
|
||||
|
@ -1014,7 +1070,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
if (viewModel.ephemeralChatRoom) {
|
||||
if (chatRoom.currentParams.ephemeralMode == ChatRoomEphemeralMode.AdminManaged) {
|
||||
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
|
||||
totalSize -= itemSize
|
||||
}
|
||||
|
@ -1117,7 +1175,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
R.string.chat_message_removal_info,
|
||||
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)
|
||||
task.cancel()
|
||||
}
|
||||
|
@ -1136,7 +1196,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
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)
|
||||
|
||||
if (firstUnreadMessagePosition == 0) {
|
||||
|
@ -1219,7 +1281,9 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
{
|
||||
dialog.dismiss()
|
||||
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()
|
||||
if (!FileUtils.openFileInThirdPartyApp(requireActivity(), plainFilePath)) {
|
||||
showDialogToSuggestOpeningFileAsText()
|
||||
|
@ -1245,7 +1309,10 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
}
|
||||
|
||||
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)
|
||||
|
||||
dialogViewModel.iconResource = R.drawable.icon_video_conf_incoming
|
||||
|
|
|
@ -76,7 +76,9 @@ class GroupInfoFragment : SecureFragment<ChatRoomGroupInfoFragmentBinding>() {
|
|||
binding.participants.layoutManager = layoutManager
|
||||
|
||||
// Divider between items
|
||||
binding.participants.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager))
|
||||
binding.participants.addItemDecoration(
|
||||
AppUtils.getDividerDecoration(requireContext(), layoutManager)
|
||||
)
|
||||
|
||||
viewModel.participants.observe(
|
||||
viewLifecycleOwner
|
||||
|
@ -148,7 +150,9 @@ class GroupInfoFragment : SecureFragment<ChatRoomGroupInfoFragmentBinding>() {
|
|||
}
|
||||
|
||||
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)
|
||||
|
||||
dialogViewModel.showDeleteButton(
|
||||
|
@ -190,7 +194,11 @@ class GroupInfoFragment : SecureFragment<ChatRoomGroupInfoFragmentBinding>() {
|
|||
} else {
|
||||
list.add(
|
||||
GroupInfoParticipantData(
|
||||
GroupChatRoomMember(address, false, hasLimeX3DHCapability = viewModel.isEncrypted.value == true)
|
||||
GroupChatRoomMember(
|
||||
address,
|
||||
false,
|
||||
hasLimeX3DHCapability = viewModel.isEncrypted.value == true
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -84,7 +84,9 @@ class ImdnFragment : SecureFragment<ChatRoomImdnFragmentBinding>() {
|
|||
binding.participantsList.layoutManager = layoutManager
|
||||
|
||||
// Divider between items
|
||||
binding.participantsList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager))
|
||||
binding.participantsList.addItemDecoration(
|
||||
AppUtils.getDividerDecoration(requireContext(), layoutManager)
|
||||
)
|
||||
|
||||
// Displays state header
|
||||
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter)
|
||||
|
|
|
@ -133,7 +133,9 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
|
|||
val navHostFragment =
|
||||
childFragmentManager.findFragmentById(R.id.chat_nav_container) as NavHostFragment
|
||||
if (navHostFragment.navController.currentDestination?.id == R.id.emptyChatFragment) {
|
||||
Log.i("[Chat] Foldable device has been folded, closing side pane with empty fragment")
|
||||
Log.i(
|
||||
"[Chat] Foldable device has been folded, closing side pane with empty fragment"
|
||||
)
|
||||
binding.slidingPane.closePane()
|
||||
}
|
||||
}
|
||||
|
@ -212,7 +214,9 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
|
|||
if (!binding.slidingPane.isSlideable &&
|
||||
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()
|
||||
}
|
||||
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)
|
||||
|
||||
// Divider between items
|
||||
binding.chatList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager))
|
||||
binding.chatList.addItemDecoration(
|
||||
AppUtils.getDividerDecoration(requireContext(), layoutManager)
|
||||
)
|
||||
|
||||
listViewModel.chatRooms.observe(
|
||||
viewLifecycleOwner
|
||||
|
@ -256,7 +266,9 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
|
|||
if (!binding.slidingPane.isOpen) {
|
||||
Log.w("[Chat] Chat room is displayed but sliding pane is closed...")
|
||||
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 {
|
||||
Log.w("[Chat] This chat room is already displayed!")
|
||||
|
@ -314,11 +326,18 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
|
|||
val localSipUri = arguments?.getString("LocalSipUri")
|
||||
val remoteSipUri = arguments?.getString("RemoteSipUri")
|
||||
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()
|
||||
val localAddress = Factory.instance().createAddress(localSipUri)
|
||||
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) {
|
||||
Log.i("[Chat] Found matching chat room $chatRoom")
|
||||
adapter.selectedChatRoomEvent.value = Event(chatRoom)
|
||||
|
|
|
@ -209,7 +209,9 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
|||
attachments.value = list
|
||||
|
||||
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)
|
||||
|
||||
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 {
|
||||
val pendingMessageToReplyTo = pendingChatMessageToReplyTo.value
|
||||
return if (isPendingAnswer.value == true && pendingMessageToReplyTo != null)
|
||||
return if (isPendingAnswer.value == true && pendingMessageToReplyTo != null) {
|
||||
chatRoom.createReplyMessage(pendingMessageToReplyTo.chatMessage)
|
||||
else
|
||||
} else {
|
||||
chatRoom.createEmptyMessage()
|
||||
}
|
||||
}
|
||||
|
||||
fun sendMessage() {
|
||||
|
@ -249,7 +252,9 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
|||
if (isPendingVoiceRecord.value == true && recorder.file != null) {
|
||||
val content = recorder.createContent()
|
||||
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)
|
||||
voiceRecord = true
|
||||
} else {
|
||||
|
@ -373,7 +378,9 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
|||
}
|
||||
val tempFileName = "voice-recording-${System.currentTimeMillis()}.$extension"
|
||||
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.start()
|
||||
}
|
||||
|
@ -393,10 +400,14 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
|||
withContext(Dispatchers.Main) {
|
||||
val duration = recorder.duration
|
||||
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) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -463,7 +474,11 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -508,7 +523,9 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
|||
|
||||
val recordingAudioDevice = AudioRouteUtils.getAudioRecordingDeviceForVoiceMessage()
|
||||
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)
|
||||
}
|
||||
|
@ -517,7 +534,9 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
|||
Log.i("[Chat Message Sending] Creating player for voice record")
|
||||
|
||||
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)
|
||||
if (localPlayer != null) {
|
||||
|
@ -559,6 +578,10 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
|||
}
|
||||
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,7 +107,9 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -163,7 +165,10 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
|
|||
upperBound = maxSize
|
||||
}
|
||||
|
||||
val history: Array<EventLog> = chatRoom.getHistoryRangeEvents(totalItemsCount, upperBound)
|
||||
val history: Array<EventLog> = chatRoom.getHistoryRangeEvents(
|
||||
totalItemsCount,
|
||||
upperBound
|
||||
)
|
||||
val list = arrayListOf<EventLogData>()
|
||||
for (eventLog in history) {
|
||||
list.add(EventLogData(eventLog))
|
||||
|
@ -187,7 +192,9 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
|
|||
val list = arrayListOf<EventLogData>()
|
||||
val unreadCount = chatRoom.unreadMessagesCount
|
||||
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)
|
||||
var messageCount = 0
|
||||
|
@ -200,8 +207,13 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
|
|||
|
||||
// Load enough events to have at least all unread messages
|
||||
while (unreadCount > 0 && messageCount < unreadCount) {
|
||||
Log.w("[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)
|
||||
Log.w(
|
||||
"[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
|
||||
for (eventLog in moreHistory) {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
if (!PermissionHelper.get().hasWriteExternalStoragePermission()) {
|
||||
for (content in chatMessage.contents) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,9 @@ class ChatRoomCreationViewModel : ContactsSelectionViewModel() {
|
|||
|
||||
fun updateEncryption(encrypted: Boolean) {
|
||||
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
|
||||
}
|
||||
isEncrypted.value = encrypted
|
||||
|
@ -79,7 +81,10 @@ class ChatRoomCreationViewModel : ContactsSelectionViewModel() {
|
|||
val defaultAccount = coreContext.core.defaultAccount
|
||||
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) {
|
||||
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)
|
||||
|
@ -94,12 +99,15 @@ class ChatRoomCreationViewModel : ContactsSelectionViewModel() {
|
|||
if (encrypted) {
|
||||
params.isEncryptionEnabled = true
|
||||
params.backend = ChatRoomBackend.FlexisipChat
|
||||
params.ephemeralMode = if (corePreferences.useEphemeralPerDeviceMode)
|
||||
params.ephemeralMode = if (corePreferences.useEphemeralPerDeviceMode) {
|
||||
ChatRoomEphemeralMode.DeviceManaged
|
||||
else
|
||||
} else {
|
||||
ChatRoomEphemeralMode.AdminManaged
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -108,7 +116,9 @@ class ChatRoomCreationViewModel : ContactsSelectionViewModel() {
|
|||
|
||||
room = coreContext.core.searchChatRoom(params, localAddress, null, participants)
|
||||
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)
|
||||
|
||||
if (room != null) {
|
||||
|
@ -119,7 +129,9 @@ class ChatRoomCreationViewModel : ContactsSelectionViewModel() {
|
|||
chatRoomCreatedEvent.value = Event(room)
|
||||
waitForChatRoomCreation.value = false
|
||||
} 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)
|
||||
}
|
||||
} else {
|
||||
|
@ -127,11 +139,15 @@ class ChatRoomCreationViewModel : ContactsSelectionViewModel() {
|
|||
waitForChatRoomCreation.value = false
|
||||
}
|
||||
} 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
|
||||
}
|
||||
} 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)
|
||||
waitForChatRoomCreation.value = false
|
||||
}
|
||||
|
|
|
@ -111,7 +111,10 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
|||
private var addressToCall: Address? = null
|
||||
|
||||
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 {
|
||||
val value = it.animatedValue as Float
|
||||
chatUnreadCountTranslateY.value = value
|
||||
|
@ -265,11 +268,11 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
|||
val localAddress = chatRoom.localAddress.clone()
|
||||
localAddress.clean() // Remove GRUU
|
||||
val addresses = Array(chatRoom.participants.size) {
|
||||
index ->
|
||||
index ->
|
||||
chatRoom.participants[index].address
|
||||
}
|
||||
val localAccount = coreContext.core.accountList.find {
|
||||
account ->
|
||||
account ->
|
||||
account.params.identityAddress?.weakEqual(localAddress) ?: false
|
||||
}
|
||||
|
||||
|
@ -298,7 +301,9 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
|||
if (chatRoom.participants.isNotEmpty()) {
|
||||
chatRoom.participants[0].address
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
@ -338,11 +343,18 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
|||
}
|
||||
TimestampUtils.isYesterday(timestamp) -> {
|
||||
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"
|
||||
}
|
||||
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)
|
||||
"$text $date"
|
||||
}
|
||||
|
@ -390,7 +402,11 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
|||
composing += if (composing.isNotEmpty()) ", " else ""
|
||||
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() {
|
||||
|
@ -400,10 +416,11 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
|||
chatRoom.me?.devices?.size == 1 &&
|
||||
participants.firstOrNull()?.devices?.size == 1
|
||||
|
||||
addressToCall = if (basicChatRoom)
|
||||
addressToCall = if (basicChatRoom) {
|
||||
chatRoom.peerAddress
|
||||
else
|
||||
} else {
|
||||
participants.firstOrNull()?.address
|
||||
}
|
||||
|
||||
onlyParticipantOnlyDeviceAddress = participants.firstOrNull()?.devices?.firstOrNull()?.address
|
||||
}
|
||||
|
@ -411,7 +428,8 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
|||
private fun updateUnreadMessageCount() {
|
||||
val count = chatRoom.unreadMessagesCount
|
||||
unreadMessagesCount.value = count
|
||||
if (count > 0 && corePreferences.enableAnimations) bounceAnimator.start()
|
||||
else if (count == 0 && bounceAnimator.isStarted) bounceAnimator.end()
|
||||
if (count > 0 && corePreferences.enableAnimations) {
|
||||
bounceAnimator.start()
|
||||
} else if (count == 0 && bounceAnimator.isStarted) bounceAnimator.end()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,9 @@ class ChatRoomsListViewModel : MessageNotifierViewModel() {
|
|||
private val chatRoomListener = object : ChatRoomListenerStub() {
|
||||
override fun onStateChanged(chatRoom: ChatRoom, newState: ChatRoom.State) {
|
||||
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 id = LinphoneUtils.getChatRoomId(chatRoom)
|
||||
for (data in chatRooms.value.orEmpty()) {
|
||||
|
@ -64,14 +66,18 @@ class ChatRoomsListViewModel : MessageNotifierViewModel() {
|
|||
private val listener: CoreListenerStub = object : CoreListenerStub() {
|
||||
override fun onChatRoomStateChanged(core: Core, chatRoom: ChatRoom, state: ChatRoom.State) {
|
||||
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 list = arrayListOf<ChatRoomData>()
|
||||
list.add(data)
|
||||
list.addAll(chatRooms.value.orEmpty())
|
||||
chatRooms.value = list
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
@ -155,8 +161,11 @@ class ChatRoomsListViewModel : MessageNotifierViewModel() {
|
|||
|
||||
fun notifyChatRoomUpdate(chatRoom: ChatRoom) {
|
||||
val index = findChatRoomIndex(chatRoom)
|
||||
if (index == -1) updateChatRooms()
|
||||
else chatRoomIndexUpdatedEvent.value = Event(index)
|
||||
if (index == -1) {
|
||||
updateChatRooms()
|
||||
} else {
|
||||
chatRoomIndexUpdatedEvent.value = Event(index)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reorderChatRooms() {
|
||||
|
|
|
@ -50,7 +50,9 @@ class EphemeralViewModel(private val chatRoom: ChatRoom) : ViewModel() {
|
|||
}
|
||||
|
||||
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
|
||||
computeEphemeralDurationValues()
|
||||
}
|
||||
|
@ -59,10 +61,14 @@ class EphemeralViewModel(private val chatRoom: ChatRoom) : ViewModel() {
|
|||
Log.i("[Ephemeral Messages] Selected value is $currentSelectedDuration")
|
||||
if (currentSelectedDuration > 0) {
|
||||
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
|
||||
} 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) {
|
||||
|
@ -77,12 +83,54 @@ class EphemeralViewModel(private val chatRoom: ChatRoom) : ViewModel() {
|
|||
|
||||
private fun computeEphemeralDurationValues() {
|
||||
val list = arrayListOf<EphemeralDurationData>()
|
||||
list.add(EphemeralDurationData(R.string.chat_room_ephemeral_message_disabled, currentSelectedDuration, 0, 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))
|
||||
list.add(
|
||||
EphemeralDurationData(
|
||||
R.string.chat_room_ephemeral_message_disabled,
|
||||
currentSelectedDuration,
|
||||
0,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,7 +99,9 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : MessageNotifierViewModel() {
|
|||
subject.value = chatRoom?.subject
|
||||
isMeAdmin.value = chatRoom == null || (chatRoom.me?.isAdmin == true && !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()
|
||||
|
||||
|
@ -120,13 +122,16 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : MessageNotifierViewModel() {
|
|||
params.isEncryptionEnabled = corePreferences.forceEndToEndEncryptedChat || isEncrypted.value == true
|
||||
params.isGroupEnabled = true
|
||||
if (params.isEncryptionEnabled) {
|
||||
params.ephemeralMode = if (corePreferences.useEphemeralPerDeviceMode)
|
||||
params.ephemeralMode = if (corePreferences.useEphemeralPerDeviceMode) {
|
||||
ChatRoomEphemeralMode.DeviceManaged
|
||||
else
|
||||
} else {
|
||||
ChatRoomEphemeralMode.AdminManaged
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
val addresses = arrayOfNulls<Address>(participants.value.orEmpty().size)
|
||||
|
@ -137,7 +142,11 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : MessageNotifierViewModel() {
|
|||
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)
|
||||
if (chatRoom == null) {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -180,12 +191,19 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : MessageNotifierViewModel() {
|
|||
// Participant found, check if admin status needs to be updated
|
||||
if (member.participant.isAdmin != participant.isAdmin) {
|
||||
if (chatRoom.me?.isAdmin == true) {
|
||||
Log.i("[Chat Room Group Info] Participant ${member.sipUri} will be admin? ${member.isAdmin}")
|
||||
chatRoom.setParticipantAdminStatus(participant, member.participant.isAdmin)
|
||||
Log.i(
|
||||
"[Chat Room Group Info] Participant ${member.sipUri} will be admin? ${member.isAdmin}"
|
||||
)
|
||||
chatRoom.setParticipantAdminStatus(
|
||||
participant,
|
||||
member.participant.isAdmin
|
||||
)
|
||||
}
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
@ -223,7 +241,12 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : MessageNotifierViewModel() {
|
|||
for (participant in chatRoom.participants) {
|
||||
list.add(
|
||||
GroupInfoParticipantData(
|
||||
GroupChatRoomMember(participant.address, participant.isAdmin, participant.securityLevel, canBeSetAdmin = true)
|
||||
GroupChatRoomMember(
|
||||
participant.address,
|
||||
participant.isAdmin,
|
||||
participant.securityLevel,
|
||||
canBeSetAdmin = true
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -68,7 +68,9 @@ class ImdnViewModel(private val chatMessage: ChatMessage) : ViewModel() {
|
|||
for (participant in chatMessage.getParticipantsByImdnState(ChatMessage.State.Displayed)) {
|
||||
list.add(ImdnParticipantData(participant))
|
||||
}
|
||||
for (participant in chatMessage.getParticipantsByImdnState(ChatMessage.State.DeliveredToUser)) {
|
||||
for (participant in chatMessage.getParticipantsByImdnState(
|
||||
ChatMessage.State.DeliveredToUser
|
||||
)) {
|
||||
list.add(ImdnParticipantData(participant))
|
||||
}
|
||||
for (participant in chatMessage.getParticipantsByImdnState(ChatMessage.State.Delivered)) {
|
||||
|
|
|
@ -62,7 +62,8 @@ class RichEditText : AppCompatEditText {
|
|||
|
||||
private fun initReceiveContentListener() {
|
||||
ViewCompat.setOnReceiveContentListener(
|
||||
this, RichContentReceiver.MIME_TYPES,
|
||||
this,
|
||||
RichContentReceiver.MIME_TYPES,
|
||||
RichContentReceiver { uri ->
|
||||
Log.i("[Rich Edit Text] Received URI: $uri")
|
||||
val activity = context as Activity
|
||||
|
|
|
@ -41,7 +41,10 @@ import org.linphone.utils.TimestampUtils
|
|||
class ScheduledConferencesAdapter(
|
||||
selectionVM: ListTopBarViewModel,
|
||||
private val viewLifecycleOwner: LifecycleOwner
|
||||
) : SelectionListAdapter<ScheduledConferenceData, RecyclerView.ViewHolder>(selectionVM, ConferenceInfoDiffCallback()),
|
||||
) : SelectionListAdapter<ScheduledConferenceData, RecyclerView.ViewHolder>(
|
||||
selectionVM,
|
||||
ConferenceInfoDiffCallback()
|
||||
),
|
||||
HeaderAdapter {
|
||||
val copyAddressToClipboardEvent: MutableLiveData<Event<String>> by lazy {
|
||||
MutableLiveData<Event<String>>()
|
||||
|
@ -62,7 +65,9 @@ class ScheduledConferencesAdapter(
|
|||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ScheduledConferencesAdapter.ViewHolder {
|
||||
val binding: ConferenceScheduleCellBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
R.layout.conference_schedule_cell, parent, false
|
||||
R.layout.conference_schedule_cell,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
@ -77,15 +82,22 @@ class ScheduledConferencesAdapter(
|
|||
val previousPosition = position - 1
|
||||
return if (previousPosition >= 0) {
|
||||
val previousItem = getItem(previousPosition)
|
||||
!TimestampUtils.isSameDay(previousItem.conferenceInfo.dateTime, conferenceInfo.conferenceInfo.dateTime)
|
||||
} else true
|
||||
!TimestampUtils.isSameDay(
|
||||
previousItem.conferenceInfo.dateTime,
|
||||
conferenceInfo.conferenceInfo.dateTime
|
||||
)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun getHeaderViewForPosition(context: Context, position: Int): View {
|
||||
val data = getItem(position)
|
||||
val binding: ConferenceScheduleListHeaderBinding = DataBindingUtil.inflate(
|
||||
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.executePendingBindings()
|
||||
|
@ -143,7 +155,9 @@ class ScheduledConferencesAdapter(
|
|||
setJoinConferenceClickListener {
|
||||
val address = conferenceData.conferenceInfo.uri
|
||||
if (address != null) {
|
||||
joinConferenceEvent.value = Event(Pair(address.asStringUriOnly(), conferenceData.conferenceInfo.subject))
|
||||
joinConferenceEvent.value = Event(
|
||||
Pair(address.asStringUriOnly(), conferenceData.conferenceInfo.subject)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,12 @@ class ScheduledConferenceData(val conferenceInfo: ConferenceInfo, private val is
|
|||
description.value = conferenceInfo.description
|
||||
|
||||
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
|
||||
|
||||
val minutes = conferenceInfo.duration
|
||||
|
@ -72,13 +77,16 @@ class ScheduledConferenceData(val conferenceInfo: ConferenceInfo, private val is
|
|||
canEdit.value = localAccount != null
|
||||
|
||||
val contact = coreContext.contactsManager.findContactByAddress(organizerAddress)
|
||||
organizer.value = if (contact != null)
|
||||
organizer.value = if (contact != null) {
|
||||
contact.name
|
||||
else
|
||||
} else {
|
||||
LinphoneUtils.getDisplayName(conferenceInfo.organizer)
|
||||
}
|
||||
} else {
|
||||
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()
|
||||
|
@ -88,7 +96,9 @@ class ScheduledConferenceData(val conferenceInfo: ConferenceInfo, private val is
|
|||
fun destroy() {}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -134,7 +144,13 @@ class ScheduledConferenceData(val conferenceInfo: ConferenceInfo, private val is
|
|||
|
||||
for (participant in conferenceInfo.participants) {
|
||||
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()
|
||||
participantsListShort += "$name, "
|
||||
participantsListExpanded += "$name ($address)\n"
|
||||
|
|
|
@ -38,7 +38,9 @@ import org.linphone.core.tools.Log
|
|||
import org.linphone.databinding.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
|
||||
|
||||
|
@ -53,7 +55,9 @@ class ConferenceSchedulingFragment : GenericFragment<ConferenceSchedulingFragmen
|
|||
viewLifecycleOwner
|
||||
) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -64,12 +68,18 @@ class ConferenceSchedulingFragment : GenericFragment<ConferenceSchedulingFragmen
|
|||
it.consume { address ->
|
||||
val conferenceAddress = Factory.instance().createAddress(address)
|
||||
if (conferenceAddress != null) {
|
||||
Log.i("[Conference Scheduling] Trying to edit conference info using address: $address")
|
||||
val conferenceInfo = coreContext.core.findConferenceInformationFromUri(conferenceAddress)
|
||||
Log.i(
|
||||
"[Conference Scheduling] Trying to edit conference info using address: $address"
|
||||
)
|
||||
val conferenceInfo = coreContext.core.findConferenceInformationFromUri(
|
||||
conferenceAddress
|
||||
)
|
||||
if (conferenceInfo != null) {
|
||||
viewModel.populateFromConferenceInfo(conferenceInfo)
|
||||
} else {
|
||||
Log.e("[Conference Scheduling] Failed to find ConferenceInfo matching address: $address")
|
||||
Log.e(
|
||||
"[Conference Scheduling] Failed to find ConferenceInfo matching address: $address"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Log.e("[Conference Scheduling] Failed to parse conference address: $address")
|
||||
|
|
|
@ -37,7 +37,9 @@ import org.linphone.utils.AppUtils
|
|||
import org.linphone.utils.PermissionHelper
|
||||
|
||||
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
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.conference_scheduling_participants_list_fragment
|
||||
|
@ -57,7 +59,9 @@ class ConferenceSchedulingParticipantsListFragment : GenericFragment<ConferenceS
|
|||
binding.contactsList.layoutManager = layoutManager
|
||||
|
||||
// Divider between items
|
||||
binding.contactsList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager))
|
||||
binding.contactsList.addItemDecoration(
|
||||
AppUtils.getDividerDecoration(requireContext(), layoutManager)
|
||||
)
|
||||
|
||||
binding.setNextClickListener {
|
||||
navigateToSummary()
|
||||
|
|
|
@ -31,7 +31,9 @@ import org.linphone.activities.navigateToScheduledConferences
|
|||
import org.linphone.databinding.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
|
||||
|
||||
|
@ -47,7 +49,9 @@ class ConferenceSchedulingSummaryFragment : GenericFragment<ConferenceScheduling
|
|||
) {
|
||||
it.consume {
|
||||
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()
|
||||
} else {
|
||||
navigateToDialer()
|
||||
|
|
|
@ -63,10 +63,14 @@ class ConferenceWaitingRoomFragment : GenericFragment<ConferenceWaitingRoomFragm
|
|||
call.remoteAddress.asStringUriOnly() == conferenceUri
|
||||
}
|
||||
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()
|
||||
} 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()
|
||||
|
@ -81,13 +85,19 @@ class ConferenceWaitingRoomFragment : GenericFragment<ConferenceWaitingRoomFragm
|
|||
if (conferenceUri != null) {
|
||||
val conferenceAddress = coreContext.core.interpretUrl(conferenceUri, false)
|
||||
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)
|
||||
} 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 {
|
||||
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()
|
||||
}
|
||||
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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,7 +82,9 @@ class ScheduledConferencesFragment : MasterFragment<ConferencesScheduledFragment
|
|||
override fun onRightToLeftSwipe(viewHolder: RecyclerView.ViewHolder) {
|
||||
val index = viewHolder.bindingAdapterPosition
|
||||
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 {
|
||||
val deletedConfInfo = adapter.currentList[index]
|
||||
showConfInfoDeleteConfirmationDialog(deletedConfInfo, index)
|
||||
|
@ -111,7 +113,9 @@ class ScheduledConferencesFragment : MasterFragment<ConferencesScheduledFragment
|
|||
val clip = ClipData.newPlainText("Conference address", address)
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -82,7 +82,9 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
|
|||
Log.i("[Conference Creation] Conference scheduler state is $state")
|
||||
if (state == ConferenceScheduler.State.Ready) {
|
||||
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
|
||||
|
||||
address.value = conferenceAddress!!
|
||||
|
@ -110,11 +112,17 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
|
|||
|
||||
if (failedInvitations?.isNotEmpty() == true) {
|
||||
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 {
|
||||
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
|
||||
|
@ -313,7 +321,9 @@ class ConferenceSchedulingViewModel : ContactsSelectionViewModel() {
|
|||
}
|
||||
|
||||
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.set(Calendar.HOUR_OF_DAY, hour)
|
||||
calendar.set(Calendar.MINUTE, minutes)
|
||||
|
|
|
@ -125,7 +125,9 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
|
|||
leaveWaitingRoomEvent.value = Event(true)
|
||||
}
|
||||
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)
|
||||
}
|
||||
else -> {}
|
||||
|
@ -138,7 +140,9 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
|
|||
state: Conference.State?
|
||||
) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -156,8 +160,12 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
|
|||
val core = coreContext.core
|
||||
core.addListener(listener)
|
||||
|
||||
audioRoutesMenuTranslateY.value = AppUtils.getDimension(R.dimen.voip_audio_routes_menu_translate_y)
|
||||
conferenceLayoutMenuTranslateY.value = AppUtils.getDimension(R.dimen.voip_audio_routes_menu_translate_y)
|
||||
audioRoutesMenuTranslateY.value = AppUtils.getDimension(
|
||||
R.dimen.voip_audio_routes_menu_translate_y
|
||||
)
|
||||
conferenceLayoutMenuTranslateY.value = AppUtils.getDimension(
|
||||
R.dimen.voip_audio_routes_menu_translate_y
|
||||
)
|
||||
|
||||
val reachable = core.isNetworkReachable
|
||||
networkReachable.value = reachable
|
||||
|
@ -166,7 +174,9 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
|
|||
}
|
||||
|
||||
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()
|
||||
|
||||
callParams.isVideoEnabled = isVideoAvailableInCore()
|
||||
|
@ -175,7 +185,9 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
|
|||
|
||||
isLowBandwidth.value = false
|
||||
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.isVideoEnabled = false
|
||||
callParams.videoDirection = MediaDirection.Inactive
|
||||
|
@ -224,7 +236,9 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
|
|||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
@ -254,10 +268,14 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
|
|||
fun setBluetoothAudioRoute() {
|
||||
Log.i("[Conference Waiting Room] Set default output audio device to Bluetooth")
|
||||
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 {
|
||||
it.type == AudioDevice.Type.Bluetooth && it.hasCapability(AudioDevice.Capabilities.CapabilityRecord)
|
||||
it.type == AudioDevice.Type.Bluetooth && it.hasCapability(
|
||||
AudioDevice.Capabilities.CapabilityRecord
|
||||
)
|
||||
}
|
||||
updateAudioRouteState()
|
||||
|
||||
|
@ -270,10 +288,14 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
|
|||
fun setSpeakerAudioRoute() {
|
||||
Log.i("[Conference Waiting Room] Set default output audio device to Speaker")
|
||||
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 {
|
||||
it.type == AudioDevice.Type.Microphone && it.hasCapability(AudioDevice.Capabilities.CapabilityRecord)
|
||||
it.type == AudioDevice.Type.Microphone && it.hasCapability(
|
||||
AudioDevice.Capabilities.CapabilityRecord
|
||||
)
|
||||
}
|
||||
updateAudioRouteState()
|
||||
|
||||
|
@ -286,10 +308,14 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
|
|||
fun setEarpieceAudioRoute() {
|
||||
Log.i("[Conference Waiting Room] Set default output audio device to Earpiece")
|
||||
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 {
|
||||
it.type == AudioDevice.Type.Microphone && it.hasCapability(AudioDevice.Capabilities.CapabilityRecord)
|
||||
it.type == AudioDevice.Type.Microphone && it.hasCapability(
|
||||
AudioDevice.Capabilities.CapabilityRecord
|
||||
)
|
||||
}
|
||||
updateAudioRouteState()
|
||||
|
||||
|
@ -376,7 +402,9 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
|
|||
|
||||
if (!bluetoothDeviceAvailable) {
|
||||
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) {
|
||||
for (audioDevice in coreContext.core.audioDevices) {
|
||||
if (isVideoEnabled.value == true) {
|
||||
|
@ -418,7 +446,9 @@ class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() {
|
|||
private fun updateVideoState() {
|
||||
isVideoAvailable.value = callParams.isVideoEnabled
|
||||
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()
|
||||
coreContext.core.isVideoPreviewEnabled = isVideoEnabled.value == true
|
||||
|
|
|
@ -52,7 +52,9 @@ class ScheduledConferencesViewModel : ViewModel() {
|
|||
) {
|
||||
Log.i("[Scheduled Conferences] Conference scheduler state is $state")
|
||||
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()
|
||||
conferenceScheduler.sendInvitations(chatRoomParams) // Send cancel ICS
|
||||
}
|
||||
|
@ -64,10 +66,14 @@ class ScheduledConferencesViewModel : ViewModel() {
|
|||
) {
|
||||
if (failedInvitations?.isNotEmpty() == true) {
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
val data = ScheduledConferenceData(conferenceInfo, false)
|
||||
conferencesList.add(data)
|
||||
|
|
|
@ -42,7 +42,11 @@ import org.linphone.utils.HeaderAdapter
|
|||
class ContactsListAdapter(
|
||||
selectionVM: ListTopBarViewModel,
|
||||
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 {
|
||||
MutableLiveData<Event<Friend>>()
|
||||
}
|
||||
|
@ -50,7 +54,9 @@ class ContactsListAdapter(
|
|||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding: ContactListCellBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
R.layout.contact_list_cell, parent, false
|
||||
R.layout.contact_list_cell,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
@ -108,7 +114,9 @@ class ContactsListAdapter(
|
|||
return if (previousPosition >= 0) {
|
||||
val previousItemFirstLetter = getItem(previousPosition).fullName.firstOrNull().toString()
|
||||
!firstLetter.equals(previousItemFirstLetter, ignoreCase = true)
|
||||
} else true
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun getHeaderViewForPosition(context: Context, position: Int): View {
|
||||
|
@ -116,7 +124,9 @@ class ContactsListAdapter(
|
|||
val firstLetter = AppUtils.getInitials(contact.fullName, 1)
|
||||
val binding: GenericListHeaderBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(context),
|
||||
R.layout.generic_list_header, null, false
|
||||
R.layout.generic_list_header,
|
||||
null,
|
||||
false
|
||||
)
|
||||
binding.title = firstLetter
|
||||
binding.executePendingBindings()
|
||||
|
|
|
@ -19,7 +19,11 @@ class SyncAccountAdapter : BaseAdapter() {
|
|||
}
|
||||
|
||||
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 icon = view.findViewById<ImageView>(R.id.account_icon)
|
||||
|
|
|
@ -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")
|
||||
fetchFirstAndLastNames(refKey)
|
||||
} 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 {
|
||||
firstName.value = vCard?.givenName
|
||||
|
@ -246,10 +248,16 @@ class ContactEditorData(val friend: Friend?) : ContactDataInterface {
|
|||
while (cursor != null && cursor.moveToNext()) {
|
||||
val linphoneMime = AppUtils.getString(R.string.linphone_address_mime_type)
|
||||
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) {
|
||||
val data1: String? =
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER))
|
||||
cursor.getString(
|
||||
cursor.getColumnIndexOrThrow(
|
||||
ContactsContract.CommonDataKinds.Phone.NUMBER
|
||||
)
|
||||
)
|
||||
if (data1 != null) {
|
||||
phoneNumbers.add(NumberOrAddressEditorData(data1, false))
|
||||
}
|
||||
|
@ -258,7 +266,11 @@ class ContactEditorData(val friend: Friend?) : ContactDataInterface {
|
|||
mime == linphoneMime
|
||||
) {
|
||||
val data1: String? =
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS))
|
||||
cursor.getString(
|
||||
cursor.getColumnIndexOrThrow(
|
||||
ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS
|
||||
)
|
||||
)
|
||||
if (data1 != null) {
|
||||
sipAddresses.add(NumberOrAddressEditorData(data1, true))
|
||||
}
|
||||
|
@ -274,7 +286,9 @@ class ContactEditorData(val friend: Friend?) : ContactDataInterface {
|
|||
}
|
||||
|
||||
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()) {
|
||||
phoneNumbers.add(NumberOrAddressEditorData(number, false))
|
||||
}
|
||||
|
@ -310,17 +324,27 @@ class ContactEditorData(val friend: Friend?) : ContactDataInterface {
|
|||
)
|
||||
|
||||
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) {
|
||||
val givenName: String? =
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME))
|
||||
cursor.getString(
|
||||
cursor.getColumnIndexOrThrow(
|
||||
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME
|
||||
)
|
||||
)
|
||||
if (!givenName.isNullOrEmpty()) {
|
||||
friend?.vcard?.givenName = givenName
|
||||
firstName.value = givenName!!
|
||||
}
|
||||
|
||||
val familyName: String? =
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME))
|
||||
cursor.getString(
|
||||
cursor.getColumnIndexOrThrow(
|
||||
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME
|
||||
)
|
||||
)
|
||||
if (!familyName.isNullOrEmpty()) {
|
||||
friend?.vcard?.familyName = familyName
|
||||
lastName.value = familyName!!
|
||||
|
|
|
@ -116,7 +116,9 @@ class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), S
|
|||
Log.i("[Contact Editor] WRITE_CONTACTS permission granted")
|
||||
} else {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +128,10 @@ class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), S
|
|||
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
lifecycleScope.launch {
|
||||
val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(intent, temporaryPicturePath)
|
||||
val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(
|
||||
intent,
|
||||
temporaryPicturePath
|
||||
)
|
||||
if (contactImageFilePath != null) {
|
||||
data.setPictureFromPath(contactImageFilePath)
|
||||
}
|
||||
|
@ -171,7 +176,10 @@ class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), S
|
|||
|
||||
val chooserIntent =
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -88,7 +88,9 @@ class DetailContactFragment : GenericFragment<ContactDetailFragmentBinding>() {
|
|||
) {
|
||||
it.consume { address ->
|
||||
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 =
|
||||
Event(R.id.dialerFragment)
|
||||
sharedViewModel.updateDialerAnimationsBasedOnDestination.value =
|
||||
|
@ -186,7 +188,9 @@ class DetailContactFragment : GenericFragment<ContactDetailFragmentBinding>() {
|
|||
val smsIntent = Intent(Intent.ACTION_SENDTO)
|
||||
smsIntent.putExtra("address", 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)
|
||||
startActivity(smsIntent)
|
||||
}
|
||||
|
|
|
@ -115,7 +115,9 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
|
|||
val navHostFragment =
|
||||
childFragmentManager.findFragmentById(R.id.contacts_nav_container) as NavHostFragment
|
||||
if (navHostFragment.navController.currentDestination?.id == R.id.emptyContactFragment) {
|
||||
Log.i("[Contacts] Foldable device has been folded, closing side pane with empty fragment")
|
||||
Log.i(
|
||||
"[Contacts] Foldable device has been folded, closing side pane with empty fragment"
|
||||
)
|
||||
binding.slidingPane.closePane()
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +186,9 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
|
|||
if (!binding.slidingPane.isSlideable &&
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -204,7 +208,9 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
|
|||
}
|
||||
|
||||
// Divider between items
|
||||
binding.contactsList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager))
|
||||
binding.contactsList.addItemDecoration(
|
||||
AppUtils.getDividerDecoration(requireContext(), layoutManager)
|
||||
)
|
||||
|
||||
// Displays the first letter header
|
||||
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter)
|
||||
|
@ -255,7 +261,9 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
|
|||
viewLifecycleOwner
|
||||
) {
|
||||
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}]")
|
||||
adapter.selectedContactEvent.value = Event(contact)
|
||||
} 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
|
||||
}
|
||||
} else if (sipUri != null) {
|
||||
Log.i("[Contacts] Found sipUri parameter in arguments [$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
|
||||
} else if (addressString != null) {
|
||||
val address = Factory.instance().createAddress(addressString)
|
||||
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)
|
||||
if (contact != null) {
|
||||
Log.i("[Contacts] Found matching contact $contact")
|
||||
adapter.selectedContactEvent.value = Event(contact)
|
||||
} 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)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -215,10 +215,20 @@ class ContactViewModel(friend: Friend, async: Boolean = false) : MessageNotifier
|
|||
val presenceModel = friend.getPresenceModelForUriOrTel(value)
|
||||
val hasPresence = presenceModel?.basicStatus == PresenceBasicStatus.Open
|
||||
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 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)
|
||||
}
|
||||
|
||||
|
@ -229,11 +239,32 @@ class ContactViewModel(friend: Friend, async: Boolean = false) : MessageNotifier
|
|||
val contactAddress = presenceModel?.contact ?: number
|
||||
val address = coreContext.core.interpretUrl(contactAddress, true)
|
||||
address?.displayName = displayName.value.orEmpty()
|
||||
val isMe = if (address != null) coreContext.core.defaultAccount?.params?.identityAddress?.weakEqual(address) ?: false else false
|
||||
val hasLimeCapability = corePreferences.allowEndToEndEncryptedChatWithoutPresence || (friend.getPresenceModelForUriOrTel(number)?.hasCapability(FriendCapability.LimeX3Dh) ?: false)
|
||||
val isMe = if (address != null) {
|
||||
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 label = PhoneNumberUtils.vcardParamStringToAddressBookLabel(coreContext.context.resources, phoneNumber.label ?: "")
|
||||
val noa = ContactNumberOrAddressData(address, hasPresence, number, isSip = false, showSecureChat = secureChatAllowed, typeLabel = label, listener = listener)
|
||||
val label = PhoneNumberUtils.vcardParamStringToAddressBookLabel(
|
||||
coreContext.context.resources,
|
||||
phoneNumber.label ?: ""
|
||||
)
|
||||
val noa = ContactNumberOrAddressData(
|
||||
address,
|
||||
hasPresence,
|
||||
number,
|
||||
isSip = false,
|
||||
showSecureChat = secureChatAllowed,
|
||||
typeLabel = label,
|
||||
listener = listener
|
||||
)
|
||||
list.add(noa)
|
||||
}
|
||||
numbersAndAddresses.postValue(list)
|
||||
|
|
|
@ -123,8 +123,15 @@ class ContactsListViewModel : ViewModel() {
|
|||
val aggregation = MagicSearchAggregation.Friend
|
||||
searchResultsPending = true
|
||||
fastFetchJob?.cancel()
|
||||
Log.i("[Contacts] Asking Magic search for contacts matching filter [$filterValue], domain [$domain] and in sources [$filter]")
|
||||
coreContext.contactsManager.magicSearch.getContactsListAsync(filterValue, domain, filter, aggregation)
|
||||
Log.i(
|
||||
"[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()
|
||||
fastFetchJob = viewModelScope.launch {
|
||||
|
@ -154,7 +161,9 @@ class ContactsListViewModel : ViewModel() {
|
|||
ContactViewModel(friend, true)
|
||||
} else {
|
||||
Log.w("[Contacts] SearchResult [$result] has no Friend!")
|
||||
val fakeFriend = coreContext.contactsManager.createFriendFromSearchResult(result)
|
||||
val fakeFriend = coreContext.contactsManager.createFriendFromSearchResult(
|
||||
result
|
||||
)
|
||||
ContactViewModel(fakeFriend, true)
|
||||
}
|
||||
|
||||
|
|
|
@ -93,13 +93,19 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
|
|||
}
|
||||
|
||||
binding.setNewContactClickListener {
|
||||
sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(R.id.masterContactsFragment)
|
||||
sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(R.id.dialerFragment)
|
||||
sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(
|
||||
R.id.masterContactsFragment
|
||||
)
|
||||
sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(
|
||||
R.id.dialerFragment
|
||||
)
|
||||
navigateToContacts(viewModel.enteredUri.value)
|
||||
}
|
||||
|
||||
binding.setNewConferenceClickListener {
|
||||
sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(R.id.conferenceSchedulingFragment)
|
||||
sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(
|
||||
R.id.conferenceSchedulingFragment
|
||||
)
|
||||
navigateToConferenceScheduling()
|
||||
}
|
||||
|
||||
|
@ -157,7 +163,9 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -168,10 +176,14 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
|
|||
|
||||
if (corePreferences.skipDialerForNewCallAndTransfer) {
|
||||
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)
|
||||
} 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)
|
||||
}
|
||||
} else if (corePreferences.callRightAway && !skipAutoCall) {
|
||||
|
@ -289,7 +301,9 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
|
|||
if (Compatibility.hasTelecomManagerFeature(requireContext())) {
|
||||
TelecomHelper.create(requireContext())
|
||||
} 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
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -112,7 +112,9 @@ class DialerViewModel : LogsUploadViewModel() {
|
|||
if (reachable && address.isNotEmpty()) {
|
||||
val now = System.currentTimeMillis()
|
||||
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
|
||||
} else {
|
||||
Log.i("[Dialer] Network is available, continue auto call to $address")
|
||||
|
@ -198,7 +200,9 @@ class DialerViewModel : LogsUploadViewModel() {
|
|||
if (coreContext.core.isNetworkReachable) {
|
||||
coreContext.startCall(to)
|
||||
} 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()
|
||||
addressWaitingNetworkToBeCalled = to
|
||||
}
|
||||
|
|
|
@ -29,7 +29,9 @@ import org.linphone.activities.main.files.viewmodels.PdfFileViewModel
|
|||
class PdfPagesListAdapter(private val pdfViewModel: PdfFileViewModel) : RecyclerView.Adapter<PdfPagesListAdapter.PdfPageViewHolder>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PdfPageViewHolder {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -98,10 +98,14 @@ class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() {
|
|||
Compatibility.addImageToMediaStore(requireContext(), content)
|
||||
}
|
||||
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()
|
||||
} 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) -> {
|
||||
|
@ -109,10 +113,14 @@ class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() {
|
|||
Compatibility.addVideoToMediaStore(requireContext(), content)
|
||||
}
|
||||
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()
|
||||
} 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) -> {
|
||||
|
@ -120,32 +128,46 @@ class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() {
|
|||
Compatibility.addAudioToMediaStore(requireContext(), content)
|
||||
}
|
||||
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()
|
||||
} 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 -> {
|
||||
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 {
|
||||
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) {
|
||||
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()
|
||||
plainFilePath = filePath.ifEmpty { content.filePath.orEmpty() }
|
||||
Log.i("[File Viewer] Plain file path is: $plainFilePath")
|
||||
if (plainFilePath.isNotEmpty()) {
|
||||
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()) {
|
||||
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)
|
||||
}
|
||||
plainFilePath = ""
|
||||
|
|
|
@ -41,7 +41,11 @@ class AudioFileViewModel(content: Content) : FileViewerViewModel(content), Media
|
|||
|
||||
init {
|
||||
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)
|
||||
prepare()
|
||||
start()
|
||||
|
|
|
@ -33,7 +33,9 @@ open class FileViewerViewModel(val content: Content) : ViewModel() {
|
|||
|
||||
init {
|
||||
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()
|
||||
} else {
|
||||
content.filePath.orEmpty()
|
||||
|
|
|
@ -136,14 +136,15 @@ abstract class MasterFragment<T : ViewDataBinding, U : SelectionListAdapter<*, *
|
|||
abstract fun deleteItems(indexesOfItemToDelete: ArrayList<Int>)
|
||||
|
||||
class SlidingPaneBackPressedCallback(private val slidingPaneLayout: SlidingPaneLayout) :
|
||||
OnBackPressedCallback
|
||||
(
|
||||
OnBackPressedCallback(
|
||||
slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen
|
||||
),
|
||||
SlidingPaneLayout.PanelSlideListener {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -77,7 +77,9 @@ abstract class SecureFragment<T : ViewDataBinding> : GenericFragment<T>() {
|
|||
if ((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
|
||||
}
|
||||
|
||||
|
|
|
@ -54,32 +54,48 @@ class TabsFragment : GenericFragment<TabsFragmentBinding>(), NavController.OnDes
|
|||
|
||||
binding.setHistoryClickListener {
|
||||
when (findNavController().currentDestination?.id) {
|
||||
R.id.masterContactsFragment -> sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(R.id.masterCallLogsFragment)
|
||||
R.id.dialerFragment -> sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(R.id.masterCallLogsFragment)
|
||||
R.id.masterContactsFragment -> sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(
|
||||
R.id.masterCallLogsFragment
|
||||
)
|
||||
R.id.dialerFragment -> sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(
|
||||
R.id.masterCallLogsFragment
|
||||
)
|
||||
}
|
||||
navigateToCallHistory()
|
||||
}
|
||||
|
||||
binding.setContactsClickListener {
|
||||
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()
|
||||
}
|
||||
|
||||
binding.setDialerClickListener {
|
||||
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()
|
||||
}
|
||||
|
||||
binding.setChatClickListener {
|
||||
when (findNavController().currentDestination?.id) {
|
||||
R.id.masterContactsFragment -> sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(R.id.masterChatRoomsFragment)
|
||||
R.id.dialerFragment -> sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(R.id.masterChatRoomsFragment)
|
||||
R.id.masterContactsFragment -> sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(
|
||||
R.id.masterChatRoomsFragment
|
||||
)
|
||||
R.id.dialerFragment -> sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(
|
||||
R.id.masterChatRoomsFragment
|
||||
)
|
||||
}
|
||||
navigateToChatRooms()
|
||||
}
|
||||
|
@ -102,17 +118,30 @@ class TabsFragment : GenericFragment<TabsFragmentBinding>(), NavController.OnDes
|
|||
) {
|
||||
if (corePreferences.enableAnimations) {
|
||||
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.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 {
|
||||
when (destination.id) {
|
||||
R.id.masterCallLogsFragment -> binding.motionLayout.setTransition(R.id.call_history, R.id.call_history)
|
||||
R.id.masterContactsFragment -> binding.motionLayout.setTransition(R.id.contacts, R.id.contacts)
|
||||
R.id.masterCallLogsFragment -> binding.motionLayout.setTransition(
|
||||
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.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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,11 @@ import org.linphone.utils.*
|
|||
class CallLogsListAdapter(
|
||||
selectionVM: ListTopBarViewModel,
|
||||
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 {
|
||||
MutableLiveData<Event<GroupedCallLogData>>()
|
||||
}
|
||||
|
@ -51,7 +55,9 @@ class CallLogsListAdapter(
|
|||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val binding: HistoryListCellBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
R.layout.history_list_cell, parent, false
|
||||
R.layout.history_list_cell,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
@ -115,7 +121,9 @@ class CallLogsListAdapter(
|
|||
return if (previousPosition >= 0) {
|
||||
val previousItemDate = getItem(previousPosition).lastCallLogStartTimestamp
|
||||
!TimestampUtils.isSameDay(date, previousItemDate)
|
||||
} else true
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun getHeaderViewForPosition(context: Context, position: Int): View {
|
||||
|
@ -123,7 +131,9 @@ class CallLogsListAdapter(
|
|||
val date = formatDate(context, callLog.lastCallLogStartTimestamp)
|
||||
val binding: GenericListHeaderBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(context),
|
||||
R.layout.generic_list_header, null, false
|
||||
R.layout.generic_list_header,
|
||||
null,
|
||||
false
|
||||
)
|
||||
binding.title = date
|
||||
binding.executePendingBindings()
|
||||
|
|
|
@ -62,12 +62,16 @@ class DetailCallLogFragment : GenericFragment<HistoryDetailFragmentBinding>() {
|
|||
copy.clean()
|
||||
val address = copy.asStringUriOnly()
|
||||
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)
|
||||
}
|
||||
|
||||
binding.setContactClickListener {
|
||||
sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(R.id.masterCallLogsFragment)
|
||||
sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(
|
||||
R.id.masterCallLogsFragment
|
||||
)
|
||||
val contactId = viewModel.contact.value?.refKey
|
||||
if (contactId != null) {
|
||||
Log.i("[History] Displaying native contact [$contactId]")
|
||||
|
@ -90,7 +94,9 @@ class DetailCallLogFragment : GenericFragment<HistoryDetailFragmentBinding>() {
|
|||
address.clean()
|
||||
|
||||
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 =
|
||||
Event(R.id.masterCallLogsFragment)
|
||||
|
||||
|
@ -104,7 +110,9 @@ class DetailCallLogFragment : GenericFragment<HistoryDetailFragmentBinding>() {
|
|||
navigateToDialer(args)
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,7 +105,9 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
|
|||
val navHostFragment =
|
||||
childFragmentManager.findFragmentById(R.id.history_nav_container) as NavHostFragment
|
||||
if (navHostFragment.navController.currentDestination?.id == R.id.emptyCallHistoryFragment) {
|
||||
Log.i("[History] Foldable device has been folded, closing side pane with empty fragment")
|
||||
Log.i(
|
||||
"[History] Foldable device has been folded, closing side pane with empty fragment"
|
||||
)
|
||||
binding.slidingPane.closePane()
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +164,9 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
|
|||
if (!binding.slidingPane.isSlideable &&
|
||||
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()
|
||||
}
|
||||
dialog.dismiss()
|
||||
|
@ -178,7 +182,9 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
|
|||
.attachToRecyclerView(binding.callLogsList)
|
||||
|
||||
// Divider between items
|
||||
binding.callLogsList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager))
|
||||
binding.callLogsList.addItemDecoration(
|
||||
AppUtils.getDividerDecoration(requireContext(), layoutManager)
|
||||
)
|
||||
|
||||
// Displays formatted date header
|
||||
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter)
|
||||
|
@ -212,7 +218,7 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
|
|||
}
|
||||
|
||||
adapter.startCallToEvent.observe(
|
||||
viewLifecycleOwner,
|
||||
viewLifecycleOwner
|
||||
) {
|
||||
it.consume { callLogGroup ->
|
||||
val callLog = callLogGroup.lastCallLog
|
||||
|
@ -244,8 +250,12 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
|
|||
}
|
||||
coreContext.core.callsNb > 0 -> {
|
||||
val cleanAddress = LinphoneUtils.getCleanedAddress(callLog.remoteAddress)
|
||||
Log.i("[History] Starting dialer with pre-filled URI ${cleanAddress.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}")
|
||||
sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(R.id.masterCallLogsFragment)
|
||||
Log.i(
|
||||
"[History] Starting dialer with pre-filled URI ${cleanAddress.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}"
|
||||
)
|
||||
sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(
|
||||
R.id.masterCallLogsFragment
|
||||
)
|
||||
val args = Bundle()
|
||||
args.putString("URI", cleanAddress.asStringUriOnly())
|
||||
args.putBoolean("Transfer", sharedViewModel.pendingCallTransfer)
|
||||
|
@ -255,7 +265,9 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
|
|||
else -> {
|
||||
val cleanAddress = LinphoneUtils.getCleanedAddress(callLog.remoteAddress)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -289,7 +301,9 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
|
|||
listViewModel.deleteCallLogGroups(list)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,9 @@ import org.linphone.utils.Event
|
|||
import org.linphone.utils.LinphoneUtils
|
||||
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 {
|
||||
LinphoneUtils.getDisplayableAddress(callLog.remoteAddress)
|
||||
}
|
||||
|
@ -77,7 +79,10 @@ class CallLogViewModel(val callLog: CallLog, private val isRelated: Boolean = fa
|
|||
}
|
||||
|
||||
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()
|
||||
cal[0, 0, 0, 0, 0] = callLog.duration
|
||||
dateFormat.format(cal.time)
|
||||
|
@ -101,14 +106,25 @@ class CallLogViewModel(val callLog: CallLog, private val isRelated: Boolean = fa
|
|||
|
||||
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>>()
|
||||
|
||||
private val listener = object : CoreListenerStub() {
|
||||
override fun onCallLogUpdated(core: Core, log: CallLog) {
|
||||
if (callLog.remoteAddress.weakEqual(log.remoteAddress) && callLog.localAddress.weakEqual(log.localAddress)) {
|
||||
Log.i("[History Detail] New call log for ${callLog.remoteAddress.asStringUriOnly()} with local address ${callLog.localAddress.asStringUriOnly()}")
|
||||
if (callLog.remoteAddress.weakEqual(log.remoteAddress) && callLog.localAddress.weakEqual(
|
||||
log.localAddress
|
||||
)
|
||||
) {
|
||||
Log.i(
|
||||
"[History Detail] New call log for ${callLog.remoteAddress.asStringUriOnly()} with local address ${callLog.localAddress.asStringUriOnly()}"
|
||||
)
|
||||
addRelatedCallLogs(arrayListOf(log))
|
||||
}
|
||||
}
|
||||
|
@ -161,11 +177,21 @@ class CallLogViewModel(val callLog: CallLog, private val isRelated: Boolean = fa
|
|||
val organizer = conferenceInfo.organizer
|
||||
if (organizer != null) {
|
||||
organizerParticipantData.value =
|
||||
ConferenceSchedulingParticipantData(organizer, showLimeBadge = false, showDivider = false)
|
||||
ConferenceSchedulingParticipantData(
|
||||
organizer,
|
||||
showLimeBadge = false,
|
||||
showDivider = false
|
||||
)
|
||||
}
|
||||
val list = arrayListOf<ConferenceSchedulingParticipantData>()
|
||||
for (participant in conferenceInfo.participants) {
|
||||
list.add(ConferenceSchedulingParticipantData(participant, showLimeBadge = false, showDivider = true))
|
||||
list.add(
|
||||
ConferenceSchedulingParticipantData(
|
||||
participant,
|
||||
showLimeBadge = false,
|
||||
showDivider = true
|
||||
)
|
||||
)
|
||||
}
|
||||
conferenceParticipantsData.value = list
|
||||
}
|
||||
|
@ -205,7 +231,9 @@ class CallLogViewModel(val callLog: CallLog, private val isRelated: Boolean = fa
|
|||
}
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -153,7 +153,11 @@ class CallLogsListViewModel : ViewModel() {
|
|||
|
||||
callLogs.value = when (filter.value) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,11 @@ import org.linphone.utils.*
|
|||
class RecordingsListAdapter(
|
||||
selectionVM: ListTopBarViewModel,
|
||||
private val viewLifecycleOwner: LifecycleOwner
|
||||
) : SelectionListAdapter<RecordingData, RecyclerView.ViewHolder>(selectionVM, RecordingDiffCallback()), HeaderAdapter {
|
||||
) : SelectionListAdapter<RecordingData, RecyclerView.ViewHolder>(
|
||||
selectionVM,
|
||||
RecordingDiffCallback()
|
||||
),
|
||||
HeaderAdapter {
|
||||
|
||||
private lateinit var videoSurface: TextureView
|
||||
|
||||
|
@ -50,7 +54,9 @@ class RecordingsListAdapter(
|
|||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val binding: RecordingListCellBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
R.layout.recording_list_cell, parent, false
|
||||
R.layout.recording_list_cell,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
@ -104,7 +110,9 @@ class RecordingsListAdapter(
|
|||
return if (previousPosition >= 0) {
|
||||
val previousItemDate = getItem(previousPosition).date
|
||||
!TimestampUtils.isSameDay(date, previousItemDate)
|
||||
} else true
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun getHeaderViewForPosition(context: Context, position: Int): View {
|
||||
|
@ -112,7 +120,9 @@ class RecordingsListAdapter(
|
|||
val date = formatDate(context, recording.date.time)
|
||||
val binding: GenericListHeaderBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(context),
|
||||
R.layout.generic_list_header, null, false
|
||||
R.layout.generic_list_header,
|
||||
null,
|
||||
false
|
||||
)
|
||||
binding.title = date
|
||||
binding.executePendingBindings()
|
||||
|
@ -126,7 +136,13 @@ class RecordingsListAdapter(
|
|||
} else if (TimestampUtils.isYesterday(date, false)) {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
val localPlayer = coreContext.core.createLocalPlayer(playbackSoundCard, null, null)
|
||||
if (localPlayer != null) player = localPlayer
|
||||
else Log.e("[Recording] Couldn't create local player!")
|
||||
if (localPlayer != null) {
|
||||
player = localPlayer
|
||||
} else {
|
||||
Log.e("[Recording] Couldn't create local player!")
|
||||
}
|
||||
player.addListener(listener)
|
||||
|
||||
player.open(path)
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,9 @@ class RecordingsFragment : MasterFragment<RecordingsFragmentBinding, RecordingsL
|
|||
binding.recordingsList.layoutManager = layoutManager
|
||||
|
||||
// Divider between items
|
||||
binding.recordingsList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager))
|
||||
binding.recordingsList.addItemDecoration(
|
||||
AppUtils.getDividerDecoration(requireContext(), layoutManager)
|
||||
)
|
||||
|
||||
// Displays the first letter header
|
||||
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter)
|
||||
|
@ -90,7 +92,9 @@ class RecordingsFragment : MasterFragment<RecordingsFragmentBinding, RecordingsL
|
|||
intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.recordings_export))
|
||||
|
||||
try {
|
||||
requireActivity().startActivity(Intent.createChooser(intent, getString(R.string.recordings_export)))
|
||||
requireActivity().startActivity(
|
||||
Intent.createChooser(intent, getString(R.string.recordings_export))
|
||||
)
|
||||
} catch (anfe: ActivityNotFoundException) {
|
||||
Log.e(anfe)
|
||||
}
|
||||
|
@ -136,7 +140,9 @@ class RecordingsFragment : MasterFragment<RecordingsFragmentBinding, RecordingsL
|
|||
if (this::viewModel.isInitialized) {
|
||||
viewModel.updateRecordingsList()
|
||||
} 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()
|
||||
}
|
||||
|
|
|
@ -68,7 +68,9 @@ class AccountSettingsFragment : GenericSettingFragment<SettingsAccountFragmentBi
|
|||
it.consume {
|
||||
val authInfo = viewModel.account.findAuthInfo()
|
||||
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 {
|
||||
val args = Bundle()
|
||||
args.putString("Username", authInfo.username)
|
||||
|
@ -109,11 +111,21 @@ class AccountSettingsFragment : GenericSettingFragment<SettingsAccountFragmentBi
|
|||
) {
|
||||
it.consume {
|
||||
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) {
|
||||
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 {
|
||||
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)
|
||||
|
||||
|
|
|
@ -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(
|
||||
viewLifecycleOwner
|
||||
) {
|
||||
|
|
|
@ -53,7 +53,9 @@ class AudioSettingsFragment : GenericSettingFragment<SettingsAudioFragmentBindin
|
|||
viewLifecycleOwner
|
||||
) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +99,12 @@ class AudioSettingsFragment : GenericSettingFragment<SettingsAudioFragmentBindin
|
|||
private fun initAudioCodecsList() {
|
||||
val list = arrayListOf<ViewDataBinding>()
|
||||
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.subtitle, "${payload.clockRate} Hz")
|
||||
binding.setVariable(BR.checked, payload.enabled())
|
||||
|
|
|
@ -99,7 +99,9 @@ class CallSettingsFragment : GenericSettingFragment<SettingsCallFragmentBinding>
|
|||
updateTelecomManagerAccount()
|
||||
}
|
||||
} 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())) {
|
||||
TelecomHelper.create(requireContext())
|
||||
} 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()
|
||||
|
@ -168,7 +172,9 @@ class CallSettingsFragment : GenericSettingFragment<SettingsCallFragmentBinding>
|
|||
for (index in grantResults.indices) {
|
||||
val result = grantResults[index]
|
||||
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
|
||||
corePreferences.useTelecomManager = false
|
||||
return
|
||||
|
@ -179,7 +185,9 @@ class CallSettingsFragment : GenericSettingFragment<SettingsCallFragmentBinding>
|
|||
TelecomHelper.create(requireContext())
|
||||
updateTelecomManagerAccount()
|
||||
} 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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,9 @@ class ContactsSettingsFragment : GenericSettingFragment<SettingsContactsFragment
|
|||
viewLifecycleOwner
|
||||
) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,9 @@ class SettingsFragment : SecureFragment<SettingsFragmentBinding>() {
|
|||
val navHostFragment =
|
||||
childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment
|
||||
if (navHostFragment.navController.currentDestination?.id == R.id.emptySettingsFragment) {
|
||||
Log.i("[Settings] Foldable device has been folded, closing side pane with empty fragment")
|
||||
Log.i(
|
||||
"[Settings] Foldable device has been folded, closing side pane with empty fragment"
|
||||
)
|
||||
binding.slidingPane.closePane()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,12 @@ class VideoSettingsFragment : GenericSettingFragment<SettingsVideoFragmentBindin
|
|||
private fun initVideoCodecsList() {
|
||||
val list = arrayListOf<ViewDataBinding>()
|
||||
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_subtitle, "")
|
||||
binding.setVariable(BR.text_title, "recv-fmtp")
|
||||
|
|
|
@ -89,7 +89,9 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
|
|||
message: String
|
||||
) {
|
||||
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
|
||||
deleteAccount(account)
|
||||
} else {
|
||||
|
@ -121,7 +123,9 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
|
|||
params.identityAddress = newIdentityAddress
|
||||
account.params = params
|
||||
} 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
|
||||
if (identity != null && identity.username != null) {
|
||||
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)
|
||||
} 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() {
|
||||
override fun onTextValueChanged(newValue: String) {
|
||||
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
|
||||
account.params = params
|
||||
}
|
||||
|
@ -405,7 +420,9 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
|
|||
override fun onTextValueChanged(newValue: String) {
|
||||
val params = account.params.clone()
|
||||
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
|
||||
account.params = params
|
||||
}
|
||||
|
@ -510,7 +527,9 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
|
|||
}
|
||||
|
||||
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
|
||||
|
||||
val registered = account.state == RegistrationState.Ok
|
||||
|
@ -521,7 +540,9 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
|
|||
for (accountIterator in core.accountList) {
|
||||
if (account != 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
|
||||
}
|
||||
}
|
||||
|
@ -532,10 +553,14 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
|
|||
account.params = params
|
||||
|
||||
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)
|
||||
} 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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,9 @@ class AudioSettingsViewModel : GenericSettingsViewModel() {
|
|||
core.isEchoCancellationEnabled = newValue
|
||||
if (!newValue) {
|
||||
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
|
||||
adaptiveRateControl.value = core.isAdaptiveRateControlEnabled
|
||||
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 {
|
||||
prefs.getString(R.string.audio_settings_echo_canceller_calibration_summary)
|
||||
}
|
||||
|
@ -183,7 +187,9 @@ class AudioSettingsViewModel : GenericSettingsViewModel() {
|
|||
|
||||
core.addListener(listener)
|
||||
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) {
|
||||
|
@ -191,31 +197,43 @@ class AudioSettingsViewModel : GenericSettingsViewModel() {
|
|||
|
||||
when (status) {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
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() {
|
||||
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)
|
||||
}
|
||||
|
||||
fun stopEchoTester() {
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
|
@ -291,7 +291,9 @@ class CallSettingsViewModel : GenericSettingsViewModel() {
|
|||
}
|
||||
if (core.mediaEncryptionSupported(MediaEncryption.ZRTP)) {
|
||||
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 {
|
||||
labels.add(prefs.getString(R.string.call_settings_media_encryption_zrtp))
|
||||
}
|
||||
|
|
|
@ -123,7 +123,9 @@ class LdapSettingsViewModel(private val ldap: Ldap, val index: String) : Generic
|
|||
val ldapCertCheckListener = object : SettingListenerStub() {
|
||||
override fun onListValueChanged(position: Int) {
|
||||
val params = ldap.params.clone()
|
||||
params.serverCertificatesVerificationMode = LdapCertVerificationMode.fromInt(ldapCertCheckValues[position])
|
||||
params.serverCertificatesVerificationMode = LdapCertVerificationMode.fromInt(
|
||||
ldapCertCheckValues[position]
|
||||
)
|
||||
ldap.params = params
|
||||
ldapCertCheckIndex.value = position
|
||||
}
|
||||
|
@ -291,6 +293,8 @@ class LdapSettingsViewModel(private val ldap: Ldap, val index: String) : Generic
|
|||
ldapCertCheckValues.add(LdapCertVerificationMode.Enabled.toInt())
|
||||
|
||||
ldapCertCheckLabels.value = labels
|
||||
ldapCertCheckIndex.value = ldapCertCheckValues.indexOf(ldap.params.serverCertificatesVerificationMode.toInt())
|
||||
ldapCertCheckIndex.value = ldapCertCheckValues.indexOf(
|
||||
ldap.params.serverCertificatesVerificationMode.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,7 +143,9 @@ class VideoSettingsViewModel : GenericSettingsViewModel() {
|
|||
val index = labels.indexOf(core.videoDevice)
|
||||
if (index == -1) {
|
||||
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) {
|
||||
cameraDeviceIndex.value = 0
|
||||
core.videoDevice = firstDevice
|
||||
|
|
|
@ -142,7 +142,10 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
|
|||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
lifecycleScope.launch {
|
||||
val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(data, temporaryPicturePath)
|
||||
val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(
|
||||
data,
|
||||
temporaryPicturePath
|
||||
)
|
||||
if (contactImageFilePath != null) {
|
||||
viewModel.setPictureFromPath(contactImageFilePath)
|
||||
}
|
||||
|
@ -176,7 +179,10 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
|
|||
|
||||
val chooserIntent =
|
||||
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)
|
||||
}
|
||||
|
@ -186,11 +192,15 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
|
|||
goToAccountSettings: Boolean = false,
|
||||
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.iconResource = R.drawable.security_toggle_icon_green
|
||||
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)
|
||||
|
||||
dialogViewModel.showCancelButton {
|
||||
|
@ -206,13 +216,19 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
|
|||
} else {
|
||||
val authInfo = defaultAccount.findAuthInfo()
|
||||
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)
|
||||
} else {
|
||||
val expectedHash = authInfo.ha1
|
||||
if (expectedHash == null) {
|
||||
Log.e("[Side Menu] No ha1 found in auth info, can't check password input!")
|
||||
(requireActivity() as MainActivity).showSnackBar(R.string.error_unexpected)
|
||||
Log.e(
|
||||
"[Side Menu] No ha1 found in auth info, can't check password input!"
|
||||
)
|
||||
(requireActivity() as MainActivity).showSnackBar(
|
||||
R.string.error_unexpected
|
||||
)
|
||||
} else {
|
||||
val hashAlgorithm = authInfo.algorithm ?: "MD5"
|
||||
val userId = (authInfo.userid ?: authInfo.username).orEmpty()
|
||||
|
@ -225,8 +241,12 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
|
|||
hashAlgorithm
|
||||
)
|
||||
if (computedHash != expectedHash) {
|
||||
Log.e("[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)
|
||||
Log.e(
|
||||
"[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 {
|
||||
if (goToSettings) {
|
||||
navigateToSettings()
|
||||
|
|
|
@ -41,7 +41,10 @@ class TabsViewModel : ViewModel() {
|
|||
val chatUnreadCountTranslateY = MutableLiveData<Float>()
|
||||
|
||||
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 {
|
||||
val value = it.animatedValue as Float
|
||||
historyMissedCountTranslateY.value = -value
|
||||
|
|
|
@ -104,14 +104,20 @@ class CallActivity : ProximitySensorActivity() {
|
|||
controlsViewModel.proximitySensorEnabled.observe(
|
||||
this
|
||||
) { 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)
|
||||
}
|
||||
|
||||
controlsViewModel.isVideoEnabled.observe(
|
||||
this
|
||||
) { enabled ->
|
||||
Compatibility.enableAutoEnterPiP(this, enabled, conferenceViewModel.conferenceExists.value == true)
|
||||
Compatibility.enableAutoEnterPiP(
|
||||
this,
|
||||
enabled,
|
||||
conferenceViewModel.conferenceExists.value == true
|
||||
)
|
||||
}
|
||||
|
||||
controlsViewModel.callStatsVisible.observe(
|
||||
|
@ -136,10 +142,14 @@ class CallActivity : ProximitySensorActivity() {
|
|||
) { callData ->
|
||||
val call = callData.call
|
||||
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()
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
|
@ -157,10 +167,14 @@ class CallActivity : ProximitySensorActivity() {
|
|||
this
|
||||
) { 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()
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
|
@ -193,7 +207,9 @@ class CallActivity : ProximitySensorActivity() {
|
|||
) {
|
||||
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) {
|
||||
// To hide UI except for TextureViews
|
||||
controlsViewModel.pipMode.value = isInPictureInPictureMode
|
||||
|
@ -299,7 +315,9 @@ class CallActivity : ProximitySensorActivity() {
|
|||
Log.i("[Call Activity] BLUETOOTH_CONNECT permission has been 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()
|
||||
}
|
||||
}
|
||||
|
@ -310,7 +328,9 @@ class CallActivity : ProximitySensorActivity() {
|
|||
|
||||
override fun onLayoutChanges(foldingFeature: FoldingFeature?) {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -101,7 +101,10 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
|
|||
scope.launch {
|
||||
if (Compatibility.addImageToMediaStore(coreContext.context, content)) {
|
||||
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()
|
||||
} else {
|
||||
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 -> {
|
||||
val conference = call.conference
|
||||
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
|
||||
} else {
|
||||
true
|
||||
|
@ -237,7 +242,9 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
|
|||
if (conference != null) {
|
||||
Log.d("[Call] Found conference attached to call")
|
||||
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>()
|
||||
for (participant in conference.participantList) {
|
||||
|
@ -246,12 +253,23 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
|
|||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
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
|
||||
|
||||
val participantsList = arrayListOf<ConferenceInfoParticipantData>()
|
||||
|
@ -271,7 +289,10 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
|
|||
}
|
||||
|
||||
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
Loading…
Add table
Add a link
Reference in a new issue