From 2959d8e13a1fb044f7afd3de89d0f192ce9844c6 Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Tue, 20 Aug 2024 23:54:05 +0200 Subject: [PATCH] Create public conversation Signed-off-by: sowjanyakch --- .../com/nextcloud/talk/api/NcApiCoroutines.kt | 7 ++ .../talk/contacts/ContactsActivityCompose.kt | 34 ++++----- .../talk/contacts/ContactsViewModel.kt | 2 +- .../ConversationCreationActivity.kt | 69 +++++++++++++++---- .../ConversationCreationRepository.kt | 4 ++ .../ConversationCreationRepositoryImpl.kt | 56 +++++++++++++++ .../ConversationCreationViewModel.kt | 24 +++++++ 7 files changed, 167 insertions(+), 29 deletions(-) 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 21b3e4a99..b05714c0a 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt +++ b/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt @@ -11,6 +11,7 @@ import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.participants.AddParticipantOverall +import retrofit2.http.DELETE import retrofit2.http.Field import retrofit2.http.FormUrlEncoded import retrofit2.http.GET @@ -73,4 +74,10 @@ interface NcApiCoroutines { @Url url: String?, @QueryMap options: Map? ): AddParticipantOverall + + @POST + fun makeRoomPublic(@Header("Authorization") authorization: String?, @Url url: String?): GenericOverall + + @DELETE + fun makeRoomPrivate(@Header("Authorization") authorization: String?, @Url url: String?): GenericOverall } diff --git a/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivityCompose.kt b/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivityCompose.kt index aae3316a3..d00079974 100644 --- a/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivityCompose.kt +++ b/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivityCompose.kt @@ -49,6 +49,7 @@ import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -101,12 +102,15 @@ class ContactsActivityCompose : BaseActivity() { } val colorScheme = viewThemeUtils.getColorScheme(this) val uiState = contactsViewModel.contactsViewState.collectAsState() - val selectedParticipants: List = if (isAddParticipantsEdit) { - intent.getParcelableArrayListExtra("selectedParticipants") ?: emptyList() - } else { - emptyList() + val selectedParticipants: List = remember { + if (isAddParticipants) { + intent.getParcelableArrayListExtra("selected participants") ?: emptyList() + } else { + emptyList() + } } - contactsViewModel.updateSelectedParticipants(selectedParticipants) + val participants = selectedParticipants.toSet().toMutableList() + contactsViewModel.updateSelectedParticipants(participants) MaterialTheme( colorScheme = colorScheme ) { @@ -143,7 +147,7 @@ fun ContactItemRow( context: Context, selectedContacts: MutableList ) { - val isSelected = contact in selectedContacts + val isSelected = selectedContacts.contains(contact) val roomUiState by contactsViewModel.roomViewState.collectAsState() val isAddParticipants = contactsViewModel.isAddParticipantsView.collectAsState() Row( @@ -177,16 +181,14 @@ fun ContactItemRow( modifier = Modifier.size(width = 45.dp, height = 45.dp) ) Text(modifier = Modifier.padding(16.dp), text = contact.label!!) - if (isAddParticipants.value) { - if (isSelected) { - Spacer(modifier = Modifier.weight(1f)) - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_check_circle), - contentDescription = "Selected", - tint = Color.Blue, - modifier = Modifier.padding(end = 8.dp) - ) - } + if (isAddParticipants.value && isSelected) { + Spacer(modifier = Modifier.weight(1f)) + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_check_circle), + contentDescription = "Selected", + tint = Color.Blue, + modifier = Modifier.padding(end = 8.dp) + ) } } when (roomUiState) { diff --git a/app/src/main/java/com/nextcloud/talk/contacts/ContactsViewModel.kt b/app/src/main/java/com/nextcloud/talk/contacts/ContactsViewModel.kt index 6de632a7e..7cbf47db4 100644 --- a/app/src/main/java/com/nextcloud/talk/contacts/ContactsViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/contacts/ContactsViewModel.kt @@ -31,7 +31,7 @@ class ContactsViewModel @Inject constructor( private val _searchState = MutableStateFlow(false) val searchState: StateFlow = _searchState private val selectedParticipants = mutableListOf() - val selectedParticipantsList: MutableList = selectedParticipants + val selectedParticipantsList: List = selectedParticipants private val _isAddParticipantsView = MutableStateFlow(false) val isAddParticipantsView: StateFlow = _isAddParticipantsView diff --git a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationActivity.kt index f7363fdd0..31d98af12 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationActivity.kt @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -45,6 +46,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -60,9 +62,12 @@ import coil.compose.AsyncImage import com.nextcloud.talk.R import com.nextcloud.talk.activities.BaseActivity import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.contacts.ContactsActivityCompose +import com.nextcloud.talk.contacts.RoomUiState import com.nextcloud.talk.contacts.loadImage import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser +import com.nextcloud.talk.utils.bundle.BundleKeys import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) @@ -80,10 +85,11 @@ class ConversationCreationActivity : BaseActivity() { )[ConversationCreationViewModel::class.java] setContent { val colorScheme = viewThemeUtils.getColorScheme(this) + val context = LocalContext.current MaterialTheme( colorScheme = colorScheme ) { - ConversationCreationScreen(conversationCreationViewModel) + ConversationCreationScreen(conversationCreationViewModel, context) } } } @@ -91,7 +97,7 @@ class ConversationCreationActivity : BaseActivity() { @OptIn(ExperimentalMaterial3Api::class) @Composable -fun ConversationCreationScreen(conversationCreationViewModel: ConversationCreationViewModel) { +fun ConversationCreationScreen(conversationCreationViewModel: ConversationCreationViewModel, context: Context) { val context = LocalContext.current val launcher = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult(), @@ -101,7 +107,8 @@ fun ConversationCreationScreen(conversationCreationViewModel: ConversationCreati val data = result.data val selectedParticipants = data?.getParcelableArrayListExtra("selectedParticipants") ?: emptyList() - conversationCreationViewModel.updateSelectedParticipants(selectedParticipants) + val participants = selectedParticipants.toMutableList() + conversationCreationViewModel.updateSelectedParticipants(participants) } } ) @@ -133,7 +140,7 @@ fun ConversationCreationScreen(conversationCreationViewModel: ConversationCreati ConversationNameAndDescription(conversationCreationViewModel) AddParticipants(launcher, context, conversationCreationViewModel) RoomCreationOptions(conversationCreationViewModel) - CreateConversation() + CreateConversation(conversationCreationViewModel, context) } } ) @@ -234,10 +241,11 @@ fun AddParticipants( context: Context, conversationCreationViewModel: ConversationCreationViewModel ) { - val participants = conversationCreationViewModel.selectedParticipants.collectAsState() + val participants = conversationCreationViewModel.selectedParticipants.collectAsState().value Column( - modifier = Modifier.fillMaxHeight() + modifier = Modifier + .fillMaxHeight() .padding(start = 16.dp, end = 16.dp, top = 16.dp) ) { Row { @@ -247,7 +255,7 @@ fun AddParticipants( modifier = Modifier.padding(start = 16.dp, bottom = 16.dp) ) Spacer(modifier = Modifier.weight(1f)) - if (participants.value.isNotEmpty()) { + if (participants.isNotEmpty()) { Text( text = stringResource(id = R.string.nc_edit), fontSize = 12.sp, @@ -257,8 +265,9 @@ fun AddParticipants( val intent = Intent(context, ContactsActivityCompose::class.java) intent.putParcelableArrayListExtra( "selected participants", - participants.value as ArrayList + participants as ArrayList ) + intent.putExtra("isAddParticipants", true) intent.putExtra("isAddParticipantsEdit", true) launcher.launch(intent) }, @@ -267,7 +276,9 @@ fun AddParticipants( } } - participants.value.forEach { participant -> + val participant = participants.toSet() + + participant.forEach { participant -> Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { val imageUri = participant.id?.let { conversationCreationViewModel.getImageUri(it, true) } val errorPlaceholderImage: Int = R.drawable.account_circle_96dp @@ -298,7 +309,7 @@ fun AddParticipants( }, verticalAlignment = Alignment.CenterVertically ) { - if (participants.value.isEmpty()) { + if (participant.isEmpty()) { Icon( painter = painterResource(id = R.drawable.ic_account_plus), contentDescription = null, @@ -402,18 +413,52 @@ fun ConversationOptions(icon: Int? = null, text: Int, switch: @Composable (() -> } @Composable -fun CreateConversation() { +fun CreateConversation(conversationCreationViewModel: ConversationCreationViewModel, context: Context) { + val roomUiState by conversationCreationViewModel.roomViewState.collectAsState() Box( modifier = Modifier .fillMaxWidth() - .padding(all = 16.dp), + .padding(all = 16.dp) + .clickable { + }, contentAlignment = Alignment.Center ) { Button( onClick = { + val roomType = if (conversationCreationViewModel.isGuestsAllowed.value) { + "ROOM_TYPE_PUBLIC" + } else { + "ROOM_TYPE_PRIVATE" + } + conversationCreationViewModel.createRoom( + roomType, + conversationCreationViewModel.roomName.value + ) } ) { Text(text = stringResource(id = R.string.create_conversation)) } } + when (roomUiState) { + is RoomUiState.Success -> { + val conversation = (roomUiState as RoomUiState.Success).conversation + val token = conversation?.token + if (token != null) { + conversationCreationViewModel.allowGuests(token, conversationCreationViewModel.isGuestsAllowed.value) + } + val bundle = Bundle() + bundle.putString(BundleKeys.KEY_ROOM_TOKEN, token) + val chatIntent = Intent(context, ChatActivity::class.java) + chatIntent.putExtras(bundle) + chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + context.startActivity(chatIntent) + } + is RoomUiState.Error -> { + val errorMessage = (roomUiState as RoomUiState.Error).message + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text(text = "Error: $errorMessage", color = Color.Red) + } + } + is RoomUiState.None -> {} + } } diff --git a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepository.kt b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepository.kt index f419d326a..f561fd862 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepository.kt @@ -7,12 +7,16 @@ package com.nextcloud.talk.conversationcreation +import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.participants.AddParticipantOverall interface ConversationCreationRepository { + + fun allowGuests(token: String, allow: Boolean): ConversationCreationRepositoryImpl.AllowGuestsResult suspend fun renameConversation(roomToken: String, roomNameNew: String?): GenericOverall suspend fun setConversationDescription(roomToken: String, description: String?): GenericOverall suspend fun addParticipants(conversationToken: String?, userId: String, sourceType: String): AddParticipantOverall + suspend fun createRoom(roomType: String, conversationName: String?): RoomOverall fun getImageUri(avatarId: String, requestBigSize: Boolean): String } 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 0f84331a4..2e3f6088e 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepositoryImpl.kt @@ -10,8 +10,10 @@ package com.nextcloud.talk.conversationcreation import com.nextcloud.talk.api.NcApiCoroutines import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.models.RetrofitBucket +import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.participants.AddParticipantOverall +import com.nextcloud.talk.repositories.conversations.ConversationsRepositoryImpl.Companion.STATUS_CODE_OK import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils.getRetrofitBucketForAddParticipant @@ -25,6 +27,9 @@ class ConversationCreationRepositoryImpl( val currentUser: User = _currentUser val credentials = ApiUtils.getCredentials(_currentUser.username, _currentUser.token) val apiVersion = ApiUtils.getConversationApiVersion(_currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1)) + data class AllowGuestsResult( + val allow: Boolean + ) override suspend fun renameConversation(roomToken: String, roomNameNew: String?): GenericOverall { return ncApiCoroutines.renameRoom( @@ -81,4 +86,55 @@ class ConversationCreationRepositoryImpl( requestBigSize ) } + + override suspend fun createRoom(roomType: String, conversationName: String?): RoomOverall { + val retrofitBucket: RetrofitBucket = + if (roomType == "ROOM_TYPE_PUBLIC") { + ApiUtils.getRetrofitBucketForCreateRoom( + apiVersion, + currentUser.baseUrl!!, + "ROOM_TYPE_PUBLIC", + null, + null, + conversationName + ) + } else { + ApiUtils.getRetrofitBucketForCreateRoom( + apiVersion, + currentUser.baseUrl!!, + "ROOM_TYPE_GROUP", + null, + null, + conversationName + ) + } + val response = ncApiCoroutines.createRoom( + credentials, + retrofitBucket.url, + retrofitBucket.queryMap + ) + return response + } + + override fun allowGuests(token: String, allow: Boolean): AllowGuestsResult { + val url = ApiUtils.getUrlForRoomPublic( + apiVersion, + _currentUser.baseUrl!!, + token + ) + + val result = if (allow) { + ncApiCoroutines.makeRoomPublic( + credentials, + url + ) + } else { + ncApiCoroutines.makeRoomPrivate( + credentials, + url + ) + } + + return AllowGuestsResult(result.ocs!!.meta!!.statusCode == STATUS_CODE_OK && allow) + } } 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 e022f0dfc..df32c5bb2 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationViewModel.kt @@ -12,7 +12,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.nextcloud.talk.contacts.AddParticipantsUiState +import com.nextcloud.talk.contacts.RoomUiState import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser +import com.nextcloud.talk.models.json.conversations.Conversation import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -23,6 +25,8 @@ class ConversationCreationViewModel @Inject constructor( ) : ViewModel() { private val _selectedParticipants = MutableStateFlow>(emptyList()) val selectedParticipants: StateFlow> = _selectedParticipants + private val _roomViewState = MutableStateFlow(RoomUiState.None) + val roomViewState: StateFlow = _roomViewState fun updateSelectedParticipants(participants: List) { _selectedParticipants.value = participants @@ -80,4 +84,24 @@ class ConversationCreationViewModel @Inject constructor( fun getImageUri(avatarId: String, requestBigSize: Boolean): String { return repository.getImageUri(avatarId, requestBigSize) } + + 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 ?: "") + } + } + } + + fun allowGuests(token: String, allow: Boolean): ConversationCreationRepositoryImpl.AllowGuestsResult { + return repository.allowGuests(token, allow) + } }