diff --git a/app/build.gradle b/app/build.gradle
index 993794069..469f4a39b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -309,7 +309,7 @@ dependencies {
//compose
implementation(platform("androidx.compose:compose-bom:2024.09.00"))
implementation("androidx.compose.ui:ui")
- implementation 'androidx.compose.material3:material3'
+ implementation 'androidx.compose.material3:material3:1.2.1'
implementation("androidx.compose.ui:ui-tooling-preview")
implementation 'androidx.activity:activity-compose:1.9.2'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.5'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 410dd82da..256e2b2b5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -129,6 +129,9 @@
+
+
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 c330c33cb..d08d2b317 100644
--- a/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt
+++ b/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt
@@ -9,9 +9,15 @@ package com.nextcloud.talk.api
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
import retrofit2.http.Header
import retrofit2.http.POST
+import retrofit2.http.PUT
import retrofit2.http.Query
import retrofit2.http.QueryMap
import retrofit2.http.Url
@@ -39,4 +45,55 @@ interface NcApiCoroutines {
@Url url: String?,
@QueryMap options: Map?
): RoomOverall
+
+ /*
+ QueryMap items are as follows:
+ - "roomName" : "newName"
+
+ Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room/roomToken
+ */
+ @FormUrlEncoded
+ @PUT
+ suspend fun renameRoom(
+ @Header("Authorization") authorization: String?,
+ @Url url: String,
+ @Field("roomName") roomName: String?
+ ): GenericOverall
+
+ @FormUrlEncoded
+ @PUT
+ suspend fun openConversation(
+ @Header("Authorization") authorization: String?,
+ @Url url: String,
+ @Field("scope") scope: Int
+ ): GenericOverall
+
+ @FormUrlEncoded
+ @PUT
+ suspend fun setConversationDescription(
+ @Header("Authorization") authorization: String?,
+ @Url url: String,
+ @Field("description") description: String?
+ ): GenericOverall
+
+ @POST
+ suspend fun addParticipant(
+ @Header("Authorization") authorization: String?,
+ @Url url: String?,
+ @QueryMap options: Map?
+ ): AddParticipantOverall
+
+ @POST
+ suspend fun makeRoomPublic(@Header("Authorization") authorization: String?, @Url url: String): GenericOverall
+
+ @DELETE
+ suspend fun makeRoomPrivate(@Header("Authorization") authorization: String?, @Url url: String): GenericOverall
+
+ @FormUrlEncoded
+ @PUT
+ suspend fun setPassword(
+ @Header("Authorization") authorization: String?,
+ @Url url: String?,
+ @Field("password") password: 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 fe828a4b7..5fdbc3c73 100644
--- a/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivityCompose.kt
+++ b/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivityCompose.kt
@@ -11,17 +11,20 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
+import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.activity.compose.setContent
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -46,17 +49,25 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.core.view.WindowCompat
import androidx.lifecycle.ViewModelProvider
import autodagger.AutoInjector
import coil.compose.AsyncImage
@@ -64,6 +75,7 @@ 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.conversationcreation.ConversationCreationActivity
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
import com.nextcloud.talk.openconversations.ListOpenConversationsActivity
import com.nextcloud.talk.utils.bundle.BundleKeys
@@ -82,8 +94,31 @@ class ContactsActivityCompose : BaseActivity() {
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
contactsViewModel = ViewModelProvider(this, viewModelFactory)[ContactsViewModel::class.java]
setContent {
+ val isAddParticipants = intent.getBooleanExtra("isAddParticipants", false)
+ contactsViewModel.updateIsAddParticipants(isAddParticipants)
+ if (isAddParticipants) {
+ contactsViewModel.updateShareTypes(
+ listOf(
+ ShareType.Group.shareType,
+ ShareType.Email.shareType,
+ ShareType.Circle.shareType
+ )
+ )
+ contactsViewModel.getContactsFromSearchParams()
+ }
val colorScheme = viewThemeUtils.getColorScheme(this)
val uiState = contactsViewModel.contactsViewState.collectAsState()
+ val selectedParticipants = remember {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableArrayListExtra("selectedParticipants", AutocompleteUser::class.java)
+ ?: emptyList()
+ } else {
+ @Suppress("DEPRECATION")
+ intent.getParcelableArrayListExtra("selectedParticipants") ?: emptyList()
+ }
+ }
+ val participants = selectedParticipants.toSet().toMutableList()
+ contactsViewModel.updateSelectedParticipants(participants)
MaterialTheme(
colorScheme = colorScheme
) {
@@ -98,23 +133,242 @@ class ContactsActivityCompose : BaseActivity() {
},
content = {
Column(Modifier.padding(it)) {
- ConversationCreationOptions(context = context)
+ ConversationCreationOptions(context = context, contactsViewModel = contactsViewModel)
ContactsList(
contactsUiState = uiState.value,
contactsViewModel = contactsViewModel,
- context = context
+ context = context,
+ selectedParticipants = selectedParticipants.toMutableList()
)
}
}
)
}
+
+ SetStatusBarColor()
+ }
+ }
+
+ @Composable
+ private fun SetStatusBarColor() {
+ val view = LocalView.current
+ val isDarkMod = isSystemInDarkTheme()
+
+ DisposableEffect(isDarkMod) {
+ val activity = view.context as Activity
+ activity.window.statusBarColor = resources.getColor(R.color.bg_default)
+
+ WindowCompat.getInsetsController(activity.window, activity.window.decorView).apply {
+ isAppearanceLightStatusBars = !isDarkMod
+ }
+
+ onDispose { }
}
- setupSystemColors()
}
}
@Composable
-fun ContactsList(contactsUiState: ContactsUiState, contactsViewModel: ContactsViewModel, context: Context) {
+fun ContactItemRow(
+ contact: AutocompleteUser,
+ contactsViewModel: ContactsViewModel,
+ context: Context,
+ selectedContacts: MutableList
+) {
+ var isSelected by remember { mutableStateOf(selectedContacts.contains(contact)) }
+ val roomUiState by contactsViewModel.roomViewState.collectAsState()
+ val isAddParticipants = contactsViewModel.isAddParticipantsView.collectAsState()
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable(
+ onClick = {
+ if (!isAddParticipants.value) {
+ contactsViewModel.createRoom(
+ CompanionClass.ROOM_TYPE_ONE_ONE,
+ contact.source!!,
+ contact.id!!,
+ null
+ )
+ } else {
+ isSelected = !isSelected
+ selectedContacts.apply {
+ if (isSelected) {
+ add(contact)
+ } else {
+ remove(contact)
+ }
+ }
+ contactsViewModel.updateSelectedParticipants(selectedContacts)
+ }
+ }
+ ),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ val imageUri = contact.id?.let { contactsViewModel.getImageUri(it, true) }
+ val errorPlaceholderImage: Int = R.drawable.account_circle_96dp
+ val loadedImage = loadImage(imageUri, context, errorPlaceholderImage)
+ AsyncImage(
+ model = loadedImage,
+ contentDescription = stringResource(R.string.user_avatar),
+ 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)
+ )
+ }
+ }
+ }
+ when (roomUiState) {
+ is RoomUiState.Success -> {
+ val conversation = (roomUiState as RoomUiState.Success).conversation
+ val bundle = Bundle()
+ bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation?.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 -> {}
+ }
+}
+
+@SuppressLint("UnrememberedMutableState")
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AppBar(title: String, context: Context, contactsViewModel: ContactsViewModel) {
+ val searchQuery by contactsViewModel.searchQuery.collectAsState()
+ val searchState = contactsViewModel.searchState.collectAsState()
+ val isAddParticipants = contactsViewModel.isAddParticipantsView.collectAsState()
+
+ TopAppBar(
+ title = { Text(text = title) },
+ navigationIcon = {
+ IconButton(onClick = {
+ (context as? Activity)?.finish()
+ }) {
+ Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back_button))
+ }
+ },
+ actions = {
+ IconButton(onClick = {
+ contactsViewModel.updateSearchState(true)
+ }) {
+ Icon(Icons.Filled.Search, contentDescription = stringResource(R.string.search_icon))
+ }
+ if (isAddParticipants.value) {
+ Text(
+ text = stringResource(id = R.string.nc_contacts_done),
+ modifier = Modifier.clickable {
+ val resultIntent = Intent().apply {
+ putParcelableArrayListExtra(
+ "selectedParticipants",
+ ArrayList(
+ contactsViewModel
+ .selectedParticipantsList.value
+ )
+ )
+ }
+ (context as? Activity)?.setResult(Activity.RESULT_OK, resultIntent)
+ (context as? Activity)?.finish()
+ }
+ )
+ }
+ }
+ )
+ if (searchState.value) {
+ Row {
+ DisplaySearch(
+ text = searchQuery,
+ onTextChange = { searchQuery ->
+ contactsViewModel.updateSearchQuery(query = searchQuery)
+ contactsViewModel.getContactsFromSearchParams()
+ },
+ contactsViewModel = contactsViewModel
+ )
+ }
+ }
+}
+
+@Composable
+fun ConversationCreationOptions(context: Context, contactsViewModel: ContactsViewModel) {
+ val isAddParticipants by contactsViewModel.isAddParticipantsView.collectAsState()
+ if (!isAddParticipants) {
+ Column {
+ Row(
+ modifier = Modifier
+ .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp)
+ .clickable {
+ val intent = Intent(context, ConversationCreationActivity::class.java)
+ context.startActivity(intent)
+ },
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.baseline_chat_bubble_outline_24),
+ modifier = Modifier
+ .width(40.dp)
+ .height(40.dp)
+ .padding(8.dp),
+ contentDescription = null
+ )
+ Text(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ text = stringResource(R.string.nc_create_new_conversation),
+ maxLines = 1,
+ fontSize = 16.sp
+ )
+ }
+ Row(
+ modifier = Modifier
+ .padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp)
+ .clickable {
+ val intent = Intent(context, ListOpenConversationsActivity::class.java)
+ context.startActivity(intent)
+ },
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ Icons.AutoMirrored.Filled.List,
+ modifier = Modifier
+ .width(40.dp)
+ .height(40.dp)
+ .padding(8.dp),
+ contentDescription = null
+ )
+ Text(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ text = stringResource(R.string.nc_join_open_conversations),
+ fontSize = 16.sp
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun ContactsList(
+ contactsUiState: ContactsUiState,
+ contactsViewModel: ContactsViewModel,
+ context: Context,
+ selectedParticipants: MutableList
+) {
when (contactsUiState) {
is ContactsUiState.None -> {
}
@@ -127,13 +381,13 @@ fun ContactsList(contactsUiState: ContactsUiState, contactsViewModel: ContactsVi
val contacts = contactsUiState.contacts
Log.d(CompanionClass.TAG, "Contacts:$contacts")
if (contacts != null) {
- ContactsItem(contacts, contactsViewModel, context)
+ ContactsItem(contacts, contactsViewModel, context, selectedParticipants)
}
}
is ContactsUiState.Error -> {
val errorMessage = contactsUiState.message
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- Text(text = "Error: $errorMessage", color = MaterialTheme.colorScheme.error)
+ Text(text = "Error: $errorMessage", color = Color.Red)
}
}
}
@@ -141,7 +395,12 @@ fun ContactsList(contactsUiState: ContactsUiState, contactsViewModel: ContactsVi
@OptIn(ExperimentalFoundationApi::class)
@Composable
-fun ContactsItem(contacts: List, contactsViewModel: ContactsViewModel, context: Context) {
+fun ContactsItem(
+ contacts: List,
+ contactsViewModel: ContactsViewModel,
+ context: Context,
+ selectedParticipants: MutableList
+) {
val groupedContacts: Map> = contacts.groupBy { contact ->
(
if (contact.source == "users") {
@@ -166,11 +425,16 @@ fun ContactsItem(contacts: List, contactsViewModel: ContactsVi
Surface(Modifier.fillParentMaxWidth()) {
Header(initial)
}
- HorizontalDivider(thickness = 1.dp, color = MaterialTheme.colorScheme.outlineVariant)
+ HorizontalDivider(thickness = 0.1.dp, color = Color.Black)
}
}
items(contactsForInitial) { contact ->
- ContactItemRow(contact = contact, contactsViewModel = contactsViewModel, context = context)
+ ContactItemRow(
+ contact = contact,
+ contactsViewModel = contactsViewModel,
+ context = context,
+ selectedContacts = selectedParticipants
+ )
Log.d(CompanionClass.TAG, "Contacts:$contact")
}
}
@@ -185,147 +449,11 @@ fun Header(header: String) {
.fillMaxSize()
.background(Color.Transparent)
.padding(start = 60.dp),
- color = MaterialTheme.colorScheme.primary,
+ color = Color.Blue,
fontWeight = FontWeight.Bold
)
}
-@Composable
-fun ContactItemRow(contact: AutocompleteUser, contactsViewModel: ContactsViewModel, context: Context) {
- val roomUiState by contactsViewModel.roomViewState.collectAsState()
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .clickable {
- contactsViewModel.createRoom(
- CompanionClass.ROOM_TYPE_ONE_ONE,
- contact.source!!,
- contact.id!!,
- null
- )
- },
- verticalAlignment = Alignment.CenterVertically
- ) {
- val imageUri = contact.id?.let { contactsViewModel.getImageUri(it, true) }
- val errorPlaceholderImage: Int = R.drawable.account_circle_96dp
- val loadedImage = loadImage(imageUri, context, errorPlaceholderImage)
- AsyncImage(
- model = loadedImage,
- contentDescription = stringResource(R.string.user_avatar),
- modifier = Modifier.size(width = 45.dp, height = 45.dp)
- )
- Text(modifier = Modifier.padding(16.dp), text = contact.label!!)
- }
- when (roomUiState) {
- is RoomUiState.Success -> {
- val conversation = (roomUiState as RoomUiState.Success).conversation
- val bundle = Bundle()
- bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation?.token)
- // bundle.putString(BundleKeys.KEY_ROOM_ID, conversation?.roomId)
- 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 = MaterialTheme.colorScheme.error)
- }
- }
- is RoomUiState.None -> {}
- }
-}
-
-@SuppressLint("UnrememberedMutableState")
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun AppBar(title: String, context: Context, contactsViewModel: ContactsViewModel) {
- val searchQuery by contactsViewModel.searchQuery.collectAsState()
- val searchState = contactsViewModel.searchState.collectAsState()
-
- TopAppBar(
- title = { Text(text = title) },
-
- navigationIcon = {
- IconButton(onClick = {
- (context as? Activity)?.finish()
- }) {
- Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back_button))
- }
- },
- actions = {
- IconButton(onClick = {
- contactsViewModel.updateSearchState(true)
- }) {
- Icon(Icons.Filled.Search, contentDescription = stringResource(R.string.search_icon))
- }
- }
- )
- if (searchState.value) {
- DisplaySearch(
- text = searchQuery,
- onTextChange = { searchQuery ->
- contactsViewModel.updateSearchQuery(query = searchQuery)
- contactsViewModel.getContactsFromSearchParams()
- },
- contactsViewModel = contactsViewModel
- )
- }
-}
-
-@Composable
-fun ConversationCreationOptions(context: Context) {
- Column {
- Row(
- modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Icon(
- painter = painterResource(id = R.drawable.baseline_chat_bubble_outline_24),
- modifier = Modifier
- .width(40.dp)
- .height(40.dp)
- .padding(8.dp),
- contentDescription = null
- )
- Text(
- modifier = Modifier
- .fillMaxWidth()
- .wrapContentHeight(),
- text = stringResource(R.string.nc_create_new_conversation),
- maxLines = 1,
- fontSize = 16.sp
- )
- }
- Row(
- modifier = Modifier
- .padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp)
- .clickable {
- val intent = Intent(context, ListOpenConversationsActivity::class.java)
- context.startActivity(intent)
- },
- verticalAlignment = Alignment.CenterVertically
- ) {
- Icon(
- Icons.AutoMirrored.Filled.List,
- modifier = Modifier
- .width(40.dp)
- .height(40.dp)
- .padding(8.dp),
- contentDescription = null
- )
- Text(
- modifier = Modifier
- .fillMaxWidth()
- .wrapContentHeight(),
- text = stringResource(R.string.nc_join_open_conversations),
- fontSize = 16.sp
- )
- }
- }
-}
-
class CompanionClass {
companion object {
internal val TAG = ContactsActivityCompose::class.simpleName
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 7b0a2e450..7dda67591 100644
--- a/app/src/main/java/com/nextcloud/talk/contacts/ContactsViewModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/contacts/ContactsViewModel.kt
@@ -30,6 +30,10 @@ class ContactsViewModel @Inject constructor(
val shareTypeList: List = shareTypes
private val _searchState = MutableStateFlow(false)
val searchState: StateFlow = _searchState
+ private val selectedParticipants = MutableStateFlow>(emptyList())
+ val selectedParticipantsList: StateFlow> = selectedParticipants
+ private val _isAddParticipantsView = MutableStateFlow(false)
+ val isAddParticipantsView: StateFlow = _isAddParticipantsView
init {
getContactsFromSearchParams()
@@ -39,12 +43,19 @@ class ContactsViewModel @Inject constructor(
_searchQuery.value = query
}
+ fun updateSelectedParticipants(participants: List) {
+ selectedParticipants.value = participants
+ }
fun updateSearchState(searchState: Boolean) {
_searchState.value = searchState
}
- fun updateShareTypes(value: String) {
- shareTypes.add(value)
+ fun updateShareTypes(value: List) {
+ shareTypes.addAll(value)
+ }
+
+ fun updateIsAddParticipants(value: Boolean) {
+ _isAddParticipantsView.value = value
}
fun getContactsFromSearchParams() {
@@ -62,7 +73,6 @@ class ContactsViewModel @Inject constructor(
}
}
}
-
fun createRoom(roomType: String, sourceType: String, userId: String, conversationName: String?) {
viewModelScope.launch {
try {
diff --git a/app/src/main/java/com/nextcloud/talk/contacts/SearchComponent.kt b/app/src/main/java/com/nextcloud/talk/contacts/SearchComponent.kt
index f7ba7d61b..5a793d40e 100644
--- a/app/src/main/java/com/nextcloud/talk/contacts/SearchComponent.kt
+++ b/app/src/main/java/com/nextcloud/talk/contacts/SearchComponent.kt
@@ -19,6 +19,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
@@ -30,6 +31,7 @@ import com.nextcloud.talk.R
@Composable
fun DisplaySearch(text: String, onTextChange: (String) -> Unit, contactsViewModel: ContactsViewModel) {
+ val isAddParticipants = contactsViewModel.isAddParticipantsView.collectAsState()
val keyboardController = LocalSoftwareKeyboardController.current
TextField(
modifier = Modifier
@@ -42,7 +44,6 @@ fun DisplaySearch(text: String, onTextChange: (String) -> Unit, contactsViewMode
text = stringResource(R.string.nc_search)
)
},
-
textStyle = TextStyle(
fontSize = 16.sp
),
diff --git a/app/src/main/java/com/nextcloud/talk/contacts/ShareType.kt b/app/src/main/java/com/nextcloud/talk/contacts/ShareType.kt
index fb8f2dc5b..39fad325c 100644
--- a/app/src/main/java/com/nextcloud/talk/contacts/ShareType.kt
+++ b/app/src/main/java/com/nextcloud/talk/contacts/ShareType.kt
@@ -10,7 +10,7 @@ package com.nextcloud.talk.contacts
enum class ShareType(val shareType: String) {
User("0"),
Group("1"),
- Email(""),
- Circle(""),
- Federated("")
+ Email("4"),
+ Remote("5"),
+ Circle("7")
}
diff --git a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationActivity.kt
new file mode 100644
index 000000000..51297510a
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationActivity.kt
@@ -0,0 +1,535 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Sowjanya Kota
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+@file:Suppress("DEPRECATION")
+
+package com.nextcloud.talk.conversationcreation
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.compose.ManagedActivityResultLauncher
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.compose.setContent
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+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.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Switch
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.core.view.WindowCompat
+import androidx.lifecycle.ViewModelProvider
+import autodagger.AutoInjector
+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.loadImage
+import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
+import com.nextcloud.talk.utils.bundle.BundleKeys
+import javax.inject.Inject
+
+@AutoInjector(NextcloudTalkApplication::class)
+class ConversationCreationActivity : BaseActivity() {
+ @Inject
+ lateinit var viewModelFactory: ViewModelProvider.Factory
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
+ val conversationCreationViewModel = ViewModelProvider(
+ this,
+ viewModelFactory
+ )[ConversationCreationViewModel::class.java]
+ setContent {
+ val colorScheme = viewThemeUtils.getColorScheme(this)
+ val context = LocalContext.current
+ MaterialTheme(
+ colorScheme = colorScheme
+ ) {
+ ConversationCreationScreen(conversationCreationViewModel, context)
+ }
+ SetStatusBarColor()
+ }
+ }
+}
+
+@Composable
+private fun SetStatusBarColor() {
+ val view = LocalView.current
+ val isDarkMod = isSystemInDarkTheme()
+
+ DisposableEffect(isDarkMod) {
+ val activity = view.context as Activity
+ activity.window.statusBarColor = activity.getColor(R.color.bg_default)
+
+ WindowCompat.getInsetsController(activity.window, activity.window.decorView).apply {
+ isAppearanceLightStatusBars = !isDarkMod
+ }
+
+ onDispose { }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ConversationCreationScreen(conversationCreationViewModel: ConversationCreationViewModel, context: Context) {
+ val launcher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.StartActivityForResult(),
+
+ onResult = { result ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ val data = result.data
+ val selectedParticipants = data?.getParcelableArrayListExtra("selectedParticipants")
+ ?: emptyList()
+ val participants = selectedParticipants.toMutableList()
+ conversationCreationViewModel.updateSelectedParticipants(participants)
+ }
+ }
+ )
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text(text = stringResource(id = R.string.nc_new_conversation)) },
+ navigationIcon = {
+ IconButton(onClick = {
+ (context as? Activity)?.finish()
+ }) {
+ Icon(
+ Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = stringResource(id = R.string.back_button)
+ )
+ }
+ }
+ )
+ },
+ content = { paddingValues ->
+ Column(
+ modifier = Modifier
+ .padding(paddingValues)
+ .verticalScroll(rememberScrollState())
+ ) {
+ DefaultUserAvatar()
+ UploadAvatar()
+ ConversationNameAndDescription(conversationCreationViewModel)
+ AddParticipants(launcher, context, conversationCreationViewModel)
+ RoomCreationOptions(conversationCreationViewModel)
+ CreateConversation(conversationCreationViewModel, context)
+ }
+ }
+ )
+}
+
+@Composable
+fun DefaultUserAvatar() {
+ Box(
+ modifier = Modifier.fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ AsyncImage(
+ model = R.drawable.ic_circular_group,
+ contentDescription = stringResource(id = R.string.user_avatar),
+ modifier = Modifier
+ .size(width = 84.dp, height = 84.dp)
+ .padding(top = 8.dp)
+ )
+ }
+}
+
+@Composable
+fun UploadAvatar() {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ horizontalArrangement = Arrangement.Center
+ ) {
+ IconButton(onClick = {
+ }) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_baseline_photo_camera_24),
+ contentDescription = null,
+ modifier = Modifier.size(24.dp)
+ )
+ }
+
+ IconButton(onClick = {
+ }) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_folder_multiple_image),
+ contentDescription = null,
+ modifier = Modifier.size(24.dp)
+ )
+ }
+
+ IconButton(onClick = {
+ }) {
+ Icon(
+ painter = painterResource(id = R.drawable.baseline_tag_faces_24),
+ contentDescription = null,
+ modifier = Modifier.size(24.dp)
+ )
+ }
+
+ IconButton(onClick = {
+ }) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_delete_grey600_24dp),
+ contentDescription = null,
+ modifier = Modifier.size(24.dp)
+ )
+ }
+ }
+}
+
+@Composable
+fun ConversationNameAndDescription(conversationCreationViewModel: ConversationCreationViewModel) {
+ val conversationRoomName = conversationCreationViewModel.roomName.collectAsState()
+ val conversationDescription = conversationCreationViewModel.conversationDescription.collectAsState()
+ OutlinedTextField(
+ value = conversationRoomName.value,
+ onValueChange = {
+ conversationCreationViewModel.updateRoomName(it)
+ },
+ label = { Text(text = stringResource(id = R.string.nc_call_name)) },
+ modifier = Modifier
+ .padding(start = 16.dp, end = 16.dp)
+ .fillMaxWidth()
+ )
+ OutlinedTextField(
+ value = conversationDescription.value,
+ onValueChange = {
+ conversationCreationViewModel.updateConversationDescription(it)
+ },
+ label = { Text(text = stringResource(id = R.string.nc_conversation_description)) },
+ modifier = Modifier
+ .padding(top = 8.dp, start = 16.dp, end = 16.dp)
+ .fillMaxWidth()
+ )
+}
+
+@SuppressLint("SuspiciousIndentation")
+@Composable
+fun AddParticipants(
+ launcher: ManagedActivityResultLauncher,
+ context: Context,
+ conversationCreationViewModel: ConversationCreationViewModel
+) {
+ val participants = conversationCreationViewModel.selectedParticipants.collectAsState().value
+
+ Column(
+ modifier = Modifier
+ .fillMaxHeight()
+ .padding(start = 16.dp, end = 16.dp, top = 16.dp)
+ ) {
+ Row {
+ Text(
+ text = stringResource(id = R.string.nc_participants).uppercase(),
+ fontSize = 14.sp,
+ modifier = Modifier.padding(start = 0.dp, bottom = 16.dp)
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ if (participants.isNotEmpty()) {
+ Text(
+ text = stringResource(id = R.string.nc_edit),
+ fontSize = 12.sp,
+ modifier = Modifier
+ .padding(start = 16.dp, bottom = 16.dp)
+ .clickable {
+ val intent = Intent(context, ContactsActivityCompose::class.java)
+ intent.putParcelableArrayListExtra(
+ "selectedParticipants",
+ participants as ArrayList
+ )
+ intent.putExtra("isAddParticipants", true)
+ intent.putExtra("isAddParticipantsEdit", true)
+ launcher.launch(intent)
+ },
+ textAlign = TextAlign.Right
+ )
+ }
+ }
+ participants.toSet().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
+ val loadedImage = loadImage(imageUri, context, errorPlaceholderImage)
+ AsyncImage(
+ model = loadedImage,
+ contentDescription = stringResource(id = R.string.user_avatar),
+ modifier = Modifier.size(width = 32.dp, height = 32.dp)
+ )
+ participant.label?.let {
+ Text(
+ text = it,
+ modifier = Modifier.padding(all = 16.dp),
+ fontSize = 15.sp
+ )
+ }
+ }
+ HorizontalDivider(thickness = 0.1.dp, color = Color.Black)
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable {
+ val intent = Intent(context, ContactsActivityCompose::class.java)
+ intent.putExtra("isAddParticipants", true)
+ launcher.launch(intent)
+ },
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ if (participants.isEmpty()) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_account_plus),
+ contentDescription = null,
+ modifier = Modifier.size(24.dp)
+ )
+ Text(
+ text = stringResource(id = R.string.nc_add_participants),
+ modifier = Modifier.padding(start = 16.dp)
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun RoomCreationOptions(conversationCreationViewModel: ConversationCreationViewModel) {
+ val isGuestsAllowed = conversationCreationViewModel.isGuestsAllowed.value
+ val isConversationAvailableForRegisteredUsers = conversationCreationViewModel
+ .isConversationAvailableForRegisteredUsers.value
+ val isOpenForGuestAppUsers = conversationCreationViewModel.openForGuestAppUsers.value
+
+ Text(
+ text = stringResource(id = R.string.nc_new_conversation_visibility).uppercase(),
+ fontSize = 14.sp,
+ modifier = Modifier.padding(top = 24.dp, start = 16.dp, end = 16.dp)
+ )
+ ConversationOptions(
+ icon = R.drawable.ic_avatar_link,
+ text = R.string.nc_guest_access_allow_title,
+ switch = {
+ Switch(
+ checked = isGuestsAllowed,
+ onCheckedChange = {
+ conversationCreationViewModel.isGuestsAllowed.value = it
+ }
+ )
+ },
+ showDialog = false,
+ conversationCreationViewModel = conversationCreationViewModel
+ )
+
+ if (isGuestsAllowed) {
+ ConversationOptions(
+ icon = R.drawable.ic_lock_grey600_24px,
+ text = R.string.nc_set_password,
+ showDialog = true,
+ conversationCreationViewModel = conversationCreationViewModel
+ )
+ }
+
+ ConversationOptions(
+ icon = R.drawable.baseline_format_list_bulleted_24,
+ text = R.string.nc_open_conversation_to_registered_users,
+ switch = {
+ Switch(
+ checked = isConversationAvailableForRegisteredUsers,
+ onCheckedChange = {
+ conversationCreationViewModel.isConversationAvailableForRegisteredUsers.value = it
+ }
+ )
+ },
+ showDialog = false,
+ conversationCreationViewModel = conversationCreationViewModel
+ )
+
+ if (isConversationAvailableForRegisteredUsers) {
+ ConversationOptions(
+ text = R.string.nc_open_to_guest_app_users,
+ switch = {
+ Switch(
+ checked = isOpenForGuestAppUsers,
+ onCheckedChange = {
+ conversationCreationViewModel.openForGuestAppUsers.value = it
+ }
+ )
+ },
+ showDialog = false,
+ conversationCreationViewModel = conversationCreationViewModel
+ )
+ }
+}
+
+@Composable
+fun ConversationOptions(
+ icon: Int? = null,
+ text: Int,
+ switch: @Composable (() -> Unit)? = null,
+ showDialog: Boolean,
+ conversationCreationViewModel: ConversationCreationViewModel
+) {
+ var showPasswordDialog by remember { mutableStateOf(false) }
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
+ .then(
+ if (showDialog) {
+ Modifier.clickable {
+ showPasswordDialog = true
+ }
+ } else {
+ Modifier
+ }
+ ),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ if (icon != null) {
+ Icon(
+ painter = painterResource(id = icon),
+ contentDescription = null,
+ modifier = Modifier.size(24.dp)
+ )
+ Spacer(modifier = Modifier.width(16.dp))
+ } else {
+ Spacer(modifier = Modifier.width(40.dp))
+ }
+ Text(
+ text = stringResource(id = text),
+ modifier = Modifier.weight(1f)
+ )
+ if (switch != null) {
+ switch()
+ }
+ if (showPasswordDialog) {
+ ShowPasswordDialog(
+ onDismiss = { showPasswordDialog = false },
+ conversationCreationViewModel = conversationCreationViewModel
+ )
+ }
+ }
+}
+
+@Composable
+fun ShowPasswordDialog(onDismiss: () -> Unit, conversationCreationViewModel: ConversationCreationViewModel) {
+ var password by remember { mutableStateOf("") }
+
+ AlertDialog(
+ onDismissRequest = onDismiss,
+ confirmButton = {
+ Button(onClick = {
+ conversationCreationViewModel.updatePassword(password)
+ onDismiss()
+ }) {
+ Text(text = stringResource(id = R.string.save))
+ }
+ },
+ title = { Text(text = stringResource(id = R.string.nc_set_password)) },
+ text = {
+ TextField(
+ value = password,
+ onValueChange = {
+ password = it
+ },
+ label = { Text(text = stringResource(id = R.string.nc_guest_access_password_dialog_hint)) }
+ )
+ },
+ dismissButton = {
+ Button(onClick = { onDismiss() }) {
+ Text(text = stringResource(id = R.string.nc_cancel))
+ }
+ }
+ )
+}
+
+@Composable
+fun CreateConversation(conversationCreationViewModel: ConversationCreationViewModel, context: Context) {
+ val selectedParticipants by conversationCreationViewModel.selectedParticipants.collectAsState()
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(all = 16.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Button(
+ onClick = {
+ conversationCreationViewModel.createRoomAndAddParticipants(
+ roomType = CompanionClass.ROOM_TYPE_GROUP,
+ conversationName = conversationCreationViewModel.roomName.value,
+ participants = selectedParticipants.toSet()
+ ) { roomToken ->
+ val bundle = Bundle()
+ bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
+ val chatIntent = Intent(context, ChatActivity::class.java)
+ chatIntent.putExtras(bundle)
+ chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ context.startActivity(chatIntent)
+ }
+ }
+ ) {
+ Text(text = stringResource(id = R.string.create_conversation))
+ }
+ }
+}
+class CompanionClass {
+ companion object {
+ internal val TAG = ConversationCreationActivity::class.simpleName
+ internal const val ROOM_TYPE_GROUP = "2"
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepository.kt b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepository.kt
new file mode 100644
index 000000000..6ebb6c350
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepository.kt
@@ -0,0 +1,24 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Sowjanya Kota
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+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 {
+
+ suspend fun allowGuests(token: String, allow: Boolean): GenericOverall
+ suspend fun renameConversation(roomToken: String, roomNameNew: String?): GenericOverall
+ suspend fun setConversationDescription(roomToken: String, description: String?): GenericOverall
+ suspend fun openConversation(roomToken: String, scope: Int): 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
+ suspend fun setPassword(roomToken: String, password: String): GenericOverall
+}
diff --git a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepositoryImpl.kt
new file mode 100644
index 000000000..4e00f174c
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationRepositoryImpl.kt
@@ -0,0 +1,150 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Sowjanya Kota
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+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.users.UserManager
+import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.ApiUtils.getRetrofitBucketForAddParticipant
+import com.nextcloud.talk.utils.ApiUtils.getRetrofitBucketForAddParticipantWithSource
+
+class ConversationCreationRepositoryImpl(
+ private val ncApiCoroutines: NcApiCoroutines,
+ private val userManager: UserManager
+) : ConversationCreationRepository {
+ private val _currentUser = userManager.currentUser.blockingGet()
+ val currentUser: User = _currentUser
+ val credentials = ApiUtils.getCredentials(_currentUser.username, _currentUser.token)
+ val apiVersion = ApiUtils.getConversationApiVersion(_currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
+
+ override suspend fun renameConversation(roomToken: String, roomNameNew: String?): GenericOverall {
+ return ncApiCoroutines.renameRoom(
+ credentials,
+ ApiUtils.getUrlForRoom(
+ apiVersion,
+ _currentUser.baseUrl,
+ roomToken
+ ),
+ roomNameNew
+ )
+ }
+
+ override suspend fun setConversationDescription(roomToken: String, description: String?): GenericOverall {
+ return ncApiCoroutines.setConversationDescription(
+ credentials,
+ ApiUtils.getUrlForConversationDescription(
+ apiVersion,
+ _currentUser.baseUrl,
+ roomToken
+ ),
+ description
+ )
+ }
+
+ override suspend fun openConversation(roomToken: String, scope: Int): GenericOverall {
+ return ncApiCoroutines.openConversation(
+ credentials,
+ ApiUtils.getUrlForOpeningConversations(
+ apiVersion,
+ _currentUser.baseUrl,
+ roomToken
+ ),
+ scope
+ )
+ }
+
+ override suspend fun addParticipants(
+ conversationToken: String?,
+ userId: String,
+ sourceType: String
+ ): AddParticipantOverall {
+ val retrofitBucket: RetrofitBucket = if (sourceType == "users") {
+ getRetrofitBucketForAddParticipant(
+ apiVersion,
+ _currentUser.baseUrl,
+ conversationToken,
+ userId
+ )
+ } else {
+ getRetrofitBucketForAddParticipantWithSource(
+ apiVersion,
+ _currentUser.baseUrl,
+ conversationToken,
+ sourceType,
+ userId
+ )
+ }
+ val participants = ncApiCoroutines.addParticipant(credentials, retrofitBucket.url, retrofitBucket.queryMap)
+ return participants
+ }
+
+ override suspend fun createRoom(roomType: String, conversationName: String?): RoomOverall {
+ val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
+ apiVersion,
+ _currentUser.baseUrl,
+ roomType,
+ null,
+ null,
+ conversationName
+ )
+ val response = ncApiCoroutines.createRoom(
+ credentials,
+ retrofitBucket.url,
+ retrofitBucket.queryMap
+ )
+ return response
+ }
+
+ override fun getImageUri(avatarId: String, requestBigSize: Boolean): String {
+ return ApiUtils.getUrlForAvatar(
+ _currentUser.baseUrl,
+ avatarId,
+ requestBigSize
+ )
+ }
+
+ override suspend fun setPassword(roomToken: String, password: String): GenericOverall {
+ val result = ncApiCoroutines.setPassword(
+ credentials,
+ ApiUtils.getUrlForRoomPassword(
+ apiVersion,
+ _currentUser.baseUrl!!,
+ roomToken
+ ),
+ password
+ )
+ return result
+ }
+
+ override suspend fun allowGuests(token: String, allow: Boolean): GenericOverall {
+ val url = ApiUtils.getUrlForRoomPublic(
+ apiVersion,
+ _currentUser.baseUrl!!,
+ token
+ )
+
+ val result: GenericOverall = if (allow) {
+ ncApiCoroutines.makeRoomPublic(
+ credentials,
+ url
+ )
+ } else {
+ ncApiCoroutines.makeRoomPrivate(
+ credentials,
+ url
+ )
+ }
+
+ return result
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationViewModel.kt b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationViewModel.kt
new file mode 100644
index 000000000..759637cde
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationViewModel.kt
@@ -0,0 +1,156 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Sowjanya Kota
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.conversationcreation
+
+import android.util.Log
+import androidx.compose.runtime.mutableStateOf
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
+import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.json.generic.GenericMeta
+import com.nextcloud.talk.repositories.conversations.ConversationsRepositoryImpl.Companion.STATUS_CODE_OK
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+class ConversationCreationViewModel @Inject constructor(
+ private val repository: ConversationCreationRepository
+) : ViewModel() {
+ private val _selectedParticipants = MutableStateFlow>(emptyList())
+ val selectedParticipants: StateFlow> = _selectedParticipants
+ private val roomViewState = MutableStateFlow(RoomUIState.None)
+
+ fun updateSelectedParticipants(participants: List) {
+ _selectedParticipants.value = participants
+ }
+
+ private val _roomName = MutableStateFlow("")
+ val roomName: StateFlow = _roomName
+ private val _password = MutableStateFlow("")
+ val password: StateFlow = _password
+ private val _conversationDescription = MutableStateFlow("")
+ val conversationDescription: StateFlow = _conversationDescription
+ var isGuestsAllowed = mutableStateOf(false)
+ var isConversationAvailableForRegisteredUsers = mutableStateOf(false)
+ var openForGuestAppUsers = mutableStateOf(false)
+ private val addParticipantsViewState = MutableStateFlow(AddParticipantsUiState.None)
+ private val allowGuestsResult = MutableStateFlow(AllowGuestsUiState.None)
+ fun updateRoomName(roomName: String) {
+ _roomName.value = roomName
+ }
+
+ fun updatePassword(password: String) {
+ _password.value = password
+ }
+
+ fun updateConversationDescription(conversationDescription: String) {
+ _conversationDescription.value = conversationDescription
+ }
+
+ fun createRoomAndAddParticipants(
+ roomType: String,
+ conversationName: String,
+ participants: Set,
+ onRoomCreated: (String) -> Unit
+ ) {
+ val scope = when {
+ isConversationAvailableForRegisteredUsers.value && !openForGuestAppUsers.value -> 1
+ isConversationAvailableForRegisteredUsers.value && openForGuestAppUsers.value -> 2
+ else -> 0
+ }
+ viewModelScope.launch {
+ roomViewState.value = RoomUIState.None
+ try {
+ val roomResult = repository.createRoom(roomType, conversationName)
+ val conversation = roomResult.ocs?.data
+
+ if (conversation != null) {
+ val token = conversation.token
+ if (token != null) {
+ try {
+ repository.setConversationDescription(
+ token,
+ _conversationDescription.value
+ )
+ val allowGuestResultOverall = repository.allowGuests(token, isGuestsAllowed.value)
+ val statusCode: GenericMeta? = allowGuestResultOverall.ocs?.meta
+ val result = (statusCode?.statusCode == STATUS_CODE_OK)
+ if (result) {
+ allowGuestsResult.value = AllowGuestsUiState.Success(result)
+ for (participant in participants) {
+ if (participant.id != null) {
+ val participantOverall = repository.addParticipants(
+ token,
+ participant.id!!,
+ participant.source!!
+ ).ocs?.data
+ addParticipantsViewState.value =
+ AddParticipantsUiState.Success(participantOverall)
+ }
+ }
+ }
+ if (_password.value.isNotEmpty()) {
+ repository.setPassword(token, _password.value)
+ }
+ repository.openConversation(token, scope)
+ onRoomCreated(token)
+ } catch (exception: Exception) {
+ allowGuestsResult.value = AllowGuestsUiState.Error(exception.message ?: "")
+ }
+ }
+ roomViewState.value = RoomUIState.Success(conversation)
+ } else {
+ roomViewState.value = RoomUIState.Error("Conversation is null")
+ }
+ } catch (e: Exception) {
+ roomViewState.value = RoomUIState.Error(e.message ?: "Unknown error")
+ Log.e("ConversationCreationViewModel", "Error - ${e.message}")
+ }
+ }
+ }
+
+ 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 ?: "")
+ }
+ }
+ }
+}
+
+sealed class AllowGuestsUiState {
+ data object None : AllowGuestsUiState()
+ data class Success(val result: Boolean) : AllowGuestsUiState()
+ data class Error(val message: String) : AllowGuestsUiState()
+}
+
+sealed class RoomUIState {
+ data object None : RoomUIState()
+ data class Success(val conversation: Conversation?) : RoomUIState()
+ data class Error(val message: String) : RoomUIState()
+}
+
+sealed class AddParticipantsUiState {
+ data object None : AddParticipantsUiState()
+ data class Success(val participants: List?) : AddParticipantsUiState()
+ data class Error(val message: String) : AddParticipantsUiState()
+}
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 4085d4114..f00c502ba 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
@@ -19,6 +19,8 @@ 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
import com.nextcloud.talk.conversationinfoedit.data.ConversationInfoEditRepositoryImpl
import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository
@@ -208,4 +210,12 @@ class RepositoryModule {
fun provideContactsRepository(ncApiCoroutines: NcApiCoroutines, userManager: UserManager): ContactsRepository {
return ContactsRepositoryImpl(ncApiCoroutines, userManager)
}
+
+ @Provides
+ fun provideConversationCreationRepository(
+ ncApiCoroutines: NcApiCoroutines,
+ userManager: UserManager
+ ): ConversationCreationRepository {
+ return ConversationCreationRepositoryImpl(ncApiCoroutines, userManager)
+ }
}
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 511e6d7e2..f1ac935fe 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
@@ -14,6 +14,7 @@ import com.nextcloud.talk.contacts.ContactsViewModel
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel
import com.nextcloud.talk.conversation.viewmodel.ConversationViewModel
import com.nextcloud.talk.conversation.viewmodel.RenameConversationViewModel
+import com.nextcloud.talk.conversationcreation.ConversationCreationViewModel
import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
import com.nextcloud.talk.conversationinfoedit.viewmodel.ConversationInfoEditViewModel
import com.nextcloud.talk.conversationlist.viewmodels.ConversationsListViewModel
@@ -156,4 +157,9 @@ abstract class ViewModelModule {
@IntoMap
@ViewModelKey(ContactsViewModel::class)
abstract fun contactsViewModel(viewModel: ContactsViewModel): ViewModel
+
+ @Binds
+ @IntoMap
+ @ViewModelKey(ConversationCreationViewModel::class)
+ abstract fun conversationCreationViewModel(viewModel: ConversationCreationViewModel): ViewModel
}
diff --git a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt
index cb34a2372..30b856a6b 100644
--- a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt
+++ b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt
@@ -7,9 +7,9 @@
package com.nextcloud.talk.data.database.mappers
-import com.nextcloud.talk.chat.data.model.ChatMessage
-import com.nextcloud.talk.data.database.model.ChatMessageEntity
import com.nextcloud.talk.models.json.chat.ChatMessageJson
+import com.nextcloud.talk.data.database.model.ChatMessageEntity
+import com.nextcloud.talk.chat.data.model.ChatMessage
fun ChatMessageJson.asEntity(accountId: Long) =
ChatMessageEntity(
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 28d4e4ff7..c0168c413 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt
@@ -539,6 +539,10 @@ object ApiUtils {
return getUrlForRoom(version, baseUrl, token) + "/description"
}
+ fun getUrlForOpeningConversations(version: Int, baseUrl: String?, token: String): String {
+ return getUrlForRoom(version, baseUrl, token) + "/listable"
+ }
+
fun getUrlForTranslation(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/translation/translate"
}
diff --git a/app/src/main/res/drawable/baseline_tag_faces_24.xml b/app/src/main/res/drawable/baseline_tag_faces_24.xml
new file mode 100644
index 000000000..3a4f45877
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_tag_faces_24.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6fa26e80e..13ed2b442 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -242,6 +242,9 @@ How to translate with transifex:
Remove from favorites
Create a new conversation
Join open conversations
+ Open conversation to registered users
+ Also open to guest app users
+ Visibility
Added conversation %1$s to favorites
Removed conversation %1$s from favorites
@@ -386,6 +389,7 @@ How to translate with transifex:
Close Icon
Refresh
Please check your internet connection
+ Visible
Enter a message …
@@ -429,6 +433,7 @@ How to translate with transifex:
Allow guests
Allow guests to share a public link to join this conversation.
Cannot enable/disable guest access.
+ Set Password
Password protection
Set a password to restrict who can use the public link.
Guest access password
@@ -563,6 +568,7 @@ How to translate with transifex:
No phone number integration due to missing permissions
Chat via %s
Account not found
+ Edit
Save
diff --git a/app/src/test/java/com/nextcloud/talk/contacts/ContactsViewModelTest.kt b/app/src/test/java/com/nextcloud/talk/contacts/ContactsViewModelTest.kt
index 7bb905b43..8a9a56937 100644
--- a/app/src/test/java/com/nextcloud/talk/contacts/ContactsViewModelTest.kt
+++ b/app/src/test/java/com/nextcloud/talk/contacts/ContactsViewModelTest.kt
@@ -81,7 +81,7 @@ class ContactsViewModelTest {
@Test
fun `update shareTypes`() {
- viewModel.updateShareTypes(ShareType.Group.shareType)
+ viewModel.updateShareTypes(listOf(ShareType.Group.shareType))
assert(viewModel.shareTypeList.contains(ShareType.Group.shareType))
}
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index f1531e06f..f55002312 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -4,13 +4,13 @@
true
true
-
-
-
-
+
+
+
+
@@ -145,7 +145,6 @@
-
@@ -157,6 +156,7 @@
+
@@ -246,6 +246,7 @@
+
@@ -343,6 +344,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -359,6 +404,14 @@
+
+
+
+
+
+
+
+
@@ -372,6 +425,11 @@
+
+
+
+
+
@@ -393,6 +451,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+