From faa4309ece40d0ae5e17728785f46d62d2b8e0e8 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 16 Aug 2023 17:50:33 +0200 Subject: [PATCH] Started contact editor --- .../org/linphone/contacts/ContactLoader.kt | 27 +- .../org/linphone/contacts/ContactsManager.kt | 7 + .../main/contacts/fragment/ContactFragment.kt | 9 +- .../contacts/fragment/EditContactFragment.kt | 95 +++++++ .../contacts/fragment/NewContactFragment.kt | 20 +- .../viewmodel/ContactNewOrEditViewModel.kt | 111 ++++++++ .../contacts/viewmodel/ContactViewModel.kt | 3 +- app/src/main/res/drawable/pick_picture.xml | 13 + .../layout/contact_new_or_edit_fragment.xml | 241 ++++++++++++++++++ .../main/res/layout/new_contact_fragment.xml | 82 ------ .../res/navigation/contact_left_nav_graph.xml | 2 +- .../navigation/contact_right_nav_graph.xml | 18 ++ 12 files changed, 538 insertions(+), 90 deletions(-) create mode 100644 app/src/main/java/org/linphone/ui/main/contacts/fragment/EditContactFragment.kt create mode 100644 app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt create mode 100644 app/src/main/res/drawable/pick_picture.xml create mode 100644 app/src/main/res/layout/contact_new_or_edit_fragment.xml delete mode 100644 app/src/main/res/layout/new_contact_fragment.xml diff --git a/app/src/main/java/org/linphone/contacts/ContactLoader.kt b/app/src/main/java/org/linphone/contacts/ContactLoader.kt index 5c6f3fff3..f9d6d86ad 100644 --- a/app/src/main/java/org/linphone/contacts/ContactLoader.kt +++ b/app/src/main/java/org/linphone/contacts/ContactLoader.kt @@ -52,10 +52,11 @@ class ContactLoader : LoaderManager.LoaderCallbacks { override fun onCreateLoader(id: Int, args: Bundle?): Loader { val mimeType = ContactsContract.Data.MIMETYPE - val mimeSelection = "$mimeType = ? OR $mimeType = ? OR $mimeType = ?" + val mimeSelection = "$mimeType = ? OR $mimeType = ? OR $mimeType = ? OR $mimeType = ?" val selection = ContactsContract.Data.IN_DEFAULT_DIRECTORY + " == 1 AND ($mimeSelection)" val selectionArgs = arrayOf( + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE @@ -247,6 +248,30 @@ class ContactLoader : LoaderManager.LoaderCallbacks { friend.jobTitle = job } } + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> { + val vCard = friend.vcard + if (vCard != null) { + val givenName: String? = + cursor.getString( + cursor.getColumnIndexOrThrow( + ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME + ) + ) + if (!givenName.isNullOrEmpty()) { + vCard.givenName = givenName + } + + val familyName: String? = + cursor.getString( + cursor.getColumnIndexOrThrow( + ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME + ) + ) + if (!familyName.isNullOrEmpty()) { + vCard.familyName = familyName + } + } + } } friends[id] = friend diff --git a/app/src/main/java/org/linphone/contacts/ContactsManager.kt b/app/src/main/java/org/linphone/contacts/ContactsManager.kt index 3684b1be5..852c4d3f0 100644 --- a/app/src/main/java/org/linphone/contacts/ContactsManager.kt +++ b/app/src/main/java/org/linphone/contacts/ContactsManager.kt @@ -28,11 +28,13 @@ class ContactsManager { private val listeners = arrayListOf() fun loadContacts(activity: MainActivity) { + // UI thread val manager = LoaderManager.getInstance(activity) manager.restartLoader(0, null, ContactLoader()) } fun addListener(listener: ContactsListener) { + // UI thread if (coreContext.isReady()) { coreContext.postOnCoreThread { listeners.add(listener) @@ -41,6 +43,7 @@ class ContactsManager { } fun removeListener(listener: ContactsListener) { + // UI thread if (coreContext.isReady()) { coreContext.postOnCoreThread { listeners.remove(listener) @@ -49,6 +52,7 @@ class ContactsManager { } fun onContactsLoaded() { + // UI thread coreContext.postOnCoreThread { for (listener in listeners) { listener.onContactsLoaded() @@ -57,13 +61,16 @@ class ContactsManager { } fun findContactById(id: String): Friend? { + // Core thread return coreContext.core.defaultFriendList?.findFriendByRefKey(id) } fun onCoreStarted() { + // Core thread } fun onCoreStopped() { + // Core thread } } diff --git a/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactFragment.kt b/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactFragment.kt index 602788a26..3913d6007 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactFragment.kt @@ -32,6 +32,8 @@ import android.view.ViewGroup import androidx.core.view.doOnPreDraw import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.navigation.findNavController +import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import org.linphone.R import org.linphone.core.tools.Log @@ -135,8 +137,11 @@ class ContactFragment : GenericFragment() { } viewModel.openLinphoneContactEditor.observe(viewLifecycleOwner) { - it.consume { - // TODO + it.consume { refKey -> + val action = ContactFragmentDirections.actionContactFragmentToEditContactFragment( + refKey + ) + findNavController().navigate(action) } } } diff --git a/app/src/main/java/org/linphone/ui/main/contacts/fragment/EditContactFragment.kt b/app/src/main/java/org/linphone/ui/main/contacts/fragment/EditContactFragment.kt new file mode 100644 index 000000000..2a2fdc754 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/contacts/fragment/EditContactFragment.kt @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2010-2023 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 . + */ +package org.linphone.ui.main.contacts.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs +import androidx.navigation.navGraphViewModels +import org.linphone.R +import org.linphone.core.tools.Log +import org.linphone.databinding.ContactNewOrEditFragmentBinding +import org.linphone.ui.main.contacts.viewmodel.ContactNewOrEditViewModel +import org.linphone.ui.main.fragment.GenericFragment + +class EditContactFragment : GenericFragment() { + companion object { + const val TAG = "[Contact Edit Fragment]" + } + + private lateinit var binding: ContactNewOrEditFragmentBinding + + private val viewModel: ContactNewOrEditViewModel by navGraphViewModels( + R.id.editContactFragment + ) + + private val args: EditContactFragmentArgs by navArgs() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = ContactNewOrEditFragmentBinding.inflate(layoutInflater) + return binding.root + } + + override fun goBack() { + findNavController().popBackStack() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + binding.viewModel = viewModel + + postponeEnterTransition() + + val refKey = args.contactRefKey + Log.i("[Contact Edit Fragment] Looking up for contact with ref key [$refKey]") + viewModel.findFriendByRefKey(refKey) + + binding.setCancelClickListener { + goBack() + } + + viewModel.saveChangesEvent.observe(viewLifecycleOwner) { + it.consume { ok -> + if (ok) { + Log.i("$TAG Changes were applied, going back to details page") + goBack() + } else { + Log.e("$TAG Changes couldn't be applied!") + // TODO FIXME : show error + } + } + } + + viewModel.friendFoundEvent.observe(viewLifecycleOwner) { + it.consume { + startPostponedEnterTransition() + } + } + } +} diff --git a/app/src/main/java/org/linphone/ui/main/contacts/fragment/NewContactFragment.kt b/app/src/main/java/org/linphone/ui/main/contacts/fragment/NewContactFragment.kt index a91124713..e8b096e0e 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/fragment/NewContactFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/fragment/NewContactFragment.kt @@ -24,18 +24,25 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.navigation.fragment.findNavController -import org.linphone.databinding.NewContactFragmentBinding +import androidx.navigation.navGraphViewModels +import org.linphone.R +import org.linphone.databinding.ContactNewOrEditFragmentBinding +import org.linphone.ui.main.contacts.viewmodel.ContactNewOrEditViewModel import org.linphone.ui.main.fragment.GenericFragment class NewContactFragment : GenericFragment() { - private lateinit var binding: NewContactFragmentBinding + private lateinit var binding: ContactNewOrEditFragmentBinding + + private val viewModel: ContactNewOrEditViewModel by navGraphViewModels( + R.id.newContactFragment + ) override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - binding = NewContactFragmentBinding.inflate(layoutInflater) + binding = ContactNewOrEditFragmentBinding.inflate(layoutInflater) return binding.root } @@ -47,9 +54,16 @@ class NewContactFragment : GenericFragment() { super.onViewCreated(view, savedInstanceState) binding.lifecycleOwner = viewLifecycleOwner + binding.viewModel = viewModel binding.setCancelClickListener { goBack() } + + viewModel.saveChangesEvent.observe(viewLifecycleOwner) { + it.consume { + goBack() // TODO FIXME : go to contact detail view + } + } } } diff --git a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt new file mode 100644 index 000000000..a75e2165e --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2010-2023 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 . + */ +package org.linphone.ui.main.contacts.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.Friend +import org.linphone.core.FriendList.Status +import org.linphone.core.tools.Log +import org.linphone.utils.Event + +class ContactNewOrEditViewModel() : ViewModel() { + companion object { + const val TAG = "[Contact New/Edit View Model]" + } + + private lateinit var friend: Friend + + val isEdit = MutableLiveData() + + val firstName = MutableLiveData() + + val lastName = MutableLiveData() + + val company = MutableLiveData() + + val jobTitle = MutableLiveData() + + val saveChangesEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val friendFoundEvent = MutableLiveData>() + + fun findFriendByRefKey(refKey: String?) { + // UI thread + coreContext.postOnCoreThread { core -> + friend = if (refKey.isNullOrEmpty()) { + core.createFriend() + } else { + coreContext.contactsManager.findContactById(refKey) ?: core.createFriend() + } + val exists = !friend.refKey.isNullOrEmpty() + isEdit.postValue(exists) + + if (exists) { + Log.i("$TAG Found friend [$friend] using ref key [$refKey]") + val vCard = friend.vcard + if (vCard != null) { + firstName.postValue(vCard.givenName) + lastName.postValue(vCard.familyName) + } else { + // TODO + } + + company.postValue(friend.organization) + jobTitle.postValue(friend.jobTitle) + + friendFoundEvent.postValue(Event(true)) + } else { + Log.e("$TAG No friend found using ref key [$refKey]") + } + } + } + + fun saveChanges() { + // UI thread + coreContext.postOnCoreThread { core -> + var status = Status.OK + + if (::friend.isInitialized) { + friend.name = "${firstName.value.orEmpty()} ${lastName.value.orEmpty()}" + + val vCard = friend.vcard + if (vCard != null) { + vCard.familyName = lastName.value + vCard.givenName = firstName.value + } + + friend.organization = company.value.orEmpty() + friend.jobTitle = jobTitle.value.orEmpty() + + if (isEdit.value == false) { + status = core.defaultFriendList?.addFriend(friend) ?: Status.InvalidFriend + } + } else { + status = Status.NonExistentFriend + } + + saveChangesEvent.postValue(Event(status == Status.OK)) + } + } +} diff --git a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt index eb0e4feb2..87277b1d1 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt @@ -169,7 +169,8 @@ class ContactViewModel : ViewModel() { // UI thread val uri = contact.value?.friend?.nativeUri if (uri != null) { - openNativeContactEditor.value = Event(uri) + openLinphoneContactEditor.value = Event(contact.value?.id.orEmpty()) + // TODO FIXME : openNativeContactEditor.value = Event(uri) } else { openLinphoneContactEditor.value = Event(contact.value?.id.orEmpty()) } diff --git a/app/src/main/res/drawable/pick_picture.xml b/app/src/main/res/drawable/pick_picture.xml new file mode 100644 index 000000000..e4baca765 --- /dev/null +++ b/app/src/main/res/drawable/pick_picture.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/layout/contact_new_or_edit_fragment.xml b/app/src/main/res/layout/contact_new_or_edit_fragment.xml new file mode 100644 index 000000000..5f2fee6b8 --- /dev/null +++ b/app/src/main/res/layout/contact_new_or_edit_fragment.xml @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/new_contact_fragment.xml b/app/src/main/res/layout/new_contact_fragment.xml deleted file mode 100644 index 6037033bb..000000000 --- a/app/src/main/res/layout/new_contact_fragment.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/contact_left_nav_graph.xml b/app/src/main/res/navigation/contact_left_nav_graph.xml index b1f02d8ea..b4ba17b5c 100644 --- a/app/src/main/res/navigation/contact_left_nav_graph.xml +++ b/app/src/main/res/navigation/contact_left_nav_graph.xml @@ -17,7 +17,7 @@ android:id="@+id/newContactFragment" android:name="org.linphone.ui.main.contacts.fragment.NewContactFragment" android:label="NewContactFragment" - tools:layout="@layout/new_contact_fragment"/> + tools:layout="@layout/contact_new_or_edit_fragment"/> + + + + + \ No newline at end of file