ConversationInfoEditActivity.kt 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. /*
  2. * Nextcloud Talk application
  3. *
  4. * @author Marcel Hibbe
  5. * @author Ezhil Shanmugham
  6. * Copyright (C) 2023 Marcel Hibbe (dev@mhibbe.de)
  7. * Copyright (C) 2023 Ezhil Shanmugham <ezhil56x.contact@gmail.com>
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation, either version 3 of the License, or
  12. * at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. */
  22. package com.nextcloud.talk.conversationinfoedit
  23. import android.app.Activity
  24. import android.content.Intent
  25. import android.graphics.drawable.ColorDrawable
  26. import android.os.Bundle
  27. import android.text.TextUtils
  28. import android.util.Log
  29. import android.view.Menu
  30. import android.view.MenuItem
  31. import android.view.View
  32. import androidx.core.net.toFile
  33. import androidx.core.view.ViewCompat
  34. import androidx.lifecycle.ViewModelProvider
  35. import autodagger.AutoInjector
  36. import com.github.dhaval2404.imagepicker.ImagePicker
  37. import com.google.android.material.snackbar.Snackbar
  38. import com.nextcloud.talk.R
  39. import com.nextcloud.talk.activities.BaseActivity
  40. import com.nextcloud.talk.api.NcApi
  41. import com.nextcloud.talk.application.NextcloudTalkApplication
  42. import com.nextcloud.talk.conversationinfoedit.viewmodel.ConversationInfoEditViewModel
  43. import com.nextcloud.talk.data.user.model.User
  44. import com.nextcloud.talk.databinding.ActivityConversationInfoEditBinding
  45. import com.nextcloud.talk.extensions.loadConversationAvatar
  46. import com.nextcloud.talk.extensions.loadSystemAvatar
  47. import com.nextcloud.talk.extensions.loadUserAvatar
  48. import com.nextcloud.talk.models.domain.ConversationModel
  49. import com.nextcloud.talk.models.domain.ConversationType
  50. import com.nextcloud.talk.models.json.capabilities.SpreedCapability
  51. import com.nextcloud.talk.models.json.generic.GenericOverall
  52. import com.nextcloud.talk.utils.ApiUtils
  53. import com.nextcloud.talk.utils.CapabilitiesUtil
  54. import com.nextcloud.talk.utils.PickImage
  55. import com.nextcloud.talk.utils.bundle.BundleKeys
  56. import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
  57. import io.reactivex.Observer
  58. import io.reactivex.android.schedulers.AndroidSchedulers
  59. import io.reactivex.disposables.Disposable
  60. import io.reactivex.schedulers.Schedulers
  61. import java.io.File
  62. import javax.inject.Inject
  63. @AutoInjector(NextcloudTalkApplication::class)
  64. class ConversationInfoEditActivity :
  65. BaseActivity() {
  66. private lateinit var binding: ActivityConversationInfoEditBinding
  67. @Inject
  68. lateinit var ncApi: NcApi
  69. @Inject
  70. lateinit var currentUserProvider: CurrentUserProviderNew
  71. @Inject
  72. lateinit var viewModelFactory: ViewModelProvider.Factory
  73. lateinit var conversationInfoEditViewModel: ConversationInfoEditViewModel
  74. private lateinit var roomToken: String
  75. private lateinit var conversationUser: User
  76. private lateinit var credentials: String
  77. private var conversation: ConversationModel? = null
  78. private lateinit var pickImage: PickImage
  79. private lateinit var spreedCapabilities: SpreedCapability
  80. override fun onCreate(savedInstanceState: Bundle?) {
  81. super.onCreate(savedInstanceState)
  82. NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
  83. binding = ActivityConversationInfoEditBinding.inflate(layoutInflater)
  84. setupActionBar()
  85. setContentView(binding.root)
  86. setupSystemColors()
  87. val extras: Bundle? = intent.extras
  88. conversationUser = currentUserProvider.currentUser.blockingGet()
  89. roomToken = extras?.getString(BundleKeys.KEY_ROOM_TOKEN)!!
  90. conversationInfoEditViewModel =
  91. ViewModelProvider(this, viewModelFactory)[ConversationInfoEditViewModel::class.java]
  92. conversationInfoEditViewModel.getRoom(conversationUser, roomToken)
  93. viewThemeUtils.material.colorTextInputLayout(binding.conversationNameInputLayout)
  94. viewThemeUtils.material.colorTextInputLayout(binding.conversationDescriptionInputLayout)
  95. credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)!!
  96. pickImage = PickImage(this, conversationUser)
  97. initObservers()
  98. }
  99. override fun onResume() {
  100. super.onResume()
  101. }
  102. private fun initObservers() {
  103. conversationInfoEditViewModel.viewState.observe(this) { state ->
  104. when (state) {
  105. is ConversationInfoEditViewModel.GetRoomSuccessState -> {
  106. conversation = state.conversationModel
  107. spreedCapabilities = conversationUser.capabilities!!.spreedCapability!!
  108. binding.conversationName.setText(conversation!!.displayName)
  109. if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) {
  110. binding.conversationDescription.setText(conversation!!.description)
  111. }
  112. if (!CapabilitiesUtil.isConversationDescriptionEndpointAvailable(spreedCapabilities)) {
  113. binding.conversationDescription.isEnabled = false
  114. }
  115. loadConversationAvatar()
  116. }
  117. is ConversationInfoEditViewModel.GetRoomErrorState -> {
  118. Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
  119. }
  120. is ConversationInfoEditViewModel.UploadAvatarSuccessState -> {
  121. conversation = state.conversationModel
  122. loadConversationAvatar()
  123. }
  124. is ConversationInfoEditViewModel.UploadAvatarErrorState -> {
  125. Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
  126. }
  127. is ConversationInfoEditViewModel.DeleteAvatarSuccessState -> {
  128. conversation = state.conversationModel
  129. loadConversationAvatar()
  130. }
  131. is ConversationInfoEditViewModel.DeleteAvatarErrorState -> {
  132. Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
  133. }
  134. else -> {}
  135. }
  136. }
  137. }
  138. private fun setupAvatarOptions() {
  139. binding.avatarUpload.setOnClickListener { pickImage.selectLocal() }
  140. binding.avatarChoose.setOnClickListener { pickImage.selectRemote() }
  141. binding.avatarCamera.setOnClickListener { pickImage.takePicture() }
  142. if (conversation?.hasCustomAvatar == true) {
  143. binding.avatarDelete.visibility = View.VISIBLE
  144. binding.avatarDelete.setOnClickListener { deleteAvatar() }
  145. } else {
  146. binding.avatarDelete.visibility = View.GONE
  147. }
  148. binding.avatarImage.let { ViewCompat.setTransitionName(it, "userAvatar.transitionTag") }
  149. binding.let {
  150. viewThemeUtils.material.themeFAB(it.avatarUpload)
  151. viewThemeUtils.material.themeFAB(it.avatarChoose)
  152. viewThemeUtils.material.themeFAB(it.avatarCamera)
  153. viewThemeUtils.material.themeFAB(it.avatarDelete)
  154. }
  155. }
  156. private fun setupActionBar() {
  157. setSupportActionBar(binding.conversationInfoEditToolbar)
  158. binding.conversationInfoEditToolbar.setNavigationOnClickListener {
  159. onBackPressedDispatcher.onBackPressed()
  160. }
  161. supportActionBar?.setDisplayHomeAsUpEnabled(true)
  162. supportActionBar?.setDisplayShowHomeEnabled(true)
  163. supportActionBar?.setIcon(ColorDrawable(resources!!.getColor(android.R.color.transparent, null)))
  164. supportActionBar?.title = resources!!.getString(R.string.nc_conversation_menu_conversation_info)
  165. viewThemeUtils.material.themeToolbar(binding.conversationInfoEditToolbar)
  166. }
  167. override fun onCreateOptionsMenu(menu: Menu): Boolean {
  168. super.onCreateOptionsMenu(menu)
  169. menuInflater.inflate(R.menu.menu_conversation_info_edit, menu)
  170. return true
  171. }
  172. override fun onPrepareOptionsMenu(menu: Menu): Boolean {
  173. super.onPrepareOptionsMenu(menu)
  174. return true
  175. }
  176. override fun onOptionsItemSelected(item: MenuItem): Boolean {
  177. if (item.itemId == R.id.save) {
  178. saveConversationNameAndDescription()
  179. }
  180. return true
  181. }
  182. private fun saveConversationNameAndDescription() {
  183. val apiVersion =
  184. ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
  185. ncApi.renameRoom(
  186. credentials,
  187. ApiUtils.getUrlForRoom(
  188. apiVersion,
  189. conversationUser.baseUrl!!,
  190. conversation!!.token
  191. ),
  192. binding.conversationName.text.toString()
  193. )
  194. .subscribeOn(Schedulers.io())
  195. .observeOn(AndroidSchedulers.mainThread())
  196. .retry(1)
  197. .subscribe(object : Observer<GenericOverall> {
  198. override fun onSubscribe(d: Disposable) {
  199. // unused atm
  200. }
  201. override fun onNext(genericOverall: GenericOverall) {
  202. if (CapabilitiesUtil.isConversationDescriptionEndpointAvailable(spreedCapabilities)) {
  203. saveConversationDescription()
  204. } else {
  205. finish()
  206. }
  207. }
  208. override fun onError(e: Throwable) {
  209. Snackbar.make(
  210. binding.root,
  211. context.getString(R.string.default_error_msg),
  212. Snackbar.LENGTH_LONG
  213. ).show()
  214. Log.e(TAG, "Error while saving conversation name", e)
  215. }
  216. override fun onComplete() {
  217. // unused atm
  218. }
  219. })
  220. }
  221. fun saveConversationDescription() {
  222. val apiVersion =
  223. ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
  224. ncApi.setConversationDescription(
  225. credentials,
  226. ApiUtils.getUrlForConversationDescription(
  227. apiVersion,
  228. conversationUser.baseUrl!!,
  229. conversation!!.token
  230. ),
  231. binding.conversationDescription.text.toString()
  232. )
  233. .subscribeOn(Schedulers.io())
  234. .observeOn(AndroidSchedulers.mainThread())
  235. .retry(1)
  236. .subscribe(object : Observer<GenericOverall> {
  237. override fun onSubscribe(d: Disposable) {
  238. // unused atm
  239. }
  240. override fun onNext(genericOverall: GenericOverall) {
  241. finish()
  242. }
  243. override fun onError(e: Throwable) {
  244. Snackbar.make(
  245. binding.root,
  246. context.getString(R.string.default_error_msg),
  247. Snackbar.LENGTH_LONG
  248. ).show()
  249. Log.e(TAG, "Error while saving conversation description", e)
  250. }
  251. override fun onComplete() {
  252. // unused atm
  253. }
  254. })
  255. }
  256. override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
  257. super.onActivityResult(requestCode, resultCode, data)
  258. when (resultCode) {
  259. Activity.RESULT_OK -> {
  260. pickImage.handleActivityResult(
  261. requestCode,
  262. resultCode,
  263. data
  264. ) { uploadAvatar(it.toFile()) }
  265. }
  266. ImagePicker.RESULT_ERROR -> {
  267. Snackbar.make(binding.root, ImagePicker.getError(data), Snackbar.LENGTH_SHORT).show()
  268. }
  269. else -> {
  270. Log.i(TAG, "Task Cancelled")
  271. }
  272. }
  273. }
  274. private fun uploadAvatar(file: File) {
  275. conversationInfoEditViewModel.uploadConversationAvatar(conversationUser, file, roomToken)
  276. }
  277. private fun deleteAvatar() {
  278. conversationInfoEditViewModel.deleteConversationAvatar(conversationUser, roomToken)
  279. }
  280. private fun loadConversationAvatar() {
  281. setupAvatarOptions()
  282. when (conversation!!.type) {
  283. ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(conversation!!.name)) {
  284. conversation!!.name?.let { binding.avatarImage.loadUserAvatar(conversationUser, it, true, false) }
  285. }
  286. ConversationType.ROOM_GROUP_CALL, ConversationType.ROOM_PUBLIC_CALL -> {
  287. binding.avatarImage.loadConversationAvatar(conversationUser, conversation!!, false, viewThemeUtils)
  288. }
  289. ConversationType.ROOM_SYSTEM -> {
  290. binding.avatarImage.loadSystemAvatar()
  291. }
  292. else -> {
  293. // unused atm
  294. }
  295. }
  296. }
  297. companion object {
  298. private val TAG = ConversationInfoEditActivity::class.simpleName
  299. }
  300. }