talk-android/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivityCompose.kt
sowjanyakch 922c329fab
Fix bug - select contacts onclick
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2024-09-11 09:30:41 +02:00

443 lines
17 KiB
Kotlin

/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.contacts
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
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.CircularProgressIndicator
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.Scaffold
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
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
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class ContactsActivityCompose : BaseActivity() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var contactsViewModel: ContactsViewModel
@SuppressLint("UnrememberedMutableState")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
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()
}
}.toSet().toMutableList()
contactsViewModel.updateSelectedParticipants(selectedParticipants)
MaterialTheme(
colorScheme = colorScheme
) {
val context = LocalContext.current
Scaffold(
topBar = {
AppBar(
title = stringResource(R.string.nc_app_product_name),
context = context,
contactsViewModel = contactsViewModel
)
},
content = {
Column(Modifier.padding(it)) {
ConversationCreationOptions(context = context, contactsViewModel = contactsViewModel)
ContactsList(
contactsUiState = uiState.value,
contactsViewModel = contactsViewModel,
context = context
)
}
}
)
}
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 { }
}
}
}
@Composable
fun ContactItemRow(contact: AutocompleteUser, contactsViewModel: ContactsViewModel, context: Context) {
var isSelected by remember { mutableStateOf(contactsViewModel.selectedParticipantsList.value.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
if (isSelected) {
contactsViewModel.selectContact(contact)
} else {
contactsViewModel.deselectContact(contact)
}
}
}
),
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) {
when (contactsUiState) {
is ContactsUiState.None -> {
}
is ContactsUiState.Loading -> {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
is ContactsUiState.Success -> {
val contacts = contactsUiState.contacts
Log.d(CompanionClass.TAG, "Contacts:$contacts")
if (contacts != null) {
ContactsItem(contacts, contactsViewModel, context)
}
}
is ContactsUiState.Error -> {
val errorMessage = contactsUiState.message
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(text = "Error: $errorMessage", color = Color.Red)
}
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ContactsItem(contacts: List<AutocompleteUser>, contactsViewModel: ContactsViewModel, context: Context) {
val groupedContacts: Map<String, List<AutocompleteUser>> = contacts.groupBy { contact ->
(
if (contact.source == "users") {
contact.label?.first()?.uppercase()
} else {
contact.source?.replaceFirstChar { actorType ->
actorType.uppercase()
}
}
).toString()
}
LazyColumn(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth(),
contentPadding = PaddingValues(all = 10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
groupedContacts.forEach { (initial, contactsForInitial) ->
stickyHeader {
Column {
Surface(Modifier.fillParentMaxWidth()) {
Header(initial)
}
HorizontalDivider(thickness = 0.1.dp, color = Color.Black)
}
}
items(contactsForInitial) { contact ->
ContactItemRow(
contact = contact,
contactsViewModel = contactsViewModel,
context = context
)
Log.d(CompanionClass.TAG, "Contacts:$contact")
}
}
}
}
@Composable
fun Header(header: String) {
Text(
text = header,
modifier = Modifier
.fillMaxSize()
.background(Color.Transparent)
.padding(start = 60.dp),
color = Color.Blue,
fontWeight = FontWeight.Bold
)
}
class CompanionClass {
companion object {
internal val TAG = ContactsActivityCompose::class.simpleName
internal const val ROOM_TYPE_ONE_ONE = "1"
}
}