ConversationInfoActivity.kt 50 KB

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