Cleaned up code, fixed contact avatar blinking when toggling selection in list while creating chat room/conference
This commit is contained in:
parent
4e0d38ca35
commit
5dfc18a78d
17 changed files with 74 additions and 74 deletions
|
@ -313,7 +313,7 @@ class ChatMessagesListAdapter(
|
||||||
popupView.addToContactsHidden = true
|
popupView.addToContactsHidden = true
|
||||||
totalSize -= itemSize
|
totalSize -= itemSize
|
||||||
}
|
}
|
||||||
if (chatMessage.chatRoom.isReadOnly()) {
|
if (chatMessage.chatRoom.isReadOnly) {
|
||||||
popupView.replyHidden = true
|
popupView.replyHidden = true
|
||||||
totalSize -= itemSize
|
totalSize -= itemSize
|
||||||
}
|
}
|
||||||
|
|
|
@ -402,12 +402,16 @@ class ChatMessageContentData(
|
||||||
var earpieceCard: String? = null
|
var earpieceCard: String? = null
|
||||||
for (device in coreContext.core.audioDevices) {
|
for (device in coreContext.core.audioDevices) {
|
||||||
if (device.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) {
|
if (device.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) {
|
||||||
if (device.type == AudioDevice.Type.Speaker) {
|
when (device.type) {
|
||||||
speakerCard = device.id
|
AudioDevice.Type.Speaker -> {
|
||||||
} else if (device.type == AudioDevice.Type.Earpiece) {
|
speakerCard = device.id
|
||||||
earpieceCard = device.id
|
}
|
||||||
} else if (device.type == AudioDevice.Type.Headphones || device.type == AudioDevice.Type.Headset) {
|
AudioDevice.Type.Earpiece -> {
|
||||||
headphonesCard = device.id
|
earpieceCard = device.id
|
||||||
|
}
|
||||||
|
AudioDevice.Type.Headphones, AudioDevice.Type.Headset -> {
|
||||||
|
headphonesCard = device.id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,4 @@ class ImdnParticipantData(val imdnState: ParticipantImdnState) : GenericContactD
|
||||||
val sipUri: String = imdnState.participant.address.asStringUriOnly()
|
val sipUri: String = imdnState.participant.address.asStringUriOnly()
|
||||||
|
|
||||||
val time: String = TimestampUtils.toString(imdnState.stateChangeTime)
|
val time: String = TimestampUtils.toString(imdnState.stateChangeTime)
|
||||||
|
|
||||||
override fun destroy() {
|
|
||||||
super.destroy()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ class GroupInfoFragment : SecureFragment<ChatRoomGroupInfoFragmentBinding>() {
|
||||||
|
|
||||||
adapter = GroupInfoParticipantsAdapter(
|
adapter = GroupInfoParticipantsAdapter(
|
||||||
viewLifecycleOwner,
|
viewLifecycleOwner,
|
||||||
chatRoom?.hasCapability(ChatRoomCapabilities.Encrypted.toInt()) ?: viewModel.isEncrypted.value == true
|
chatRoom?.hasCapability(ChatRoomCapabilities.Encrypted.toInt()) ?: (viewModel.isEncrypted.value == true)
|
||||||
)
|
)
|
||||||
binding.participants.adapter = adapter
|
binding.participants.adapter = adapter
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
||||||
private val chatRoomListener: ChatRoomListenerStub = object : ChatRoomListenerStub() {
|
private val chatRoomListener: ChatRoomListenerStub = object : ChatRoomListenerStub() {
|
||||||
override fun onStateChanged(chatRoom: ChatRoom, state: ChatRoom.State) {
|
override fun onStateChanged(chatRoom: ChatRoom, state: ChatRoom.State) {
|
||||||
if (state == ChatRoom.State.Created || state == ChatRoom.State.Terminated) {
|
if (state == ChatRoom.State.Created || state == ChatRoom.State.Terminated) {
|
||||||
isReadOnly.value = chatRoom.isReadOnly()
|
isReadOnly.value = chatRoom.isReadOnly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -457,12 +457,16 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
||||||
var earpieceCard: String? = null
|
var earpieceCard: String? = null
|
||||||
for (device in coreContext.core.audioDevices) {
|
for (device in coreContext.core.audioDevices) {
|
||||||
if (device.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) {
|
if (device.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) {
|
||||||
if (device.type == AudioDevice.Type.Speaker) {
|
when (device.type) {
|
||||||
speakerCard = device.id
|
AudioDevice.Type.Speaker -> {
|
||||||
} else if (device.type == AudioDevice.Type.Earpiece) {
|
speakerCard = device.id
|
||||||
earpieceCard = device.id
|
}
|
||||||
} else if (device.type == AudioDevice.Type.Headphones || device.type == AudioDevice.Type.Headset) {
|
AudioDevice.Type.Earpiece -> {
|
||||||
headphonesCard = device.id
|
earpieceCard = device.id
|
||||||
|
}
|
||||||
|
AudioDevice.Type.Headphones, AudioDevice.Type.Headset -> {
|
||||||
|
headphonesCard = device.id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,10 +63,6 @@ class ChatRoomCreationViewModel : ContactsSelectionViewModel() {
|
||||||
waitForChatRoomCreation.value = false
|
waitForChatRoomCreation.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
|
||||||
super.onCleared()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateEncryption(encrypted: Boolean) {
|
fun updateEncryption(encrypted: Boolean) {
|
||||||
isEncrypted.value = encrypted
|
isEncrypted.value = encrypted
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,8 +97,8 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : MessageNotifierViewModel() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
subject.value = chatRoom?.subject
|
subject.value = chatRoom?.subject
|
||||||
isMeAdmin.value = chatRoom == null || (chatRoom.me?.isAdmin == true && !chatRoom.isReadOnly())
|
isMeAdmin.value = chatRoom == null || (chatRoom.me?.isAdmin == true && !chatRoom.isReadOnly)
|
||||||
canLeaveGroup.value = chatRoom != null && !chatRoom.isReadOnly()
|
canLeaveGroup.value = chatRoom != null && !chatRoom.isReadOnly
|
||||||
isEncrypted.value = chatRoom?.hasCapability(ChatRoomCapabilities.Encrypted.toInt())
|
isEncrypted.value = chatRoom?.hasCapability(ChatRoomCapabilities.Encrypted.toInt())
|
||||||
|
|
||||||
if (chatRoom != null) updateParticipants()
|
if (chatRoom != null) updateParticipants()
|
||||||
|
|
|
@ -171,12 +171,16 @@ class RecordingData(val path: String, private val recordingListener: RecordingLi
|
||||||
var earpieceCard: String? = null
|
var earpieceCard: String? = null
|
||||||
for (device in coreContext.core.audioDevices) {
|
for (device in coreContext.core.audioDevices) {
|
||||||
if (device.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) {
|
if (device.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) {
|
||||||
if (device.type == AudioDevice.Type.Speaker) {
|
when (device.type) {
|
||||||
speakerCard = device.id
|
AudioDevice.Type.Speaker -> {
|
||||||
} else if (device.type == AudioDevice.Type.Earpiece) {
|
speakerCard = device.id
|
||||||
earpieceCard = device.id
|
}
|
||||||
} else if (device.type == AudioDevice.Type.Headphones || device.type == AudioDevice.Type.Headset) {
|
AudioDevice.Type.Earpiece -> {
|
||||||
headphonesCard = device.id
|
earpieceCard = device.id
|
||||||
|
}
|
||||||
|
AudioDevice.Type.Headphones, AudioDevice.Type.Headset -> {
|
||||||
|
headphonesCard = device.id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ open class StatusViewModel : ViewModel() {
|
||||||
if (body.type == "application" && body.subtype == "simple-message-summary" && body.size > 0) {
|
if (body.type == "application" && body.subtype == "simple-message-summary" && body.size > 0) {
|
||||||
val data = body.utf8Text?.lowercase(Locale.getDefault())
|
val data = body.utf8Text?.lowercase(Locale.getDefault())
|
||||||
val voiceMail = data?.split("voice-message: ")
|
val voiceMail = data?.split("voice-message: ")
|
||||||
if (voiceMail?.size ?: 0 >= 2) {
|
if ((voiceMail?.size ?: 0) >= 2) {
|
||||||
val toParse = voiceMail!![1].split("/", limit = 0)
|
val toParse = voiceMail!![1].split("/", limit = 0)
|
||||||
try {
|
try {
|
||||||
val unreadCount: Int = toParse[0].toInt()
|
val unreadCount: Int = toParse[0].toInt()
|
||||||
|
|
|
@ -26,6 +26,7 @@ import androidx.navigation.navGraphViewModels
|
||||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
import org.linphone.R
|
import org.linphone.R
|
||||||
import org.linphone.activities.GenericFragment
|
import org.linphone.activities.GenericFragment
|
||||||
|
import org.linphone.activities.navigateToAddParticipants
|
||||||
import org.linphone.activities.voip.viewmodels.ConferenceViewModel
|
import org.linphone.activities.voip.viewmodels.ConferenceViewModel
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
import org.linphone.databinding.VoipConferenceParticipantsFragmentBinding
|
import org.linphone.databinding.VoipConferenceParticipantsFragmentBinding
|
||||||
|
@ -76,7 +77,7 @@ class ConferenceParticipantsFragment : GenericFragment<VoipConferenceParticipant
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.setEditClickListener {
|
binding.setEditClickListener {
|
||||||
// TODO: go to conferences view outside of call activity in edition mode
|
navigateToAddParticipants()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,8 +40,8 @@ class GridBoxLayout : GridLayout {
|
||||||
)
|
)
|
||||||
|
|
||||||
var centerContent: Boolean = false
|
var centerContent: Boolean = false
|
||||||
var previousChildCount = 0
|
private var previousChildCount = 0
|
||||||
var previousCellSize = 0
|
private var previousCellSize = 0
|
||||||
|
|
||||||
@SuppressLint("DrawAllocation")
|
@SuppressLint("DrawAllocation")
|
||||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||||
|
|
|
@ -169,7 +169,7 @@ class HorizontalScrollDotsView : View {
|
||||||
setMeasuredDimension(width, height)
|
setMeasuredDimension(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDotCount(count: Int) {
|
private fun setDotCount(count: Int) {
|
||||||
this.count = count
|
this.count = count
|
||||||
requestLayout()
|
requestLayout()
|
||||||
invalidate()
|
invalidate()
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
*/
|
*/
|
||||||
package org.linphone.contact
|
package org.linphone.contact
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
|
@ -41,25 +40,26 @@ class ContactsSelectionAdapter(
|
||||||
) : ListAdapter<SearchResult, RecyclerView.ViewHolder>(SearchResultDiffCallback()) {
|
) : ListAdapter<SearchResult, RecyclerView.ViewHolder>(SearchResultDiffCallback()) {
|
||||||
val selectedContact = MutableLiveData<Event<SearchResult>>()
|
val selectedContact = MutableLiveData<Event<SearchResult>>()
|
||||||
|
|
||||||
private var selectedAddresses = ArrayList<Address>()
|
private val selection = MutableLiveData<List<Address>>()
|
||||||
|
|
||||||
private var requireGroupChatCapability: Boolean = false
|
private val requireGroupChatCapability = MutableLiveData<Boolean>()
|
||||||
private var requireLimeCapability: Boolean = false
|
private var requireLimeCapability = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
init {
|
||||||
fun updateSelectedAddresses(selection: ArrayList<Address>) {
|
requireGroupChatCapability.value = false
|
||||||
selectedAddresses = selection
|
requireLimeCapability.value = false
|
||||||
notifyDataSetChanged()
|
}
|
||||||
|
|
||||||
|
fun updateSelectedAddresses(selectedAddresses: List<Address>) {
|
||||||
|
selection.value = selectedAddresses
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
fun setLimeCapabilityRequired(enabled: Boolean) {
|
fun setLimeCapabilityRequired(enabled: Boolean) {
|
||||||
requireLimeCapability = enabled
|
requireLimeCapability.value = enabled
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setGroupChatCapabilityRequired(enabled: Boolean) {
|
fun setGroupChatCapabilityRequired(enabled: Boolean) {
|
||||||
requireGroupChatCapability = enabled
|
requireGroupChatCapability.value = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
@ -84,13 +84,20 @@ class ContactsSelectionAdapter(
|
||||||
|
|
||||||
lifecycleOwner = viewLifecycleOwner
|
lifecycleOwner = viewLifecycleOwner
|
||||||
|
|
||||||
updateSecurity(searchResult, searchResultViewModel, requireLimeCapability)
|
requireLimeCapability.observe(viewLifecycleOwner) {
|
||||||
|
updateSecurity(searchResult, searchResultViewModel)
|
||||||
val selected = selectedAddresses.find { address ->
|
}
|
||||||
val searchAddress = searchResult.address
|
requireGroupChatCapability.observe(viewLifecycleOwner) {
|
||||||
if (searchAddress != null) address.weakEqual(searchAddress) else false
|
updateSecurity(searchResult, searchResultViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
selection.observe(viewLifecycleOwner) { selectedAddresses ->
|
||||||
|
val selected = selectedAddresses.find { address ->
|
||||||
|
val searchAddress = searchResult.address
|
||||||
|
if (searchAddress != null) address.weakEqual(searchAddress) else false
|
||||||
|
}
|
||||||
|
searchResultViewModel.isSelected.value = selected != null
|
||||||
}
|
}
|
||||||
searchResultViewModel.isSelected.value = selected != null
|
|
||||||
|
|
||||||
setClickListener {
|
setClickListener {
|
||||||
selectedContact.value = Event(searchResult)
|
selectedContact.value = Event(searchResult)
|
||||||
|
@ -102,13 +109,14 @@ class ContactsSelectionAdapter(
|
||||||
|
|
||||||
private fun updateSecurity(
|
private fun updateSecurity(
|
||||||
searchResult: SearchResult,
|
searchResult: SearchResult,
|
||||||
viewModel: ContactSelectionData,
|
viewModel: ContactSelectionData
|
||||||
securityEnabled: Boolean
|
|
||||||
) {
|
) {
|
||||||
|
val securityEnabled = requireLimeCapability.value ?: false
|
||||||
|
val groupCapabilityRequired = requireGroupChatCapability.value ?: false
|
||||||
val searchAddress = searchResult.address
|
val searchAddress = searchResult.address
|
||||||
val isMyself = securityEnabled && searchAddress != null && coreContext.core.defaultAccount?.params?.identityAddress?.weakEqual(searchAddress) ?: false
|
val isMyself = securityEnabled && searchAddress != null && coreContext.core.defaultAccount?.params?.identityAddress?.weakEqual(searchAddress) ?: false
|
||||||
val limeCheck = !securityEnabled || (securityEnabled && searchResult.hasCapability(FriendCapability.LimeX3Dh))
|
val limeCheck = !securityEnabled || (securityEnabled && searchResult.hasCapability(FriendCapability.LimeX3Dh))
|
||||||
val groupCheck = !requireGroupChatCapability || (requireGroupChatCapability && searchResult.hasCapability(FriendCapability.GroupChat))
|
val groupCheck = !groupCapabilityRequired || (groupCapabilityRequired && searchResult.hasCapability(FriendCapability.GroupChat))
|
||||||
val disabled = if (searchResult.friend != null) !limeCheck || !groupCheck || isMyself else false // Generated entry from search filter
|
val disabled = if (searchResult.friend != null) !limeCheck || !groupCheck || isMyself else false // Generated entry from search filter
|
||||||
|
|
||||||
viewModel.isDisabled.value = disabled
|
viewModel.isDisabled.value = disabled
|
||||||
|
|
|
@ -97,7 +97,7 @@ open class ContactsSelectionViewModel : MessageNotifierViewModel() {
|
||||||
val domain = if (sipContactsSelected.value == true) coreContext.core.defaultAccount?.params?.domain ?: "" else ""
|
val domain = if (sipContactsSelected.value == true) coreContext.core.defaultAccount?.params?.domain ?: "" else ""
|
||||||
searchResultsPending = true
|
searchResultsPending = true
|
||||||
fastFetchJob?.cancel()
|
fastFetchJob?.cancel()
|
||||||
coreContext.contactsManager.magicSearch.getContactsAsync(filter.value.orEmpty(), domain, MagicSearchSource.All.toInt())
|
coreContext.contactsManager.magicSearch.getContactsListAsync(filter.value.orEmpty(), domain, MagicSearchSource.All.toInt(), MagicSearchAggregation.None)
|
||||||
|
|
||||||
val spinnerDelay = corePreferences.delayBeforeShowingContactsSearchSpinner.toLong()
|
val spinnerDelay = corePreferences.delayBeforeShowingContactsSearchSpinner.toLong()
|
||||||
fastFetchJob = viewModelScope.launch {
|
fastFetchJob = viewModelScope.launch {
|
||||||
|
|
|
@ -22,7 +22,6 @@ package org.linphone.utils
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
|
@ -65,11 +64,6 @@ fun ImageView.setSourceImageResource(resource: Int) {
|
||||||
this.setImageResource(resource)
|
this.setImageResource(resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
@BindingAdapter("android:src")
|
|
||||||
fun ImageView.setSourceImageBitmap(bitmap: Bitmap?) {
|
|
||||||
if (bitmap != null) this.setImageBitmap(bitmap)
|
|
||||||
}
|
|
||||||
|
|
||||||
@BindingAdapter("android:contentDescription")
|
@BindingAdapter("android:contentDescription")
|
||||||
fun ImageView.setContentDescription(resource: Int) {
|
fun ImageView.setContentDescription(resource: Int) {
|
||||||
if (resource == 0) {
|
if (resource == 0) {
|
||||||
|
@ -116,13 +110,6 @@ fun setRightMargin(view: View, margin: Float) {
|
||||||
view.layoutParams = layoutParams
|
view.layoutParams = layoutParams
|
||||||
}
|
}
|
||||||
|
|
||||||
@BindingAdapter("android:layout_weight")
|
|
||||||
fun setLayoutWeight(view: View, weight: Float) {
|
|
||||||
val layoutParams = view.layoutParams as LinearLayout.LayoutParams
|
|
||||||
layoutParams.weight = weight
|
|
||||||
view.layoutParams = layoutParams
|
|
||||||
}
|
|
||||||
|
|
||||||
@BindingAdapter("android:layout_alignLeft")
|
@BindingAdapter("android:layout_alignLeft")
|
||||||
fun setLayoutLeftAlign(view: View, oldTargetId: Int, newTargetId: Int) {
|
fun setLayoutLeftAlign(view: View, oldTargetId: Int, newTargetId: Int) {
|
||||||
val layoutParams = view.layoutParams as RelativeLayout.LayoutParams
|
val layoutParams = view.layoutParams as RelativeLayout.LayoutParams
|
||||||
|
@ -494,7 +481,7 @@ fun addUsernameEditTextValidation(editText: EditText, enabled: Boolean) {
|
||||||
s?.matches(Regex(usernameRegexp)) == false ->
|
s?.matches(Regex(usernameRegexp)) == false ->
|
||||||
editText.error =
|
editText.error =
|
||||||
editText.context.getString(R.string.assistant_error_username_invalid_characters)
|
editText.context.getString(R.string.assistant_error_username_invalid_characters)
|
||||||
s?.length ?: 0 > usernameMaxLength -> {
|
(s?.length ?: 0) > usernameMaxLength -> {
|
||||||
editText.error =
|
editText.error =
|
||||||
editText.context.getString(R.string.assistant_error_username_too_long)
|
editText.context.getString(R.string.assistant_error_username_too_long)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import android.widget.TextView
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
class PatternClickableSpan {
|
class PatternClickableSpan {
|
||||||
var patterns: ArrayList<SpannablePatternItem> = ArrayList()
|
private var patterns: ArrayList<SpannablePatternItem> = ArrayList()
|
||||||
|
|
||||||
inner class SpannablePatternItem(
|
inner class SpannablePatternItem(
|
||||||
var pattern: Pattern,
|
var pattern: Pattern,
|
||||||
|
|
|
@ -15,7 +15,7 @@ buildscript {
|
||||||
classpath 'com.google.gms:google-services:4.3.10'
|
classpath 'com.google.gms:google-services:4.3.10'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21"
|
||||||
classpath "org.jlleitschuh.gradle:ktlint-gradle:10.1.0"
|
classpath "org.jlleitschuh.gradle:ktlint-gradle:10.1.0"
|
||||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
|
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue