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 d88f4178f..0828b475f 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt @@ -186,12 +186,10 @@ class MainActivity : BaseActivity(), ActionBarProvider { val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, 1)) val credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token) val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom( - apiVersion, - currentUser?.baseUrl!!, - roomType, - null, - userId, - null + version = apiVersion, + baseUrl = currentUser?.baseUrl!!, + roomType = roomType, + invite = userId ) ncApi.createRoom( diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt b/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt index cef8a27c0..08cc2f469 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt +++ b/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt @@ -2,11 +2,13 @@ * Nextcloud Talk - Android Client * * SPDX-FileCopyrightText: 2024 Sowjanya Kota + * SPDX-FileCopyrightText: 2025 Marcel Hibbe * SPDX-License-Identifier: GPL-3.0-or-later */ package com.nextcloud.talk.api +import com.nextcloud.talk.conversationinfo.CreateRoomRequest import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall import com.nextcloud.talk.models.json.chat.ChatOverall import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage @@ -57,6 +59,13 @@ interface NcApiCoroutines { @QueryMap options: Map? ): RoomOverall + @POST + suspend fun createRoomWithBody( + @Header("Authorization") authorization: String?, + @Url url: String?, + @Body roomRequest: CreateRoomRequest + ): RoomOverall + /* QueryMap items are as follows: - "roomName" : "newName" diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index b82e4c090..bd3816542 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -45,6 +45,7 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.PopupMenu import android.widget.TextView +import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultLauncher @@ -1597,19 +1598,17 @@ class ChatActivity : private fun switchToRoom(token: String, startCallAfterRoomSwitch: Boolean, isVoiceOnlyCall: Boolean) { if (conversationUser != null) { runOnUiThread { - if (currentConversation?.objectType == ConversationEnums.ObjectType.ROOM) { - Snackbar.make( - binding.root, - context.resources.getString(R.string.switch_to_main_room), - Snackbar.LENGTH_LONG - ).show() + val toastInfo = if (currentConversation?.objectType == ConversationEnums.ObjectType.ROOM) { + context.resources.getString(R.string.switch_to_main_room) } else { - Snackbar.make( - binding.root, - context.resources.getString(R.string.switch_to_breakout_room), - Snackbar.LENGTH_LONG - ).show() + context.resources.getString(R.string.switch_to_breakout_room) } + // do not replace with snackbar, as it would disappear with the activity switch + Toast.makeText( + context, + toastInfo, + Toast.LENGTH_LONG + ).show() } val bundle = Bundle() @@ -3167,12 +3166,10 @@ class ChatActivity : val apiVersion = ApiUtils.getConversationApiVersion(conversationUser!!, intArrayOf(ApiUtils.API_V4, 1)) val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom( - apiVersion, - conversationUser?.baseUrl!!, - "1", - null, - message?.user?.id?.substring(INVITE_LENGTH), - null + version = apiVersion, + baseUrl = conversationUser?.baseUrl!!, + roomType = "1", + invite = message?.user?.id?.substring(INVITE_LENGTH) ) chatViewModel.createRoom( credentials!!, @@ -3600,12 +3597,10 @@ class ChatActivity : } val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom( - apiVersion, - conversationUser?.baseUrl!!, - "1", - null, - userMentionClickEvent.userId, - null + version = apiVersion, + baseUrl = conversationUser?.baseUrl!!, + roomType = "1", + invite = userMentionClickEvent.userId ) chatViewModel.createRoom( @@ -3712,12 +3707,11 @@ class ChatActivity : val apiVersion = ApiUtils.getConversationApiVersion(conversationUser!!, intArrayOf(ApiUtils.API_V4, 1)) val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom( - apiVersion, - conversationUser?.baseUrl!!, - ROOM_TYPE_ONE_TO_ONE, - ACTOR_TYPE, - userId, - null + version = apiVersion, + baseUrl = conversationUser?.baseUrl!!, + roomType = ROOM_TYPE_ONE_TO_ONE, + source = ACTOR_TYPE, + invite = userId ) chatViewModel.createRoom( credentials!!, diff --git a/app/src/main/java/com/nextcloud/talk/contacts/ContactsRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/contacts/ContactsRepositoryImpl.kt index 54fc64a06..669e89b1f 100644 --- a/app/src/main/java/com/nextcloud/talk/contacts/ContactsRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/contacts/ContactsRepositoryImpl.kt @@ -71,12 +71,12 @@ class ContactsRepositoryImpl @Inject constructor( } val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom( - apiVersion, - _currentUser.baseUrl, - roomType, - sourceType, - userId, - conversationName + version = apiVersion, + baseUrl = _currentUser.baseUrl, + roomType = roomType, + source = sourceType, + invite = userId, + conversationName = conversationName ) val response = ncApiCoroutines.createRoom( credentials, diff --git a/app/src/main/java/com/nextcloud/talk/conversation/CreateConversationDialogFragment.kt b/app/src/main/java/com/nextcloud/talk/conversation/CreateConversationDialogFragment.kt deleted file mode 100644 index 94f12e5b2..000000000 --- a/app/src/main/java/com/nextcloud/talk/conversation/CreateConversationDialogFragment.kt +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Nextcloud Talk - Android Client - * - * SPDX-FileCopyrightText: 2023 Marcel Hibbe - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package com.nextcloud.talk.conversation - -import android.annotation.SuppressLint -import android.app.Dialog -import android.content.Intent -import android.content.res.ColorStateList -import android.os.Bundle -import android.os.Parcelable -import android.text.Editable -import android.text.TextUtils -import android.text.TextWatcher -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.app.AlertDialog -import androidx.core.content.res.ResourcesCompat -import androidx.fragment.app.DialogFragment -import androidx.lifecycle.ViewModelProvider -import androidx.work.Data -import androidx.work.OneTimeWorkRequest -import androidx.work.WorkInfo -import androidx.work.WorkManager -import autodagger.AutoInjector -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.snackbar.Snackbar -import com.nextcloud.android.common.ui.theme.utils.ColorRole -import com.nextcloud.talk.R -import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.chat.ChatActivity -import com.nextcloud.talk.conversation.viewmodel.ConversationViewModel -import com.nextcloud.talk.databinding.DialogCreateConversationBinding -import com.nextcloud.talk.jobs.AddParticipantsToConversation -import com.nextcloud.talk.models.json.conversations.ConversationEnums -import com.nextcloud.talk.ui.theme.ViewThemeUtils -import com.nextcloud.talk.utils.bundle.BundleKeys -import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew -import com.vanniktech.emoji.EmojiPopup -import org.greenrobot.eventbus.EventBus -import org.parceler.Parcels -import javax.inject.Inject - -@AutoInjector(NextcloudTalkApplication::class) -class CreateConversationDialogFragment : DialogFragment() { - - @Inject - lateinit var viewModelFactory: ViewModelProvider.Factory - - @Inject - lateinit var viewThemeUtils: ViewThemeUtils - - @Inject - lateinit var eventBus: EventBus - - @Inject - lateinit var currentUserProvider: CurrentUserProviderNew - - private lateinit var binding: DialogCreateConversationBinding - private lateinit var viewModel: ConversationViewModel - - private var emojiPopup: EmojiPopup? = null - - private var conversationType: ConversationEnums.ConversationType? = null - private var usersToInvite: ArrayList = ArrayList() - private var groupsToInvite: ArrayList = ArrayList() - private var emailsToInvite: ArrayList = ArrayList() - private var circlesToInvite: ArrayList = ArrayList() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) - - viewModel = ViewModelProvider(this, viewModelFactory)[ConversationViewModel::class.java] - - if (arguments?.containsKey(USERS_TO_INVITE) == true) { - usersToInvite = arguments?.getStringArrayList(USERS_TO_INVITE)!! - } - if (arguments?.containsKey(GROUPS_TO_INVITE) == true) { - groupsToInvite = arguments?.getStringArrayList(GROUPS_TO_INVITE)!! - } - if (arguments?.containsKey(EMAILS_TO_INVITE) == true) { - emailsToInvite = arguments?.getStringArrayList(EMAILS_TO_INVITE)!! - } - if (arguments?.containsKey(CIRCLES_TO_INVITE) == true) { - circlesToInvite = arguments?.getStringArrayList(CIRCLES_TO_INVITE)!! - } - if (arguments?.containsKey(KEY_CONVERSATION_TYPE) == true) { - conversationType = Parcels.unwrap(arguments?.getParcelable(KEY_CONVERSATION_TYPE)) - } - } - - @SuppressLint("InflateParams") - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - binding = DialogCreateConversationBinding.inflate(layoutInflater) - - val dialogBuilder = MaterialAlertDialogBuilder(binding.root.context) - .setTitle(resources.getString(R.string.create_conversation)) - // listener is null for now to avoid closing after button was clicked. - // listener is set later in onStart - .setPositiveButton(R.string.nc_common_create, null) - .setNegativeButton(R.string.nc_common_dismiss, null) - .setView(binding.root) - viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.root.context, dialogBuilder) - - return dialogBuilder.create() - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupListeners() - setupStateObserver() - - setupEmojiPopup() - } - - override fun onStart() { - super.onStart() - - val positiveButton = (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE) - positiveButton.isEnabled = false - positiveButton.setOnClickListener { - viewModel.createConversation( - binding.textEdit.text.toString(), - conversationType - ) - } - - themeDialog() - } - - private fun themeDialog() { - viewThemeUtils.platform.themeDialog(binding.root) - viewThemeUtils.platform.colorTextButtons((dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)) - viewThemeUtils.platform.colorTextButtons((dialog as AlertDialog).getButton(AlertDialog.BUTTON_NEGATIVE)) - viewThemeUtils.material.colorTextInputLayout(binding.textInputLayout) - } - - private fun setupEmojiPopup() { - emojiPopup = binding.let { - EmojiPopup( - rootView = requireView(), - editText = it.textEdit, - onEmojiPopupShownListener = { - viewThemeUtils.platform.colorImageView(it.smileyButton, ColorRole.PRIMARY) - }, - onEmojiPopupDismissListener = { - it.smileyButton.imageTintList = ColorStateList.valueOf( - ResourcesCompat.getColor( - resources, - R.color.medium_emphasis_text, - context?.theme - ) - ) - }, - onEmojiClickListener = { - binding.textEdit.editableText?.append(" ") - } - ) - } - } - - private fun setupListeners() { - binding.smileyButton.setOnClickListener { emojiPopup?.toggle() } - binding.textEdit.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { - // unused atm - } - - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - // unused atm - } - - override fun afterTextChanged(s: Editable) { - val positiveButton = (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE) - - if (!TextUtils.isEmpty(s)) { - if (!positiveButton.isEnabled) { - positiveButton.isEnabled = true - } - } else { - if (positiveButton.isEnabled) { - positiveButton.isEnabled = false - } - } - } - }) - } - - private fun setupStateObserver() { - viewModel.viewState.observe(viewLifecycleOwner) { state -> - when (state) { - is ConversationViewModel.InitialState -> {} - is ConversationViewModel.CreatingState -> {} - is ConversationViewModel.CreatingSuccessState -> addParticipants(state.roomToken) - is ConversationViewModel.CreatingFailedState -> { - Log.e(TAG, "Failed to create conversation") - showError() - } - - else -> {} - } - } - } - - private fun addParticipants(roomToken: String) { - val data = Data.Builder() - data.putLong(BundleKeys.KEY_INTERNAL_USER_ID, currentUserProvider.currentUser.blockingGet().id!!) - data.putString(BundleKeys.KEY_TOKEN, roomToken) - data.putStringArray(BundleKeys.KEY_SELECTED_USERS, usersToInvite.toTypedArray()) - data.putStringArray(BundleKeys.KEY_SELECTED_GROUPS, groupsToInvite.toTypedArray()) - data.putStringArray(BundleKeys.KEY_SELECTED_EMAILS, emailsToInvite.toTypedArray()) - data.putStringArray(BundleKeys.KEY_SELECTED_CIRCLES, circlesToInvite.toTypedArray()) - - val addParticipantsToConversationWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder( - AddParticipantsToConversation::class.java - ) - .setInputData(data.build()) - .build() - - WorkManager.getInstance(requireContext()).enqueue(addParticipantsToConversationWorker) - - WorkManager.getInstance(requireContext()).getWorkInfoByIdLiveData(addParticipantsToConversationWorker.id) - .observeForever { workInfo: WorkInfo? -> - if (workInfo != null) { - when (workInfo.state) { - WorkInfo.State.RUNNING -> { - Log.d(TAG, "running AddParticipantsToConversation") - } - - WorkInfo.State.SUCCEEDED -> { - Log.d(TAG, "success AddParticipantsToConversation") - initiateConversation(roomToken) - } - - WorkInfo.State.FAILED -> { - Log.e(TAG, "failed to AddParticipantsToConversation") - showError() - } - - else -> { - } - } - } - } - } - - private fun initiateConversation(roomToken: String) { - activity?.let { - val bundle = Bundle() - bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken) - val chatIntent = Intent(it, ChatActivity::class.java) - chatIntent.putExtras(bundle) - chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - startActivity(chatIntent) - } - - dismiss() - } - - private fun showError() { - dismiss() - Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() - } - - /** - * Fragment creator - */ - companion object { - private val TAG = CreateConversationDialogFragment::class.java.simpleName - private const val USERS_TO_INVITE = "usersToInvite" - private const val GROUPS_TO_INVITE = "groupsToInvite" - private const val EMAILS_TO_INVITE = "emailsToInvite" - private const val CIRCLES_TO_INVITE = "circlesToInvite" - private const val KEY_CONVERSATION_TYPE = "keyConversationType" - - @JvmStatic - fun newInstance( - usersToInvite: ArrayList?, - groupsToInvite: ArrayList?, - emailsToInvite: ArrayList?, - circlesToInvite: ArrayList?, - conversationType: Parcelable - ): CreateConversationDialogFragment { - val args = Bundle() - args.putStringArrayList(USERS_TO_INVITE, usersToInvite) - args.putStringArrayList(GROUPS_TO_INVITE, groupsToInvite) - args.putStringArrayList(EMAILS_TO_INVITE, emailsToInvite) - args.putStringArrayList(CIRCLES_TO_INVITE, circlesToInvite) - args.putParcelable(KEY_CONVERSATION_TYPE, conversationType) - val fragment = CreateConversationDialogFragment() - fragment.arguments = args - return fragment - } - } -} diff --git a/app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepository.kt b/app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepository.kt deleted file mode 100644 index 96580c646..000000000 --- a/app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepository.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Nextcloud Talk - Android Client - * - * SPDX-FileCopyrightText: 2023 Marcel Hibbe - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package com.nextcloud.talk.conversation.repository - -import com.nextcloud.talk.models.json.conversations.ConversationEnums -import com.nextcloud.talk.models.json.conversations.RoomOverall -import io.reactivex.Observable - -interface ConversationRepository { - - fun createConversation( - roomName: String, - conversationType: ConversationEnums.ConversationType? - ): Observable -} diff --git a/app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepositoryImpl.kt deleted file mode 100644 index d7b3467ef..000000000 --- a/app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepositoryImpl.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Nextcloud Talk - Android Client - * - * SPDX-FileCopyrightText: 2023 Marcel Hibbe - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package com.nextcloud.talk.conversation.repository - -import com.nextcloud.talk.api.NcApi -import com.nextcloud.talk.data.user.model.User -import com.nextcloud.talk.models.RetrofitBucket -import com.nextcloud.talk.models.json.conversations.ConversationEnums -import com.nextcloud.talk.models.json.conversations.RoomOverall -import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers - -class ConversationRepositoryImpl(private val ncApi: NcApi, currentUserProvider: CurrentUserProviderNew) : - ConversationRepository { - - val currentUser: User = currentUserProvider.currentUser.blockingGet() - val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!! - - override fun createConversation( - roomName: String, - conversationType: ConversationEnums.ConversationType? - ): Observable { - val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1)) - - val retrofitBucket: RetrofitBucket = - if (conversationType == ConversationEnums.ConversationType.ROOM_PUBLIC_CALL) { - ApiUtils.getRetrofitBucketForCreateRoom( - apiVersion, - currentUser.baseUrl!!, - ROOM_TYPE_PUBLIC, - null, - null, - roomName - ) - } else { - ApiUtils.getRetrofitBucketForCreateRoom( - apiVersion, - currentUser.baseUrl!!, - ROOM_TYPE_GROUP, - null, - null, - roomName - ) - } - return ncApi.createRoom(credentials, retrofitBucket.url, retrofitBucket.queryMap) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .retry(1) - } - - companion object { - private const val ROOM_TYPE_PUBLIC = "3" - private const val ROOM_TYPE_GROUP = "2" - } -} diff --git a/app/src/main/java/com/nextcloud/talk/conversation/viewmodel/ConversationViewModel.kt b/app/src/main/java/com/nextcloud/talk/conversation/viewmodel/ConversationViewModel.kt deleted file mode 100644 index 7783199f1..000000000 --- a/app/src/main/java/com/nextcloud/talk/conversation/viewmodel/ConversationViewModel.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Nextcloud Talk - Android Client - * - * SPDX-FileCopyrightText: 2023 Marcel Hibbe - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package com.nextcloud.talk.conversation.viewmodel - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import com.nextcloud.talk.conversation.repository.ConversationRepository -import com.nextcloud.talk.models.json.conversations.ConversationEnums -import com.nextcloud.talk.models.json.conversations.RoomOverall -import io.reactivex.Observer -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers -import javax.inject.Inject - -class ConversationViewModel @Inject constructor(private val repository: ConversationRepository) : ViewModel() { - - sealed class ViewState - object InitialState : ViewState() - - object CreatingState : ViewState() - class CreatingSuccessState(val roomToken: String) : ViewState() - object CreatingFailedState : ViewState() - - private val _viewState: MutableLiveData = MutableLiveData( - InitialState - ) - val viewState: LiveData - get() = _viewState - - private var disposable: Disposable? = null - - override fun onCleared() { - super.onCleared() - disposable?.dispose() - } - - fun createConversation(roomName: String, conversationType: ConversationEnums.ConversationType?) { - _viewState.value = CreatingState - - repository.createConversation( - roomName, - conversationType - ) - .doOnSubscribe { disposable = it } - ?.subscribeOn(Schedulers.io()) - ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(CreateConversationObserver()) - } - - inner class CreateConversationObserver : Observer { - override fun onSubscribe(d: Disposable) { - // unused atm - } - - override fun onNext(roomOverall: RoomOverall) { - val conversation = roomOverall.ocs!!.data - _viewState.value = CreatingSuccessState(conversation?.token!!) - } - - override fun onError(e: Throwable) { - // dispose() - } - - override fun onComplete() { - // dispose() - } - } - - companion object { - private val TAG = ConversationViewModel::class.java.simpleName - } -} diff --git a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepositoryImpl.kt index 9c127b300..4ac3d1a3f 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepositoryImpl.kt @@ -97,12 +97,10 @@ class ConversationCreationRepositoryImpl @Inject constructor( override suspend fun createRoom(roomType: String, conversationName: String?): RoomOverall { val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom( - apiVersion, - _currentUser.baseUrl, - roomType, - null, - null, - conversationName + version = apiVersion, + baseUrl = _currentUser.baseUrl, + roomType = roomType, + conversationName = conversationName ) val response = ncApiCoroutines.createRoom( credentials, diff --git a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationViewModel.kt b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationViewModel.kt index d38c9e823..d070f7282 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationViewModel.kt @@ -142,23 +142,6 @@ class ConversationCreationViewModel @Inject constructor( fun getImageUri(avatarId: String, requestBigSize: Boolean): String { return repository.getImageUri(avatarId, requestBigSize) } - - @Suppress("Detekt.TooGenericExceptionCaught") - fun createRoom(roomType: String, conversationName: String?) { - viewModelScope.launch { - try { - val room = repository.createRoom( - roomType, - conversationName - ) - - val conversation: Conversation? = room.ocs?.data - roomViewState.value = RoomUIState.Success(conversation) - } catch (exception: Exception) { - roomViewState.value = RoomUIState.Error(exception.message ?: "") - } - } - } } sealed class AllowGuestsUiState { diff --git a/app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt index dde55d7a1..4ab59c9b3 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt @@ -11,7 +11,6 @@ package com.nextcloud.talk.conversationinfo import android.annotation.SuppressLint -import android.app.Activity import android.content.Intent import android.os.Bundle import android.text.TextUtils @@ -66,7 +65,7 @@ import com.nextcloud.talk.extensions.loadConversationAvatar import com.nextcloud.talk.extensions.loadNoteToSelfAvatar import com.nextcloud.talk.extensions.loadSystemAvatar import com.nextcloud.talk.extensions.loadUserAvatar -import com.nextcloud.talk.jobs.AddParticipantsToConversation +import com.nextcloud.talk.jobs.AddParticipantsToConversationWorker import com.nextcloud.talk.jobs.DeleteConversationWorker import com.nextcloud.talk.jobs.LeaveConversationWorker import com.nextcloud.talk.models.domain.ConversationModel @@ -93,6 +92,7 @@ import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.ShareUtils import com.nextcloud.talk.utils.SpreedFeatures import com.nextcloud.talk.utils.bundle.BundleKeys +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager @@ -142,6 +142,8 @@ class ConversationInfoActivity : private var adapter: FlexibleAdapter? = null private var userItems: MutableList = ArrayList() + private var startGroupChat: Boolean = false + private lateinit var optionsMenu: Menu private val workerData: Data? @@ -157,13 +159,22 @@ class ConversationInfoActivity : private val addParticipantsResult = registerForActivityResult( ActivityResultContracts.StartActivityForResult() - ) { + ) { it -> executeIfResultOk(it) { intent -> - val selectedParticipants = + val selectedAutocompleteUsers = intent?.getParcelableArrayListExtraProvider("selectedParticipants") ?: emptyList() - val participants = selectedParticipants.toMutableList() - addParticipantsToConversation(participants) + + if (startGroupChat) { + viewModel.createRoomFromOneToOne( + conversationUser, + userItems.map { it.model }, + selectedAutocompleteUsers, + conversationToken + ) + } else { + addParticipantsToConversation(selectedAutocompleteUsers) + } } } @@ -206,7 +217,14 @@ class ConversationInfoActivity : binding.deleteConversationAction.setOnClickListener { showDeleteConversationDialog() } binding.leaveConversationAction.setOnClickListener { leaveConversation() } binding.clearConversationHistory.setOnClickListener { showClearHistoryDialog() } - binding.addParticipantsAction.setOnClickListener { selectParticipantsToAdd() } + binding.addParticipantsAction.setOnClickListener { + startGroupChat = false + selectParticipantsToAdd() + } + binding.startGroupChat.setOnClickListener { + startGroupChat = true + selectParticipantsToAdd() + } binding.listBansButton.setOnClickListener { listBans() } viewModel.getRoom(conversationUser, conversationToken) @@ -222,47 +240,14 @@ class ConversationInfoActivity : private fun initObservers() { initViewStateObserver() + initCapabilitiesObersver() + initRoomOberserver() + initBanActorObserver() + initConversationReadOnlyObserver() + initClearChatHistoryObserver() + } - viewModel.getCapabilitiesViewState.observe(this) { state -> - when (state) { - is ConversationInfoViewModel.GetCapabilitiesSuccessState -> { - spreedCapabilities = state.spreedCapabilities - - handleConversation() - } - - else -> {} - } - } - - viewModel.getBanActorState.observe(this) { state -> - when (state) { - is ConversationInfoViewModel.BanActorSuccessState -> { - getListOfParticipants() // Refresh the list of participants - } - - ConversationInfoViewModel.BanActorErrorState -> { - Snackbar.make(binding.root, "Error banning actor", Snackbar.LENGTH_SHORT).show() - } - - else -> {} - } - } - - viewModel.getConversationReadOnlyState.observe(this) { state -> - when (state) { - is ConversationInfoViewModel.SetConversationReadOnlyViewState.Success -> { - } - - is ConversationInfoViewModel.SetConversationReadOnlyViewState.Error -> { - Snackbar.make(binding.root, R.string.conversation_read_only_failed, Snackbar.LENGTH_LONG).show() - } - - is ConversationInfoViewModel.SetConversationReadOnlyViewState.None -> { - } - } - } - + private fun initClearChatHistoryObserver() { viewModel.clearChatHistoryViewState.observe(this) { uiState -> when (uiState) { is ConversationInfoViewModel.ClearChatHistoryViewState.None -> { @@ -284,6 +269,73 @@ class ConversationInfoActivity : } } + private fun initConversationReadOnlyObserver() { + viewModel.getConversationReadOnlyState.observe(this) { state -> + when (state) { + is ConversationInfoViewModel.SetConversationReadOnlyViewState.Success -> { + } + + is ConversationInfoViewModel.SetConversationReadOnlyViewState.Error -> { + Snackbar.make(binding.root, R.string.conversation_read_only_failed, Snackbar.LENGTH_LONG).show() + } + + is ConversationInfoViewModel.SetConversationReadOnlyViewState.None -> { + } + } + } + } + + private fun initBanActorObserver() { + viewModel.getBanActorState.observe(this) { state -> + when (state) { + is ConversationInfoViewModel.BanActorSuccessState -> { + getListOfParticipants() // Refresh the list of participants + } + + ConversationInfoViewModel.BanActorErrorState -> { + Snackbar.make(binding.root, "Error banning actor", Snackbar.LENGTH_SHORT).show() + } + + else -> {} + } + } + } + + private fun initRoomOberserver() { + viewModel.createRoomViewState.observe(this) { state -> + when (state) { + is ConversationInfoViewModel.CreateRoomUIState.Success -> { + state.room.ocs?.data?.token?.let { token -> + val chatIntent = Intent(context, ChatActivity::class.java).apply { + putExtra(KEY_ROOM_TOKEN, token) + } + startActivity(chatIntent) + } + } + + is ConversationInfoViewModel.CreateRoomUIState.Error -> { + Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() + } + + else -> {} + } + } + } + + private fun initCapabilitiesObersver() { + viewModel.getCapabilitiesViewState.observe(this) { state -> + when (state) { + is ConversationInfoViewModel.GetCapabilitiesSuccessState -> { + spreedCapabilities = state.spreedCapabilities + + handleConversation() + } + + else -> {} + } + } + } + private fun initViewStateObserver() { viewModel.viewState.observe(this) { state -> when (state) { @@ -673,7 +725,7 @@ class ConversationInfoActivity : } private fun executeIfResultOk(result: ActivityResult, onResult: (intent: Intent?) -> Unit) { - if (result.resultCode == Activity.RESULT_OK) { + if (result.resultCode == RESULT_OK) { onResult(result.data) } else { Log.e(ChatActivity.TAG, "resultCode for received intent was != ok") @@ -706,17 +758,17 @@ class ConversationInfoActivity : addParticipantsResult.launch(intent) } - private fun addParticipantsToConversation(participants: List) { + private fun addParticipantsToConversation(autocompleteUsers: List) { val groupIdsArray: MutableSet = HashSet() val emailIdsArray: MutableSet = HashSet() val circleIdsArray: MutableSet = HashSet() val userIdsArray: MutableSet = HashSet() - participants.forEach { participant -> + autocompleteUsers.forEach { participant -> when (participant.source) { - Participant.ActorType.GROUPS.name.lowercase() -> groupIdsArray.add(participant.id!!) + GROUPS.name.lowercase() -> groupIdsArray.add(participant.id!!) Participant.ActorType.EMAILS.name.lowercase() -> emailIdsArray.add(participant.id!!) - Participant.ActorType.CIRCLES.name.lowercase() -> circleIdsArray.add(participant.id!!) + CIRCLES.name.lowercase() -> circleIdsArray.add(participant.id!!) else -> userIdsArray.add(participant.id!!) } } @@ -729,7 +781,7 @@ class ConversationInfoActivity : data.putStringArray(BundleKeys.KEY_SELECTED_EMAILS, emailIdsArray.toTypedArray()) data.putStringArray(BundleKeys.KEY_SELECTED_CIRCLES, circleIdsArray.toTypedArray()) val addParticipantsToConversationWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder( - AddParticipantsToConversation::class.java + AddParticipantsToConversationWorker::class.java ).setInputData(data.build()).build() WorkManager.getInstance().enqueue(addParticipantsToConversationWorker) @@ -865,7 +917,12 @@ class ConversationInfoActivity : binding.sharedItems.visibility = GONE } - if (ConversationUtils.canModerate(conversationCopy, spreedCapabilities)) { + if (conversation!!.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL && + hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.CONVERSATION_CREATION_ALL) + ) { + binding.addParticipantsAction.visibility = GONE + binding.startGroupChat.visibility = VISIBLE + } else if (ConversationUtils.canModerate(conversationCopy, spreedCapabilities)) { binding.addParticipantsAction.visibility = VISIBLE if (hasSpreedFeatureCapability( spreedCapabilities, diff --git a/app/src/main/java/com/nextcloud/talk/conversationinfo/CreateRoomRequest.kt b/app/src/main/java/com/nextcloud/talk/conversationinfo/CreateRoomRequest.kt new file mode 100644 index 000000000..9aa8b6b02 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/conversationinfo/CreateRoomRequest.kt @@ -0,0 +1,72 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2025 Marcel Hibbe + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.conversationinfo + +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject + +@JsonObject +data class CreateRoomRequest( + @JsonField(name = ["roomType"]) + var roomType: String, + @JsonField(name = ["roomName"]) + var roomName: String? = null, + @JsonField(name = ["objectType"]) + var objectType: String? = null, + @JsonField(name = ["objectId"]) + var objectId: String? = null, + @JsonField(name = ["password"]) + var password: String? = null, + @JsonField(name = ["readOnly"]) + var readOnly: Int, + @JsonField(name = ["listable"]) + var listable: Int, + @JsonField(name = ["messageExpiration"]) + var messageExpiration: Int? = null, + @JsonField(name = ["lobbyState"]) + var lobbyState: Int? = null, + @JsonField(name = ["lobbyTimer"]) + var lobbyTimer: Int, + @JsonField(name = ["sipEnabled"]) + var sipEnabled: Int, + @JsonField(name = ["permissions"]) + var permissions: Int, + @JsonField(name = ["recordingConsent"]) + var recordingConsent: Int, + @JsonField(name = ["mentionPermissions"]) + var mentionPermissions: Int, + @JsonField(name = ["description"]) + var description: String? = null, + @JsonField(name = ["emoji"]) + var emoji: String? = null, + @JsonField(name = ["avatarColor"]) + var avatarColor: String? = null, + @JsonField(name = ["participants"]) + var participants: Participants? = null +) { + constructor() : this( + 0.toString(), + "", + "", + "", + "", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "", + "", + "", + Participants() + ) +} diff --git a/app/src/main/java/com/nextcloud/talk/conversationinfo/Participants.kt b/app/src/main/java/com/nextcloud/talk/conversationinfo/Participants.kt new file mode 100644 index 000000000..1f1e8c834 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/conversationinfo/Participants.kt @@ -0,0 +1,27 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2025 Marcel Hibbe + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.conversationinfo + +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject + +@JsonObject +data class Participants( + @JsonField(name = ["users"]) + var users: MutableList = arrayListOf(), + @JsonField(name = ["federated_users"]) + var federatedUsers: MutableList = arrayListOf(), + @JsonField(name = ["groups"]) + var groups: MutableList = arrayListOf(), + @JsonField(name = ["emails"]) + var emails: MutableList = arrayListOf(), + @JsonField(name = ["phones"]) + var phones: MutableList = arrayListOf(), + @JsonField(name = ["teams"]) + var teams: MutableList = arrayListOf() +) diff --git a/app/src/main/java/com/nextcloud/talk/conversationinfo/viewmodel/ConversationInfoViewModel.kt b/app/src/main/java/com/nextcloud/talk/conversationinfo/viewmodel/ConversationInfoViewModel.kt index f6debb14f..8ed98e29d 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationinfo/viewmodel/ConversationInfoViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationinfo/viewmodel/ConversationInfoViewModel.kt @@ -15,12 +15,23 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource +import com.nextcloud.talk.conversationinfo.CreateRoomRequest +import com.nextcloud.talk.conversationinfo.Participants import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.models.domain.ConversationModel +import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser import com.nextcloud.talk.models.json.capabilities.SpreedCapability +import com.nextcloud.talk.models.json.conversations.RoomOverall +import com.nextcloud.talk.models.json.participants.Participant +import com.nextcloud.talk.models.json.participants.Participant.ActorType.CIRCLES +import com.nextcloud.talk.models.json.participants.Participant.ActorType.EMAILS +import com.nextcloud.talk.models.json.participants.Participant.ActorType.FEDERATED +import com.nextcloud.talk.models.json.participants.Participant.ActorType.GROUPS import com.nextcloud.talk.models.json.participants.TalkBan import com.nextcloud.talk.repositories.conversations.ConversationsRepository import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.ApiUtils.getUrlForRooms +import com.nextcloud.talk.utils.DisplayUtils import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable @@ -112,6 +123,10 @@ class ConversationInfoViewModel @Inject constructor( val getConversationReadOnlyState: LiveData get() = _getConversationReadOnlyState + private val _createRoomViewState = MutableLiveData(CreateRoomUIState.None) + val createRoomViewState: LiveData + get() = _createRoomViewState + fun getRoom(user: User, token: String) { _viewState.value = GetRoomStartState chatNetworkDataSource.getRoom(user, token) @@ -120,6 +135,69 @@ class ConversationInfoViewModel @Inject constructor( ?.subscribe(GetRoomObserver()) } + @Suppress("Detekt.TooGenericExceptionCaught") + fun createRoomFromOneToOne( + user: User, + userItems: List, + autocompleteUsers: List, + roomToken: String + ) { + val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, 1)) + val url = getUrlForRooms(apiVersion, user.baseUrl!!) + val credentials = ApiUtils.getCredentials(user.username, user.token)!! + + val participantsBody = convertAutocompleteUserToParticipant(autocompleteUsers) + + val body = CreateRoomRequest( + roomName = createConversationNameByParticipants( + userItems.map { it.displayName }, + autocompleteUsers.map { it.label } + ), + roomType = GROUP_CONVERSATION_TYPE, + readOnly = 0, + listable = 1, + lobbyTimer = 0, + sipEnabled = 0, + permissions = 0, + recordingConsent = 0, + mentionPermissions = 0, + participants = participantsBody, + objectType = EXTENDED_CONVERSATION, + objectId = roomToken + ) + + viewModelScope.launch { + try { + val roomOverall = conversationsRepository.createRoom( + credentials, + url, + body + ) + _createRoomViewState.value = CreateRoomUIState.Success(roomOverall) + } catch (e: Exception) { + Log.e(TAG, "Failed to create room", e) + _createRoomViewState.value = CreateRoomUIState.Error(e) + } + } + } + + private fun convertAutocompleteUserToParticipant(autocompleteUsers: List): Participants { + val participants = Participants() + + autocompleteUsers.forEach { autocompleteUser -> + when (autocompleteUser.source) { + GROUPS.name.lowercase() -> participants.groups.add(autocompleteUser.id!!) + EMAILS.name.lowercase() -> participants.emails.add(autocompleteUser.id!!) + CIRCLES.name.lowercase() -> participants.teams.add(autocompleteUser.id!!) + FEDERATED.name.lowercase() -> participants.federatedUsers.add(autocompleteUser.id!!) + "phones".lowercase() -> participants.phones.add(autocompleteUser.id!!) + else -> participants.users.add(autocompleteUser.id!!) + } + } + + return participants + } + fun getCapabilities(user: User, token: String, conversationModel: ConversationModel) { _getCapabilitiesViewState.value = GetCapabilitiesStartState @@ -280,6 +358,26 @@ class ConversationInfoViewModel @Inject constructor( companion object { private val TAG = ConversationInfoViewModel::class.simpleName + private const val NEW_CONVERSATION_PARTICIPANTS_SEPARATOR = ", " + private const val EXTENDED_CONVERSATION = "extended_conversation" + private const val GROUP_CONVERSATION_TYPE = "2" + private const val MAX_ROOM_NAME_LENGTH = 255 + + fun createConversationNameByParticipants( + originalParticipants: List, + allParticipants: List + ): String { + fun List.sortedJoined() = + sortedBy { it?.lowercase() } + .joinToString(NEW_CONVERSATION_PARTICIPANTS_SEPARATOR) + + val addedParticipants = allParticipants - originalParticipants.toSet() + val conversationName = originalParticipants.mapNotNull { it }.sortedJoined() + + NEW_CONVERSATION_PARTICIPANTS_SEPARATOR + + addedParticipants.mapNotNull { it }.sortedJoined() + + return DisplayUtils.ellipsize(conversationName, MAX_ROOM_NAME_LENGTH) + } } sealed class ClearChatHistoryViewState { @@ -300,6 +398,12 @@ class ConversationInfoViewModel @Inject constructor( data class Error(val exception: Exception) : AllowGuestsUIState() } + sealed class CreateRoomUIState { + data object None : CreateRoomUIState() + data class Success(val room: RoomOverall) : CreateRoomUIState() + data class Error(val exception: Exception) : CreateRoomUIState() + } + sealed class PasswordUiState { data object None : PasswordUiState() data object Success : PasswordUiState() diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt index 6243c3198..6eb4d0aa4 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt @@ -17,8 +17,6 @@ import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository import com.nextcloud.talk.chat.data.network.RetrofitChatNetwork import com.nextcloud.talk.contacts.ContactsRepository import com.nextcloud.talk.contacts.ContactsRepositoryImpl -import com.nextcloud.talk.conversation.repository.ConversationRepository -import com.nextcloud.talk.conversation.repository.ConversationRepositoryImpl import com.nextcloud.talk.conversationcreation.ConversationCreationRepository import com.nextcloud.talk.conversationcreation.ConversationCreationRepositoryImpl import com.nextcloud.talk.conversationinfoedit.data.ConversationInfoEditRepository @@ -142,10 +140,6 @@ class RepositoryModule { return ConversationInfoEditRepositoryImpl(ncApi, ncApiCoroutines, userProvider) } - @Provides - fun provideConversationRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): ConversationRepository = - ConversationRepositoryImpl(ncApi, userProvider) - @Provides fun provideInvitationsRepository(ncApi: NcApi): InvitationsRepository = InvitationsRepositoryImpl(ncApi) diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt index 823cab6a5..a5626faf2 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt @@ -12,7 +12,6 @@ import androidx.lifecycle.ViewModelProvider import com.nextcloud.talk.chat.viewmodels.ChatViewModel import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel import com.nextcloud.talk.contacts.ContactsViewModel -import com.nextcloud.talk.conversation.viewmodel.ConversationViewModel import com.nextcloud.talk.conversationcreation.ConversationCreationViewModel import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel import com.nextcloud.talk.conversationinfoedit.viewmodel.ConversationInfoEditViewModel @@ -135,11 +134,6 @@ abstract class ViewModelModule { @ViewModelKey(ConversationInfoEditViewModel::class) abstract fun conversationInfoEditViewModel(viewModel: ConversationInfoEditViewModel): ViewModel - @Binds - @IntoMap - @ViewModelKey(ConversationViewModel::class) - abstract fun conversationViewModel(viewModel: ConversationViewModel): ViewModel - @Binds @IntoMap @ViewModelKey(InvitationsViewModel::class) diff --git a/app/src/main/java/com/nextcloud/talk/jobs/AddParticipantsToConversation.java b/app/src/main/java/com/nextcloud/talk/jobs/AddParticipantsToConversationWorker.java similarity index 96% rename from app/src/main/java/com/nextcloud/talk/jobs/AddParticipantsToConversation.java rename to app/src/main/java/com/nextcloud/talk/jobs/AddParticipantsToConversationWorker.java index 794adefe4..3d533f0cb 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/AddParticipantsToConversation.java +++ b/app/src/main/java/com/nextcloud/talk/jobs/AddParticipantsToConversationWorker.java @@ -28,7 +28,7 @@ import autodagger.AutoInjector; import io.reactivex.schedulers.Schedulers; @AutoInjector(NextcloudTalkApplication.class) -public class AddParticipantsToConversation extends Worker { +public class AddParticipantsToConversationWorker extends Worker { @Inject NcApi ncApi; @@ -38,7 +38,7 @@ public class AddParticipantsToConversation extends Worker { @Inject EventBus eventBus; - public AddParticipantsToConversation(@NonNull Context context, @NonNull WorkerParameters workerParams) { + public AddParticipantsToConversationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this); } diff --git a/app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepository.kt b/app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepository.kt index df4350504..c68b581d6 100644 --- a/app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepository.kt @@ -7,6 +7,8 @@ */ package com.nextcloud.talk.repositories.conversations +import com.nextcloud.talk.conversationinfo.CreateRoomRequest +import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.participants.TalkBan import io.reactivex.Observable @@ -42,4 +44,6 @@ interface ConversationsRepository { suspend fun setConversationReadOnly(roomToken: String, state: Int): GenericOverall suspend fun clearChatHistory(apiVersion: Int, roomToken: String): GenericOverall + + suspend fun createRoom(credentials: String, url: String, body: CreateRoomRequest): RoomOverall } diff --git a/app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepositoryImpl.kt index 5732b07cd..92886680e 100644 --- a/app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepositoryImpl.kt @@ -9,7 +9,9 @@ package com.nextcloud.talk.repositories.conversations import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApiCoroutines +import com.nextcloud.talk.conversationinfo.CreateRoomRequest import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.participants.TalkBan import com.nextcloud.talk.repositories.conversations.ConversationsRepository.ResendInvitationsResult @@ -105,6 +107,15 @@ class ConversationsRepositoryImpl( ) } + override suspend fun createRoom(credentials: String, url: String, body: CreateRoomRequest): RoomOverall { + val response = coroutineApi.createRoomWithBody( + credentials, + url, + body + ) + return response + } + override suspend fun banActor( credentials: String, url: String, diff --git a/app/src/main/java/com/nextcloud/talk/ui/bottom/sheet/ProfileBottomSheet.kt b/app/src/main/java/com/nextcloud/talk/ui/bottom/sheet/ProfileBottomSheet.kt index 6f561ef2d..814692f32 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/bottom/sheet/ProfileBottomSheet.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/bottom/sheet/ProfileBottomSheet.kt @@ -118,12 +118,10 @@ class ProfileBottomSheet(val ncApi: NcApi, val userModel: User, val viewThemeUti val apiVersion = ApiUtils.getConversationApiVersion(userModel, intArrayOf(ApiUtils.API_V4, 1)) val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom( - apiVersion, - userModel.baseUrl!!, - "1", - null, - userId, - null + version = apiVersion, + baseUrl = userModel.baseUrl!!, + roomType = "1", + invite = userId ) val credentials = ApiUtils.getCredentials(userModel.username, userModel.token) ncApi.createRoom( diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt index ff2956e5e..8dc117490 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt @@ -298,25 +298,19 @@ object ApiUtils { @Suppress("LongParameterList") fun getRetrofitBucketForCreateRoom( version: Int, - baseUrl: String?, roomType: String, - source: String?, - invite: String?, - conversationName: String? + baseUrl: String? = null, + source: String? = null, + invite: String? = null, + conversationName: String? = null ): RetrofitBucket { val retrofitBucket = RetrofitBucket() retrofitBucket.url = getUrlForRooms(version, baseUrl) val queryMap: MutableMap = HashMap() queryMap["roomType"] = roomType - if (invite != null) { - queryMap["invite"] = invite - } - if (source != null) { - queryMap["source"] = source - } - if (conversationName != null) { - queryMap["roomName"] = conversationName - } + invite?.let { queryMap["invite"] = it } + source?.let { queryMap["source"] = it } + conversationName?.let { queryMap["roomName"] = it } retrofitBucket.queryMap = queryMap return retrofitBucket } diff --git a/app/src/main/java/com/nextcloud/talk/utils/CapabilitiesUtil.kt b/app/src/main/java/com/nextcloud/talk/utils/CapabilitiesUtil.kt index 90fdbe322..8ed61b5c7 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/CapabilitiesUtil.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/CapabilitiesUtil.kt @@ -56,7 +56,8 @@ enum class SpreedFeatures(val value: String) { DELETE_MESSAGES_UNLIMITED("delete-messages-unlimited"), BAN_V1("ban-v1"), EDIT_MESSAGES_NOTE_TO_SELF("edit-messages-note-to-self"), - ARCHIVE_CONVERSATIONS("archived-conversations-v2") + ARCHIVE_CONVERSATIONS("archived-conversations-v2"), + CONVERSATION_CREATION_ALL("conversation-creation-all") } @Suppress("TooManyFunctions") diff --git a/app/src/main/res/layout/activity_conversation_info.xml b/app/src/main/res/layout/activity_conversation_info.xml index f67dac31e..bbaba30e7 100644 --- a/app/src/main/res/layout/activity_conversation_info.xml +++ b/app/src/main/res/layout/activity_conversation_info.xml @@ -392,6 +392,34 @@ + + + + + + + + + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.conversationinfo.viewmodel + +import org.junit.Test +import org.junit.Assert.assertEquals + +class ConversationInfoViewModelTest { + + @Test + fun `createConversationNameByParticipants should combine names correctly`() { + val original = listOf("Dave", null, "Charlie") + val all = listOf("Bob", "Charlie", "Dave", "Alice", null, "Simon") + + val expectedName = "Charlie, Dave, Alice, Bob, Simon" + val result = ConversationInfoViewModel.createConversationNameByParticipants(original, all) + + assertEquals(expectedName, result) + } +}