ConversationInfoActivity.kt 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399
  1. /*
  2. * Nextcloud Talk application
  3. *
  4. * @author Mario Danic
  5. * @author Andy Scherzinger
  6. * @author Tim Krüger
  7. * @author Marcel Hibbe
  8. * @author Ezhil Shanmugham
  9. * Copyright (C) 2022-2023 Marcel Hibbe (dev@mhibbe.de)
  10. * Copyright (C) 2021-2022 Tim Krüger <t@timkrueger.me>
  11. * Copyright (C) 2021 Andy Scherzinger (info@andy-scherzinger.de)
  12. * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
  13. * Copyright (C) 2023 Ezhil Shanmugham <ezhil56x.contact@gmail.com>
  14. *
  15. * This program is free software: you can redistribute it and/or modify
  16. * it under the terms of the GNU General Public License as published by
  17. * the Free Software Foundation, either version 3 of the License, or
  18. * at your option) any later version.
  19. *
  20. * This program is distributed in the hope that it will be useful,
  21. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. * GNU General Public License for more details.
  24. *
  25. * You should have received a copy of the GNU General Public License
  26. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  27. */
  28. package com.nextcloud.talk.conversationinfo
  29. import android.annotation.SuppressLint
  30. import android.content.Intent
  31. import android.graphics.drawable.ColorDrawable
  32. import android.os.Bundle
  33. import android.text.TextUtils
  34. import android.util.Log
  35. import android.view.Menu
  36. import android.view.MenuItem
  37. import android.view.View
  38. import android.view.View.GONE
  39. import android.view.View.VISIBLE
  40. import androidx.appcompat.app.AlertDialog
  41. import androidx.lifecycle.ViewModelProvider
  42. import androidx.work.Data
  43. import androidx.work.OneTimeWorkRequest
  44. import androidx.work.WorkManager
  45. import autodagger.AutoInjector
  46. import com.afollestad.materialdialogs.LayoutMode.WRAP_CONTENT
  47. import com.afollestad.materialdialogs.MaterialDialog
  48. import com.afollestad.materialdialogs.bottomsheets.BottomSheet
  49. import com.afollestad.materialdialogs.datetime.dateTimePicker
  50. import com.google.android.material.dialog.MaterialAlertDialogBuilder
  51. import com.google.android.material.snackbar.Snackbar
  52. import com.nextcloud.android.common.ui.theme.utils.ColorRole
  53. import com.nextcloud.talk.R
  54. import com.nextcloud.talk.activities.BaseActivity
  55. import com.nextcloud.talk.activities.MainActivity
  56. import com.nextcloud.talk.adapters.items.ParticipantItem
  57. import com.nextcloud.talk.api.NcApi
  58. import com.nextcloud.talk.application.NextcloudTalkApplication
  59. import com.nextcloud.talk.bottomsheet.items.BasicListItemWithImage
  60. import com.nextcloud.talk.bottomsheet.items.listItemsWithImage
  61. import com.nextcloud.talk.contacts.ContactsActivity
  62. import com.nextcloud.talk.conversationinfoedit.ConversationInfoEditActivity
  63. import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
  64. import com.nextcloud.talk.data.user.model.User
  65. import com.nextcloud.talk.databinding.ActivityConversationInfoBinding
  66. import com.nextcloud.talk.events.EventStatus
  67. import com.nextcloud.talk.extensions.loadConversationAvatar
  68. import com.nextcloud.talk.extensions.loadNoteToSelfAvatar
  69. import com.nextcloud.talk.extensions.loadSystemAvatar
  70. import com.nextcloud.talk.extensions.loadUserAvatar
  71. import com.nextcloud.talk.jobs.DeleteConversationWorker
  72. import com.nextcloud.talk.jobs.LeaveConversationWorker
  73. import com.nextcloud.talk.models.domain.ConversationModel
  74. import com.nextcloud.talk.models.domain.ConversationType
  75. import com.nextcloud.talk.models.domain.LobbyState
  76. import com.nextcloud.talk.models.domain.NotificationLevel
  77. import com.nextcloud.talk.models.domain.converters.DomainEnumNotificationLevelConverter
  78. import com.nextcloud.talk.models.json.capabilities.SpreedCapability
  79. import com.nextcloud.talk.models.json.generic.GenericOverall
  80. import com.nextcloud.talk.models.json.participants.Participant
  81. import com.nextcloud.talk.models.json.participants.Participant.ActorType.CIRCLES
  82. import com.nextcloud.talk.models.json.participants.Participant.ActorType.GROUPS
  83. import com.nextcloud.talk.models.json.participants.Participant.ActorType.USERS
  84. import com.nextcloud.talk.models.json.participants.ParticipantsOverall
  85. import com.nextcloud.talk.repositories.conversations.ConversationsRepository
  86. import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
  87. import com.nextcloud.talk.utils.ApiUtils
  88. import com.nextcloud.talk.utils.SpreedFeatures
  89. import com.nextcloud.talk.utils.ConversationUtils
  90. import com.nextcloud.talk.utils.DateConstants
  91. import com.nextcloud.talk.utils.DateUtils
  92. import com.nextcloud.talk.utils.bundle.BundleKeys
  93. import com.nextcloud.talk.utils.CapabilitiesUtil
  94. import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
  95. import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule
  96. import eu.davidea.flexibleadapter.FlexibleAdapter
  97. import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
  98. import io.reactivex.Observer
  99. import io.reactivex.android.schedulers.AndroidSchedulers
  100. import io.reactivex.disposables.Disposable
  101. import io.reactivex.schedulers.Schedulers
  102. import org.greenrobot.eventbus.Subscribe
  103. import org.greenrobot.eventbus.ThreadMode
  104. import java.util.Calendar
  105. import java.util.Collections
  106. import java.util.Locale
  107. import javax.inject.Inject
  108. @AutoInjector(NextcloudTalkApplication::class)
  109. class ConversationInfoActivity :
  110. BaseActivity(),
  111. FlexibleAdapter.OnItemClickListener {
  112. private lateinit var binding: ActivityConversationInfoBinding
  113. @Inject
  114. lateinit var viewModelFactory: ViewModelProvider.Factory
  115. @Inject
  116. lateinit var ncApi: NcApi
  117. @Inject
  118. lateinit var currentUserProvider: CurrentUserProviderNew
  119. @Inject
  120. lateinit var conversationsRepository: ConversationsRepository
  121. @Inject
  122. lateinit var dateUtils: DateUtils
  123. lateinit var viewModel: ConversationInfoViewModel
  124. private lateinit var spreedCapabilities: SpreedCapability
  125. private lateinit var conversationToken: String
  126. private lateinit var conversationUser: User
  127. private var hasAvatarSpacing: Boolean = false
  128. private lateinit var credentials: String
  129. private var roomDisposable: Disposable? = null
  130. private var participantsDisposable: Disposable? = null
  131. private var databaseStorageModule: DatabaseStorageModule? = null
  132. // private var conversation: Conversation? = null
  133. private var conversation: ConversationModel? = null
  134. private var adapter: FlexibleAdapter<ParticipantItem>? = null
  135. private var userItems: MutableList<ParticipantItem> = ArrayList()
  136. private lateinit var optionsMenu: Menu
  137. private val workerData: Data?
  138. get() {
  139. if (!TextUtils.isEmpty(conversationToken)) {
  140. val data = Data.Builder()
  141. data.putString(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
  142. data.putLong(BundleKeys.KEY_INTERNAL_USER_ID, conversationUser.id!!)
  143. return data.build()
  144. }
  145. return null
  146. }
  147. override fun onCreate(savedInstanceState: Bundle?) {
  148. super.onCreate(savedInstanceState)
  149. NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
  150. binding = ActivityConversationInfoBinding.inflate(layoutInflater)
  151. setupActionBar()
  152. setContentView(binding.root)
  153. setupSystemColors()
  154. viewModel =
  155. ViewModelProvider(this, viewModelFactory)[ConversationInfoViewModel::class.java]
  156. conversationUser = currentUserProvider.currentUser.blockingGet()
  157. conversationToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!!
  158. hasAvatarSpacing = intent.getBooleanExtra(BundleKeys.KEY_ROOM_ONE_TO_ONE, false)
  159. credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)!!
  160. initObservers()
  161. }
  162. override fun onStart() {
  163. super.onStart()
  164. this.lifecycle.addObserver(ConversationInfoViewModel.LifeCycleObserver)
  165. }
  166. override fun onStop() {
  167. super.onStop()
  168. this.lifecycle.removeObserver(ConversationInfoViewModel.LifeCycleObserver)
  169. }
  170. override fun onResume() {
  171. super.onResume()
  172. if (databaseStorageModule == null) {
  173. databaseStorageModule = DatabaseStorageModule(conversationUser, conversationToken)
  174. }
  175. setUpNotificationSettings(databaseStorageModule!!)
  176. binding.deleteConversationAction.setOnClickListener { showDeleteConversationDialog() }
  177. binding.leaveConversationAction.setOnClickListener { leaveConversation() }
  178. binding.clearConversationHistory.setOnClickListener { showClearHistoryDialog() }
  179. binding.addParticipantsAction.setOnClickListener { addParticipants() }
  180. viewModel.getRoom(conversationUser, conversationToken)
  181. themeTextViews()
  182. themeSwitchPreferences()
  183. binding.addParticipantsAction.visibility = GONE
  184. binding.progressBar.let { viewThemeUtils.platform.colorCircularProgressBar(it, ColorRole.PRIMARY) }
  185. }
  186. private fun initObservers() {
  187. viewModel.viewState.observe(this) { state ->
  188. when (state) {
  189. is ConversationInfoViewModel.GetRoomSuccessState -> {
  190. conversation = state.conversationModel
  191. viewModel.getCapabilities(conversationUser, conversationToken, conversation!!)
  192. }
  193. is ConversationInfoViewModel.GetRoomErrorState -> {
  194. Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
  195. }
  196. else -> {}
  197. }
  198. }
  199. viewModel.getCapabilitiesViewState.observe(this) { state ->
  200. when (state) {
  201. is ConversationInfoViewModel.GetCapabilitiesSuccessState -> {
  202. spreedCapabilities = state.spreedCapabilities
  203. handleConversation()
  204. }
  205. else -> {}
  206. }
  207. }
  208. }
  209. private fun setupActionBar() {
  210. setSupportActionBar(binding.conversationInfoToolbar)
  211. binding.conversationInfoToolbar.setNavigationOnClickListener {
  212. onBackPressedDispatcher.onBackPressed()
  213. }
  214. supportActionBar?.setDisplayHomeAsUpEnabled(true)
  215. supportActionBar?.setDisplayShowHomeEnabled(true)
  216. supportActionBar?.setIcon(ColorDrawable(resources!!.getColor(android.R.color.transparent, null)))
  217. supportActionBar?.title = if (hasAvatarSpacing) {
  218. " " + resources!!.getString(R.string.nc_conversation_menu_conversation_info)
  219. } else {
  220. resources!!.getString(R.string.nc_conversation_menu_conversation_info)
  221. }
  222. viewThemeUtils.material.themeToolbar(binding.conversationInfoToolbar)
  223. }
  224. override fun onCreateOptionsMenu(menu: Menu): Boolean {
  225. super.onCreateOptionsMenu(menu)
  226. optionsMenu = menu
  227. return true
  228. }
  229. fun showOptionsMenu() {
  230. if (::optionsMenu.isInitialized) {
  231. optionsMenu.clear()
  232. if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.AVATAR)) {
  233. menuInflater.inflate(R.menu.menu_conversation_info, optionsMenu)
  234. }
  235. }
  236. }
  237. override fun onPrepareOptionsMenu(menu: Menu): Boolean {
  238. super.onPrepareOptionsMenu(menu)
  239. return true
  240. }
  241. override fun onOptionsItemSelected(item: MenuItem): Boolean {
  242. if (item.itemId == R.id.edit) {
  243. val bundle = Bundle()
  244. bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
  245. val intent = Intent(this, ConversationInfoEditActivity::class.java)
  246. intent.putExtras(bundle)
  247. startActivity(intent)
  248. }
  249. return true
  250. }
  251. private fun themeSwitchPreferences() {
  252. binding.run {
  253. listOf(
  254. binding.webinarInfoView.lobbySwitch,
  255. binding.notificationSettingsView.callNotificationsSwitch,
  256. binding.notificationSettingsView.importantConversationSwitch,
  257. binding.guestAccessView.allowGuestsSwitch,
  258. binding.guestAccessView.passwordProtectionSwitch,
  259. binding.recordingConsentView.recordingConsentForConversationSwitch
  260. ).forEach(viewThemeUtils.talk::colorSwitch)
  261. }
  262. }
  263. private fun themeTextViews() {
  264. binding.run {
  265. listOf(
  266. binding.notificationSettingsView.notificationSettingsCategory,
  267. binding.webinarInfoView.webinarSettingsCategory,
  268. binding.guestAccessView.guestAccessSettingsCategory,
  269. binding.sharedItemsTitle,
  270. binding.recordingConsentView.recordingConsentSettingsCategory,
  271. binding.conversationSettingsTitle,
  272. binding.participantsListCategory
  273. )
  274. }.forEach(viewThemeUtils.platform::colorTextView)
  275. }
  276. private fun showSharedItems() {
  277. val intent = Intent(this, SharedItemsActivity::class.java)
  278. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
  279. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
  280. intent.putExtra(BundleKeys.KEY_CONVERSATION_NAME, conversation?.displayName)
  281. intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
  282. intent.putExtra(
  283. SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR,
  284. ConversationUtils.isParticipantOwnerOrModerator(conversation!!)
  285. )
  286. startActivity(intent)
  287. }
  288. private fun setupWebinaryView() {
  289. if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.WEBINARY_LOBBY) &&
  290. webinaryRoomType(conversation!!) &&
  291. ConversationUtils.canModerate(conversation!!, spreedCapabilities)
  292. ) {
  293. binding.webinarInfoView.webinarSettings.visibility = VISIBLE
  294. val isLobbyOpenToModeratorsOnly =
  295. conversation!!.lobbyState == LobbyState.LOBBY_STATE_MODERATORS_ONLY
  296. binding.webinarInfoView.lobbySwitch.isChecked = isLobbyOpenToModeratorsOnly
  297. reconfigureLobbyTimerView()
  298. binding.webinarInfoView.startTimeButton.setOnClickListener {
  299. MaterialDialog(this, BottomSheet(WRAP_CONTENT)).show {
  300. val currentTimeCalendar = Calendar.getInstance()
  301. if (conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != 0L) {
  302. currentTimeCalendar.timeInMillis = conversation!!.lobbyTimer!! * DateConstants.SECOND_DIVIDER
  303. }
  304. dateTimePicker(
  305. minDateTime = Calendar.getInstance(),
  306. requireFutureDateTime = true,
  307. currentDateTime = currentTimeCalendar,
  308. show24HoursView = true,
  309. dateTimeCallback = { _, dateTime ->
  310. reconfigureLobbyTimerView(dateTime)
  311. submitLobbyChanges()
  312. }
  313. )
  314. }
  315. }
  316. binding.webinarInfoView.webinarSettingsLobby.setOnClickListener {
  317. binding.webinarInfoView.lobbySwitch.isChecked = !binding.webinarInfoView.lobbySwitch.isChecked
  318. reconfigureLobbyTimerView()
  319. submitLobbyChanges()
  320. }
  321. } else {
  322. binding.webinarInfoView.webinarSettings.visibility = GONE
  323. }
  324. }
  325. private fun webinaryRoomType(conversation: ConversationModel): Boolean {
  326. return conversation.type == ConversationType.ROOM_GROUP_CALL ||
  327. conversation.type == ConversationType.ROOM_PUBLIC_CALL
  328. }
  329. private fun reconfigureLobbyTimerView(dateTime: Calendar? = null) {
  330. val isChecked = binding.webinarInfoView.lobbySwitch.isChecked
  331. if (dateTime != null && isChecked) {
  332. conversation!!.lobbyTimer = (
  333. dateTime.timeInMillis - (dateTime.time.seconds * DateConstants.SECOND_DIVIDER)
  334. ) / DateConstants.SECOND_DIVIDER
  335. } else if (!isChecked) {
  336. conversation!!.lobbyTimer = 0
  337. }
  338. conversation!!.lobbyState = if (isChecked) {
  339. LobbyState.LOBBY_STATE_MODERATORS_ONLY
  340. } else {
  341. LobbyState.LOBBY_STATE_ALL_PARTICIPANTS
  342. }
  343. if (
  344. conversation!!.lobbyTimer != null &&
  345. conversation!!.lobbyTimer != Long.MIN_VALUE &&
  346. conversation!!.lobbyTimer != 0L
  347. ) {
  348. binding.webinarInfoView.startTimeButtonSummary.text = (
  349. dateUtils.getLocalDateTimeStringFromTimestamp(
  350. conversation!!.lobbyTimer!! * DateConstants.SECOND_DIVIDER
  351. )
  352. )
  353. } else {
  354. binding.webinarInfoView.startTimeButtonSummary.setText(R.string.nc_manual)
  355. }
  356. if (isChecked) {
  357. binding.webinarInfoView.startTimeButton.visibility = VISIBLE
  358. } else {
  359. binding.webinarInfoView.startTimeButton.visibility = GONE
  360. }
  361. }
  362. private fun submitLobbyChanges() {
  363. val state = if (binding.webinarInfoView.lobbySwitch.isChecked) {
  364. 1
  365. } else {
  366. 0
  367. }
  368. val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
  369. ncApi.setLobbyForConversation(
  370. ApiUtils.getCredentials(conversationUser.username, conversationUser.token),
  371. ApiUtils.getUrlForRoomWebinaryLobby(apiVersion, conversationUser.baseUrl!!, conversation!!.token),
  372. state,
  373. conversation!!.lobbyTimer
  374. )
  375. ?.subscribeOn(Schedulers.io())
  376. ?.observeOn(AndroidSchedulers.mainThread())
  377. ?.subscribe(object : Observer<GenericOverall> {
  378. override fun onComplete() {
  379. // unused atm
  380. }
  381. override fun onSubscribe(d: Disposable) {
  382. // unused atm
  383. }
  384. override fun onNext(t: GenericOverall) {
  385. // unused atm
  386. }
  387. override fun onError(e: Throwable) {
  388. Log.e(TAG, "Failed to setLobbyForConversation", e)
  389. Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
  390. }
  391. })
  392. }
  393. @Subscribe(threadMode = ThreadMode.MAIN)
  394. fun onMessageEvent(eventStatus: EventStatus) {
  395. getListOfParticipants()
  396. }
  397. private fun showDeleteConversationDialog() {
  398. binding.conversationInfoName.let {
  399. val dialogBuilder = MaterialAlertDialogBuilder(it.context)
  400. .setIcon(
  401. viewThemeUtils.dialog.colorMaterialAlertDialogIcon(
  402. context,
  403. R.drawable.ic_delete_black_24dp
  404. )
  405. )
  406. .setTitle(R.string.nc_delete_call)
  407. .setMessage(R.string.nc_delete_conversation_more)
  408. .setPositiveButton(R.string.nc_delete) { _, _ ->
  409. deleteConversation()
  410. }
  411. .setNegativeButton(R.string.nc_cancel) { _, _ ->
  412. // unused atm
  413. }
  414. viewThemeUtils.dialog
  415. .colorMaterialAlertDialogBackground(it.context, dialogBuilder)
  416. val dialog = dialogBuilder.show()
  417. viewThemeUtils.platform.colorTextButtons(
  418. dialog.getButton(AlertDialog.BUTTON_POSITIVE),
  419. dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
  420. )
  421. }
  422. }
  423. private fun setupAdapter() {
  424. if (adapter == null) {
  425. adapter = FlexibleAdapter(userItems, this, true)
  426. }
  427. val layoutManager = SmoothScrollLinearLayoutManager(this)
  428. binding.recyclerView.layoutManager = layoutManager
  429. binding.recyclerView.setHasFixedSize(true)
  430. binding.recyclerView.adapter = adapter
  431. binding.recyclerView.isNestedScrollingEnabled = false
  432. adapter!!.addListener(this)
  433. }
  434. private fun handleParticipants(participants: List<Participant>) {
  435. var userItem: ParticipantItem
  436. var participant: Participant
  437. userItems = ArrayList()
  438. var ownUserItem: ParticipantItem? = null
  439. for (i in participants.indices) {
  440. participant = participants[i]
  441. userItem = ParticipantItem(this, participant, conversationUser, viewThemeUtils)
  442. if (participant.sessionId != null) {
  443. userItem.isOnline = !participant.sessionId.equals("0")
  444. } else {
  445. userItem.isOnline = participant.sessionIds.isNotEmpty()
  446. }
  447. if (participant.calculatedActorType == USERS &&
  448. participant.calculatedActorId == conversationUser.userId
  449. ) {
  450. ownUserItem = userItem
  451. ownUserItem.model.sessionId = "-1"
  452. ownUserItem.isOnline = true
  453. } else {
  454. userItems.add(userItem)
  455. }
  456. }
  457. Collections.sort(userItems, ParticipantItemComparator())
  458. if (ownUserItem != null) {
  459. userItems.add(0, ownUserItem)
  460. }
  461. setupAdapter()
  462. binding.participants.visibility = VISIBLE
  463. adapter!!.updateDataSet(userItems)
  464. }
  465. private fun getListOfParticipants() {
  466. // FIXME Fix API checking with guests?
  467. val apiVersion: Int = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
  468. val fieldMap = HashMap<String, Boolean>()
  469. fieldMap["includeStatus"] = true
  470. ncApi.getPeersForCall(
  471. credentials,
  472. ApiUtils.getUrlForParticipants(
  473. apiVersion,
  474. conversationUser.baseUrl!!,
  475. conversationToken
  476. ),
  477. fieldMap
  478. )
  479. ?.subscribeOn(Schedulers.io())
  480. ?.observeOn(AndroidSchedulers.mainThread())
  481. ?.subscribe(object : Observer<ParticipantsOverall> {
  482. override fun onSubscribe(d: Disposable) {
  483. participantsDisposable = d
  484. }
  485. @Suppress("Detekt.TooGenericExceptionCaught")
  486. override fun onNext(participantsOverall: ParticipantsOverall) {
  487. handleParticipants(participantsOverall.ocs!!.data!!)
  488. }
  489. override fun onError(e: Throwable) {
  490. // unused atm
  491. }
  492. override fun onComplete() {
  493. participantsDisposable!!.dispose()
  494. }
  495. })
  496. }
  497. private fun addParticipants() {
  498. val bundle = Bundle()
  499. val existingParticipantsId = arrayListOf<String>()
  500. for (userItem in userItems) {
  501. if (userItem.model.calculatedActorType == USERS) {
  502. existingParticipantsId.add(userItem.model.calculatedActorId!!)
  503. }
  504. }
  505. bundle.putBoolean(BundleKeys.KEY_ADD_PARTICIPANTS, true)
  506. bundle.putStringArrayList(BundleKeys.KEY_EXISTING_PARTICIPANTS, existingParticipantsId)
  507. bundle.putString(BundleKeys.KEY_TOKEN, conversation!!.token)
  508. val intent = Intent(this, ContactsActivity::class.java)
  509. intent.putExtras(bundle)
  510. startActivity(intent)
  511. }
  512. private fun leaveConversation() {
  513. workerData?.let {
  514. WorkManager.getInstance(context).enqueue(
  515. OneTimeWorkRequest.Builder(
  516. LeaveConversationWorker::class
  517. .java
  518. ).setInputData(it).build()
  519. )
  520. val intent = Intent(context, MainActivity::class.java)
  521. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
  522. startActivity(intent)
  523. }
  524. }
  525. private fun showClearHistoryDialog() {
  526. binding.conversationInfoName.context.let {
  527. val dialogBuilder = MaterialAlertDialogBuilder(it)
  528. .setIcon(
  529. viewThemeUtils.dialog.colorMaterialAlertDialogIcon(
  530. context,
  531. R.drawable.ic_delete_black_24dp
  532. )
  533. )
  534. .setTitle(R.string.nc_clear_history)
  535. .setMessage(R.string.nc_clear_history_warning)
  536. .setPositiveButton(R.string.nc_delete_all) { _, _ ->
  537. clearHistory()
  538. }
  539. .setNegativeButton(R.string.nc_cancel) { _, _ ->
  540. // unused atm
  541. }
  542. viewThemeUtils.dialog
  543. .colorMaterialAlertDialogBackground(it, dialogBuilder)
  544. val dialog = dialogBuilder.show()
  545. viewThemeUtils.platform.colorTextButtons(
  546. dialog.getButton(AlertDialog.BUTTON_POSITIVE),
  547. dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
  548. )
  549. }
  550. }
  551. private fun clearHistory() {
  552. val apiVersion = ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(1))
  553. ncApi.clearChatHistory(
  554. credentials,
  555. ApiUtils.getUrlForChat(apiVersion, conversationUser.baseUrl!!, conversationToken)
  556. )
  557. ?.subscribeOn(Schedulers.io())
  558. ?.observeOn(AndroidSchedulers.mainThread())
  559. ?.subscribe(object : Observer<GenericOverall> {
  560. override fun onSubscribe(d: Disposable) {
  561. // unused atm
  562. }
  563. override fun onNext(genericOverall: GenericOverall) {
  564. Snackbar.make(
  565. binding.root,
  566. context.getString(R.string.nc_clear_history_success),
  567. Snackbar.LENGTH_LONG
  568. ).show()
  569. }
  570. override fun onError(e: Throwable) {
  571. Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
  572. Log.e(TAG, "failed to clear chat history", e)
  573. }
  574. override fun onComplete() {
  575. // unused atm
  576. }
  577. })
  578. }
  579. private fun deleteConversation() {
  580. workerData?.let {
  581. WorkManager.getInstance(context).enqueue(
  582. OneTimeWorkRequest.Builder(
  583. DeleteConversationWorker::class.java
  584. ).setInputData(it).build()
  585. )
  586. val intent = Intent(context, MainActivity::class.java)
  587. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
  588. startActivity(intent)
  589. }
  590. }
  591. @Suppress("LongMethod")
  592. private fun handleConversation() {
  593. val conversationCopy = conversation!!
  594. if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.RICH_OBJECT_LIST_MEDIA)) {
  595. binding.sharedItemsButton.setOnClickListener { showSharedItems() }
  596. } else {
  597. binding.sharedItems.visibility = GONE
  598. }
  599. if (ConversationUtils.canModerate(conversationCopy, spreedCapabilities)) {
  600. binding.addParticipantsAction.visibility = VISIBLE
  601. if (CapabilitiesUtil.hasSpreedFeatureCapability(
  602. spreedCapabilities,
  603. SpreedFeatures.CLEAR_HISTORY
  604. )
  605. ) {
  606. binding.clearConversationHistory.visibility = VISIBLE
  607. } else {
  608. binding.clearConversationHistory.visibility = GONE
  609. }
  610. showOptionsMenu()
  611. } else {
  612. binding.addParticipantsAction.visibility = GONE
  613. if (ConversationUtils.isNoteToSelfConversation(conversation)) {
  614. binding.notificationSettingsView.notificationSettings.visibility = VISIBLE
  615. } else {
  616. binding.clearConversationHistory.visibility = GONE
  617. }
  618. }
  619. if (!isDestroyed) {
  620. binding.dangerZoneOptions.visibility = VISIBLE
  621. setupWebinaryView()
  622. if (ConversationUtils.canLeave(conversation!!)) {
  623. binding.leaveConversationAction.visibility = GONE
  624. } else {
  625. binding.leaveConversationAction.visibility = VISIBLE
  626. }
  627. if (ConversationUtils.canDelete(conversation!!, spreedCapabilities)) {
  628. binding.deleteConversationAction.visibility = GONE
  629. } else {
  630. binding.deleteConversationAction.visibility = VISIBLE
  631. }
  632. if (ConversationType.ROOM_SYSTEM == conversation!!.type) {
  633. binding.notificationSettingsView.callNotificationsSwitch.visibility = GONE
  634. }
  635. if (conversation!!.notificationCalls === null) {
  636. binding.notificationSettingsView.callNotificationsSwitch.visibility = GONE
  637. } else {
  638. binding.notificationSettingsView.callNotificationsSwitch.isChecked =
  639. (conversationCopy.notificationCalls == 1)
  640. }
  641. getListOfParticipants()
  642. binding.progressBar.visibility = GONE
  643. binding.conversationInfoName.visibility = VISIBLE
  644. binding.displayNameText.text = conversation!!.displayName
  645. if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) {
  646. binding.descriptionText.text = conversation!!.description
  647. binding.conversationDescription.visibility = VISIBLE
  648. }
  649. loadConversationAvatar()
  650. adjustNotificationLevelUI()
  651. initRecordingConsentOption()
  652. initExpiringMessageOption()
  653. binding.let {
  654. GuestAccessHelper(
  655. this@ConversationInfoActivity,
  656. it,
  657. conversation!!,
  658. spreedCapabilities,
  659. conversationUser
  660. ).setupGuestAccess()
  661. }
  662. if (ConversationUtils.isNoteToSelfConversation(conversation!!)) {
  663. binding.notificationSettingsView.notificationSettings.visibility = GONE
  664. } else {
  665. binding.notificationSettingsView.notificationSettings.visibility = VISIBLE
  666. }
  667. }
  668. }
  669. private fun initRecordingConsentOption() {
  670. fun hide() {
  671. binding.recordingConsentView.recordingConsentSettingsCategory.visibility = GONE
  672. binding.recordingConsentView.recordingConsentForConversation.visibility = GONE
  673. binding.recordingConsentView.recordingConsentAll.visibility = GONE
  674. }
  675. fun showAlwaysRequiredInfo() {
  676. binding.recordingConsentView.recordingConsentForConversation.visibility = GONE
  677. binding.recordingConsentView.recordingConsentAll.visibility = VISIBLE
  678. }
  679. fun showSwitch() {
  680. binding.recordingConsentView.recordingConsentForConversation.visibility = VISIBLE
  681. binding.recordingConsentView.recordingConsentAll.visibility = GONE
  682. if (conversation!!.hasCall) {
  683. binding.recordingConsentView.recordingConsentForConversation.isEnabled = false
  684. binding.recordingConsentView.recordingConsentForConversation.alpha = LOW_EMPHASIS_OPACITY
  685. } else {
  686. binding.recordingConsentView.recordingConsentForConversationSwitch.isChecked =
  687. conversation!!.recordingConsentRequired == RECORDING_CONSENT_REQUIRED_FOR_CONVERSATION
  688. binding.recordingConsentView.recordingConsentForConversation.setOnClickListener {
  689. binding.recordingConsentView.recordingConsentForConversationSwitch.isChecked =
  690. !binding.recordingConsentView.recordingConsentForConversationSwitch.isChecked
  691. submitRecordingConsentChanges()
  692. }
  693. }
  694. }
  695. if (ConversationUtils.isParticipantOwnerOrModerator(conversation!!) &&
  696. !ConversationUtils.isNoteToSelfConversation(conversation!!)
  697. ) {
  698. when (CapabilitiesUtil.getRecordingConsentType(spreedCapabilities)) {
  699. CapabilitiesUtil.RECORDING_CONSENT_NOT_REQUIRED -> hide()
  700. CapabilitiesUtil.RECORDING_CONSENT_REQUIRED -> showAlwaysRequiredInfo()
  701. CapabilitiesUtil.RECORDING_CONSENT_DEPEND_ON_CONVERSATION -> showSwitch()
  702. }
  703. } else {
  704. hide()
  705. }
  706. }
  707. private fun submitRecordingConsentChanges() {
  708. val state = if (binding.recordingConsentView.recordingConsentForConversationSwitch.isChecked) {
  709. RECORDING_CONSENT_REQUIRED_FOR_CONVERSATION
  710. } else {
  711. RECORDING_CONSENT_NOT_REQUIRED_FOR_CONVERSATION
  712. }
  713. val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
  714. ncApi.setRecordingConsent(
  715. ApiUtils.getCredentials(conversationUser.username, conversationUser.token),
  716. ApiUtils.getUrlForRecordingConsent(apiVersion, conversationUser.baseUrl!!, conversation!!.token),
  717. state
  718. )
  719. ?.subscribeOn(Schedulers.io())
  720. ?.observeOn(AndroidSchedulers.mainThread())
  721. ?.subscribe(object : Observer<GenericOverall> {
  722. override fun onComplete() {
  723. // unused atm
  724. }
  725. override fun onSubscribe(d: Disposable) {
  726. // unused atm
  727. }
  728. override fun onNext(t: GenericOverall) {
  729. // unused atm
  730. }
  731. override fun onError(e: Throwable) {
  732. Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
  733. Log.e(TAG, "Error when setting recording consent option for conversation", e)
  734. }
  735. })
  736. }
  737. private fun initExpiringMessageOption() {
  738. if (ConversationUtils.isParticipantOwnerOrModerator(conversation!!) &&
  739. CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MESSAGE_EXPIRATION)
  740. ) {
  741. databaseStorageModule?.setMessageExpiration(conversation!!.messageExpiration)
  742. val value = databaseStorageModule!!.getString("conversation_settings_dropdown", "")
  743. val pos = resources.getStringArray(R.array.message_expiring_values).indexOf(value)
  744. val text = resources.getStringArray(R.array.message_expiring_descriptions)[pos]
  745. binding.conversationSettingsDropdown.setText(text)
  746. binding.conversationSettingsDropdown
  747. .setSimpleItems(resources.getStringArray(R.array.message_expiring_descriptions))
  748. binding.conversationSettingsDropdown.setOnItemClickListener { _, _, position, _ ->
  749. val v: String = resources.getStringArray(R.array.message_expiring_values)[position]
  750. databaseStorageModule!!.saveString("conversation_settings_dropdown", v)
  751. }
  752. binding.conversationSettingsDropdown.visibility = VISIBLE
  753. binding.conversationInfoExpireMessagesExplanation.visibility = VISIBLE
  754. } else {
  755. binding.conversationSettings.visibility = GONE
  756. }
  757. }
  758. private fun adjustNotificationLevelUI() {
  759. if (conversation != null) {
  760. if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.NOTIFICATION_LEVELS)) {
  761. binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.isEnabled = true
  762. binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.alpha = 1.0f
  763. if (conversation!!.notificationLevel != NotificationLevel.DEFAULT) {
  764. val stringValue: String =
  765. when (
  766. DomainEnumNotificationLevelConverter()
  767. .convertToInt(conversation!!.notificationLevel!!)
  768. ) {
  769. NOTIFICATION_LEVEL_ALWAYS -> resources.getString(R.string.nc_notify_me_always)
  770. NOTIFICATION_LEVEL_MENTION -> resources.getString(R.string.nc_notify_me_mention)
  771. NOTIFICATION_LEVEL_NEVER -> resources.getString(R.string.nc_notify_me_never)
  772. else -> resources.getString(R.string.nc_notify_me_mention)
  773. }
  774. binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.setText(
  775. stringValue
  776. )
  777. } else {
  778. setProperNotificationValue(conversation)
  779. }
  780. } else {
  781. binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.isEnabled = false
  782. binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.alpha =
  783. LOW_EMPHASIS_OPACITY
  784. setProperNotificationValue(conversation)
  785. }
  786. binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown
  787. .setSimpleItems(resources.getStringArray(R.array.message_notification_levels))
  788. }
  789. }
  790. private fun setProperNotificationValue(conversation: ConversationModel?) {
  791. if (conversation!!.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
  792. if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MENTION_FLAG)) {
  793. binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.setText(
  794. resources.getString(R.string.nc_notify_me_always)
  795. )
  796. } else {
  797. binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.setText(
  798. resources.getString(R.string.nc_notify_me_mention)
  799. )
  800. }
  801. } else {
  802. binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.setText(
  803. resources.getString(R.string.nc_notify_me_mention)
  804. )
  805. }
  806. }
  807. private fun loadConversationAvatar() {
  808. when (conversation!!.type) {
  809. ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(conversation!!.name)) {
  810. conversation!!.name?.let {
  811. binding.avatarImage.loadUserAvatar(
  812. conversationUser,
  813. it,
  814. true,
  815. false
  816. )
  817. }
  818. }
  819. ConversationType.ROOM_GROUP_CALL, ConversationType.ROOM_PUBLIC_CALL -> {
  820. binding.avatarImage.loadConversationAvatar(
  821. conversationUser,
  822. conversation!!,
  823. false,
  824. viewThemeUtils
  825. )
  826. }
  827. ConversationType.ROOM_SYSTEM -> {
  828. binding.avatarImage.loadSystemAvatar()
  829. }
  830. ConversationType.DUMMY -> {
  831. if (ConversationUtils.isNoteToSelfConversation(conversation!!)) {
  832. binding.avatarImage.loadNoteToSelfAvatar()
  833. }
  834. }
  835. else -> {
  836. // unused atm
  837. }
  838. }
  839. }
  840. private fun toggleModeratorStatus(apiVersion: Int, participant: Participant) {
  841. val subscriber = object : Observer<GenericOverall> {
  842. override fun onSubscribe(d: Disposable) {
  843. // unused atm
  844. }
  845. override fun onNext(genericOverall: GenericOverall) {
  846. getListOfParticipants()
  847. }
  848. @SuppressLint("LongLogTag")
  849. override fun onError(e: Throwable) {
  850. Log.e(TAG, "Error toggling moderator status", e)
  851. }
  852. override fun onComplete() {
  853. // unused atm
  854. }
  855. }
  856. if (participant.type == Participant.ParticipantType.MODERATOR ||
  857. participant.type == Participant.ParticipantType.GUEST_MODERATOR
  858. ) {
  859. ncApi.demoteAttendeeFromModerator(
  860. credentials,
  861. ApiUtils.getUrlForRoomModerators(
  862. apiVersion,
  863. conversationUser.baseUrl!!,
  864. conversation!!.token
  865. ),
  866. participant.attendeeId
  867. )
  868. ?.subscribeOn(Schedulers.io())
  869. ?.observeOn(AndroidSchedulers.mainThread())
  870. ?.subscribe(subscriber)
  871. } else if (participant.type == Participant.ParticipantType.USER ||
  872. participant.type == Participant.ParticipantType.GUEST
  873. ) {
  874. ncApi.promoteAttendeeToModerator(
  875. credentials,
  876. ApiUtils.getUrlForRoomModerators(
  877. apiVersion,
  878. conversationUser.baseUrl!!,
  879. conversation!!.token
  880. ),
  881. participant.attendeeId
  882. )
  883. ?.subscribeOn(Schedulers.io())
  884. ?.observeOn(AndroidSchedulers.mainThread())
  885. ?.subscribe(subscriber)
  886. }
  887. }
  888. private fun toggleModeratorStatusLegacy(apiVersion: Int, participant: Participant) {
  889. val subscriber = object : Observer<GenericOverall> {
  890. override fun onSubscribe(d: Disposable) {
  891. // unused atm
  892. }
  893. override fun onNext(genericOverall: GenericOverall) {
  894. getListOfParticipants()
  895. }
  896. @SuppressLint("LongLogTag")
  897. override fun onError(e: Throwable) {
  898. Log.e(TAG, "Error toggling moderator status", e)
  899. }
  900. override fun onComplete() {
  901. // unused atm
  902. }
  903. }
  904. if (participant.type == Participant.ParticipantType.MODERATOR) {
  905. ncApi.demoteModeratorToUser(
  906. credentials,
  907. ApiUtils.getUrlForRoomModerators(
  908. apiVersion,
  909. conversationUser.baseUrl!!,
  910. conversation!!.token
  911. ),
  912. participant.userId
  913. )
  914. ?.subscribeOn(Schedulers.io())
  915. ?.observeOn(AndroidSchedulers.mainThread())
  916. ?.subscribe(subscriber)
  917. } else if (participant.type == Participant.ParticipantType.USER) {
  918. ncApi.promoteUserToModerator(
  919. credentials,
  920. ApiUtils.getUrlForRoomModerators(
  921. apiVersion,
  922. conversationUser.baseUrl!!,
  923. conversation!!.token
  924. ),
  925. participant.userId
  926. )
  927. ?.subscribeOn(Schedulers.io())
  928. ?.observeOn(AndroidSchedulers.mainThread())
  929. ?.subscribe(subscriber)
  930. }
  931. }
  932. private fun removeAttendeeFromConversation(apiVersion: Int, participant: Participant) {
  933. if (apiVersion >= ApiUtils.API_V4) {
  934. ncApi.removeAttendeeFromConversation(
  935. credentials,
  936. ApiUtils.getUrlForAttendees(
  937. apiVersion,
  938. conversationUser.baseUrl!!,
  939. conversation!!.token
  940. ),
  941. participant.attendeeId
  942. )
  943. ?.subscribeOn(Schedulers.io())
  944. ?.observeOn(AndroidSchedulers.mainThread())
  945. ?.subscribe(object : Observer<GenericOverall> {
  946. override fun onSubscribe(d: Disposable) {
  947. // unused atm
  948. }
  949. override fun onNext(genericOverall: GenericOverall) {
  950. getListOfParticipants()
  951. }
  952. @SuppressLint("LongLogTag")
  953. override fun onError(e: Throwable) {
  954. Log.e(TAG, "Error removing attendee from conversation", e)
  955. }
  956. override fun onComplete() {
  957. // unused atm
  958. }
  959. })
  960. } else {
  961. if (participant.type == Participant.ParticipantType.GUEST ||
  962. participant.type == Participant.ParticipantType.USER_FOLLOWING_LINK
  963. ) {
  964. ncApi.removeParticipantFromConversation(
  965. credentials,
  966. ApiUtils.getUrlForRemovingParticipantFromConversation(
  967. conversationUser.baseUrl!!,
  968. conversation!!.token,
  969. true
  970. ),
  971. participant.sessionId
  972. )
  973. ?.subscribeOn(Schedulers.io())
  974. ?.observeOn(AndroidSchedulers.mainThread())
  975. ?.subscribe(object : Observer<GenericOverall> {
  976. override fun onSubscribe(d: Disposable) {
  977. // unused atm
  978. }
  979. override fun onNext(genericOverall: GenericOverall) {
  980. getListOfParticipants()
  981. }
  982. @SuppressLint("LongLogTag")
  983. override fun onError(e: Throwable) {
  984. Log.e(TAG, "Error removing guest from conversation", e)
  985. }
  986. override fun onComplete() {
  987. // unused atm
  988. }
  989. })
  990. } else {
  991. ncApi.removeParticipantFromConversation(
  992. credentials,
  993. ApiUtils.getUrlForRemovingParticipantFromConversation(
  994. conversationUser.baseUrl!!,
  995. conversation!!.token,
  996. false
  997. ),
  998. participant.userId
  999. )
  1000. ?.subscribeOn(Schedulers.io())
  1001. ?.observeOn(AndroidSchedulers.mainThread())
  1002. ?.subscribe(object : Observer<GenericOverall> {
  1003. override fun onSubscribe(d: Disposable) {
  1004. // unused atm
  1005. }
  1006. override fun onNext(genericOverall: GenericOverall) {
  1007. getListOfParticipants()
  1008. }
  1009. @SuppressLint("LongLogTag")
  1010. override fun onError(e: Throwable) {
  1011. Log.e(TAG, "Error removing user from conversation", e)
  1012. }
  1013. override fun onComplete() {
  1014. // unused atm
  1015. }
  1016. })
  1017. }
  1018. }
  1019. }
  1020. @SuppressLint("CheckResult")
  1021. override fun onItemClick(view: View?, position: Int): Boolean {
  1022. if (ConversationUtils.canModerate(conversation!!, spreedCapabilities)) {
  1023. return true
  1024. }
  1025. val userItem = adapter?.getItem(position) as ParticipantItem
  1026. val participant = userItem.model
  1027. val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
  1028. if (participant.calculatedActorType == USERS && participant.calculatedActorId == conversationUser.userId) {
  1029. if (participant.attendeePin?.isNotEmpty() == true) {
  1030. val items = mutableListOf(
  1031. BasicListItemWithImage(
  1032. R.drawable.ic_lock_grey600_24px,
  1033. context.getString(R.string.nc_attendee_pin, participant.attendeePin)
  1034. )
  1035. )
  1036. MaterialDialog(this, BottomSheet(WRAP_CONTENT)).show {
  1037. cornerRadius(res = R.dimen.corner_radius)
  1038. title(text = participant.displayName)
  1039. listItemsWithImage(items = items) { _, index, _ ->
  1040. if (index == 0) {
  1041. removeAttendeeFromConversation(apiVersion, participant)
  1042. }
  1043. }
  1044. }
  1045. }
  1046. return true
  1047. }
  1048. if (participant.type == Participant.ParticipantType.OWNER) {
  1049. // Can not moderate owner
  1050. return true
  1051. }
  1052. if (participant.calculatedActorType == GROUPS) {
  1053. val items = mutableListOf(
  1054. BasicListItemWithImage(
  1055. R.drawable.ic_delete_grey600_24dp,
  1056. context.getString(R.string.nc_remove_group_and_members)
  1057. )
  1058. )
  1059. MaterialDialog(this, BottomSheet(WRAP_CONTENT)).show {
  1060. cornerRadius(res = R.dimen.corner_radius)
  1061. title(text = participant.displayName)
  1062. listItemsWithImage(items = items) { _, index, _ ->
  1063. if (index == 0) {
  1064. removeAttendeeFromConversation(apiVersion, participant)
  1065. }
  1066. }
  1067. }
  1068. return true
  1069. }
  1070. if (participant.calculatedActorType == CIRCLES) {
  1071. val items = mutableListOf(
  1072. BasicListItemWithImage(
  1073. R.drawable.ic_delete_grey600_24dp,
  1074. context.getString(R.string.nc_remove_circle_and_members)
  1075. )
  1076. )
  1077. MaterialDialog(this, BottomSheet(WRAP_CONTENT)).show {
  1078. cornerRadius(res = R.dimen.corner_radius)
  1079. title(text = participant.displayName)
  1080. listItemsWithImage(items = items) { _, index, _ ->
  1081. if (index == 0) {
  1082. removeAttendeeFromConversation(apiVersion, participant)
  1083. }
  1084. }
  1085. }
  1086. return true
  1087. }
  1088. val items = mutableListOf(
  1089. BasicListItemWithImage(
  1090. R.drawable.ic_lock_grey600_24px,
  1091. context.getString(R.string.nc_attendee_pin, participant.attendeePin)
  1092. ),
  1093. BasicListItemWithImage(
  1094. R.drawable.ic_pencil_grey600_24dp,
  1095. context.getString(R.string.nc_promote)
  1096. ),
  1097. BasicListItemWithImage(
  1098. R.drawable.ic_pencil_grey600_24dp,
  1099. context.getString(R.string.nc_demote)
  1100. ),
  1101. BasicListItemWithImage(
  1102. R.drawable.ic_delete_grey600_24dp,
  1103. context.getString(R.string.nc_remove_participant)
  1104. )
  1105. )
  1106. if (participant.type == Participant.ParticipantType.MODERATOR ||
  1107. participant.type == Participant.ParticipantType.GUEST_MODERATOR
  1108. ) {
  1109. items.removeAt(1)
  1110. } else if (participant.type == Participant.ParticipantType.USER ||
  1111. participant.type == Participant.ParticipantType.GUEST
  1112. ) {
  1113. items.removeAt(2)
  1114. } else {
  1115. // Self joined users can not be promoted nor demoted
  1116. items.removeAt(2)
  1117. items.removeAt(1)
  1118. }
  1119. if (participant.attendeePin == null || participant.attendeePin!!.isEmpty()) {
  1120. items.removeAt(0)
  1121. }
  1122. if (items.isNotEmpty()) {
  1123. MaterialDialog(this, BottomSheet(WRAP_CONTENT)).show {
  1124. cornerRadius(res = R.dimen.corner_radius)
  1125. title(text = participant.displayName)
  1126. listItemsWithImage(items = items) { _, index, _ ->
  1127. var actionToTrigger = index
  1128. if (participant.attendeePin == null || participant.attendeePin!!.isEmpty()) {
  1129. actionToTrigger++
  1130. }
  1131. if (participant.type == Participant.ParticipantType.USER_FOLLOWING_LINK) {
  1132. actionToTrigger++
  1133. }
  1134. if (actionToTrigger == 0) {
  1135. // Pin, nothing to do
  1136. } else if (actionToTrigger == 1) {
  1137. // Promote/demote
  1138. if (apiVersion >= ApiUtils.API_V4) {
  1139. toggleModeratorStatus(apiVersion, participant)
  1140. } else {
  1141. toggleModeratorStatusLegacy(apiVersion, participant)
  1142. }
  1143. } else if (actionToTrigger == 2) {
  1144. // Remove from conversation
  1145. removeAttendeeFromConversation(apiVersion, participant)
  1146. }
  1147. }
  1148. }
  1149. }
  1150. return true
  1151. }
  1152. private fun setUpNotificationSettings(module: DatabaseStorageModule) {
  1153. binding.notificationSettingsView.notificationSettingsImportantConversation.setOnClickListener {
  1154. val isChecked = binding.notificationSettingsView.importantConversationSwitch.isChecked
  1155. binding.notificationSettingsView.importantConversationSwitch.isChecked = !isChecked
  1156. module.saveBoolean("important_conversation_switch", !isChecked)
  1157. }
  1158. binding.notificationSettingsView.notificationSettingsCallNotifications.setOnClickListener {
  1159. val isChecked = binding.notificationSettingsView.callNotificationsSwitch.isChecked
  1160. binding.notificationSettingsView.callNotificationsSwitch.isChecked = !isChecked
  1161. module.saveBoolean("call_notifications_switch", !isChecked)
  1162. }
  1163. binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown
  1164. .setOnItemClickListener { _, _, position, _ ->
  1165. val value = resources.getStringArray(R.array.message_notification_levels_entry_values)[position]
  1166. Log.i(TAG, "saved $value to module from $position")
  1167. module.saveString("conversation_info_message_notifications_dropdown", value)
  1168. }
  1169. binding.notificationSettingsView.importantConversationSwitch.isChecked = module
  1170. .getBoolean("important_conversation_switch", false)
  1171. binding.notificationSettingsView.callNotificationsSwitch.isChecked = module
  1172. .getBoolean("call_notifications_switch", true)
  1173. }
  1174. companion object {
  1175. private val TAG = ConversationInfoActivity::class.java.simpleName
  1176. private const val NOTIFICATION_LEVEL_ALWAYS: Int = 1
  1177. private const val NOTIFICATION_LEVEL_MENTION: Int = 2
  1178. private const val NOTIFICATION_LEVEL_NEVER: Int = 3
  1179. private const val LOW_EMPHASIS_OPACITY: Float = 0.38f
  1180. private const val RECORDING_CONSENT_NOT_REQUIRED_FOR_CONVERSATION: Int = 0
  1181. private const val RECORDING_CONSENT_REQUIRED_FOR_CONVERSATION: Int = 1
  1182. }
  1183. /**
  1184. * Comparator for participants, sorts by online-status, moderator-status and display name.
  1185. */
  1186. class ParticipantItemComparator : Comparator<ParticipantItem> {
  1187. override fun compare(left: ParticipantItem, right: ParticipantItem): Int {
  1188. val leftIsGroup = left.model.actorType == GROUPS || left.model.actorType == CIRCLES
  1189. val rightIsGroup = right.model.actorType == GROUPS || right.model.actorType == CIRCLES
  1190. if (leftIsGroup != rightIsGroup) {
  1191. // Groups below participants
  1192. return if (rightIsGroup) {
  1193. -1
  1194. } else {
  1195. 1
  1196. }
  1197. }
  1198. if (left.isOnline && !right.isOnline) {
  1199. return -1
  1200. } else if (!left.isOnline && right.isOnline) {
  1201. return 1
  1202. }
  1203. val moderatorTypes = ArrayList<Participant.ParticipantType>()
  1204. moderatorTypes.add(Participant.ParticipantType.MODERATOR)
  1205. moderatorTypes.add(Participant.ParticipantType.OWNER)
  1206. moderatorTypes.add(Participant.ParticipantType.GUEST_MODERATOR)
  1207. if (moderatorTypes.contains(left.model.type) && !moderatorTypes.contains(right.model.type)) {
  1208. return -1
  1209. } else if (!moderatorTypes.contains(left.model.type) && moderatorTypes.contains(right.model.type)) {
  1210. return 1
  1211. }
  1212. return left.model.displayName!!.lowercase(Locale.ROOT).compareTo(
  1213. right.model.displayName!!.lowercase(Locale.ROOT)
  1214. )
  1215. }
  1216. }
  1217. }