CreateConversationDialogFragment.kt 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. /*
  2. * Nextcloud Talk - Android Client
  3. *
  4. * SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
  5. * SPDX-License-Identifier: GPL-3.0-or-later
  6. */
  7. package com.nextcloud.talk.conversation
  8. import android.annotation.SuppressLint
  9. import android.app.Dialog
  10. import android.content.Intent
  11. import android.content.res.ColorStateList
  12. import android.os.Bundle
  13. import android.os.Parcelable
  14. import android.text.Editable
  15. import android.text.TextUtils
  16. import android.text.TextWatcher
  17. import android.util.Log
  18. import android.view.LayoutInflater
  19. import android.view.View
  20. import android.view.ViewGroup
  21. import androidx.appcompat.app.AlertDialog
  22. import androidx.core.content.res.ResourcesCompat
  23. import androidx.fragment.app.DialogFragment
  24. import androidx.lifecycle.ViewModelProvider
  25. import androidx.work.Data
  26. import androidx.work.OneTimeWorkRequest
  27. import androidx.work.WorkInfo
  28. import androidx.work.WorkManager
  29. import autodagger.AutoInjector
  30. import com.google.android.material.dialog.MaterialAlertDialogBuilder
  31. import com.google.android.material.snackbar.Snackbar
  32. import com.nextcloud.android.common.ui.theme.utils.ColorRole
  33. import com.nextcloud.talk.R
  34. import com.nextcloud.talk.application.NextcloudTalkApplication
  35. import com.nextcloud.talk.chat.ChatActivity
  36. import com.nextcloud.talk.conversation.viewmodel.ConversationViewModel
  37. import com.nextcloud.talk.databinding.DialogCreateConversationBinding
  38. import com.nextcloud.talk.jobs.AddParticipantsToConversation
  39. import com.nextcloud.talk.models.json.conversations.Conversation
  40. import com.nextcloud.talk.ui.theme.ViewThemeUtils
  41. import com.nextcloud.talk.utils.bundle.BundleKeys
  42. import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
  43. import com.vanniktech.emoji.EmojiPopup
  44. import org.greenrobot.eventbus.EventBus
  45. import org.parceler.Parcels
  46. import javax.inject.Inject
  47. @AutoInjector(NextcloudTalkApplication::class)
  48. class CreateConversationDialogFragment : DialogFragment() {
  49. @Inject
  50. lateinit var viewModelFactory: ViewModelProvider.Factory
  51. @Inject
  52. lateinit var viewThemeUtils: ViewThemeUtils
  53. @Inject
  54. lateinit var eventBus: EventBus
  55. @Inject
  56. lateinit var currentUserProvider: CurrentUserProviderNew
  57. private lateinit var binding: DialogCreateConversationBinding
  58. private lateinit var viewModel: ConversationViewModel
  59. private var emojiPopup: EmojiPopup? = null
  60. private var conversationType: Conversation.ConversationType? = null
  61. private var usersToInvite: ArrayList<String> = ArrayList()
  62. private var groupsToInvite: ArrayList<String> = ArrayList()
  63. private var emailsToInvite: ArrayList<String> = ArrayList()
  64. private var circlesToInvite: ArrayList<String> = ArrayList()
  65. override fun onCreate(savedInstanceState: Bundle?) {
  66. super.onCreate(savedInstanceState)
  67. NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
  68. viewModel = ViewModelProvider(this, viewModelFactory)[ConversationViewModel::class.java]
  69. if (arguments?.containsKey(USERS_TO_INVITE) == true) {
  70. usersToInvite = arguments?.getStringArrayList(USERS_TO_INVITE)!!
  71. }
  72. if (arguments?.containsKey(GROUPS_TO_INVITE) == true) {
  73. groupsToInvite = arguments?.getStringArrayList(GROUPS_TO_INVITE)!!
  74. }
  75. if (arguments?.containsKey(EMAILS_TO_INVITE) == true) {
  76. emailsToInvite = arguments?.getStringArrayList(EMAILS_TO_INVITE)!!
  77. }
  78. if (arguments?.containsKey(CIRCLES_TO_INVITE) == true) {
  79. circlesToInvite = arguments?.getStringArrayList(CIRCLES_TO_INVITE)!!
  80. }
  81. if (arguments?.containsKey(KEY_CONVERSATION_TYPE) == true) {
  82. conversationType = Parcels.unwrap(arguments?.getParcelable(KEY_CONVERSATION_TYPE))
  83. }
  84. }
  85. @SuppressLint("InflateParams")
  86. override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
  87. binding = DialogCreateConversationBinding.inflate(LayoutInflater.from(context))
  88. val dialogBuilder = MaterialAlertDialogBuilder(binding.root.context)
  89. .setTitle(resources.getString(R.string.create_conversation))
  90. // listener is null for now to avoid closing after button was clicked.
  91. // listener is set later in onStart
  92. .setPositiveButton(R.string.nc_common_create, null)
  93. .setNegativeButton(R.string.nc_common_dismiss, null)
  94. .setView(binding.root)
  95. viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.root.context, dialogBuilder)
  96. return dialogBuilder.create()
  97. }
  98. override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
  99. return binding.root
  100. }
  101. override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  102. super.onViewCreated(view, savedInstanceState)
  103. setupListeners()
  104. setupStateObserver()
  105. setupEmojiPopup()
  106. }
  107. override fun onStart() {
  108. super.onStart()
  109. val positiveButton = (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)
  110. positiveButton.isEnabled = false
  111. positiveButton.setOnClickListener {
  112. viewModel.createConversation(
  113. binding.textEdit.text.toString(),
  114. conversationType
  115. )
  116. }
  117. themeDialog()
  118. }
  119. private fun themeDialog() {
  120. viewThemeUtils.platform.themeDialog(binding.root)
  121. viewThemeUtils.platform.colorTextButtons((dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE))
  122. viewThemeUtils.platform.colorTextButtons((dialog as AlertDialog).getButton(AlertDialog.BUTTON_NEGATIVE))
  123. viewThemeUtils.material.colorTextInputLayout(binding.textInputLayout)
  124. }
  125. private fun setupEmojiPopup() {
  126. emojiPopup = binding.let {
  127. EmojiPopup(
  128. rootView = requireView(),
  129. editText = it.textEdit,
  130. onEmojiPopupShownListener = {
  131. viewThemeUtils.platform.colorImageView(it.smileyButton, ColorRole.PRIMARY)
  132. },
  133. onEmojiPopupDismissListener = {
  134. it.smileyButton.imageTintList = ColorStateList.valueOf(
  135. ResourcesCompat.getColor(
  136. resources,
  137. R.color.medium_emphasis_text,
  138. context?.theme
  139. )
  140. )
  141. },
  142. onEmojiClickListener = {
  143. binding.textEdit.editableText?.append(" ")
  144. }
  145. )
  146. }
  147. }
  148. private fun setupListeners() {
  149. binding.smileyButton.setOnClickListener { emojiPopup?.toggle() }
  150. binding.textEdit.addTextChangedListener(object : TextWatcher {
  151. override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
  152. // unused atm
  153. }
  154. override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
  155. // unused atm
  156. }
  157. override fun afterTextChanged(s: Editable) {
  158. val positiveButton = (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)
  159. if (!TextUtils.isEmpty(s)) {
  160. if (!positiveButton.isEnabled) {
  161. positiveButton.isEnabled = true
  162. }
  163. } else {
  164. if (positiveButton.isEnabled) {
  165. positiveButton.isEnabled = false
  166. }
  167. }
  168. }
  169. })
  170. }
  171. private fun setupStateObserver() {
  172. viewModel.viewState.observe(viewLifecycleOwner) { state ->
  173. when (state) {
  174. is ConversationViewModel.InitialState -> {}
  175. is ConversationViewModel.CreatingState -> {}
  176. is ConversationViewModel.CreatingSuccessState -> addParticipants(state.roomToken)
  177. is ConversationViewModel.CreatingFailedState -> {
  178. Log.e(TAG, "Failed to create conversation")
  179. showError()
  180. }
  181. else -> {}
  182. }
  183. }
  184. }
  185. private fun addParticipants(roomToken: String) {
  186. val data = Data.Builder()
  187. data.putLong(BundleKeys.KEY_INTERNAL_USER_ID, currentUserProvider.currentUser.blockingGet().id!!)
  188. data.putString(BundleKeys.KEY_TOKEN, roomToken)
  189. data.putStringArray(BundleKeys.KEY_SELECTED_USERS, usersToInvite.toTypedArray())
  190. data.putStringArray(BundleKeys.KEY_SELECTED_GROUPS, groupsToInvite.toTypedArray())
  191. data.putStringArray(BundleKeys.KEY_SELECTED_EMAILS, emailsToInvite.toTypedArray())
  192. data.putStringArray(BundleKeys.KEY_SELECTED_CIRCLES, circlesToInvite.toTypedArray())
  193. val addParticipantsToConversationWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder(
  194. AddParticipantsToConversation::class.java
  195. )
  196. .setInputData(data.build())
  197. .build()
  198. WorkManager.getInstance(requireContext()).enqueue(addParticipantsToConversationWorker)
  199. WorkManager.getInstance(requireContext()).getWorkInfoByIdLiveData(addParticipantsToConversationWorker.id)
  200. .observeForever { workInfo: WorkInfo? ->
  201. if (workInfo != null) {
  202. when (workInfo.state) {
  203. WorkInfo.State.RUNNING -> {
  204. Log.d(TAG, "running AddParticipantsToConversation")
  205. }
  206. WorkInfo.State.SUCCEEDED -> {
  207. Log.d(TAG, "success AddParticipantsToConversation")
  208. initiateConversation(roomToken)
  209. }
  210. WorkInfo.State.FAILED -> {
  211. Log.e(TAG, "failed to AddParticipantsToConversation")
  212. showError()
  213. }
  214. else -> {
  215. }
  216. }
  217. }
  218. }
  219. }
  220. private fun initiateConversation(roomToken: String) {
  221. val bundle = Bundle()
  222. bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
  223. val chatIntent = Intent(context, ChatActivity::class.java)
  224. chatIntent.putExtras(bundle)
  225. chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
  226. startActivity(chatIntent)
  227. dismiss()
  228. }
  229. private fun showError() {
  230. dismiss()
  231. Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
  232. }
  233. /**
  234. * Fragment creator
  235. */
  236. companion object {
  237. private val TAG = CreateConversationDialogFragment::class.java.simpleName
  238. private const val USERS_TO_INVITE = "usersToInvite"
  239. private const val GROUPS_TO_INVITE = "groupsToInvite"
  240. private const val EMAILS_TO_INVITE = "emailsToInvite"
  241. private const val CIRCLES_TO_INVITE = "circlesToInvite"
  242. private const val KEY_CONVERSATION_TYPE = "keyConversationType"
  243. @JvmStatic
  244. fun newInstance(
  245. usersToInvite: ArrayList<String>?,
  246. groupsToInvite: ArrayList<String>?,
  247. emailsToInvite: ArrayList<String>?,
  248. circlesToInvite: ArrayList<String>?,
  249. conversationType: Parcelable
  250. ): CreateConversationDialogFragment {
  251. val args = Bundle()
  252. args.putStringArrayList(USERS_TO_INVITE, usersToInvite)
  253. args.putStringArrayList(GROUPS_TO_INVITE, groupsToInvite)
  254. args.putStringArrayList(EMAILS_TO_INVITE, emailsToInvite)
  255. args.putStringArrayList(CIRCLES_TO_INVITE, circlesToInvite)
  256. args.putParcelable(KEY_CONVERSATION_TYPE, conversationType)
  257. val fragment = CreateConversationDialogFragment()
  258. fragment.arguments = args
  259. return fragment
  260. }
  261. }
  262. }