From 4901534da7296ea4d8463c45f13b1e2f91b61ed2 Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Wed, 22 Jan 2020 16:29:06 +0100 Subject: [PATCH] Lots of cleanups & work on contacts Signed-off-by: Mario Danic --- .../nextcloud/talk/activities/MainActivity.kt | 18 +- .../application/NextcloudTalkApplication.kt | 3 +- .../controllers/BrowserController.kt | 4 +- .../talk/controllers/CallController.kt | 2 +- .../controllers/CallNotificationController.kt | 2 +- .../talk/controllers/ChatController.kt | 10 +- .../talk/controllers/ContactsController.kt | 169 +----------------- .../controllers/ConversationInfoController.kt | 4 +- .../bottomsheet/OperationsMenuController.kt | 4 +- .../talk/jobs/DeleteConversationWorker.kt | 4 +- .../talk/jobs/LeaveConversationWorker.kt | 4 +- .../nextcloud/talk/jobs/NotificationWorker.kt | 16 +- .../talk/jobs/ShareOperationWorker.kt | 4 +- .../models/json/participants/Participant.java | 5 + .../online/NextcloudTalkRepositoryImpl.kt | 12 ++ .../newarch/data/source/remote/ApiService.kt | 4 + .../domain/di/module/UseCasesModule.kt | 7 + .../online/NextcloudTalkRepository.kt | 3 + .../domain/usecases/GetContactsUseCase.kt | 39 ++++ .../domain/usecases/base/BaseUseCase.kt | 1 + .../talk/newarch/features/chat/ChatView.kt | 4 +- .../features/contactsflow/ContactPresenter.kt | 72 ++++++++ .../features/contactsflow/ContactsUtils.kt | 6 + .../features/contactsflow/ContactsView.kt | 74 +++++++- .../contactsflow/ContactsViewHeaderSource.kt | 54 ++++++ .../contactsflow/ContactsViewModel.kt | 40 ++++- .../contactsflow/ContactsViewModelFactory.kt | 19 +- .../contactsflow/ContactsViewSource.kt | 54 ++++++ .../di/module/ContactsFlowModule.kt | 26 +++ .../ConversationPresenter.kt | 4 +- .../ConversationsListView.kt | 8 +- .../services/shortcuts/ShortcutService.kt | 2 +- .../talk/newarch/utils/ElementPayload.kt | 5 + .../com/nextcloud/talk/utils/ApiUtils.java | 49 +++-- .../nextcloud/talk/utils/NotificationUtils.kt | 4 +- .../nextcloud/talk/utils/bundle/BundleKeys.kt | 2 +- .../talk/webrtc/MagicWebSocketInstance.kt | 4 +- .../main/res/layout/contacts_list_view.xml | 13 ++ app/src/main/res/layout/rv_item_contact.xml | 31 ++-- app/src/main/res/values/strings.xml | 3 +- 40 files changed, 527 insertions(+), 262 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/GetContactsUseCase.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactPresenter.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsUtils.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewHeaderSource.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewSource.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/di/module/ContactsFlowModule.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/utils/ElementPayload.kt create mode 100644 app/src/main/res/layout/contacts_list_view.xml diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt index b3c6e23cc..d6e79a76f 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt @@ -38,11 +38,11 @@ import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler import com.google.android.material.appbar.MaterialToolbar import com.nextcloud.talk.R import com.nextcloud.talk.controllers.CallNotificationController -import com.nextcloud.talk.controllers.ContactsController import com.nextcloud.talk.controllers.LockedController import com.nextcloud.talk.controllers.base.providers.ActionBarProvider import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository import com.nextcloud.talk.newarch.features.account.serverentry.ServerEntryView +import com.nextcloud.talk.newarch.features.contactsflow.ContactsView import com.nextcloud.talk.newarch.features.conversationslist.ConversationsListView import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.utils.ConductorRemapping @@ -102,12 +102,7 @@ class MainActivity : BaseActivity(), ActionBarProvider { @OnClick(R.id.floatingActionButton) fun onFloatingActionButtonClick() { - val bundle = Bundle() - bundle.putBoolean(BundleKeys.KEY_NEW_CONVERSATION, true) - router?.pushController( - RouterTransaction.with(ContactsController(bundle)) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler())) + openNewConversationScreen() } override fun onStart() { @@ -150,7 +145,7 @@ class MainActivity : BaseActivity(), ActionBarProvider { withContext(Dispatchers.Main) { ConductorRemapping.remapChatController( router!!, it.id!!, - intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!!, extras, false) + intent.getStringExtra(BundleKeys.KEY_CONVERSATION_TOKEN)!!, extras, false) } } } @@ -164,7 +159,7 @@ class MainActivity : BaseActivity(), ActionBarProvider { } else { ConductorRemapping.remapChatController( router!!, intent.getLongExtra(BundleKeys.KEY_INTERNAL_USER_ID, -1), - intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!!, intent.extras!!, false + intent.getStringExtra(BundleKeys.KEY_CONVERSATION_TOKEN)!!, intent.extras!!, false ) } } @@ -181,11 +176,8 @@ class MainActivity : BaseActivity(), ActionBarProvider { } private fun openNewConversationScreen() { - val bundle = Bundle() - bundle.putBoolean(BundleKeys.KEY_NEW_CONVERSATION, true) - router?.pushController( - RouterTransaction.with(ContactsController(bundle)) + RouterTransaction.with(ContactsView()) .pushChangeHandler(HorizontalChangeHandler()) .popChangeHandler(HorizontalChangeHandler()) ) diff --git a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt index 5628eefe2..96629a35a 100644 --- a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt +++ b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt @@ -46,6 +46,7 @@ import com.nextcloud.talk.models.json.signaling.settings.SignalingSettings import com.nextcloud.talk.newarch.di.module.* import com.nextcloud.talk.newarch.domain.di.module.UseCasesModule import com.nextcloud.talk.newarch.features.account.di.module.AccountModule +import com.nextcloud.talk.newarch.features.contactsflow.di.module.ContactsFlowModule import com.nextcloud.talk.newarch.features.conversationslist.di.module.ConversationsListModule import com.nextcloud.talk.newarch.local.dao.UsersDao import com.nextcloud.talk.newarch.local.models.UserNgEntity @@ -175,7 +176,7 @@ class NextcloudTalkApplication : Application(), LifecycleObserver, Configuration startKoin { androidContext(this@NextcloudTalkApplication) androidLogger() - modules(listOf(CommunicationModule, StorageModule, NetworkModule, ConversationsListModule, ServiceModule, AccountModule, UseCasesModule)) + modules(listOf(CommunicationModule, StorageModule, NetworkModule, ConversationsListModule, ServiceModule, AccountModule, UseCasesModule, ContactsFlowModule)) } } diff --git a/app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserController.kt b/app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserController.kt index 26e17fd91..26ade135a 100644 --- a/app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserController.kt +++ b/app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserController.kt @@ -86,7 +86,7 @@ class BrowserController(args: Bundle) : BaseController(), ListingInterface, Flex setHasOptionsMenu(true) browserType = Parcels.unwrap(args.getParcelable(BundleKeys.KEY_BROWSER_TYPE)) activeUser = Parcels.unwrap(args.getParcelable(BundleKeys.KEY_USER_ENTITY)) - roomToken = args.getString(BundleKeys.KEY_ROOM_TOKEN) + roomToken = args.getString(BundleKeys.KEY_CONVERSATION_TOKEN) currentPath = "/" if (BrowserType.DAV_BROWSER == browserType) { @@ -127,7 +127,7 @@ class BrowserController(args: Bundle) : BaseController(), ListingInterface, Flex if (paths.size == 10 || !iterator.hasNext()) { data = Data.Builder() .putLong(BundleKeys.KEY_INTERNAL_USER_ID, activeUser.id!!) - .putString(BundleKeys.KEY_ROOM_TOKEN, roomToken) + .putString(BundleKeys.KEY_CONVERSATION_TOKEN, roomToken) .putStringArray(BundleKeys.KEY_FILE_PATHS, paths.toTypedArray()) .build() shareWorker = OneTimeWorkRequest.Builder(ShareOperationWorker::class.java) diff --git a/app/src/main/java/com/nextcloud/talk/controllers/CallController.kt b/app/src/main/java/com/nextcloud/talk/controllers/CallController.kt index c0f90f368..c2fec42bf 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/CallController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/CallController.kt @@ -204,7 +204,7 @@ class CallController(args: Bundle) : BaseController() { init { roomId = args.getString(BundleKeys.KEY_ROOM_ID, "") - roomToken = args.getString(BundleKeys.KEY_ROOM_TOKEN, "") + roomToken = args.getString(BundleKeys.KEY_CONVERSATION_TOKEN, "") conversationUser = args.getParcelable(BundleKeys.KEY_USER_ENTITY) conversationPassword = args.getString(BundleKeys.KEY_CONVERSATION_PASSWORD, "") isVoiceOnlyCall = args.getBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, false) diff --git a/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.kt b/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.kt index 3facd6c3b..8e3d192cb 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.kt @@ -172,7 +172,7 @@ class CallNotificationController(private val originalBundle: Bundle) : BaseContr private fun proceedToCall() { originalBundle.putString( - BundleKeys.KEY_ROOM_TOKEN, + BundleKeys.KEY_CONVERSATION_TOKEN, currentConversation!!.token ) diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index e3154faaf..a7dafbb8f 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -179,7 +179,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter this.conversationUser = args.getParcelable(BundleKeys.KEY_USER_ENTITY) this.roomId = args.getString(BundleKeys.KEY_ROOM_ID, "") - this.roomToken = args.getString(BundleKeys.KEY_ROOM_TOKEN, "") + this.roomToken = args.getString(BundleKeys.KEY_CONVERSATION_TOKEN, "") if (args.containsKey(BundleKeys.KEY_ACTIVE_CONVERSATION)) { this.currentConversation = Parcels.unwrap( @@ -603,7 +603,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap(browserType) ) bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap(conversationUser)) - bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken) + bundle.putString(BundleKeys.KEY_CONVERSATION_TOKEN, roomToken) router.pushController( RouterTransaction.with(BrowserController(bundle)) .pushChangeHandler(VerticalChangeHandler()) @@ -614,7 +614,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter private fun showConversationInfoScreen() { val bundle = Bundle() bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationUser) - bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken) + bundle.putString(BundleKeys.KEY_CONVERSATION_TOKEN, roomToken) router.pushController( RouterTransaction.with(ConversationInfoController(bundle)) .pushChangeHandler(HorizontalChangeHandler()) @@ -1354,7 +1354,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter private fun getIntentForCall(isVoiceOnlyCall: Boolean): Intent? { if (currentConversation != null) { val bundle = Bundle() - bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken) + bundle.putString(BundleKeys.KEY_CONVERSATION_TOKEN, roomToken) bundle.putString(BundleKeys.KEY_ROOM_ID, roomId) bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationUser) bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, roomPassword) @@ -1497,7 +1497,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter val conversationIntent = Intent(activity, MagicCallActivity::class.java) val bundle = Bundle() bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationUser) - bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.ocs.data.token) + bundle.putString(BundleKeys.KEY_CONVERSATION_TOKEN, roomOverall.ocs.data.token) bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs.data.conversationId) if (conversationUser != null) { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.kt index c14c0aaf2..e62534e30 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.kt @@ -266,7 +266,7 @@ class ContactsController : BaseController, val bundle = Bundle() bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, currentUser) bundle.putString( - BundleKeys.KEY_ROOM_TOKEN, + BundleKeys.KEY_CONVERSATION_TOKEN, roomOverall.ocs.data.token ) bundle.putString( @@ -440,174 +440,7 @@ class ContactsController : BaseController, val query = adapter!!.getFilter(String::class.java) - val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForContactsSearchFor14(currentUser!!.baseUrl, query) - val modifiedQueryMap = HashMap(retrofitBucket.queryMap) - modifiedQueryMap["limit"] = 50 - var shareTypesList: MutableList = mutableListOf() - // user - shareTypesList.add("0") - // group - shareTypesList.add("1") - // remote/circles - shareTypesList.add("7"); - - conversationToken?.let { - modifiedQueryMap["itemId"] = it - // emails - shareTypesList.add("4") - } - - modifiedQueryMap["shareTypes[]"] = shareTypesList - - ncApi.getContactsWithSearchParam( - credentials, - retrofitBucket.url, shareTypesList, modifiedQueryMap - ) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .retry(3) - .`as`(AutoDispose.autoDisposable(scopeProvider)) - .subscribe(object : Observer { - override fun onSubscribe(d: Disposable) {} - - override fun onNext(responseBody: ResponseBody) { - var participant: Participant - - val newUserItemList = ArrayList>() - - try { - val autocompleteOverall = - LoganSquare.parse(responseBody.string(), AutocompleteOverall::class.java) - autocompleteUsersHashSet.addAll(autocompleteOverall.ocs.data) - - for (autocompleteUser in autocompleteUsersHashSet) { - participant = Participant() - participant.userId = autocompleteUser.id - participant.displayName = autocompleteUser.label - participant.source = autocompleteUser.source - - var headerTitle: String? - - - if (autocompleteUser.source == "groups") { - headerTitle = resources?.getString(R.string.nc_groups) - } else if (autocompleteUser.source == "users") { - headerTitle = resources?.getString(R.string.nc_contacts) - } else if (autocompleteUser.source == "emails") { - headerTitle = resources?.getString(R.string.nc_emails) - } else { - headerTitle = "" - } - - headerTitle as String - - val genericTextHeaderItem: GenericTextHeaderItem - if (!userHeaderItems.containsKey(headerTitle)) { - genericTextHeaderItem = GenericTextHeaderItem(headerTitle) - userHeaderItems[headerTitle] = genericTextHeaderItem - } - - val newContactItem = UserItem( - participant, currentUser!!, - userHeaderItems[headerTitle], activity!! - ) - - if (!contactItems!!.contains(newContactItem)) { - newUserItemList.add(newContactItem) - } - } - } catch (exception: Exception) { - Log.e(TAG, "Parsing response body failed while getting contacts") - } - - userHeaderItems = HashMap() - contactItems!!.addAll(newUserItemList) - - newUserItemList.sortWith(Comparator { o1, o2 -> - val firstName: String - val secondName: String - - if (o1 is UserItem) { - firstName = o1.model.displayName - } else { - firstName = (o1 as GenericTextHeaderItem).model - } - - if (o2 is UserItem) { - secondName = o2.model.displayName - } else { - secondName = (o2 as GenericTextHeaderItem).model - } - - if (o1 is UserItem && o2 is UserItem) { - if ("groups" == o1.model.source && "groups" == o2.model.source) { - firstName.compareTo(secondName, ignoreCase = true) - } else if ("groups" == o1.model.source) { - -1 - } else if ("groups" == o2.model.source) { - 1 - } - } - - firstName.compareTo(secondName, ignoreCase = true) - }) - - contactItems!!.sortWith(Comparator { o1, o2 -> - val firstName: String - val secondName: String - - if (o1 is UserItem) { - firstName = o1.model.displayName - } else { - firstName = (o1 as GenericTextHeaderItem).model - } - - if (o2 is UserItem) { - secondName = o2.model.displayName - } else { - secondName = (o2 as GenericTextHeaderItem).model - } - - if (o1 is UserItem && o2 is UserItem) { - if ("groups" == o1.model.source && "groups" == o2.model.source) { - firstName.compareTo(secondName, ignoreCase = true) - } else if ("groups" == o1.model.source) { - -1 - } else if ("groups" == o2.model.source) { - 1 - } - } - - firstName.compareTo(secondName, ignoreCase = true) - }) - - if (newUserItemList.size > 0) { - adapter!!.updateDataSet(newUserItemList) - } else { - adapter!!.filterItems() - } - - if (swipeRefreshLayout != null) { - swipeRefreshLayout!!.isRefreshing = false - } - } - - override fun onError(e: Throwable) { - if (swipeRefreshLayout != null) { - swipeRefreshLayout!!.isRefreshing = false - } - } - - override fun onComplete() { - if (swipeRefreshLayout != null) { - swipeRefreshLayout!!.isRefreshing = false - } - alreadyFetching = false - - disengageProgressBar() - } - }) } private fun prepareViews() { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt index 0c8825028..201b2e5f9 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt @@ -179,7 +179,7 @@ class ConversationInfoController(args: Bundle) : BaseController(), get() { if (!TextUtils.isEmpty(conversationToken) && conversationUser != null) { val data = Data.Builder() - data.putString(BundleKeys.KEY_ROOM_TOKEN, conversationToken) + data.putString(BundleKeys.KEY_CONVERSATION_TOKEN, conversationToken) data.putLong(BundleKeys.KEY_INTERNAL_USER_ID, conversationUser.id!!) return data.build() } @@ -190,7 +190,7 @@ class ConversationInfoController(args: Bundle) : BaseController(), init { setHasOptionsMenu(true) conversationUser = args.getParcelable(BundleKeys.KEY_USER_ENTITY) - conversationToken = args.getString(BundleKeys.KEY_ROOM_TOKEN) + conversationToken = args.getString(BundleKeys.KEY_CONVERSATION_TOKEN) credentials = ApiUtils.getCredentials(conversationUser!!.username, conversationUser.token) } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.kt b/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.kt index b0928962a..6b7aaa176 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.kt @@ -572,7 +572,7 @@ class OperationsMenuController(args: Bundle) : BaseController() { true, true, dismissView)) val conversationIntent = Intent(activity, MagicCallActivity::class.java) - bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation!!.token) + bundle.putString(BundleKeys.KEY_CONVERSATION_TOKEN, conversation!!.token) bundle.putString(BundleKeys.KEY_ROOM_ID, conversation!!.conversationId) bundle.putString(BundleKeys.KEY_CONVERSATION_NAME, conversation!!.displayName) @@ -604,7 +604,7 @@ class OperationsMenuController(args: Bundle) : BaseController() { private fun initiateCall() { eventBus.post(BottomSheetLockEvent(true, 0, true, true)) val bundle = Bundle() - bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation!!.token) + bundle.putString(BundleKeys.KEY_CONVERSATION_TOKEN, conversation!!.token) bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, currentUser) if (baseUrl != null && baseUrl != currentUser!!.baseUrl) { bundle.putString(BundleKeys.KEY_MODIFIED_BASE_URL, baseUrl) diff --git a/app/src/main/java/com/nextcloud/talk/jobs/DeleteConversationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/DeleteConversationWorker.kt index edfd1b2f1..21a34c9a9 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/DeleteConversationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/DeleteConversationWorker.kt @@ -30,7 +30,7 @@ import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.newarch.local.models.getCredentials import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_TOKEN import io.reactivex.Observer import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers @@ -53,7 +53,7 @@ class DeleteConversationWorker(context: Context, override suspend fun doWork(): Result { val data = inputData val operationUserId = data.getLong(KEY_INTERNAL_USER_ID, -1) - val conversationToken = data.getString(KEY_ROOM_TOKEN) + val conversationToken = data.getString(KEY_CONVERSATION_TOKEN) val operationUser: UserNgEntity? = usersRepository.getUserWithId(operationUserId) operationUser?.let { val credentials = it.getCredentials() diff --git a/app/src/main/java/com/nextcloud/talk/jobs/LeaveConversationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/LeaveConversationWorker.kt index 504815357..c9fb21982 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/LeaveConversationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/LeaveConversationWorker.kt @@ -30,7 +30,7 @@ import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.newarch.local.models.getCredentials import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_TOKEN import io.reactivex.Observer import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers @@ -51,7 +51,7 @@ class LeaveConversationWorker(context: Context, workerParams: WorkerParameters) override fun doWork(): Result { val data = inputData val operationUserId = data.getLong(KEY_INTERNAL_USER_ID, -1) - val conversationToken = data.getString(KEY_ROOM_TOKEN) + val conversationToken = data.getString(KEY_CONVERSATION_TOKEN) val operationUser: UserNgEntity? = usersRepository.getUserWithId(operationUserId) if (operationUser != null) { val credentials = operationUser.getCredentials() diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt index 3e1c927e1..932ea408e 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt @@ -91,7 +91,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_SIGNATURE import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_SUBJECT import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_TOKEN import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageUtils import com.nextcloud.talk.utils.preferences.AppPreferences @@ -143,7 +143,7 @@ class NotificationWorker( arbitraryStorageEntity = arbitraryStorageUtils.getStorageSetting( userEntity.id!!, "mute_calls", - intent.extras!!.getString(KEY_ROOM_TOKEN) + intent.extras!!.getString(KEY_CONVERSATION_TOKEN) ) if (arbitraryStorageEntity != null) { @@ -153,7 +153,7 @@ class NotificationWorker( arbitraryStorageEntity = arbitraryStorageUtils.getStorageSetting( userEntity.id!!, "important_conversation", - intent.extras!!.getString(KEY_ROOM_TOKEN) + intent.extras!!.getString(KEY_CONVERSATION_TOKEN) ) if (arbitraryStorageEntity != null) { @@ -171,7 +171,7 @@ class NotificationWorker( ncApi!!.getRoom( credentials, ApiUtils.getRoom( userEntity.baseUrl, - intent.extras!!.getString(KEY_ROOM_TOKEN) + intent.extras!!.getString(KEY_CONVERSATION_TOKEN) ) ) .blockingSubscribe(object : Observer { @@ -358,7 +358,7 @@ class NotificationWorker( // could be an ID or a TOKEN notificationInfo.putString( - KEY_ROOM_TOKEN, + KEY_CONVERSATION_TOKEN, decryptedPushMessage!!.id ) notificationInfo.putLong( @@ -681,7 +681,7 @@ class NotificationWorker( ) } else { bundle.putString( - KEY_ROOM_TOKEN, + KEY_CONVERSATION_TOKEN, decryptedPushMessage!!.id ) } @@ -696,7 +696,7 @@ class NotificationWorker( intent.putExtras(bundle) when (decryptedPushMessage!!.type) { "call" -> if (!bundle.containsKey( - KEY_ROOM_TOKEN + KEY_CONVERSATION_TOKEN ) ) { context!!.startActivity(intent) @@ -704,7 +704,7 @@ class NotificationWorker( showNotificationForCallWithNoPing(intent) } "room" -> if (bundle.containsKey( - KEY_ROOM_TOKEN + KEY_CONVERSATION_TOKEN ) ) { showNotificationWithObjectData(intent) diff --git a/app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.kt index 9bcbe8bfe..cece12170 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.kt @@ -29,7 +29,7 @@ import com.nextcloud.talk.newarch.local.models.getCredentials import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_PATHS import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_TOKEN import io.reactivex.Observer import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers @@ -74,7 +74,7 @@ class ShareOperationWorker( init { val data = workerParams.inputData userId = data.getLong(KEY_INTERNAL_USER_ID, 0) - roomToken = data.getString(KEY_ROOM_TOKEN) + roomToken = data.getString(KEY_CONVERSATION_TOKEN) Collections.addAll( filesArray, *data.getStringArray(KEY_FILE_PATHS) diff --git a/app/src/main/java/com/nextcloud/talk/models/json/participants/Participant.java b/app/src/main/java/com/nextcloud/talk/models/json/participants/Participant.java index 11c4277ce..a90356e6c 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/participants/Participant.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/participants/Participant.java @@ -21,12 +21,16 @@ package com.nextcloud.talk.models.json.participants; import com.bluelinelabs.logansquare.annotation.JsonField; +import com.bluelinelabs.logansquare.annotation.JsonIgnore; import com.bluelinelabs.logansquare.annotation.JsonObject; import com.nextcloud.talk.models.json.converters.EnumParticipantFlagsConverter; import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter; import org.parceler.Parcel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import lombok.Data; @@ -64,6 +68,7 @@ public class Participant { @JsonField(name = "source") public String source; + @JsonIgnore public boolean selected; @Override diff --git a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/online/NextcloudTalkRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/online/NextcloudTalkRepositoryImpl.kt index 1964ac9c0..ff08bb6d6 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/online/NextcloudTalkRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/online/NextcloudTalkRepositoryImpl.kt @@ -20,10 +20,12 @@ package com.nextcloud.talk.newarch.data.repository.online +import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.generic.GenericOverall +import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.models.json.push.PushRegistrationOverall import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall import com.nextcloud.talk.models.json.userprofile.UserProfileOverall @@ -89,6 +91,16 @@ class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : Nextclou } } + override suspend fun getContactsForUser(user: UserNgEntity, searchQuery: String?, conversationToken: String?): List { + return apiService.getContacts(authorization = user.getCredentials(), url = ApiUtils.getUrlForContactsSearch(user.baseUrl), shareTypes = ApiUtils.getShareTypesForContactsSearch(), options = ApiUtils.getQueryMapForContactsSearch(searchQuery, conversationToken)).ocs.data.map { + val participant = Participant() + participant.userId = it.id + participant.displayName = it.label + participant.source = it.source + participant + } + } + override suspend fun registerPushWithServerForUser(user: UserNgEntity, options: Map): PushRegistrationOverall { return apiService.registerForPushWithServer(user.getCredentials(), ApiUtils.getUrlNextcloudPush(user.baseUrl), options) } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt b/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt index a93ecb2d0..e4a5bd193 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt @@ -20,6 +20,7 @@ package com.nextcloud.talk.newarch.data.source.remote +import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.conversations.RoomsOverall @@ -31,6 +32,9 @@ import retrofit2.http.* interface ApiService { + @GET + suspend fun getContacts(@Header("Authorization") authorization: String, @Url url: String, @Query("limit") limit: Int = 50, @Query("shareTypes[]") shareTypes: List, @QueryMap options: Map): AutocompleteOverall + @GET suspend fun getCapabilities(@Url url: String): CapabilitiesOverall diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/di/module/UseCasesModule.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/di/module/UseCasesModule.kt index 615da8361..f49a1e7f7 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/domain/di/module/UseCasesModule.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/di/module/UseCasesModule.kt @@ -47,9 +47,16 @@ val UseCasesModule = module { single { createRegisterPushWithServerUseCase(get(), get()) } single { createUnregisterPushWithProxyUseCase(get(), get()) } single { createUnregisterPushWithServerUseCase(get(), get()) } + single { createGetContactsUseCase(get(), get()) } factory { createChatViewModelFactory(get(), get(), get(), get(), get(), get()) } } +fun createGetContactsUseCase(nextcloudTalkRepository: NextcloudTalkRepository, + apiErrorHandler: ApiErrorHandler +): GetContactsUseCase { + return GetContactsUseCase(nextcloudTalkRepository, apiErrorHandler) +} + fun createUnregisterPushWithServerUseCase(nextcloudTalkRepository: NextcloudTalkRepository, apiErrorHandler: ApiErrorHandler ): UnregisterPushWithServerUseCase { diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt index ac0051820..74251f542 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt @@ -20,16 +20,19 @@ package com.nextcloud.talk.newarch.domain.repository.online +import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.generic.GenericOverall +import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.models.json.push.PushRegistrationOverall import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall import com.nextcloud.talk.models.json.userprofile.UserProfileOverall import com.nextcloud.talk.newarch.local.models.UserNgEntity interface NextcloudTalkRepository { + suspend fun getContactsForUser(user: UserNgEntity, searchQuery: String?, conversationToken: String?): List suspend fun registerPushWithServerForUser(user: UserNgEntity, options: Map): PushRegistrationOverall suspend fun unregisterPushWithServerForUser(user: UserNgEntity): GenericOverall suspend fun registerPushWithProxyForUser(user: UserNgEntity, options: Map): Any diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/GetContactsUseCase.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/GetContactsUseCase.kt new file mode 100644 index 000000000..da4e25345 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/GetContactsUseCase.kt @@ -0,0 +1,39 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * 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 com.nextcloud.talk.newarch.domain.usecases + +import com.nextcloud.talk.models.json.participants.Participant +import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler +import com.nextcloud.talk.newarch.domain.repository.online.NextcloudTalkRepository +import com.nextcloud.talk.newarch.domain.usecases.base.UseCase +import org.koin.core.parameter.DefinitionParameters + +class GetContactsUseCase constructor( + private val nextcloudTalkRepository: NextcloudTalkRepository, + apiErrorHandler: ApiErrorHandler? +) : UseCase, Any?>(apiErrorHandler) { + override suspend fun run(params: Any?): List { + val definitionParameters = params as DefinitionParameters + return nextcloudTalkRepository.getContactsForUser(definitionParameters[0], definitionParameters[1], definitionParameters[2]) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/base/BaseUseCase.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/base/BaseUseCase.kt index 7cd0fedc3..31eaad957 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/base/BaseUseCase.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/base/BaseUseCase.kt @@ -42,6 +42,7 @@ abstract class UseCase(private val apiErrorHandler: ApiErrorHan onResult.onSuccess(it) } } catch (e: Exception) { + onResult.onError(apiErrorHandler?.traceErrorException(e)) } } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt index ca4d3f3d5..15b1bbd54 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt @@ -81,7 +81,7 @@ class ChatView : BaseView(), MessageHolders.ContentChecker, MessagesLi setHasOptionsMenu(true) actionBar?.show() viewModel = viewModelProvider(factory).get(ChatViewModel::class.java) - viewModel.init(args.getParcelable(BundleKeys.KEY_USER_ENTITY)!!, args.getString(BundleKeys.KEY_ROOM_TOKEN)!!, args.getString(KEY_CONVERSATION_PASSWORD)) + viewModel.init(args.getParcelable(BundleKeys.KEY_USER_ENTITY)!!, args.getString(BundleKeys.KEY_CONVERSATION_TOKEN)!!, args.getString(KEY_CONVERSATION_PASSWORD)) viewModel.apply { conversation.observe(this@ChatView) { conversation -> @@ -344,7 +344,7 @@ class ChatView : BaseView(), MessageHolders.ContentChecker, MessagesLi BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap(browserType) ) bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap(viewModel.user)) - bundle.putString(BundleKeys.KEY_ROOM_TOKEN, it.token) + bundle.putString(BundleKeys.KEY_CONVERSATION_TOKEN, it.token) router.pushController( RouterTransaction.with(BrowserController(bundle)) .pushChangeHandler(VerticalChangeHandler()) diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactPresenter.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactPresenter.kt new file mode 100644 index 000000000..3b78fe57b --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactPresenter.kt @@ -0,0 +1,72 @@ +package com.nextcloud.talk.newarch.features.contactsflow + +import android.content.Context +import android.view.ViewGroup +import androidx.core.view.isVisible +import coil.api.load +import com.nextcloud.talk.R +import com.nextcloud.talk.models.json.participants.Participant +import com.nextcloud.talk.newarch.local.models.getCredentials +import com.nextcloud.talk.newarch.services.GlobalService +import com.nextcloud.talk.newarch.utils.ElementPayload +import com.nextcloud.talk.newarch.utils.Images +import com.nextcloud.talk.utils.ApiUtils +import com.otaliastudios.elements.Element +import com.otaliastudios.elements.Page +import com.otaliastudios.elements.Presenter +import kotlinx.android.synthetic.main.rv_item_contact.view.* +import org.koin.core.KoinComponent +import org.koin.core.inject + +open class ContactPresenter(context: Context, onElementClick: ((Page, Holder, Element) -> Unit)?) : Presenter(context, onElementClick), KoinComponent { + private val globalService: GlobalService by inject() + + override val elementTypes: Collection + get() = listOf(0) + + override fun onCreate(parent: ViewGroup, elementType: Int): Holder { + return Holder(getLayoutInflater().inflate(R.layout.rv_item_contact, parent, false)) + } + + override fun onBind(page: Page, holder: Holder, element: Element, payloads: List) { + super.onBind(page, holder, element, payloads) + + val participant = element.data + val user = globalService.currentUserLiveData.value + + holder.itemView.checkedImageView.isVisible = element.data?.selected == true + + if (!payloads.contains(ElementPayload.SELECTION_TOGGLE)) { + participant?.displayName?.let { + holder.itemView.name_text.text = it + } ?: run { + holder.itemView.name_text.text = context.getString(R.string.nc_guest) + } + + when (participant?.source) { + "users" -> { + when (participant.type) { + Participant.ParticipantType.GUEST, Participant.ParticipantType.GUEST_AS_MODERATOR, Participant.ParticipantType.USER_FOLLOWING_LINK -> { + holder.itemView.avatarImageView.load(ApiUtils.getUrlForAvatarWithNameForGuests(user?.baseUrl, participant.userId, R.dimen.avatar_size)) { + user?.getCredentials()?.let { addHeader("Authorization", it) } + } + } + else -> { + holder.itemView.avatarImageView.load(ApiUtils.getUrlForAvatarWithName(user?.baseUrl, participant.userId, R.dimen.avatar_size)) { + user?.getCredentials()?.let { addHeader("Authorization", it) } + } + } + } + } + "groups", "circles" -> { + holder.itemView.avatarImageView.load(Images().getImageWithBackground(context, R.drawable.ic_people_group_white_24px)) + } + "emails" -> { + holder.itemView.avatarImageView.load(Images().getImageWithBackground(context, R.drawable.ic_baseline_email_24)) + } + else -> { + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsUtils.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsUtils.kt new file mode 100644 index 000000000..382744de0 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsUtils.kt @@ -0,0 +1,6 @@ +package com.nextcloud.talk.newarch.features.contactsflow + +enum class ParticipantElementType { + PARTICIPANT, + PARTICIPANT_HEADER +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsView.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsView.kt index 4979b50c8..3c34cedd9 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsView.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsView.kt @@ -1,9 +1,79 @@ package com.nextcloud.talk.newarch.features.contactsflow +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider +import com.nextcloud.talk.R +import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView +import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView +import com.nextcloud.talk.newarch.utils.ElementPayload +import com.nextcloud.talk.utils.bundle.BundleKeys +import com.otaliastudios.elements.Adapter +import com.otaliastudios.elements.Element +import com.otaliastudios.elements.Page +import com.otaliastudios.elements.Presenter +import com.uber.autodispose.lifecycle.LifecycleScopeProvider +import kotlinx.android.synthetic.main.conversations_list_view.view.* +import kotlinx.android.synthetic.main.message_state.view.* +import org.koin.android.ext.android.inject -class ContactsView : BaseView() { +class ContactsView(private val bundle: Bundle? = null) : BaseView() { + override val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this) + + private lateinit var viewModel: ContactsViewModel + val factory: ContactsViewModelFactory by inject() + lateinit var adapter: Adapter override fun getLayoutId(): Int { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + return R.layout.contacts_list_view } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { + actionBar?.show() + setHasOptionsMenu(true) + + viewModel = viewModelProvider(factory).get(ContactsViewModel::class.java) + viewModel.conversationToken = bundle?.getString(BundleKeys.KEY_CONVERSATION_TOKEN) + val view = super.onCreateView(inflater, container) + + // todo - change empty state magic + adapter = Adapter.builder(this) + .addSource(ContactsViewSource(viewModel.contactsLiveData, ParticipantElementType.PARTICIPANT.ordinal)) + .addSource(ContactsHeaderSource(activity as Context, ParticipantElementType.PARTICIPANT_HEADER.ordinal)) + .addPresenter(ContactPresenter(activity as Context, ::onElementClick)) + .addPresenter(Presenter.forLoadingIndicator(activity as Context, R.layout.loading_state)) + .addPresenter(Presenter.forEmptyIndicator(activity as Context, R.layout.message_state)) + .addPresenter(Presenter.forErrorIndicator(activity as Context, R.layout.message_state) { view, throwable -> + view.messageStateTextView.setText(R.string.nc_oops) + view.messageStateImageView.setImageDrawable((activity as Context).getDrawable(R.drawable.ic_announcement_white_24dp)) + }) + .setAutoScrollMode(Adapter.AUTOSCROLL_POSITION_0, true) + .into(view.recyclerView) + + view.apply { + recyclerView.initRecyclerView(LinearLayoutManager(activity), adapter, true) + } + + viewModel.loadContacts() + + return view + } + + private fun onElementClick(page: Page, holder: Presenter.Holder, element: Element) { + val isElementSelected = element.data?.selected == true + element.data?.selected = !isElementSelected + adapter.notifyItemChanged(holder.adapterPosition, ElementPayload.SELECTION_TOGGLE) + } + + override fun getTitle(): String? { + return resources?.getString(R.string.nc_select_contacts) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewHeaderSource.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewHeaderSource.kt new file mode 100644 index 000000000..c4427d6a2 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewHeaderSource.kt @@ -0,0 +1,54 @@ +package com.nextcloud.talk.newarch.features.contactsflow + +import android.content.Context +import com.nextcloud.talk.R +import com.nextcloud.talk.models.json.participants.Participant +import com.otaliastudios.elements.Page +import com.otaliastudios.elements.Source +import com.otaliastudios.elements.extensions.HeaderSource + +class ContactsHeaderSource(private val context: Context, private val elementType: Int): HeaderSource() { + + // Store the last header that was added, even if it belongs to a previous page. + private var lastHeader: String = "" + + override fun dependsOn(source: Source<*>) = source is ContactsViewSource + + override fun computeHeaders(page: Page, list: List): List> { + val results = arrayListOf>() + for (participant in list) { + val header = when (participant.source) { + "users" -> { + context.getString(R.string.nc_contacts) + } + "groups" -> { + context.getString(R.string.nc_groups) + } + "emails" -> { + context.getString(R.string.nc_emails) + } + "circles" -> { + context.getString(R.string.nc_circles) + } + else -> { + context.getString(R.string.nc_others) + } + } + + if (header != lastHeader) { + results.add(Data(participant, header)) + lastHeader = header + } + } + + return results + } + + override fun getElementType(data: Data): Int { + return elementType + } + + override fun areItemsTheSame(first: Data, second: Data): Boolean { + return first == second + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewModel.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewModel.kt index 71a9e6e27..42258c79f 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewModel.kt @@ -1,5 +1,41 @@ package com.nextcloud.talk.newarch.features.contactsflow -class ContactsViewModel { +import android.app.Application +import android.util.Log +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.nextcloud.talk.models.json.participants.Participant +import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel +import com.nextcloud.talk.newarch.data.model.ErrorModel +import com.nextcloud.talk.newarch.domain.usecases.GetContactsUseCase +import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse +import com.nextcloud.talk.newarch.features.conversationslist.ConversationsListView +import com.nextcloud.talk.newarch.services.GlobalService +import org.koin.core.parameter.parametersOf -} \ No newline at end of file +class ContactsViewModel constructor( + application: Application, + private val getContactsUseCase: GetContactsUseCase, + val globalService: GlobalService +) : BaseViewModel(application) { + val contactsLiveData = MutableLiveData>() + val searchQuery = MutableLiveData(null) + var conversationToken: String? = null + + fun loadContacts() { + getContactsUseCase.invoke(viewModelScope, parametersOf(globalService.currentUserLiveData.value, searchQuery.value, conversationToken), object : + UseCaseResponse> { + override suspend fun onSuccess(result: List) { + val sortPriority = mapOf("users" to 3, "groups" to 2, "emails" to 1, "circles" to 0) + contactsLiveData.postValue(result.sortedWith(Comparator { o1, o2 -> + sortPriority[o2.source]?.let { sortPriority[o1.source]?.compareTo(it) } + 0 + })) + } + + override suspend fun onError(errorModel: ErrorModel?) { + // handle errors here + } + }) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewModelFactory.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewModelFactory.kt index f931f8b64..8c91a10f4 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewModelFactory.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewModelFactory.kt @@ -1,5 +1,20 @@ package com.nextcloud.talk.newarch.features.contactsflow -class ContactsViewModelFactory { +import android.app.Application +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.nextcloud.talk.newarch.domain.usecases.GetContactsUseCase +import com.nextcloud.talk.newarch.services.GlobalService -} \ No newline at end of file +class ContactsViewModelFactory constructor( + private val application: Application, + private val getContactsUseCase: GetContactsUseCase, + private val globalService: GlobalService +) : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T { + return ContactsViewModel( + application, getContactsUseCase, globalService + ) as T + } +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewSource.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewSource.kt new file mode 100644 index 000000000..ea9a694fe --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/ContactsViewSource.kt @@ -0,0 +1,54 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * 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 com.nextcloud.talk.newarch.features.contactsflow + +import androidx.lifecycle.LiveData +import com.nextcloud.talk.models.json.conversations.Conversation +import com.nextcloud.talk.models.json.participants.Participant +import com.otaliastudios.elements.Element +import com.otaliastudios.elements.Page +import com.otaliastudios.elements.Source +import com.otaliastudios.elements.extensions.MainSource + +class ContactsViewSource(private val data: LiveData>, private val elementType: Int = 0, loadingIndicatorsEnabled: Boolean = true, errorIndicatorEnabled: Boolean = true, emptyIndicatorEnabled: Boolean = true) : MainSource(loadingIndicatorsEnabled, errorIndicatorEnabled, emptyIndicatorEnabled) { + + override fun onPageOpened(page: Page, dependencies: List>) { + super.onPageOpened(page, dependencies) + if (page.previous() == null) { + postResult(page, data) + } + } + + override fun dependsOn(source: Source<*>): Boolean { + return false + } + + override fun areContentsTheSame(first: T, second: T): Boolean { + return first == second + } + + override fun areItemsTheSame(first: T, second: T): Boolean { + return first.userId == second.userId + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/di/module/ContactsFlowModule.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/di/module/ContactsFlowModule.kt new file mode 100644 index 000000000..50ee110dd --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/di/module/ContactsFlowModule.kt @@ -0,0 +1,26 @@ +package com.nextcloud.talk.newarch.features.contactsflow.di.module + +import android.app.Application +import com.nextcloud.talk.newarch.domain.usecases.GetContactsUseCase +import com.nextcloud.talk.newarch.features.contactsflow.ContactsViewModelFactory +import com.nextcloud.talk.newarch.services.GlobalService +import org.koin.android.ext.koin.androidApplication +import org.koin.dsl.module + +val ContactsFlowModule = module { + factory { + createContactsViewModelFactory( + androidApplication(), get(), get() + ) + } +} + +fun createContactsViewModelFactory( + application: Application, + getContactsUseCase: GetContactsUseCase, + globalService: GlobalService +): ContactsViewModelFactory { + return ContactsViewModelFactory( + application, getContactsUseCase, globalService + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationPresenter.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationPresenter.kt index 87e62c963..b7b0b09b5 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationPresenter.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationPresenter.kt @@ -45,7 +45,7 @@ import kotlinx.android.synthetic.main.rv_item_conversation_with_last_message.vie import org.koin.core.KoinComponent import org.koin.core.inject -open class ConversationsPresenter(context: Context, onElementClick: ((Page, Holder, Element) -> Unit)?, private val onElementLongClick: ((Page, Holder, Element) -> Unit)?) : Presenter(context, onElementClick), KoinComponent { +open class ConversationPresenter(context: Context, onElementClick: ((Page, Holder, Element) -> Unit)?, private val onElementLongClick: ((Page, Holder, Element) -> Unit)?) : Presenter(context, onElementClick), KoinComponent { private val globalService: GlobalService by inject() override val elementTypes: Collection @@ -159,4 +159,6 @@ open class ConversationsPresenter(context: Context, onElementClick: ((Page, Hold } } } + + } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt index e56cc5531..2a6a369ce 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt @@ -27,6 +27,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import androidx.lifecycle.observe +import androidx.recyclerview.widget.LinearLayoutManager import com.afollestad.materialdialogs.LayoutMode import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.bottomsheets.BottomSheet @@ -57,7 +58,6 @@ import com.otaliastudios.elements.Element import com.otaliastudios.elements.Page import com.otaliastudios.elements.Presenter import com.uber.autodispose.lifecycle.LifecycleScopeProvider -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import kotlinx.android.synthetic.main.conversations_list_view.view.* import kotlinx.android.synthetic.main.message_state.view.* import kotlinx.android.synthetic.main.search_layout.* @@ -82,7 +82,7 @@ class ConversationsListView : BaseView() { val adapter = Adapter.builder(this) .addSource(ConversationsListSource(viewModel.conversationsLiveData)) - .addPresenter(ConversationsPresenter(activity as Context, ::onElementClick, ::onElementLongClick)) + .addPresenter(ConversationPresenter(activity as Context, ::onElementClick, ::onElementLongClick)) .addPresenter(Presenter.forLoadingIndicator(activity as Context, R.layout.loading_state)) .addPresenter(AdvancedEmptyPresenter(activity as Context, R.layout.message_state, ::openNewConversationScreen)) .addPresenter(Presenter.forErrorIndicator(activity as Context, R.layout.message_state) { view, throwable -> @@ -94,7 +94,7 @@ class ConversationsListView : BaseView() { view.apply { - recyclerView.initRecyclerView(SmoothScrollLinearLayoutManager(activity), adapter, false) + recyclerView.initRecyclerView(LinearLayoutManager(activity), adapter, true) swipeRefreshLayoutView.setOnRefreshListener { view.swipeRefreshLayoutView.isRefreshing = false viewModel.loadConversations() @@ -156,7 +156,7 @@ class ConversationsListView : BaseView() { conversation?.let { conversation -> val bundle = Bundle() bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, user) - bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation.token) + bundle.putString(BundleKeys.KEY_CONVERSATION_TOKEN, conversation.token) bundle.putString(BundleKeys.KEY_ROOM_ID, conversation.conversationId) bundle.putParcelable(BundleKeys.KEY_ACTIVE_CONVERSATION, Parcels.wrap(conversation)) ConductorRemapping.remapChatController( diff --git a/app/src/main/java/com/nextcloud/talk/newarch/services/shortcuts/ShortcutService.kt b/app/src/main/java/com/nextcloud/talk/newarch/services/shortcuts/ShortcutService.kt index 580950503..6653a20d1 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/services/shortcuts/ShortcutService.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/services/shortcuts/ShortcutService.kt @@ -98,7 +98,7 @@ class ShortcutService constructor(private var context: Context, val intent = Intent(context, MainActivity::class.java) intent.action = BundleKeys.KEY_OPEN_CONVERSATION intent.putExtra(BundleKeys.KEY_INTERNAL_USER_ID, user.id) - intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, conversation.token) + intent.putExtra(BundleKeys.KEY_CONVERSATION_TOKEN, conversation.token) val persons = mutableListOf() conversation.participants?.forEach { diff --git a/app/src/main/java/com/nextcloud/talk/newarch/utils/ElementPayload.kt b/app/src/main/java/com/nextcloud/talk/newarch/utils/ElementPayload.kt new file mode 100644 index 000000000..e2ab21de6 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/utils/ElementPayload.kt @@ -0,0 +1,5 @@ +package com.nextcloud.talk.newarch.utils + +enum class ElementPayload { + SELECTION_TOGGLE +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java index f9d45d860..1e39ede82 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java @@ -29,7 +29,9 @@ import com.nextcloud.talk.R; import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.models.RetrofitBucket; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.annotation.Nullable; @@ -61,23 +63,41 @@ public class ApiUtils { return url; } - public static RetrofitBucket getRetrofitBucketForContactsSearch(String baseUrl, - @Nullable String searchQuery) { - RetrofitBucket retrofitBucket = new RetrofitBucket(); - retrofitBucket.setUrl(baseUrl + ocsApiVersion + "/apps/files_sharing/api/v1/sharees"); + public static String getUrlForContactsSearch(String baseUrl) { + return baseUrl + ocsApiVersion + "/core/autocomplete/get"; + } + public static List getShareTypesForContactsSearch() { + List shareTypesList = new ArrayList<>(); + // user + shareTypesList.add("0"); + // group + shareTypesList.add("1"); + // group + shareTypesList.add("4"); + // remote/circles + shareTypesList.add("7"); + + return shareTypesList; + } + + public static Map getQueryMapForContactsSearch(@Nullable String searchQuery, @Nullable String conversationToken) { Map queryMap = new HashMap<>(); - - if (searchQuery == null) { - searchQuery = ""; + if (searchQuery != null) { + queryMap.put("search", searchQuery); + } else { + queryMap.put("search", ""); } - queryMap.put("format", "json"); - queryMap.put("search", searchQuery); + queryMap.put("itemType", "call"); - retrofitBucket.setQueryMap(queryMap); + if (conversationToken != null) { + queryMap.put("itemId", conversationToken); + } else { + queryMap.put("itemId", "new"); + } - return retrofitBucket; + return queryMap; } public static String getUrlForFilePreviewWithRemotePath(String baseUrl, String remotePath, @@ -96,13 +116,6 @@ public class ApiUtils { return baseUrl + ocsApiVersion + "/apps/files_sharing/api/v1/shares"; } - public static RetrofitBucket getRetrofitBucketForContactsSearchFor14(String baseUrl, - @Nullable String searchQuery) { - RetrofitBucket retrofitBucket = getRetrofitBucketForContactsSearch(baseUrl, searchQuery); - retrofitBucket.setUrl(baseUrl + ocsApiVersion + "/core/autocomplete/get"); - retrofitBucket.getQueryMap().put("itemId", "new"); - return retrofitBucket; - } public static String getUrlForSettingNotificationlevel(String baseUrl, String token) { return getRoom(baseUrl, token) + "/notify"; diff --git a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt index 01d28fc97..9c9a1a09f 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt @@ -163,7 +163,7 @@ object NotificationUtils { if (conversationUser.id == notification.extras.getLong( BundleKeys.KEY_INTERNAL_USER_ID ) && roomTokenOrId == statusBarNotification.notification.extras.getString( - BundleKeys.KEY_ROOM_TOKEN + BundleKeys.KEY_CONVERSATION_TOKEN ) ) { return statusBarNotification @@ -196,7 +196,7 @@ object NotificationUtils { if (conversationUser.id == notification.extras.getLong( BundleKeys.KEY_INTERNAL_USER_ID ) && roomTokenOrId == statusBarNotification.notification.extras.getString( - BundleKeys.KEY_ROOM_TOKEN + BundleKeys.KEY_CONVERSATION_TOKEN ) ) { notificationManager.cancel(statusBarNotification.id) diff --git a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt index ecfb7d923..a4a8550cf 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt @@ -35,7 +35,7 @@ object BundleKeys { val KEY_APP_ITEM_PACKAGE_NAME = "KEY_APP_ITEM_PACKAGE_NAME" val KEY_APP_ITEM_NAME = "KEY_APP_ITEM_NAME" val KEY_CONVERSATION_PASSWORD = "KEY_CONVERSATION_PASSWORD" - val KEY_ROOM_TOKEN = "KEY_ROOM_TOKEN" + val KEY_CONVERSATION_TOKEN = "KEY_CONVERSATION_TOKEN" val KEY_USER_ENTITY = "KEY_USER_ENTITY" val KEY_NEW_CONVERSATION = "KEY_NEW_CONVERSATION" val KEY_ADD_PARTICIPANTS = "KEY_ADD_PARTICIPANTS" diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.kt b/app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.kt index a2b9fc275..ab17ea0df 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.kt +++ b/app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.kt @@ -35,7 +35,7 @@ import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.utils.LoggingUtils.writeLogEntryToFile import com.nextcloud.talk.utils.MagicMap import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_TOKEN import okhttp3.OkHttpClient import okhttp3.Request.Builder import okhttp3.Response @@ -240,7 +240,7 @@ class MagicWebSocketInstance internal constructor( if (shouldRefreshChat) { val refreshChatHashMap = HashMap() - refreshChatHashMap[KEY_ROOM_TOKEN] = messageHashMap["roomid"] as String? + refreshChatHashMap[KEY_CONVERSATION_TOKEN] = messageHashMap["roomid"] as String? refreshChatHashMap[KEY_INTERNAL_USER_ID] = java.lang.Long.toString(conversationUser.id!!) eventBus.post( diff --git a/app/src/main/res/layout/contacts_list_view.xml b/app/src/main/res/layout/contacts_list_view.xml new file mode 100644 index 000000000..13c63a736 --- /dev/null +++ b/app/src/main/res/layout/contacts_list_view.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/rv_item_contact.xml b/app/src/main/res/layout/rv_item_contact.xml index dce514409..01ecd33e0 100644 --- a/app/src/main/res/layout/rv_item_contact.xml +++ b/app/src/main/res/layout/rv_item_contact.xml @@ -28,15 +28,15 @@ android:layout_margin="@dimen/margin_between_elements" android:orientation="vertical"> - + android:layout_marginStart="@dimen/double_margin_between_elements" + android:layout_marginEnd="@dimen/margin_between_elements" + app:shapeAppearanceOverlay="@style/circleImageView" + tools:srcCompat="@tools:sample/avatars"/> - + android:src="@drawable/ic_check_black_24dp" + android:tint="@color/colorPrimary" + android:visibility="gone" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2cb913766..a6dbc169c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -180,7 +180,7 @@ conversations list - Select contacts + Find participants Done @@ -289,6 +289,7 @@ Circles Emails Participants + Others Owner Moderator