ConversationCreationActivity.kt 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. /*
  2. * Nextcloud Talk - Android Client
  3. *
  4. * SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
  5. * SPDX-License-Identifier: GPL-3.0-or-later
  6. */
  7. @file:Suppress("DEPRECATION")
  8. package com.nextcloud.talk.conversationcreation
  9. import android.annotation.SuppressLint
  10. import android.app.Activity
  11. import android.content.Context
  12. import android.content.Intent
  13. import android.os.Bundle
  14. import androidx.activity.compose.ManagedActivityResultLauncher
  15. import androidx.activity.compose.rememberLauncherForActivityResult
  16. import androidx.activity.compose.setContent
  17. import androidx.activity.result.ActivityResult
  18. import androidx.activity.result.contract.ActivityResultContracts
  19. import androidx.compose.foundation.clickable
  20. import androidx.compose.foundation.layout.Arrangement
  21. import androidx.compose.foundation.layout.Box
  22. import androidx.compose.foundation.layout.Column
  23. import androidx.compose.foundation.layout.Row
  24. import androidx.compose.foundation.layout.Spacer
  25. import androidx.compose.foundation.layout.fillMaxHeight
  26. import androidx.compose.foundation.layout.fillMaxWidth
  27. import androidx.compose.foundation.layout.padding
  28. import androidx.compose.foundation.layout.size
  29. import androidx.compose.foundation.layout.width
  30. import androidx.compose.foundation.rememberScrollState
  31. import androidx.compose.foundation.verticalScroll
  32. import androidx.compose.material.icons.Icons
  33. import androidx.compose.material.icons.automirrored.filled.ArrowBack
  34. import androidx.compose.material3.AlertDialog
  35. import androidx.compose.material3.Button
  36. import androidx.compose.material3.ExperimentalMaterial3Api
  37. import androidx.compose.material3.HorizontalDivider
  38. import androidx.compose.material3.Icon
  39. import androidx.compose.material3.IconButton
  40. import androidx.compose.material3.MaterialTheme
  41. import androidx.compose.material3.OutlinedTextField
  42. import androidx.compose.material3.Scaffold
  43. import androidx.compose.material3.Switch
  44. import androidx.compose.material3.Text
  45. import androidx.compose.material3.TextField
  46. import androidx.compose.material3.TopAppBar
  47. import androidx.compose.runtime.Composable
  48. import androidx.compose.runtime.collectAsState
  49. import androidx.compose.runtime.getValue
  50. import androidx.compose.runtime.mutableStateOf
  51. import androidx.compose.runtime.remember
  52. import androidx.compose.runtime.setValue
  53. import androidx.compose.ui.Alignment
  54. import androidx.compose.ui.Modifier
  55. import androidx.compose.ui.graphics.Color
  56. import androidx.compose.ui.platform.LocalContext
  57. import androidx.compose.ui.res.painterResource
  58. import androidx.compose.ui.res.stringResource
  59. import androidx.compose.ui.text.style.TextAlign
  60. import androidx.compose.ui.unit.dp
  61. import androidx.compose.ui.unit.sp
  62. import androidx.lifecycle.ViewModelProvider
  63. import autodagger.AutoInjector
  64. import coil.compose.AsyncImage
  65. import com.nextcloud.talk.R
  66. import com.nextcloud.talk.activities.BaseActivity
  67. import com.nextcloud.talk.application.NextcloudTalkApplication
  68. import com.nextcloud.talk.chat.ChatActivity
  69. import com.nextcloud.talk.contacts.ContactsActivityCompose
  70. import com.nextcloud.talk.contacts.loadImage
  71. import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
  72. import com.nextcloud.talk.utils.bundle.BundleKeys
  73. import javax.inject.Inject
  74. @AutoInjector(NextcloudTalkApplication::class)
  75. class ConversationCreationActivity : BaseActivity() {
  76. @Inject
  77. lateinit var viewModelFactory: ViewModelProvider.Factory
  78. override fun onCreate(savedInstanceState: Bundle?) {
  79. super.onCreate(savedInstanceState)
  80. NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
  81. val conversationCreationViewModel = ViewModelProvider(
  82. this,
  83. viewModelFactory
  84. )[ConversationCreationViewModel::class.java]
  85. setContent {
  86. val colorScheme = viewThemeUtils.getColorScheme(this)
  87. val context = LocalContext.current
  88. MaterialTheme(
  89. colorScheme = colorScheme
  90. ) {
  91. ConversationCreationScreen(conversationCreationViewModel, context)
  92. }
  93. }
  94. }
  95. }
  96. @OptIn(ExperimentalMaterial3Api::class)
  97. @Composable
  98. fun ConversationCreationScreen(conversationCreationViewModel: ConversationCreationViewModel, context: Context) {
  99. val context = LocalContext.current
  100. val launcher = rememberLauncherForActivityResult(
  101. contract = ActivityResultContracts.StartActivityForResult(),
  102. onResult = { result ->
  103. if (result.resultCode == Activity.RESULT_OK) {
  104. val data = result.data
  105. val selectedParticipants = data?.getParcelableArrayListExtra<AutocompleteUser>("selectedParticipants")
  106. ?: emptyList()
  107. val participants = selectedParticipants.toMutableList()
  108. conversationCreationViewModel.updateSelectedParticipants(participants)
  109. }
  110. }
  111. )
  112. Scaffold(
  113. topBar = {
  114. TopAppBar(
  115. title = { Text(text = stringResource(id = R.string.nc_new_conversation)) },
  116. navigationIcon = {
  117. IconButton(onClick = {
  118. (context as? Activity)?.finish()
  119. }) {
  120. Icon(
  121. Icons.AutoMirrored.Filled.ArrowBack,
  122. contentDescription = stringResource(id = R.string.back_button)
  123. )
  124. }
  125. }
  126. )
  127. },
  128. content = { paddingValues ->
  129. Column(
  130. modifier = Modifier
  131. .padding(paddingValues)
  132. .verticalScroll(rememberScrollState())
  133. ) {
  134. DefaultUserAvatar()
  135. UploadAvatar()
  136. ConversationNameAndDescription(conversationCreationViewModel)
  137. AddParticipants(launcher, context, conversationCreationViewModel)
  138. RoomCreationOptions(conversationCreationViewModel)
  139. CreateConversation(conversationCreationViewModel, context)
  140. }
  141. }
  142. )
  143. }
  144. @Composable
  145. fun DefaultUserAvatar() {
  146. Box(
  147. modifier = Modifier.fillMaxWidth(),
  148. contentAlignment = Alignment.Center
  149. ) {
  150. AsyncImage(
  151. model = R.drawable.ic_circular_group,
  152. contentDescription = "User Avatar",
  153. modifier = Modifier
  154. .size(width = 84.dp, height = 84.dp)
  155. .padding(top = 8.dp)
  156. )
  157. }
  158. }
  159. @Composable
  160. fun UploadAvatar() {
  161. Row(
  162. modifier = Modifier
  163. .fillMaxWidth()
  164. .padding(16.dp),
  165. horizontalArrangement = Arrangement.Center
  166. ) {
  167. IconButton(onClick = {
  168. }) {
  169. Icon(
  170. painter = painterResource(id = R.drawable.ic_baseline_photo_camera_24),
  171. contentDescription = null,
  172. modifier = Modifier.size(24.dp)
  173. )
  174. }
  175. IconButton(onClick = {
  176. }) {
  177. Icon(
  178. painter = painterResource(id = R.drawable.ic_folder_multiple_image),
  179. contentDescription = null,
  180. modifier = Modifier.size(24.dp)
  181. )
  182. }
  183. IconButton(onClick = {
  184. }) {
  185. Icon(
  186. painter = painterResource(id = R.drawable.baseline_tag_faces_24),
  187. contentDescription = null,
  188. modifier = Modifier.size(24.dp)
  189. )
  190. }
  191. IconButton(onClick = {
  192. }) {
  193. Icon(
  194. painter = painterResource(id = R.drawable.ic_delete_grey600_24dp),
  195. contentDescription = null,
  196. modifier = Modifier.size(24.dp)
  197. )
  198. }
  199. }
  200. }
  201. @Composable
  202. fun ConversationNameAndDescription(conversationCreationViewModel: ConversationCreationViewModel) {
  203. val conversationRoomName = conversationCreationViewModel.roomName.collectAsState()
  204. val conversationDescription = conversationCreationViewModel.conversationDescription.collectAsState()
  205. OutlinedTextField(
  206. value = conversationRoomName.value,
  207. onValueChange = {
  208. conversationCreationViewModel.updateRoomName(it)
  209. },
  210. label = { Text(text = stringResource(id = R.string.nc_call_name)) },
  211. modifier = Modifier
  212. .padding(start = 16.dp, end = 16.dp)
  213. .fillMaxWidth()
  214. )
  215. OutlinedTextField(
  216. value = conversationDescription.value,
  217. onValueChange = {
  218. conversationCreationViewModel.updateConversationDescription(it)
  219. },
  220. label = { Text(text = stringResource(id = R.string.nc_conversation_description)) },
  221. modifier = Modifier
  222. .padding(top = 8.dp, start = 16.dp, end = 16.dp)
  223. .fillMaxWidth()
  224. )
  225. }
  226. @SuppressLint("SuspiciousIndentation")
  227. @Composable
  228. fun AddParticipants(
  229. launcher: ManagedActivityResultLauncher<Intent, ActivityResult>,
  230. context: Context,
  231. conversationCreationViewModel: ConversationCreationViewModel
  232. ) {
  233. val participants = conversationCreationViewModel.selectedParticipants.collectAsState().value
  234. Column(
  235. modifier = Modifier
  236. .fillMaxHeight()
  237. .padding(start = 16.dp, end = 16.dp, top = 16.dp)
  238. ) {
  239. Row {
  240. Text(
  241. text = stringResource(id = R.string.nc_participants).uppercase(),
  242. fontSize = 14.sp,
  243. modifier = Modifier.padding(start = 16.dp, bottom = 16.dp)
  244. )
  245. Spacer(modifier = Modifier.weight(1f))
  246. if (participants.isNotEmpty()) {
  247. Text(
  248. text = stringResource(id = R.string.nc_edit),
  249. fontSize = 12.sp,
  250. modifier = Modifier
  251. .padding(start = 16.dp, bottom = 16.dp)
  252. .clickable {
  253. val intent = Intent(context, ContactsActivityCompose::class.java)
  254. intent.putParcelableArrayListExtra(
  255. "selectedParticipants",
  256. participants as ArrayList<AutocompleteUser>
  257. )
  258. intent.putExtra("isAddParticipants", true)
  259. intent.putExtra("isAddParticipantsEdit", true)
  260. launcher.launch(intent)
  261. },
  262. textAlign = TextAlign.Right
  263. )
  264. }
  265. }
  266. participants.toSet().forEach { participant ->
  267. Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
  268. val imageUri = participant.id?.let { conversationCreationViewModel.getImageUri(it, true) }
  269. val errorPlaceholderImage: Int = R.drawable.account_circle_96dp
  270. val loadedImage = loadImage(imageUri, context, errorPlaceholderImage)
  271. AsyncImage(
  272. model = loadedImage,
  273. contentDescription = stringResource(id = R.string.user_avatar),
  274. modifier = Modifier.size(width = 32.dp, height = 32.dp)
  275. )
  276. participant.label?.let {
  277. Text(
  278. text = it,
  279. modifier = Modifier.padding(all = 16.dp),
  280. fontSize = 15.sp
  281. )
  282. }
  283. }
  284. HorizontalDivider(thickness = 0.1.dp, color = Color.Black)
  285. }
  286. Row(
  287. modifier = Modifier
  288. .fillMaxWidth()
  289. .clickable {
  290. val intent = Intent(context, ContactsActivityCompose::class.java)
  291. intent.putExtra("isAddParticipants", true)
  292. launcher.launch(intent)
  293. },
  294. verticalAlignment = Alignment.CenterVertically
  295. ) {
  296. if (participants.isEmpty()) {
  297. Icon(
  298. painter = painterResource(id = R.drawable.ic_account_plus),
  299. contentDescription = null,
  300. modifier = Modifier.size(24.dp)
  301. )
  302. Text(
  303. text = stringResource(id = R.string.nc_add_participants),
  304. modifier = Modifier.padding(start = 16.dp)
  305. )
  306. }
  307. }
  308. }
  309. }
  310. @Composable
  311. fun RoomCreationOptions(conversationCreationViewModel: ConversationCreationViewModel) {
  312. val isGuestsAllowed = conversationCreationViewModel.isGuestsAllowed.value
  313. val isConversationAvailableForRegisteredUsers = conversationCreationViewModel
  314. .isConversationAvailableForRegisteredUsers.value
  315. val isOpenForGuestAppUsers = conversationCreationViewModel.openForGuestAppUsers.value
  316. Text(
  317. text = stringResource(id = R.string.nc_visible).uppercase(),
  318. fontSize = 14.sp,
  319. modifier = Modifier.padding(top = 24.dp, start = 16.dp, end = 16.dp)
  320. )
  321. ConversationOptions(
  322. icon = R.drawable.ic_avatar_link,
  323. text = R.string.nc_guest_access_allow_title,
  324. switch = {
  325. Switch(
  326. checked = isGuestsAllowed,
  327. onCheckedChange = {
  328. conversationCreationViewModel.isGuestsAllowed.value = it
  329. }
  330. )
  331. },
  332. showDialog = false,
  333. conversationCreationViewModel = conversationCreationViewModel
  334. )
  335. if (isGuestsAllowed) {
  336. ConversationOptions(
  337. icon = R.drawable.ic_lock_grey600_24px,
  338. text = R.string.nc_set_password,
  339. showDialog = true,
  340. conversationCreationViewModel = conversationCreationViewModel
  341. )
  342. }
  343. ConversationOptions(
  344. icon = R.drawable.baseline_format_list_bulleted_24,
  345. text = R.string.nc_open_conversation_to_registered_users,
  346. switch = {
  347. Switch(
  348. checked = isConversationAvailableForRegisteredUsers,
  349. onCheckedChange = {
  350. conversationCreationViewModel.isConversationAvailableForRegisteredUsers.value = it
  351. }
  352. )
  353. },
  354. showDialog = false,
  355. conversationCreationViewModel = conversationCreationViewModel
  356. )
  357. if (isConversationAvailableForRegisteredUsers) {
  358. ConversationOptions(
  359. text = R.string.nc_open_to_guest_app_users,
  360. switch = {
  361. Switch(
  362. checked = isOpenForGuestAppUsers,
  363. onCheckedChange = {
  364. conversationCreationViewModel.openForGuestAppUsers.value = it
  365. }
  366. )
  367. },
  368. showDialog = false,
  369. conversationCreationViewModel = conversationCreationViewModel
  370. )
  371. }
  372. }
  373. @Composable
  374. fun ConversationOptions(
  375. icon: Int? = null,
  376. text: Int,
  377. switch: @Composable (() -> Unit)? = null,
  378. showDialog: Boolean,
  379. conversationCreationViewModel: ConversationCreationViewModel
  380. ) {
  381. var showPasswordDialog by remember { mutableStateOf(false) }
  382. Row(
  383. modifier = Modifier
  384. .fillMaxWidth()
  385. .padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
  386. .then(
  387. if (showDialog) {
  388. Modifier.clickable {
  389. showPasswordDialog = true
  390. }
  391. } else {
  392. Modifier
  393. }
  394. ),
  395. horizontalArrangement = Arrangement.SpaceBetween,
  396. verticalAlignment = Alignment.CenterVertically
  397. ) {
  398. if (icon != null) {
  399. Icon(
  400. painter = painterResource(id = icon),
  401. contentDescription = null,
  402. modifier = Modifier.size(24.dp)
  403. )
  404. Spacer(modifier = Modifier.width(16.dp))
  405. } else {
  406. Spacer(modifier = Modifier.width(40.dp))
  407. }
  408. Text(
  409. text = stringResource(id = text),
  410. modifier = Modifier.weight(1f)
  411. )
  412. if (switch != null) {
  413. switch()
  414. }
  415. if (showPasswordDialog) {
  416. ShowPasswordDialog(
  417. onDismiss = { showPasswordDialog = false },
  418. conversationCreationViewModel = conversationCreationViewModel
  419. )
  420. }
  421. }
  422. }
  423. @Composable
  424. fun ShowPasswordDialog(onDismiss: () -> Unit, conversationCreationViewModel: ConversationCreationViewModel) {
  425. var password by remember { mutableStateOf("") }
  426. AlertDialog(
  427. onDismissRequest = onDismiss,
  428. confirmButton = {
  429. Button(onClick = {
  430. conversationCreationViewModel.updatePassword(password)
  431. onDismiss()
  432. }) {
  433. Text(text = stringResource(id = R.string.save))
  434. }
  435. },
  436. title = { Text(text = stringResource(id = R.string.nc_set_password)) },
  437. text = {
  438. TextField(
  439. value = password,
  440. onValueChange = {
  441. password = it
  442. },
  443. label = { Text(text = stringResource(id = R.string.nc_guest_access_password_dialog_hint)) }
  444. )
  445. },
  446. dismissButton = {
  447. Button(onClick = { onDismiss() }) {
  448. Text(text = stringResource(id = R.string.nc_cancel))
  449. }
  450. }
  451. )
  452. }
  453. @Composable
  454. fun CreateConversation(conversationCreationViewModel: ConversationCreationViewModel, context: Context) {
  455. val selectedParticipants by conversationCreationViewModel.selectedParticipants.collectAsState()
  456. Box(
  457. modifier = Modifier
  458. .fillMaxWidth()
  459. .padding(all = 16.dp),
  460. contentAlignment = Alignment.Center
  461. ) {
  462. Button(
  463. onClick = {
  464. conversationCreationViewModel.createRoomAndAddParticipants(
  465. roomType = CompanionClass.ROOM_TYPE_GROUP,
  466. conversationName = conversationCreationViewModel.roomName.value,
  467. participants = selectedParticipants.toSet()
  468. ) { roomToken ->
  469. val bundle = Bundle()
  470. bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
  471. val chatIntent = Intent(context, ChatActivity::class.java)
  472. chatIntent.putExtras(bundle)
  473. chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
  474. context.startActivity(chatIntent)
  475. }
  476. }
  477. ) {
  478. Text(text = stringResource(id = R.string.create_conversation))
  479. }
  480. }
  481. }
  482. class CompanionClass {
  483. companion object {
  484. internal val TAG = ConversationCreationActivity::class.simpleName
  485. internal const val ROOM_TYPE_GROUP = "2"
  486. }
  487. }