ConversationInfoController.kt 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812
  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 androidx.appcompat.widget.SwitchCompat
  32. import androidx.emoji.widget.EmojiTextView
  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.RouterTransaction
  45. import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
  46. import com.facebook.drawee.backends.pipeline.Fresco
  47. import com.facebook.drawee.view.SimpleDraweeView
  48. import com.nextcloud.talk.R
  49. import com.nextcloud.talk.adapters.items.UserItem
  50. import com.nextcloud.talk.api.NcApi
  51. import com.nextcloud.talk.application.NextcloudTalkApplication
  52. import com.nextcloud.talk.controllers.base.BaseController
  53. import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage
  54. import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage
  55. import com.nextcloud.talk.events.EventStatus
  56. import com.nextcloud.talk.jobs.DeleteConversationWorker
  57. import com.nextcloud.talk.jobs.LeaveConversationWorker
  58. import com.nextcloud.talk.models.database.UserEntity
  59. import com.nextcloud.talk.models.json.conversations.Conversation
  60. import com.nextcloud.talk.models.json.conversations.RoomOverall
  61. import com.nextcloud.talk.models.json.converters.EnumNotificationLevelConverter
  62. import com.nextcloud.talk.models.json.generic.GenericOverall
  63. import com.nextcloud.talk.models.json.participants.Participant
  64. import com.nextcloud.talk.models.json.participants.ParticipantsOverall
  65. import com.nextcloud.talk.utils.ApiUtils
  66. import com.nextcloud.talk.utils.DateUtils
  67. import com.nextcloud.talk.utils.DisplayUtils
  68. import com.nextcloud.talk.utils.bundle.BundleKeys
  69. import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule
  70. import com.yarolegovich.lovelydialog.LovelySaveStateHandler
  71. import com.yarolegovich.lovelydialog.LovelyStandardDialog
  72. import com.yarolegovich.mp.MaterialChoicePreference
  73. import com.yarolegovich.mp.MaterialPreferenceCategory
  74. import com.yarolegovich.mp.MaterialPreferenceScreen
  75. import com.yarolegovich.mp.MaterialStandardPreference
  76. import com.yarolegovich.mp.MaterialSwitchPreference
  77. import eu.davidea.flexibleadapter.FlexibleAdapter
  78. import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
  79. import io.reactivex.Observer
  80. import io.reactivex.android.schedulers.AndroidSchedulers
  81. import io.reactivex.disposables.Disposable
  82. import io.reactivex.schedulers.Schedulers
  83. import org.greenrobot.eventbus.EventBus
  84. import org.greenrobot.eventbus.Subscribe
  85. import org.greenrobot.eventbus.ThreadMode
  86. import java.util.Calendar
  87. import java.util.Collections
  88. import java.util.Comparator
  89. import java.util.Locale
  90. import javax.inject.Inject
  91. @AutoInjector(NextcloudTalkApplication::class)
  92. class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleAdapter.OnItemClickListener {
  93. @BindView(R.id.notification_settings)
  94. lateinit var notificationsPreferenceScreen: MaterialPreferenceScreen
  95. @BindView(R.id.progressBar)
  96. lateinit var progressBar: ProgressBar
  97. @BindView(R.id.conversation_info_message_notifications)
  98. lateinit var messageNotificationLevel: MaterialChoicePreference
  99. @BindView(R.id.webinar_settings)
  100. lateinit var conversationInfoWebinar: MaterialPreferenceScreen
  101. @BindView(R.id.conversation_info_lobby)
  102. lateinit var conversationInfoLobby: MaterialSwitchPreference
  103. @BindView(R.id.conversation_info_name)
  104. lateinit var nameCategoryView: MaterialPreferenceCategory
  105. @BindView(R.id.start_time_preferences)
  106. lateinit var startTimeView: MaterialStandardPreference
  107. @BindView(R.id.avatar_image)
  108. lateinit var conversationAvatarImageView: SimpleDraweeView
  109. @BindView(R.id.display_name_text)
  110. lateinit var conversationDisplayName: EmojiTextView
  111. @BindView(R.id.participants_list_category)
  112. lateinit var participantsListCategory: MaterialPreferenceCategory
  113. @BindView(R.id.addParticipantsAction)
  114. lateinit var addParticipantsAction: MaterialStandardPreference
  115. @BindView(R.id.recycler_view)
  116. lateinit var recyclerView: RecyclerView
  117. @BindView(R.id.deleteConversationAction)
  118. lateinit var deleteConversationAction: MaterialStandardPreference
  119. @BindView(R.id.leaveConversationAction)
  120. lateinit var leaveConversationAction: MaterialStandardPreference
  121. @BindView(R.id.ownOptions)
  122. lateinit var ownOptionsCategory: MaterialPreferenceCategory
  123. @BindView(R.id.muteCalls)
  124. lateinit var muteCalls: MaterialSwitchPreference
  125. @set:Inject
  126. lateinit var ncApi: NcApi
  127. @set:Inject
  128. lateinit var context: Context
  129. @set:Inject
  130. lateinit var eventBus: EventBus
  131. private val conversationToken: String?
  132. private val conversationUser: UserEntity?
  133. private val hasAvatarSpacing: Boolean
  134. private val credentials: String?
  135. private var roomDisposable: Disposable? = null
  136. private var participantsDisposable: Disposable? = null
  137. private var databaseStorageModule: DatabaseStorageModule? = null
  138. private var conversation: Conversation? = null
  139. private var adapter: FlexibleAdapter<UserItem>? = null
  140. private var recyclerViewItems: MutableList<UserItem> = ArrayList()
  141. private var saveStateHandler: LovelySaveStateHandler? = null
  142. private val workerData: Data?
  143. get() {
  144. if (!TextUtils.isEmpty(conversationToken) && conversationUser != null) {
  145. val data = Data.Builder()
  146. data.putString(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
  147. data.putLong(BundleKeys.KEY_INTERNAL_USER_ID, conversationUser.id)
  148. return data.build()
  149. }
  150. return null
  151. }
  152. init {
  153. setHasOptionsMenu(true)
  154. NextcloudTalkApplication.sharedApplication?.componentApplication?.inject(this)
  155. conversationUser = args.getParcelable(BundleKeys.KEY_USER_ENTITY)
  156. conversationToken = args.getString(BundleKeys.KEY_ROOM_TOKEN)
  157. hasAvatarSpacing = args.getBoolean(BundleKeys.KEY_ROOM_ONE_TO_ONE, false)
  158. credentials = ApiUtils.getCredentials(conversationUser!!.username, conversationUser.token)
  159. }
  160. override fun onOptionsItemSelected(item: MenuItem): Boolean {
  161. when (item.itemId) {
  162. android.R.id.home -> {
  163. router.popCurrentController()
  164. return true
  165. }
  166. else -> return super.onOptionsItemSelected(item)
  167. }
  168. }
  169. override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
  170. return inflater.inflate(R.layout.controller_conversation_info, container, false)
  171. }
  172. override fun onAttach(view: View) {
  173. super.onAttach(view)
  174. eventBus.register(this)
  175. if (databaseStorageModule == null) {
  176. databaseStorageModule = DatabaseStorageModule(conversationUser!!, conversationToken)
  177. }
  178. notificationsPreferenceScreen.setStorageModule(databaseStorageModule)
  179. conversationInfoWebinar.setStorageModule(databaseStorageModule)
  180. fetchRoomInfo()
  181. }
  182. override fun onViewBound(view: View) {
  183. super.onViewBound(view)
  184. if (saveStateHandler == null) {
  185. saveStateHandler = LovelySaveStateHandler()
  186. }
  187. addParticipantsAction.visibility = View.GONE
  188. }
  189. private fun setupWebinaryView() {
  190. if (conversationUser!!.hasSpreedFeatureCapability("webinary-lobby") &&
  191. (
  192. conversation!!.type == Conversation.ConversationType.ROOM_GROUP_CALL ||
  193. conversation!!.type == Conversation.ConversationType.ROOM_PUBLIC_CALL
  194. ) &&
  195. conversation!!.canModerate(conversationUser)
  196. ) {
  197. conversationInfoWebinar.visibility = View.VISIBLE
  198. val isLobbyOpenToModeratorsOnly =
  199. conversation!!.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY
  200. (conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat)
  201. .isChecked = isLobbyOpenToModeratorsOnly
  202. reconfigureLobbyTimerView()
  203. startTimeView.setOnClickListener {
  204. MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show {
  205. val currentTimeCalendar = Calendar.getInstance()
  206. if (conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != 0L) {
  207. currentTimeCalendar.timeInMillis = conversation!!.lobbyTimer * 1000
  208. }
  209. dateTimePicker(
  210. minDateTime = Calendar.getInstance(),
  211. requireFutureDateTime =
  212. true,
  213. currentDateTime = currentTimeCalendar,
  214. show24HoursView = true,
  215. dateTimeCallback = { _,
  216. dateTime ->
  217. reconfigureLobbyTimerView(dateTime)
  218. submitLobbyChanges()
  219. }
  220. )
  221. }
  222. }
  223. (conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat).setOnCheckedChangeListener { _, _ ->
  224. reconfigureLobbyTimerView()
  225. submitLobbyChanges()
  226. }
  227. } else {
  228. conversationInfoWebinar.visibility = View.GONE
  229. }
  230. }
  231. fun reconfigureLobbyTimerView(dateTime: Calendar? = null) {
  232. val isChecked = (conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat).isChecked
  233. if (dateTime != null && isChecked) {
  234. conversation!!.lobbyTimer = (dateTime.timeInMillis - (dateTime.time.seconds * 1000)) / 1000
  235. } else if (!isChecked) {
  236. conversation!!.lobbyTimer = 0
  237. }
  238. conversation!!.lobbyState = if (isChecked) Conversation.LobbyState
  239. .LOBBY_STATE_MODERATORS_ONLY else Conversation.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS
  240. if (conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != java.lang.Long.MIN_VALUE && conversation!!.lobbyTimer != 0L) {
  241. startTimeView.setSummary(DateUtils.getLocalDateStringFromTimestampForLobby(conversation!!.lobbyTimer))
  242. } else {
  243. startTimeView.setSummary(R.string.nc_manual)
  244. }
  245. if (isChecked) {
  246. startTimeView.visibility = View.VISIBLE
  247. } else {
  248. startTimeView.visibility = View.GONE
  249. }
  250. }
  251. fun submitLobbyChanges() {
  252. val state = if (
  253. (
  254. conversationInfoLobby.findViewById<View>(
  255. R.id.mp_checkable
  256. ) as SwitchCompat
  257. ).isChecked
  258. ) 1 else 0
  259. ncApi.setLobbyForConversation(
  260. ApiUtils.getCredentials(conversationUser!!.username, conversationUser.token),
  261. ApiUtils.getUrlForLobbyForConversation(conversationUser.baseUrl, conversation!!.token),
  262. state,
  263. conversation!!.lobbyTimer
  264. )
  265. .subscribeOn(Schedulers.io())
  266. .observeOn(AndroidSchedulers.mainThread())
  267. .subscribe(object : Observer<GenericOverall> {
  268. override fun onComplete() {
  269. }
  270. override fun onSubscribe(d: Disposable) {
  271. }
  272. override fun onNext(t: GenericOverall) {
  273. }
  274. override fun onError(e: Throwable) {
  275. }
  276. })
  277. }
  278. private fun showLovelyDialog(dialogId: Int, savedInstanceState: Bundle) {
  279. when (dialogId) {
  280. ID_DELETE_CONVERSATION_DIALOG -> showDeleteConversationDialog(savedInstanceState)
  281. else -> {
  282. }
  283. }
  284. }
  285. @Subscribe(threadMode = ThreadMode.MAIN)
  286. fun onMessageEvent(eventStatus: EventStatus) {
  287. getListOfParticipants()
  288. }
  289. override fun onDetach(view: View) {
  290. super.onDetach(view)
  291. eventBus.unregister(this)
  292. }
  293. private fun showDeleteConversationDialog(savedInstanceState: Bundle?) {
  294. if (activity != null) {
  295. LovelyStandardDialog(activity, LovelyStandardDialog.ButtonLayout.HORIZONTAL)
  296. .setTopColorRes(R.color.nc_darkRed)
  297. .setIcon(
  298. DisplayUtils.getTintedDrawable(
  299. context!!.resources,
  300. R.drawable.ic_delete_black_24dp, R.color.bg_default
  301. )
  302. )
  303. .setPositiveButtonColor(context!!.resources.getColor(R.color.nc_darkRed))
  304. .setTitle(R.string.nc_delete_call)
  305. .setMessage(conversation!!.deleteWarningMessage)
  306. .setPositiveButton(R.string.nc_delete) { deleteConversation() }
  307. .setNegativeButton(R.string.nc_cancel, null)
  308. .setInstanceStateHandler(ID_DELETE_CONVERSATION_DIALOG, saveStateHandler!!)
  309. .setSavedInstanceState(savedInstanceState)
  310. .show()
  311. }
  312. }
  313. override fun onSaveViewState(view: View, outState: Bundle) {
  314. saveStateHandler!!.saveInstanceState(outState)
  315. super.onSaveViewState(view, outState)
  316. }
  317. override fun onRestoreViewState(view: View, savedViewState: Bundle) {
  318. super.onRestoreViewState(view, savedViewState)
  319. if (LovelySaveStateHandler.wasDialogOnScreen(savedViewState)) {
  320. // Dialog won't be restarted automatically, so we need to call this method.
  321. // Each dialog knows how to restore its state
  322. showLovelyDialog(LovelySaveStateHandler.getSavedDialogId(savedViewState), savedViewState)
  323. }
  324. }
  325. private fun setupAdapter() {
  326. if (activity != null) {
  327. if (adapter == null) {
  328. adapter = FlexibleAdapter(recyclerViewItems, activity, true)
  329. }
  330. val layoutManager = SmoothScrollLinearLayoutManager(activity)
  331. recyclerView.layoutManager = layoutManager
  332. recyclerView.setHasFixedSize(true)
  333. recyclerView.adapter = adapter
  334. adapter!!.addListener(this)
  335. }
  336. }
  337. private fun handleParticipants(participants: List<Participant>) {
  338. var userItem: UserItem
  339. var participant: Participant
  340. recyclerViewItems = ArrayList()
  341. var ownUserItem: UserItem? = null
  342. for (i in participants.indices) {
  343. participant = participants[i]
  344. userItem = UserItem(participant, conversationUser, null)
  345. userItem.isOnline = !participant.sessionId.equals("0")
  346. if (!TextUtils.isEmpty(participant.userId) && participant.userId == conversationUser!!.userId) {
  347. ownUserItem = userItem
  348. ownUserItem.model.sessionId = "-1"
  349. ownUserItem.isOnline = true
  350. } else {
  351. recyclerViewItems.add(userItem)
  352. }
  353. }
  354. Collections.sort(recyclerViewItems, UserItemComparator())
  355. if (ownUserItem != null) {
  356. recyclerViewItems.add(0, ownUserItem)
  357. }
  358. setupAdapter()
  359. participantsListCategory.visibility = View.VISIBLE
  360. adapter!!.updateDataSet(recyclerViewItems)
  361. }
  362. override fun getTitle(): String? {
  363. return if (hasAvatarSpacing) {
  364. " " + resources!!.getString(R.string.nc_conversation_menu_conversation_info)
  365. } else {
  366. resources!!.getString(R.string.nc_conversation_menu_conversation_info)
  367. }
  368. }
  369. private fun getListOfParticipants() {
  370. ncApi.getPeersForCall(
  371. credentials,
  372. ApiUtils.getUrlForParticipants(conversationUser!!.baseUrl, conversationToken)
  373. )
  374. .subscribeOn(Schedulers.io())
  375. .observeOn(AndroidSchedulers.mainThread())
  376. .subscribe(object : Observer<ParticipantsOverall> {
  377. override fun onSubscribe(d: Disposable) {
  378. participantsDisposable = d
  379. }
  380. override fun onNext(participantsOverall: ParticipantsOverall) {
  381. handleParticipants(participantsOverall.ocs.data)
  382. }
  383. override fun onError(e: Throwable) {
  384. }
  385. override fun onComplete() {
  386. participantsDisposable!!.dispose()
  387. }
  388. })
  389. }
  390. @OnClick(R.id.addParticipantsAction)
  391. internal fun addParticipants() {
  392. val bundle = Bundle()
  393. val existingParticipantsId = arrayListOf<String>()
  394. recyclerViewItems.forEach {
  395. val userItem = it as UserItem
  396. existingParticipantsId.add(userItem.model.userId)
  397. }
  398. bundle.putBoolean(BundleKeys.KEY_ADD_PARTICIPANTS, true)
  399. bundle.putStringArrayList(BundleKeys.KEY_EXISTING_PARTICIPANTS, existingParticipantsId)
  400. bundle.putString(BundleKeys.KEY_TOKEN, conversation!!.token)
  401. getRouter().pushController(
  402. (
  403. RouterTransaction.with(
  404. ContactsController(bundle)
  405. )
  406. .pushChangeHandler(
  407. HorizontalChangeHandler()
  408. )
  409. .popChangeHandler(
  410. HorizontalChangeHandler()
  411. )
  412. )
  413. )
  414. }
  415. @OnClick(R.id.leaveConversationAction)
  416. internal fun leaveConversation() {
  417. workerData?.let {
  418. WorkManager.getInstance().enqueue(
  419. OneTimeWorkRequest.Builder(
  420. LeaveConversationWorker::class
  421. .java
  422. ).setInputData(it).build()
  423. )
  424. popTwoLastControllers()
  425. }
  426. }
  427. private fun deleteConversation() {
  428. workerData?.let {
  429. WorkManager.getInstance().enqueue(
  430. OneTimeWorkRequest.Builder(
  431. DeleteConversationWorker::class.java
  432. ).setInputData(it).build()
  433. )
  434. popTwoLastControllers()
  435. }
  436. }
  437. @OnClick(R.id.deleteConversationAction)
  438. internal fun deleteConversationClick() {
  439. showDeleteConversationDialog(null)
  440. }
  441. private fun popTwoLastControllers() {
  442. var backstack = router.backstack
  443. backstack = backstack.subList(0, backstack.size - 2)
  444. router.setBackstack(backstack, HorizontalChangeHandler())
  445. }
  446. private fun fetchRoomInfo() {
  447. ncApi.getRoom(credentials, ApiUtils.getRoom(conversationUser!!.baseUrl, conversationToken))
  448. .subscribeOn(Schedulers.io())
  449. .observeOn(AndroidSchedulers.mainThread())
  450. .subscribe(object : Observer<RoomOverall> {
  451. override fun onSubscribe(d: Disposable) {
  452. roomDisposable = d
  453. }
  454. override fun onNext(roomOverall: RoomOverall) {
  455. conversation = roomOverall.ocs.data
  456. val conversationCopy = conversation
  457. if (conversationCopy!!.canModerate(conversationUser)) {
  458. addParticipantsAction.visibility = View.VISIBLE
  459. } else {
  460. addParticipantsAction.visibility = View.GONE
  461. }
  462. if (isAttached && (!isBeingDestroyed || !isDestroyed)) {
  463. ownOptionsCategory.visibility = View.VISIBLE
  464. setupWebinaryView()
  465. if (!conversation!!.canLeave(conversationUser)) {
  466. leaveConversationAction.visibility = View.GONE
  467. } else {
  468. leaveConversationAction.visibility = View.VISIBLE
  469. }
  470. if (!conversation!!.canModerate(conversationUser)) {
  471. deleteConversationAction.visibility = View.GONE
  472. } else {
  473. deleteConversationAction.visibility = View.VISIBLE
  474. }
  475. if (Conversation.ConversationType.ROOM_SYSTEM == conversation!!.type) {
  476. muteCalls.visibility = View.GONE
  477. }
  478. getListOfParticipants()
  479. progressBar.visibility = View.GONE
  480. nameCategoryView.visibility = View.VISIBLE
  481. conversationDisplayName.text = conversation!!.displayName
  482. loadConversationAvatar()
  483. adjustNotificationLevelUI()
  484. notificationsPreferenceScreen.visibility = View.VISIBLE
  485. }
  486. }
  487. override fun onError(e: Throwable) {
  488. }
  489. override fun onComplete() {
  490. roomDisposable!!.dispose()
  491. }
  492. })
  493. }
  494. private fun adjustNotificationLevelUI() {
  495. if (conversation != null) {
  496. if (conversationUser != null && conversationUser.hasSpreedFeatureCapability("notification-levels")) {
  497. messageNotificationLevel.isEnabled = true
  498. messageNotificationLevel.alpha = 1.0f
  499. if (conversation!!.notificationLevel != Conversation.NotificationLevel.DEFAULT) {
  500. val stringValue: String =
  501. when (EnumNotificationLevelConverter().convertToInt(conversation!!.notificationLevel)) {
  502. 1 -> "always"
  503. 2 -> "mention"
  504. 3 -> "never"
  505. else -> "mention"
  506. }
  507. messageNotificationLevel.value = stringValue
  508. } else {
  509. setProperNotificationValue(conversation)
  510. }
  511. } else {
  512. messageNotificationLevel.isEnabled = false
  513. messageNotificationLevel.alpha = 0.38f
  514. setProperNotificationValue(conversation)
  515. }
  516. }
  517. }
  518. private fun setProperNotificationValue(conversation: Conversation?) {
  519. if (conversation!!.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
  520. // hack to see if we get mentioned always or just on mention
  521. if (conversationUser!!.hasSpreedFeatureCapability("mention-flag")) {
  522. messageNotificationLevel.value = "always"
  523. } else {
  524. messageNotificationLevel.value = "mention"
  525. }
  526. } else {
  527. messageNotificationLevel.value = "mention"
  528. }
  529. }
  530. private fun loadConversationAvatar() {
  531. when (conversation!!.type) {
  532. Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (
  533. !TextUtils.isEmpty(conversation!!.name)
  534. ) {
  535. val draweeController = Fresco.newDraweeControllerBuilder()
  536. .setOldController(conversationAvatarImageView.controller)
  537. .setAutoPlayAnimations(true)
  538. .setImageRequest(
  539. DisplayUtils.getImageRequestForUrl(
  540. ApiUtils.getUrlForAvatarWithName(
  541. conversationUser!!.baseUrl,
  542. conversation!!.name, R.dimen.avatar_size_big
  543. ),
  544. conversationUser
  545. )
  546. )
  547. .build()
  548. conversationAvatarImageView.controller = draweeController
  549. }
  550. Conversation.ConversationType.ROOM_GROUP_CALL -> conversationAvatarImageView.hierarchy.setPlaceholderImage(
  551. DisplayUtils
  552. .getRoundedBitmapDrawableFromVectorDrawableResource(
  553. resources,
  554. R.drawable.ic_people_group_white_24px
  555. )
  556. )
  557. Conversation.ConversationType.ROOM_PUBLIC_CALL -> conversationAvatarImageView.hierarchy.setPlaceholderImage(
  558. DisplayUtils
  559. .getRoundedBitmapDrawableFromVectorDrawableResource(
  560. resources,
  561. R.drawable.ic_link_white_24px
  562. )
  563. )
  564. Conversation.ConversationType.ROOM_SYSTEM -> {
  565. val layers = arrayOfNulls<Drawable>(2)
  566. layers[0] = context.getDrawable(R.drawable.ic_launcher_background)
  567. layers[1] = context.getDrawable(R.drawable.ic_launcher_foreground)
  568. val layerDrawable = LayerDrawable(layers)
  569. conversationAvatarImageView.hierarchy.setPlaceholderImage(DisplayUtils.getRoundedDrawable(layerDrawable))
  570. }
  571. else -> {
  572. }
  573. }
  574. }
  575. override fun onItemClick(view: View?, position: Int): Boolean {
  576. val userItem = adapter?.getItem(position) as UserItem
  577. val participant = userItem.model
  578. if (participant.userId != conversationUser!!.userId) {
  579. var items = mutableListOf(
  580. BasicListItemWithImage(R.drawable.ic_pencil_grey600_24dp, context.getString(R.string.nc_promote)),
  581. BasicListItemWithImage(R.drawable.ic_pencil_grey600_24dp, context.getString(R.string.nc_demote)),
  582. BasicListItemWithImage(
  583. R.drawable.ic_delete_grey600_24dp,
  584. context.getString(R.string.nc_remove_participant)
  585. )
  586. )
  587. if (!conversation!!.canModerate(conversationUser)) {
  588. items = mutableListOf()
  589. } else {
  590. if (participant.type == Participant.ParticipantType.MODERATOR || participant.type == Participant.ParticipantType.OWNER) {
  591. items.removeAt(0)
  592. } else if (participant.type == Participant.ParticipantType.USER) {
  593. items.removeAt(1)
  594. }
  595. }
  596. if (items.isNotEmpty()) {
  597. MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show {
  598. cornerRadius(res = R.dimen.corner_radius)
  599. title(text = participant.displayName)
  600. listItemsWithImage(items = items) { dialog, index, _ ->
  601. if (index == 0) {
  602. if (participant.type == Participant.ParticipantType.MODERATOR) {
  603. ncApi.demoteModeratorToUser(
  604. credentials,
  605. ApiUtils.getUrlForModerators(conversationUser.baseUrl, conversation!!.token),
  606. participant.userId
  607. )
  608. .subscribeOn(Schedulers.io())
  609. .observeOn(AndroidSchedulers.mainThread())
  610. .subscribe {
  611. getListOfParticipants()
  612. }
  613. } else if (participant.type == Participant.ParticipantType.USER) {
  614. ncApi.promoteUserToModerator(
  615. credentials,
  616. ApiUtils.getUrlForModerators(conversationUser.baseUrl, conversation!!.token),
  617. participant.userId
  618. )
  619. .subscribeOn(Schedulers.io())
  620. .observeOn(AndroidSchedulers.mainThread())
  621. .subscribe {
  622. getListOfParticipants()
  623. }
  624. }
  625. } else if (index == 1) {
  626. if (participant.type == Participant.ParticipantType.GUEST ||
  627. participant.type == Participant.ParticipantType.USER_FOLLOWING_LINK
  628. ) {
  629. ncApi.removeParticipantFromConversation(
  630. credentials,
  631. ApiUtils.getUrlForRemovingParticipantFromConversation(
  632. conversationUser.baseUrl,
  633. conversation!!.token,
  634. true
  635. ),
  636. participant.sessionId
  637. )
  638. .subscribeOn(Schedulers.io())
  639. .observeOn(AndroidSchedulers.mainThread())
  640. .subscribe {
  641. getListOfParticipants()
  642. }
  643. } else {
  644. ncApi.removeParticipantFromConversation(
  645. credentials,
  646. ApiUtils.getUrlForRemovingParticipantFromConversation(
  647. conversationUser.baseUrl,
  648. conversation!!.token,
  649. false
  650. ),
  651. participant.userId
  652. )
  653. .subscribeOn(Schedulers.io())
  654. .observeOn(AndroidSchedulers.mainThread())
  655. .subscribe {
  656. getListOfParticipants()
  657. // get participants again
  658. }
  659. }
  660. }
  661. }
  662. }
  663. }
  664. }
  665. return true
  666. }
  667. companion object {
  668. private const val ID_DELETE_CONVERSATION_DIALOG = 0
  669. }
  670. /**
  671. * Comparator for participants, sorts by online-status, moderator-status and display name.
  672. */
  673. class UserItemComparator : Comparator<UserItem> {
  674. override fun compare(left: UserItem, right: UserItem): Int {
  675. if (left.isOnline && !right.isOnline) {
  676. return -1
  677. } else if (!left.isOnline && right.isOnline) {
  678. return 1
  679. }
  680. val moderatorTypes = ArrayList<Participant.ParticipantType>()
  681. moderatorTypes.add(Participant.ParticipantType.MODERATOR)
  682. moderatorTypes.add(Participant.ParticipantType.OWNER)
  683. moderatorTypes.add(Participant.ParticipantType.GUEST_MODERATOR)
  684. if (moderatorTypes.contains(left.model.type) && !moderatorTypes.contains(right.model.type)) {
  685. return -1
  686. } else if (!moderatorTypes.contains(left.model.type) && moderatorTypes.contains(right.model.type)) {
  687. return 1
  688. }
  689. return left.model.displayName.toLowerCase(Locale.ROOT).compareTo(
  690. right.model.displayName.toLowerCase(Locale.ROOT)
  691. )
  692. }
  693. }
  694. }