Contacts creator improvements to be able to choose where to store newly created contact

This commit is contained in:
Sylvain Berfini 2020-08-19 16:32:15 +02:00
parent c28f2373d9
commit 03f0f49c71
16 changed files with 329 additions and 98 deletions

View file

@ -0,0 +1,44 @@
package org.linphone.activities.main.contact.adapters
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ImageView
import android.widget.TextView
import kotlin.collections.ArrayList
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
class SyncAccountAdapter : BaseAdapter() {
private var accounts: ArrayList<Triple<String, String, Drawable>> = arrayListOf()
init {
accounts.addAll(coreContext.contactsManager.getAvailableSyncAccounts())
}
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 account = getItem(position)
val icon = view.findViewById<ImageView>(R.id.account_icon)
icon.setImageDrawable(account.third)
val name = view.findViewById<TextView>(R.id.account_name)
name.text = account.first
return view
}
override fun getItem(position: Int): Triple<String, String, Drawable> {
return accounts[position]
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getCount(): Int {
return accounts.size
}
}

View file

@ -47,7 +47,7 @@ import org.linphone.utils.FileUtils
import org.linphone.utils.ImageUtils
import org.linphone.utils.PermissionHelper
class ContactEditorFragment : Fragment() {
class ContactEditorFragment : Fragment(), SyncAccountPickerFragment.SyncAccountPickedListener {
private lateinit var binding: ContactEditorFragmentBinding
private lateinit var viewModel: ContactEditorViewModel
private lateinit var sharedViewModel: SharedMainViewModel
@ -86,13 +86,11 @@ class ContactEditorFragment : Fragment() {
}
binding.setSaveChangesClickListener {
val savedContact = viewModel.save()
if (savedContact is NativeContact) {
savedContact.syncValuesFromAndroidContact(requireContext())
Log.i("[Contact Editor] Displaying contact $savedContact")
navigateToContact(savedContact)
if (viewModel.c == null) {
Log.i("[Contact Editor] New contact, ask user where to store it")
SyncAccountPickerFragment(this).show(childFragmentManager, "SyncAccountPicker")
} else {
findNavController().popBackStack()
saveContact()
}
}
@ -114,6 +112,13 @@ class ContactEditorFragment : Fragment() {
}
}
override fun onSyncAccountClicked(name: String?, type: String?) {
Log.i("[Contact Editor] Using account $name / $type")
viewModel.syncAccountName = name
viewModel.syncAccountType = type
saveContact()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
@ -142,6 +147,17 @@ class ContactEditorFragment : Fragment() {
}
}
private fun saveContact() {
val savedContact = viewModel.save()
if (savedContact is NativeContact) {
savedContact.syncValuesFromAndroidContact(requireContext())
Log.i("[Contact Editor] Displaying contact $savedContact")
navigateToContact(savedContact)
} else {
findNavController().popBackStack()
}
}
private fun pickFile() {
val cameraIntents = ArrayList<Intent>()

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.activities.main.contact.fragments
import android.os.Bundle
import android.view.*
import androidx.fragment.app.DialogFragment
import org.linphone.R
import org.linphone.activities.main.contact.adapters.SyncAccountAdapter
import org.linphone.core.tools.Log
import org.linphone.databinding.ContactSyncAccountPickerFragmentBinding
class SyncAccountPickerFragment(private val listener: SyncAccountPickedListener) : DialogFragment() {
private lateinit var binding: ContactSyncAccountPickerFragmentBinding
private lateinit var adapter: SyncAccountAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.assistant_country_dialog_style)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = ContactSyncAccountPickerFragmentBinding.inflate(inflater, container, false)
adapter = SyncAccountAdapter()
binding.accountsList.adapter = adapter
binding.accountsList.setOnItemClickListener { _, _, position, _ ->
if (position >= 0 && position < adapter.count) {
val account = adapter.getItem(position)
Log.i("[Sync Account Picker] Picked ${account.first} / ${account.second}")
listener.onSyncAccountClicked(account.first, account.second)
}
dismiss()
}
binding.setLocalSyncAccountClickListener {
Log.i("[Sync Account Picker] Picked local account")
listener.onSyncAccountClicked(null, null)
dismiss()
}
return binding.root
}
interface SyncAccountPickedListener {
fun onSyncAccountClicked(name: String?, type: String?)
}
}

View file

@ -29,10 +29,8 @@ import java.io.ByteArrayOutputStream
import java.io.IOException
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.contact.*
import org.linphone.core.tools.Log
import org.linphone.utils.AppUtils
import org.linphone.utils.ImageUtils
import org.linphone.utils.PermissionHelper
@ -66,6 +64,9 @@ class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactViewModelInt
val addresses = MutableLiveData<ArrayList<NumberOrAddressEditorViewModel>>()
var syncAccountName: String? = null
var syncAccountType: String? = null
init {
if (c != null) {
contact.value = c
@ -83,12 +84,7 @@ class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactViewModelInt
if (contact == null) {
created = true
contact = if (PermissionHelper.get().hasWriteContactsPermission()) {
// Store native contact in default sync account
if (corePreferences.storeCreatedContactsInAppSyncAccount) {
NativeContact(NativeContactEditor.createAndroidContact(AppUtils.getString(R.string.sync_account_name), AppUtils.getString(R.string.sync_account_type)).toString())
} else {
NativeContact(NativeContactEditor.createAndroidContact(null, null).toString())
}
NativeContact(NativeContactEditor.createAndroidContact(syncAccountName, syncAccountType).toString())
} else {
Contact()
}

View file

@ -75,7 +75,7 @@ class ContactViewModel(private val c: Contact) : ErrorReportingViewModel(), Cont
override fun onContactUpdated(contact: Contact) {
if (c is NativeContact && contact is NativeContact && c.nativeId == contact.nativeId) {
Log.d("[Contact] $contact has changed")
updateNumbersAndAddresses()
updateNumbersAndAddresses(contact)
}
}
}
@ -122,7 +122,7 @@ class ContactViewModel(private val c: Contact) : ErrorReportingViewModel(), Cont
init {
contact.value = c
updateNumbersAndAddresses()
updateNumbersAndAddresses(c)
coreContext.contactsManager.addListener(contactsUpdatedListener)
waitForChatRoomCreation.value = false
}
@ -162,24 +162,24 @@ class ContactViewModel(private val c: Contact) : ErrorReportingViewModel(), Cont
}
}
private fun updateNumbersAndAddresses() {
private fun updateNumbersAndAddresses(contact: Contact) {
val list = arrayListOf<ContactNumberOrAddressViewModel>()
for (address in c.sipAddresses) {
for (address in contact.sipAddresses) {
val value = address.asStringUriOnly()
val presenceModel = c.friend?.getPresenceModelForUriOrTel(value)
val presenceModel = contact.friend?.getPresenceModelForUriOrTel(value)
val hasPresence = presenceModel?.basicStatus == PresenceBasicStatus.Open
val isMe = coreContext.core.defaultProxyConfig?.identityAddress?.weakEqual(address) ?: false
val secureChatAllowed = !isMe && c.friend?.getPresenceModelForUriOrTel(value)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
val secureChatAllowed = !isMe && contact.friend?.getPresenceModelForUriOrTel(value)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
val noa = ContactNumberOrAddressViewModel(address, hasPresence, LinphoneUtils.getDisplayName(address), showSecureChat = secureChatAllowed, listener = listener)
list.add(noa)
}
for (number in c.phoneNumbers) {
val presenceModel = c.friend?.getPresenceModelForUriOrTel(number)
for (number in contact.phoneNumbers) {
val presenceModel = contact.friend?.getPresenceModelForUriOrTel(number)
val hasPresence = presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open
val contactAddress = presenceModel?.contact ?: number
val address = coreContext.core.interpretUrl(contactAddress)
val isMe = if (address != null) coreContext.core.defaultProxyConfig?.identityAddress?.weakEqual(address) ?: false else false
val secureChatAllowed = !isMe && c.friend?.getPresenceModelForUriOrTel(number)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
val secureChatAllowed = !isMe && contact.friend?.getPresenceModelForUriOrTel(number)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
val noa = ContactNumberOrAddressViewModel(address, hasPresence, number, isSip = false, showSecureChat = secureChatAllowed, listener = listener)
list.add(noa)
}

View file

@ -38,13 +38,6 @@ class ContactsSettingsViewModel : GenericSettingsViewModel() {
}
val friendListSubscribe = MutableLiveData<Boolean>()
val contactStorageAppSyncAccountListener = object : SettingListenerStub() {
override fun onBoolValueChanged(newValue: Boolean) {
prefs.storeCreatedContactsInAppSyncAccount = newValue
}
}
val contactStorageAppSyncAccount = MutableLiveData<Boolean>()
val nativePresenceListener = object : SettingListenerStub() {
override fun onBoolValueChanged(newValue: Boolean) {
if (newValue) {
@ -80,7 +73,6 @@ class ContactsSettingsViewModel : GenericSettingsViewModel() {
readContactsPermissionGranted.value = PermissionHelper.get().hasReadContactsPermission()
friendListSubscribe.value = core.isFriendListSubscriptionEnabled
contactStorageAppSyncAccount.value = prefs.storeCreatedContactsInAppSyncAccount
nativePresence.value = prefs.storePresenceInNativeContact
showOrganization.value = prefs.displayOrganization
launcherShortcuts.value = prefs.contactsShortcuts

View file

@ -163,4 +163,8 @@ open class Contact : Comparable<Contact> {
if (presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open) return presenceModel.contact
return null
}
override fun toString(): String {
return "${super.toString()}: name [$fullName]"
}
}

View file

@ -21,9 +21,11 @@ package org.linphone.contact
import android.accounts.Account
import android.accounts.AccountManager
import android.accounts.AuthenticatorDescription
import android.content.ContentResolver
import android.content.Context
import android.database.ContentObserver
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.AsyncTask
import android.os.AsyncTask.THREAD_POOL_EXECUTOR
@ -193,7 +195,10 @@ class ContactsManager(private val context: Context) {
// Restart contacts async fetching
fetchContactsAsync()
} else {
Log.d("[Contacts Manager] Found contact with id [$id]: ${found?.fullName}")
}
return found
}
@ -249,7 +254,11 @@ class ContactsManager(private val context: Context) {
val accountManager = context.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
val accounts = accountManager.getAccountsByType(context.getString(R.string.sync_account_type))
if (accounts.isEmpty()) {
val newAccount = Account(context.getString(R.string.sync_account_name), context.getString(R.string.sync_account_type))
val newAccount = Account(
context.getString(R.string.sync_account_name), context.getString(
R.string.sync_account_type
)
)
try {
accountManager.addAccountExplicitly(newAccount, null, null)
Log.i("[Contacts Manager] Contact account added")
@ -263,19 +272,28 @@ class ContactsManager(private val context: Context) {
}
}
fun getAvailableSyncAccounts(): List<Pair<String, String>> {
fun getAvailableSyncAccounts(): List<Triple<String, String, Drawable>> {
val accountManager = context.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
val packageManager = context.packageManager
val syncAdapters = ContentResolver.getSyncAdapterTypes()
val available = arrayListOf<Pair<String, String>>()
val authenticators: Array<AuthenticatorDescription> = accountManager.authenticatorTypes
val available = arrayListOf<Triple<String, String, Drawable>>()
for (syncAdapter in syncAdapters) {
if (syncAdapter.authority == "com.android.contacts" && syncAdapter.isUserVisible) {
Log.i("[Contacts Manager] Found sync adapter for com.android.contacts authority: ${syncAdapter.accountType}")
val accounts = accountManager.getAccountsByType(syncAdapter.accountType)
for (account in accounts) {
Log.i("[Contacts Manager] Found account for account type ${syncAdapter.accountType}: ${account.name}")
val pair = Pair(account.name, account.type)
available.add(pair)
if (syncAdapter.supportsUploading() || syncAdapter.accountType == context.getString(R.string.sync_account_type)) {
Log.i("[Contacts Manager] Found sync adapter for com.android.contacts authority: ${syncAdapter.accountType}")
val accounts = accountManager.getAccountsByType(syncAdapter.accountType)
for (account in accounts) {
Log.i("[Contacts Manager] Found account for account type ${syncAdapter.accountType}: ${account.name}")
for (authenticator in authenticators) {
if (authenticator.type == account.type) {
val drawable = packageManager.getDrawable(authenticator.packageName, authenticator.smallIconId, null)
val triple = Triple(account.name, account.type, drawable)
available.add(triple)
}
}
}
}
}
}
@ -337,12 +355,15 @@ class ContactsManager(private val context: Context) {
val sipAddress = contact.getContactForPhoneNumberOrAddress(phoneNumber)
if (sipAddress != null) {
Log.d("[Contacts Manager] Found presence information to store in native contact $contact under Linphone sync account")
val contactEditor = NativeContactEditor(contact, context.getString(R.string.sync_account_name), context.getString(R.string.sync_account_type))
val contactEditor = NativeContactEditor(contact)
val coroutineScope = CoroutineScope(Dispatchers.Main)
coroutineScope.launch {
val deferred = async {
withContext(Dispatchers.IO) {
contactEditor.ensureSyncAccountRawIdExists().setPresenceInformation(phoneNumber, sipAddress).commit()
contactEditor.setPresenceInformation(
phoneNumber,
sipAddress
).commit()
}
}
deferred.await()

View file

@ -227,4 +227,8 @@ class NativeContact(val nativeId: String, private val lookupKey: String? = null)
cursor.close()
}
}
override fun toString(): String {
return "${super.toString()}: id [$nativeId], name [$fullName]"
}
}

View file

@ -21,7 +21,6 @@ package org.linphone.contact
import android.content.ContentProviderOperation
import android.content.ContentUris
import android.content.ContentValues
import android.net.Uri
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds
@ -33,20 +32,33 @@ import org.linphone.core.tools.Log
import org.linphone.utils.AppUtils
import org.linphone.utils.PermissionHelper
class NativeContactEditor(
val contact: NativeContact,
private var syncAccountName: String? = null,
private var syncAccountType: String? = null
) {
class NativeContactEditor(val contact: NativeContact) {
companion object {
fun createAndroidContact(accountName: String?, accountType: String?): Long {
val values = ContentValues()
values.put(RawContacts.ACCOUNT_NAME, accountName)
values.put(RawContacts.ACCOUNT_TYPE, accountType)
Log.i("[Native Contact Editor] Using sync account $accountName with type $accountType")
val rawContactUri = coreContext.context.contentResolver
.insert(RawContacts.CONTENT_URI, values)
return ContentUris.parseId(rawContactUri)
val changes = arrayListOf<ContentProviderOperation>()
changes.add(
ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
.withValue(RawContacts.ACCOUNT_NAME, accountName)
.withValue(RawContacts.ACCOUNT_TYPE, accountType)
.build()
)
val contentResolver = coreContext.context.contentResolver
val results = contentResolver.applyBatch(ContactsContract.AUTHORITY, changes)
for (result in results) {
Log.i("[Native Contact Editor] Contact creation result is ${result.uri}")
val cursor = contentResolver.query(result.uri, arrayOf(RawContacts.CONTACT_ID), null, null, null)
if (cursor != null) {
cursor.moveToNext()
val contactId: Long = cursor.getLong(0)
Log.i("[Native Contact Editor] New contact id is $contactId")
cursor.close()
return contactId
}
}
return 0
}
}
@ -55,8 +67,6 @@ class NativeContactEditor(
"${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} =?"
private val phoneNumberSelection =
"$selection AND (${CommonDataKinds.Phone.NUMBER}=? OR ${CommonDataKinds.Phone.NORMALIZED_NUMBER}=?)"
private val sipAddressSelection =
"${ContactsContract.Data.CONTACT_ID} =? AND (${ContactsContract.Data.MIMETYPE} =? OR ${ContactsContract.Data.MIMETYPE} =?) AND data1=?"
private val presenceUpdateSelection =
"${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} =? AND data3=?"
private val contactUri = ContactsContract.Data.CONTENT_URI
@ -66,11 +76,10 @@ class NativeContactEditor(
private var pictureByteArray: ByteArray? = null
init {
Log.d("[Native Contact Editor] Using sync account $syncAccountName with type $syncAccountType")
val contentResolver = coreContext.context.contentResolver
val cursor = contentResolver.query(
RawContacts.CONTENT_URI,
arrayOf(RawContacts._ID, RawContacts.ACCOUNT_TYPE),
arrayOf(RawContacts._ID),
"${RawContacts.CONTACT_ID} =?",
arrayOf(contact.nativeId),
null
@ -79,20 +88,11 @@ class NativeContactEditor(
do {
if (rawId == null) {
rawId = cursor.getString(cursor.getColumnIndex(RawContacts._ID))
Log.d("[Native Contact Editor] Found raw id $rawId for native contact with id ${contact.nativeId}")
Log.i("[Native Contact Editor] Found raw id $rawId for native contact with id ${contact.nativeId}")
}
val accountType = cursor.getString(cursor.getColumnIndex(RawContacts.ACCOUNT_TYPE))
if (accountType == syncAccountType && syncAccountRawId == null) {
syncAccountRawId = cursor.getString(cursor.getColumnIndex(RawContacts._ID))
Log.d("[Native Contact Editor] Found sync account raw id $syncAccountRawId for native contact with id ${contact.nativeId}")
}
} while (cursor.moveToNext() && syncAccountRawId == null)
} while (cursor.moveToNext() && rawId == null)
}
cursor?.close()
// When contact has been created with NativeContactEditor.createAndroidContact this is required
if (rawId == null) rawId = contact.nativeId
}
fun setFirstAndLastNames(firstName: String, lastName: String): NativeContactEditor {
@ -237,12 +237,34 @@ class NativeContactEditor(
return this
}
fun ensureSyncAccountRawIdExists(): NativeContactEditor {
fun setPresenceInformation(phoneNumber: String, sipAddress: String): NativeContactEditor {
if (syncAccountRawId == null) {
Log.w("[Native Contact Editor] Sync account raw id not found")
val contentResolver = coreContext.context.contentResolver
val cursor = contentResolver.query(
RawContacts.CONTENT_URI,
arrayOf(RawContacts._ID, RawContacts.ACCOUNT_TYPE),
"${RawContacts.CONTACT_ID} =?",
arrayOf(contact.nativeId),
null
)
if (cursor?.moveToFirst() == true) {
do {
val accountType =
cursor.getString(cursor.getColumnIndex(RawContacts.ACCOUNT_TYPE))
if (accountType == AppUtils.getString(R.string.sync_account_type) && syncAccountRawId == null) {
syncAccountRawId = cursor.getString(cursor.getColumnIndex(RawContacts._ID))
Log.d("[Native Contact Editor] Found linphone raw id $syncAccountRawId for native contact with id ${contact.nativeId}")
}
} while (cursor.moveToNext() && syncAccountRawId == null)
}
cursor?.close()
}
if (syncAccountRawId == null) {
Log.w("[Native Contact Editor] Linphone raw id not found")
val insert = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
.withValue(RawContacts.ACCOUNT_TYPE, syncAccountType)
.withValue(RawContacts.ACCOUNT_NAME, syncAccountName)
.withValue(RawContacts.ACCOUNT_NAME, AppUtils.getString(R.string.sync_account_name))
.withValue(RawContacts.ACCOUNT_TYPE, AppUtils.getString(R.string.sync_account_type))
.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT)
.build()
addChanges(insert)
@ -259,12 +281,9 @@ class NativeContactEditor(
)
.build()
addChanges(update)
commit()
commit(true)
}
return this
}
fun setPresenceInformation(phoneNumber: String, sipAddress: String): NativeContactEditor {
if (syncAccountRawId == null) {
Log.e("[Native Contact Editor] Can't add presence to contact in Linphone sync account, no raw id")
return this
@ -275,15 +294,15 @@ class NativeContactEditor(
return this
}
fun commit() {
fun commit(updateSyncAccountRawId: Boolean = false) {
if (PermissionHelper.get().hasWriteContactsPermission()) {
try {
if (changes.isNotEmpty()) {
val contentResolver = coreContext.context.contentResolver
val results = contentResolver.applyBatch(ContactsContract.AUTHORITY, changes)
for (result in results) {
Log.i("[Native Contact Editor] Result is $result")
if (syncAccountRawId == null && result?.uri != null) {
Log.i("[Native Contact Editor] Result is ${result.uri}")
if (updateSyncAccountRawId && syncAccountRawId == null && result?.uri != null) {
syncAccountRawId = ContentUris.parseId(result.uri).toString()
Log.i("[Native Contact Editor] Sync account raw id is $syncAccountRawId")
}
@ -422,7 +441,11 @@ class NativeContactEditor(
ContactsContract.Data.CONTENT_URI,
arrayOf("data1"),
"${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? AND data3 = ?",
arrayOf(syncAccountRawId, AppUtils.getString(R.string.linphone_address_mime_type), phoneNumber),
arrayOf(
syncAccountRawId,
AppUtils.getString(R.string.linphone_address_mime_type),
phoneNumber
),
null
)
val count = cursor?.count ?: 0

View file

@ -119,12 +119,6 @@ class CorePreferences constructor(private val context: Context) {
/* Contacts */
var storeCreatedContactsInAppSyncAccount: Boolean
get() = config.getBool("app", "store_contacts_in_app_sync_account", false)
set(value) {
config.setBool("app", "store_contacts_in_app_sync_account", value)
}
var storePresenceInNativeContact: Boolean
get() = config.getBool("app", "store_presence_in_native_contact", false)
set(value) {

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_gravity="center"
android:orientation="horizontal"
android:padding="5dp">
<ImageView
android:id="@+id/account_icon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:adjustViewBounds="true"/>
<TextView
android:id="@+id/account_name"
style="@style/standard_text_font"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="5dp" />
</LinearLayout>

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View"/>
<variable
name="localSyncAccountClickListener"
type="android.view.View.OnClickListener"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<TextView
style="@style/standard_text_font"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical|center"
android:singleLine="true"
android:text="@string/contact_new_choose_sync_account" />
</LinearLayout>
<ListView
android:id="@+id/accountsList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:cacheColorHint="@color/transparent_color"
android:divider="@color/light_grey_color"
android:dividerHeight="1dp" />
<TextView
android:onClick="@{localSyncAccountClickListener}"
style="@style/button_font"
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/contact_local_sync_account"
android:gravity="right" />
</LinearLayout>
</layout>

View file

@ -72,14 +72,6 @@
linphone:checked="@={viewModel.friendListSubscribe}"
linphone:enabled="@{viewModel.readContactsPermissionGranted}"/>
<include
layout="@layout/settings_widget_switch"
linphone:title="@{@string/contacts_settings_contact_storage_title}"
linphone:subtitle="@{@string/contacts_settings_contact_storage_summary}"
linphone:listener="@{viewModel.contactStorageAppSyncAccountListener}"
linphone:checked="@={viewModel.contactStorageAppSyncAccount}"
linphone:enabled="@{viewModel.readContactsPermissionGranted}"/>
<include
layout="@layout/settings_widget_switch"
linphone:title="@{@string/contacts_settings_native_presence_title}"

View file

@ -107,6 +107,8 @@
<item quantity="one">@string/contact_delete_one_dialog</item>
<item quantity="other">@string/contact_delete_many_dialog</item>
</plurals>
<string name="contact_new_choose_sync_account">Choose where to save the contact</string>
<string name="contact_local_sync_account">Store locally</string>
<!-- Dialer -->
<string name="dialer_address_bar_hint">Enter a number or an address</string>
@ -430,8 +432,6 @@
<!-- Contacts settings -->
<string name="contacts_settings_friendlist_subscribe_title">Friendlist subscribe</string>
<string name="contacts_settings_friendlist_subscribe_summary"></string>
<string name="contacts_settings_contact_storage_title">Store new contacts in the app sync account</string>
<string name="contacts_settings_contact_storage_summary">If enabled, contacts created via the app will be removed when the app will be uninstalled.</string>
<string name="contacts_settings_native_presence_title">Presence information in native contact</string>
<string name="contacts_settings_native_presence_summary">Inserting information shortcuts from the &appName; contact into native Android contacts</string>
<string name="contacts_settings_show_organization_title">Display contact organization</string>

View file

@ -3,4 +3,5 @@
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/sync_account_type"
android:icon="@mipmap/ic_launcher_round"
android:smallIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name" />