ConversationInfoController.kt 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. /*
  2. * Nextcloud Talk application
  3. *
  4. * @author Mario Danic
  5. * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. package com.nextcloud.talk.controllers
  21. import android.content.Context
  22. import android.graphics.drawable.Drawable
  23. import android.graphics.drawable.LayerDrawable
  24. import android.os.Bundle
  25. import android.text.TextUtils
  26. import android.view.LayoutInflater
  27. import android.view.MenuItem
  28. import android.view.View
  29. import android.view.ViewGroup
  30. import android.widget.ProgressBar
  31. import android.widget.TextView
  32. import androidx.appcompat.widget.SwitchCompat
  33. import androidx.recyclerview.widget.RecyclerView
  34. import androidx.work.Data
  35. import androidx.work.OneTimeWorkRequest
  36. import androidx.work.WorkManager
  37. import autodagger.AutoInjector
  38. import butterknife.BindView
  39. import butterknife.OnClick
  40. import com.afollestad.materialdialogs.LayoutMode.WRAP_CONTENT
  41. import com.afollestad.materialdialogs.MaterialDialog
  42. import com.afollestad.materialdialogs.bottomsheets.BottomSheet
  43. import com.afollestad.materialdialogs.datetime.dateTimePicker
  44. import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
  45. import com.facebook.drawee.backends.pipeline.Fresco
  46. import com.facebook.drawee.view.SimpleDraweeView
  47. import com.nextcloud.talk.R
  48. import com.nextcloud.talk.adapters.items.UserItem
  49. import com.nextcloud.talk.api.NcApi
  50. import com.nextcloud.talk.application.NextcloudTalkApplication
  51. import com.nextcloud.talk.controllers.base.BaseController
  52. import com.nextcloud.talk.jobs.DeleteConversationWorker
  53. import com.nextcloud.talk.jobs.LeaveConversationWorker
  54. import com.nextcloud.talk.models.database.UserEntity
  55. import com.nextcloud.talk.models.json.conversations.Conversation
  56. import com.nextcloud.talk.models.json.conversations.RoomOverall
  57. import com.nextcloud.talk.models.json.converters.EnumNotificationLevelConverter
  58. import com.nextcloud.talk.models.json.generic.GenericOverall
  59. import com.nextcloud.talk.models.json.participants.Participant
  60. import com.nextcloud.talk.models.json.participants.ParticipantsOverall
  61. import com.nextcloud.talk.utils.ApiUtils
  62. import com.nextcloud.talk.utils.DateUtils
  63. import com.nextcloud.talk.utils.DisplayUtils
  64. import com.nextcloud.talk.utils.bundle.BundleKeys
  65. import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule
  66. import com.yarolegovich.lovelydialog.LovelySaveStateHandler
  67. import com.yarolegovich.lovelydialog.LovelyStandardDialog
  68. import com.yarolegovich.mp.*
  69. import eu.davidea.flexibleadapter.FlexibleAdapter
  70. import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
  71. import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
  72. import io.reactivex.Observer
  73. import io.reactivex.android.schedulers.AndroidSchedulers
  74. import io.reactivex.disposables.Disposable
  75. import io.reactivex.schedulers.Schedulers
  76. import java.util.*
  77. import javax.inject.Inject
  78. @AutoInjector(NextcloudTalkApplication::class)
  79. class ConversationInfoController(args: Bundle) : BaseController(args) {
  80. @BindView(R.id.notification_settings)
  81. lateinit var notificationsPreferenceScreen: MaterialPreferenceScreen
  82. @BindView(R.id.progressBar)
  83. lateinit var progressBar: ProgressBar
  84. @BindView(R.id.conversation_info_message_notifications)
  85. lateinit var messageNotificationLevel: MaterialChoicePreference
  86. @BindView(R.id.webinar_settings)
  87. lateinit var conversationInfoWebinar: MaterialPreferenceScreen
  88. @BindView(R.id.conversation_info_lobby)
  89. lateinit var conversationInfoLobby: MaterialSwitchPreference
  90. @BindView(R.id.conversation_info_name)
  91. lateinit var nameCategoryView: MaterialPreferenceCategory
  92. @BindView(R.id.start_time_preferences)
  93. lateinit var startTimeView: MaterialStandardPreference
  94. @BindView(R.id.avatar_image)
  95. lateinit var conversationAvatarImageView: SimpleDraweeView
  96. @BindView(R.id.display_name_text)
  97. lateinit var conversationDisplayName: TextView
  98. @BindView(R.id.participants_list_category)
  99. lateinit var participantsListCategory: MaterialPreferenceCategory
  100. @BindView(R.id.recycler_view)
  101. lateinit var recyclerView: RecyclerView
  102. @BindView(R.id.deleteConversationAction)
  103. lateinit var deleteConversationAction: MaterialStandardPreference
  104. @BindView(R.id.leaveConversationAction)
  105. lateinit var leaveConversationAction: MaterialStandardPreference
  106. @BindView(R.id.ownOptions)
  107. lateinit var ownOptionsCategory: MaterialPreferenceCategory
  108. @BindView(R.id.muteCalls)
  109. lateinit var muteCalls: MaterialSwitchPreference
  110. @set:Inject
  111. lateinit var ncApi: NcApi
  112. @set:Inject
  113. lateinit var context: Context
  114. private val conversationToken: String?
  115. private val conversationUser: UserEntity?
  116. private val 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<AbstractFlexibleItem<*>>? = null
  122. private var recyclerViewItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
  123. private var saveStateHandler: LovelySaveStateHandler? = null
  124. private val workerData: Data?
  125. get() {
  126. if (!TextUtils.isEmpty(conversationToken) && conversationUser != null) {
  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. init {
  135. setHasOptionsMenu(true)
  136. NextcloudTalkApplication.sharedApplication?.componentApplication?.inject(this)
  137. conversationUser = args.getParcelable(BundleKeys.KEY_USER_ENTITY)
  138. conversationToken = args.getString(BundleKeys.KEY_ROOM_TOKEN)
  139. credentials = ApiUtils.getCredentials(conversationUser!!.username, conversationUser.token)
  140. }
  141. override fun onOptionsItemSelected(item: MenuItem): Boolean {
  142. when (item.itemId) {
  143. android.R.id.home -> {
  144. router.popCurrentController()
  145. return true
  146. }
  147. else -> return super.onOptionsItemSelected(item)
  148. }
  149. }
  150. override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
  151. return inflater.inflate(R.layout.controller_conversation_info, container, false)
  152. }
  153. override fun onAttach(view: View) {
  154. super.onAttach(view)
  155. if (databaseStorageModule == null) {
  156. databaseStorageModule = DatabaseStorageModule(conversationUser!!, conversationToken)
  157. }
  158. notificationsPreferenceScreen.setStorageModule(databaseStorageModule)
  159. conversationInfoWebinar.setStorageModule(databaseStorageModule)
  160. }
  161. override fun onViewBound(view: View) {
  162. super.onViewBound(view)
  163. if (saveStateHandler == null) {
  164. saveStateHandler = LovelySaveStateHandler()
  165. }
  166. if (adapter == null) {
  167. fetchRoomInfo()
  168. } else {
  169. loadConversationAvatar()
  170. notificationsPreferenceScreen.visibility = View.VISIBLE
  171. nameCategoryView.visibility = View.VISIBLE
  172. participantsListCategory.visibility = View.VISIBLE
  173. progressBar.visibility = View.GONE
  174. conversationDisplayName.text = conversation!!.displayName
  175. setupWebinaryView()
  176. setupAdapter()
  177. }
  178. }
  179. private fun setupWebinaryView() {
  180. if (conversationUser!!.hasSpreedFeatureCapability("webinary-lobby") && (conversation!!.type
  181. == Conversation.ConversationType.ROOM_GROUP_CALL || conversation!!.type ==
  182. Conversation.ConversationType.ROOM_PUBLIC_CALL) && conversation!!.canModerate(conversationUser)) {
  183. conversationInfoWebinar.visibility = View.VISIBLE
  184. val isLobbyOpenToModeratorsOnly = conversation!!.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY
  185. (conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat)
  186. .isChecked = isLobbyOpenToModeratorsOnly
  187. reconfigureLobbyTimerView()
  188. startTimeView.setOnClickListener {
  189. MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show {
  190. val currentTimeCalendar = Calendar.getInstance()
  191. if (conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != 0L) {
  192. currentTimeCalendar.timeInMillis = conversation!!.lobbyTimer * 1000
  193. }
  194. dateTimePicker(minDateTime = Calendar.getInstance(), requireFutureDateTime =
  195. true, currentDateTime = currentTimeCalendar, dateTimeCallback = { _,
  196. dateTime ->
  197. reconfigureLobbyTimerView(dateTime)
  198. submitLobbyChanges()
  199. })
  200. }
  201. }
  202. (conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat).setOnCheckedChangeListener { _, _ ->
  203. reconfigureLobbyTimerView()
  204. submitLobbyChanges()
  205. }
  206. } else {
  207. conversationInfoWebinar.visibility = View.GONE
  208. }
  209. }
  210. fun reconfigureLobbyTimerView(dateTime: Calendar? = null) {
  211. val isChecked = (conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat).isChecked
  212. if (dateTime != null && isChecked) {
  213. conversation!!.lobbyTimer = (dateTime.timeInMillis - (dateTime.time.seconds * 1000)) / 1000
  214. } else if (!isChecked) {
  215. conversation!!.lobbyTimer = 0
  216. }
  217. conversation!!.lobbyState = if (isChecked) Conversation.LobbyState
  218. .LOBBY_STATE_MODERATORS_ONLY else Conversation.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS
  219. if (conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != java.lang.Long.MIN_VALUE && conversation!!.lobbyTimer != 0L) {
  220. startTimeView.setSummary(DateUtils.getLocalDateStringFromTimestampForLobby(conversation!!.lobbyTimer))
  221. } else {
  222. startTimeView.setSummary(R.string.nc_manual)
  223. }
  224. if (isChecked) {
  225. startTimeView.visibility = View.VISIBLE
  226. } else {
  227. startTimeView.visibility = View.GONE
  228. }
  229. }
  230. fun submitLobbyChanges() {
  231. val state = if ((conversationInfoLobby.findViewById<View>(R.id
  232. .mp_checkable) as SwitchCompat).isChecked) 1 else 0
  233. ncApi.setLobbyForConversation(ApiUtils.getCredentials(conversationUser!!.username,
  234. conversationUser.token), ApiUtils.getUrlForLobbyForConversation
  235. (conversationUser.baseUrl, conversation!!.token), state, conversation!!.lobbyTimer)
  236. .subscribeOn(Schedulers.io())
  237. .observeOn(AndroidSchedulers.mainThread())
  238. .subscribe(object : Observer<GenericOverall> {
  239. override fun onComplete() {
  240. }
  241. override fun onSubscribe(d: Disposable) {
  242. }
  243. override fun onNext(t: GenericOverall) {
  244. }
  245. override fun onError(e: Throwable) {
  246. }
  247. })
  248. }
  249. private fun showLovelyDialog(dialogId: Int, savedInstanceState: Bundle) {
  250. when (dialogId) {
  251. ID_DELETE_CONVERSATION_DIALOG -> showDeleteConversationDialog(savedInstanceState)
  252. else -> {
  253. }
  254. }
  255. }
  256. private fun showDeleteConversationDialog(savedInstanceState: Bundle?) {
  257. if (activity != null) {
  258. LovelyStandardDialog(activity, LovelyStandardDialog.ButtonLayout.HORIZONTAL)
  259. .setTopColorRes(R.color.nc_darkRed)
  260. .setIcon(DisplayUtils.getTintedDrawable(context!!.resources,
  261. R.drawable.ic_delete_black_24dp, R.color.bg_default))
  262. .setPositiveButtonColor(context!!.resources.getColor(R.color.nc_darkRed))
  263. .setTitle(R.string.nc_delete_call)
  264. .setMessage(conversation!!.deleteWarningMessage)
  265. .setPositiveButton(R.string.nc_delete) { deleteConversation() }
  266. .setNegativeButton(R.string.nc_cancel, null)
  267. .setInstanceStateHandler(ID_DELETE_CONVERSATION_DIALOG, saveStateHandler!!)
  268. .setSavedInstanceState(savedInstanceState)
  269. .show()
  270. }
  271. }
  272. override fun onSaveViewState(view: View, outState: Bundle) {
  273. saveStateHandler!!.saveInstanceState(outState)
  274. super.onSaveViewState(view, outState)
  275. }
  276. override fun onRestoreViewState(view: View, savedViewState: Bundle) {
  277. super.onRestoreViewState(view, savedViewState)
  278. if (LovelySaveStateHandler.wasDialogOnScreen(savedViewState)) {
  279. //Dialog won't be restarted automatically, so we need to call this method.
  280. //Each dialog knows how to restore its state
  281. showLovelyDialog(LovelySaveStateHandler.getSavedDialogId(savedViewState), savedViewState)
  282. }
  283. }
  284. private fun setupAdapter() {
  285. if (activity != null) {
  286. if (adapter == null) {
  287. adapter = FlexibleAdapter(recyclerViewItems, activity, true)
  288. }
  289. val layoutManager = SmoothScrollLinearLayoutManager(activity)
  290. recyclerView.layoutManager = layoutManager
  291. recyclerView.setHasFixedSize(true)
  292. recyclerView.adapter = adapter
  293. }
  294. }
  295. private fun handleParticipants(participants: List<Participant>) {
  296. var userItem: UserItem
  297. var participant: Participant
  298. recyclerViewItems = ArrayList()
  299. var ownUserItem: UserItem? = null
  300. for (i in participants.indices) {
  301. participant = participants[i]
  302. userItem = UserItem(participant, conversationUser, null)
  303. userItem.isEnabled = participant.sessionId != "0"
  304. if (!TextUtils.isEmpty(participant.userId) && participant.userId == conversationUser!!.userId) {
  305. ownUserItem = userItem
  306. userItem.model.sessionId = "-1"
  307. userItem.isEnabled = true
  308. } else {
  309. recyclerViewItems.add(userItem)
  310. }
  311. }
  312. if (ownUserItem != null) {
  313. recyclerViewItems.add(0, ownUserItem)
  314. }
  315. setupAdapter()
  316. participantsListCategory.visibility = View.VISIBLE
  317. adapter!!.notifyDataSetChanged()
  318. }
  319. override fun getTitle(): String? {
  320. return resources!!.getString(R.string.nc_conversation_menu_conversation_info)
  321. }
  322. private fun getListOfParticipants() {
  323. ncApi.getPeersForCall(credentials, ApiUtils.getUrlForParticipants(conversationUser!!.baseUrl, conversationToken))
  324. .subscribeOn(Schedulers.io())
  325. .observeOn(AndroidSchedulers.mainThread())
  326. .subscribe(object : Observer<ParticipantsOverall> {
  327. override fun onSubscribe(d: Disposable) {
  328. participantsDisposable = d
  329. }
  330. override fun onNext(participantsOverall: ParticipantsOverall) {
  331. handleParticipants(participantsOverall.ocs.data)
  332. }
  333. override fun onError(e: Throwable) {
  334. }
  335. override fun onComplete() {
  336. participantsDisposable!!.dispose()
  337. }
  338. })
  339. }
  340. @OnClick(R.id.leaveConversationAction)
  341. internal fun leaveConversation() {
  342. workerData?.let {
  343. WorkManager.getInstance().enqueue(OneTimeWorkRequest.Builder
  344. (LeaveConversationWorker::class
  345. .java).setInputData(it).build()
  346. )
  347. popTwoLastControllers()
  348. }
  349. }
  350. private fun deleteConversation() {
  351. workerData?.let {
  352. WorkManager.getInstance().enqueue(OneTimeWorkRequest.Builder
  353. (DeleteConversationWorker::class.java).setInputData(it).build())
  354. popTwoLastControllers()
  355. }
  356. }
  357. @OnClick(R.id.deleteConversationAction)
  358. internal fun deleteConversationClick() {
  359. showDeleteConversationDialog(null)
  360. }
  361. private fun popTwoLastControllers() {
  362. var backstack = router.backstack
  363. backstack = backstack.subList(0, backstack.size - 2)
  364. router.setBackstack(backstack, HorizontalChangeHandler())
  365. }
  366. private fun fetchRoomInfo() {
  367. ncApi.getRoom(credentials, ApiUtils.getRoom(conversationUser!!.baseUrl, conversationToken))
  368. .subscribeOn(Schedulers.io())
  369. .observeOn(AndroidSchedulers.mainThread())
  370. .subscribe(object : Observer<RoomOverall> {
  371. override fun onSubscribe(d: Disposable) {
  372. roomDisposable = d
  373. }
  374. override fun onNext(roomOverall: RoomOverall) {
  375. conversation = roomOverall.ocs.data
  376. if (isAttached && (!isBeingDestroyed || !isDestroyed)) {
  377. ownOptionsCategory.visibility = View.VISIBLE
  378. setupWebinaryView()
  379. if (!conversation!!.canLeave(conversationUser)) {
  380. leaveConversationAction.visibility = View.GONE
  381. } else {
  382. leaveConversationAction.visibility = View.VISIBLE
  383. }
  384. if (!conversation!!.canModerate(conversationUser)) {
  385. deleteConversationAction.visibility = View.GONE
  386. } else {
  387. deleteConversationAction.visibility = View.VISIBLE
  388. }
  389. if (Conversation.ConversationType.ROOM_SYSTEM == conversation!!.type) {
  390. muteCalls.visibility = View.GONE
  391. }
  392. getListOfParticipants()
  393. progressBar.visibility = View.GONE
  394. nameCategoryView.visibility = View.VISIBLE
  395. conversationDisplayName.text = conversation!!.displayName
  396. loadConversationAvatar()
  397. adjustNotificationLevelUI()
  398. notificationsPreferenceScreen.visibility = View.VISIBLE
  399. }
  400. }
  401. override fun onError(e: Throwable) {
  402. }
  403. override fun onComplete() {
  404. roomDisposable!!.dispose()
  405. }
  406. })
  407. }
  408. private fun adjustNotificationLevelUI() {
  409. if (conversation != null) {
  410. if (conversationUser != null && conversationUser.hasSpreedFeatureCapability("notification-levels")) {
  411. messageNotificationLevel.isEnabled = true
  412. messageNotificationLevel.alpha = 1.0f
  413. if (conversation!!.notificationLevel != Conversation.NotificationLevel.DEFAULT) {
  414. val stringValue: String = when (EnumNotificationLevelConverter().convertToInt(conversation!!.notificationLevel)) {
  415. 1 -> "always"
  416. 2 -> "mention"
  417. 3 -> "never"
  418. else -> "mention"
  419. }
  420. messageNotificationLevel.value = stringValue
  421. } else {
  422. setProperNotificationValue(conversation)
  423. }
  424. } else {
  425. messageNotificationLevel.isEnabled = false
  426. messageNotificationLevel.alpha = 0.38f
  427. setProperNotificationValue(conversation)
  428. }
  429. }
  430. }
  431. private fun setProperNotificationValue(conversation: Conversation?) {
  432. if (conversation!!.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
  433. // hack to see if we get mentioned always or just on mention
  434. if (conversationUser!!.hasSpreedFeatureCapability("mention-flag")) {
  435. messageNotificationLevel.value = "always"
  436. } else {
  437. messageNotificationLevel.value = "mention"
  438. }
  439. } else {
  440. messageNotificationLevel.value = "mention"
  441. }
  442. }
  443. private fun loadConversationAvatar() {
  444. when (conversation!!.type) {
  445. Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty
  446. (conversation!!.name)) {
  447. val draweeController = Fresco.newDraweeControllerBuilder()
  448. .setOldController(conversationAvatarImageView.controller)
  449. .setAutoPlayAnimations(true)
  450. .setImageRequest(DisplayUtils.getImageRequestForUrl(ApiUtils.getUrlForAvatarWithName(conversationUser!!.baseUrl,
  451. conversation!!.name, R.dimen.avatar_size_big), null))
  452. .build()
  453. conversationAvatarImageView.controller = draweeController
  454. }
  455. Conversation.ConversationType.ROOM_GROUP_CALL -> conversationAvatarImageView.hierarchy.setPlaceholderImage(DisplayUtils
  456. .getRoundedBitmapDrawableFromVectorDrawableResource(resources,
  457. R.drawable.ic_people_group_white_24px))
  458. Conversation.ConversationType.ROOM_PUBLIC_CALL -> conversationAvatarImageView.hierarchy.setPlaceholderImage(DisplayUtils
  459. .getRoundedBitmapDrawableFromVectorDrawableResource(resources,
  460. R.drawable.ic_link_white_24px))
  461. Conversation.ConversationType.ROOM_SYSTEM -> {
  462. val layers = arrayOfNulls<Drawable>(2)
  463. layers[0] = context.getDrawable(R.drawable.ic_launcher_background)
  464. layers[1] = context.getDrawable(R.drawable.ic_launcher_foreground)
  465. val layerDrawable = LayerDrawable(layers)
  466. conversationAvatarImageView.hierarchy.setPlaceholderImage(DisplayUtils.getRoundedDrawable(layerDrawable))
  467. }
  468. else -> {
  469. }
  470. }
  471. }
  472. companion object {
  473. private const val ID_DELETE_CONVERSATION_DIALOG = 0
  474. }
  475. }