123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- /*
- * Nextcloud Talk - Android Client
- *
- * SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
- * 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.net.Uri
- 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.shape.CircleShape
- 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.draw.clip
- import androidx.compose.ui.graphics.Color
- import androidx.compose.ui.layout.ContentScale
- import androidx.compose.ui.platform.LocalContext
- import androidx.compose.ui.platform.LocalView
- import androidx.compose.ui.res.colorResource
- 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.PickImage
- import com.nextcloud.talk.utils.bundle.BundleKeys
- import javax.inject.Inject
- @AutoInjector(NextcloudTalkApplication::class)
- class ConversationCreationActivity : BaseActivity() {
- @Inject
- lateinit var viewModelFactory: ViewModelProvider.Factory
- private lateinit var pickImage: PickImage
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
- val conversationCreationViewModel = ViewModelProvider(
- this,
- viewModelFactory
- )[ConversationCreationViewModel::class.java]
- val conversationUser = conversationCreationViewModel.currentUser
- pickImage = PickImage(this, conversationUser)
- setContent {
- val colorScheme = viewThemeUtils.getColorScheme(this)
- val context = LocalContext.current
- MaterialTheme(
- colorScheme = colorScheme
- ) {
- ConversationCreationScreen(conversationCreationViewModel, context, pickImage)
- }
- 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,
- pickImage: PickImage
- ) {
- val selectedImageUri = conversationCreationViewModel.selectedImageUri.collectAsState().value
- val imagePickerLauncher = rememberLauncherForActivityResult(
- contract = ActivityResultContracts.StartActivityForResult()
- ) { result ->
- if (result.resultCode == Activity.RESULT_OK) {
- pickImage.onImagePickerResult(result.data) { uri ->
- conversationCreationViewModel.updateSelectedImageUri(uri)
- }
- }
- }
- val remoteFilePickerLauncher = rememberLauncherForActivityResult(
- contract = ActivityResultContracts.StartActivityForResult()
- ) { result ->
- if (result.resultCode == Activity.RESULT_OK) {
- pickImage.onSelectRemoteFilesResult(imagePickerLauncher, result.data)
- }
- }
- val cameraLauncher = rememberLauncherForActivityResult(
- contract = ActivityResultContracts.StartActivityForResult()
- ) { result ->
- if (result.resultCode == Activity.RESULT_OK) {
- pickImage.onTakePictureResult(imagePickerLauncher, result.data)
- }
- }
- val launcher = rememberLauncherForActivityResult(
- contract = ActivityResultContracts.StartActivityForResult(),
- onResult = { result ->
- if (result.resultCode == Activity.RESULT_OK) {
- val data = result.data
- val selectedParticipants =
- data?.getParcelableArrayListExtra<AutocompleteUser>("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(selectedImageUri)
- UploadAvatar(
- pickImage = pickImage,
- onImageSelected = { uri -> conversationCreationViewModel.updateSelectedImageUri(uri) },
- imagePickerLauncher = imagePickerLauncher,
- remoteFilePickerLauncher = remoteFilePickerLauncher,
- cameraLauncher = cameraLauncher,
- onDeleteImage = { conversationCreationViewModel.updateSelectedImageUri(null) },
- selectedImageUri = selectedImageUri
- )
- ConversationNameAndDescription(conversationCreationViewModel)
- AddParticipants(launcher, context, conversationCreationViewModel)
- RoomCreationOptions(conversationCreationViewModel)
- CreateConversation(conversationCreationViewModel, context)
- }
- }
- )
- }
- @Composable
- fun DefaultUserAvatar(selectedImageUri: Uri?) {
- Box(
- modifier = Modifier.fillMaxWidth(),
- contentAlignment = Alignment.Center
- ) {
- if (selectedImageUri != null) {
- AsyncImage(
- model = selectedImageUri,
- contentDescription = stringResource(id = R.string.user_avatar),
- contentScale = ContentScale.Crop,
- modifier = Modifier
- .size(84.dp)
- .padding(top = 8.dp)
- .clip(CircleShape)
- )
- } else {
- AsyncImage(
- model = R.drawable.ic_circular_group,
- contentDescription = stringResource(id = R.string.user_avatar),
- contentScale = ContentScale.Crop,
- modifier = Modifier
- .size(84.dp)
- .padding(top = 8.dp)
- .clip(CircleShape)
- )
- }
- }
- }
- @Composable
- fun UploadAvatar(
- pickImage: PickImage,
- onImageSelected: (Uri) -> Unit,
- imagePickerLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>,
- remoteFilePickerLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>,
- cameraLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>,
- onDeleteImage: () -> Unit,
- selectedImageUri: Uri?
- ) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(16.dp),
- horizontalArrangement = Arrangement.Center
- ) {
- IconButton(
- onClick = {
- pickImage.takePicture(cameraLauncher)
- }
- ) {
- Icon(
- painter = painterResource(id = R.drawable.ic_baseline_photo_camera_24),
- contentDescription = null,
- modifier = Modifier.size(24.dp)
- )
- }
- IconButton(onClick = {
- pickImage.selectLocal(imagePickerLauncher)
- }) {
- Icon(
- painter = painterResource(id = R.drawable.upload),
- contentDescription = null,
- modifier = Modifier.size(24.dp)
- )
- }
- IconButton(
- onClick = {
- pickImage.selectRemote(remoteFilePickerLauncher)
- }
- ) {
- Icon(
- painter = painterResource(id = R.drawable.ic_mimetype_folder),
- contentDescription = null,
- modifier = Modifier.size(24.dp)
- )
- }
- if (selectedImageUri != null) {
- IconButton(onClick = {
- onDeleteImage()
- }) {
- 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(),
- singleLine = true
- )
- 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<Intent, ActivityResult>,
- 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<AutocompleteUser>
- )
- 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(
- containerColor = colorResource(id = R.color.dialog_background),
- 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"
- }
- }
|