ProfileFragment.kt
package chat.rocket.android.profile.ui
import DrawableHelper
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.RadioGroup
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.helper.AndroidPermissionsHelper
import chat.rocket.android.helper.AndroidPermissionsHelper.getCameraPermission
import chat.rocket.android.helper.AndroidPermissionsHelper.hasCameraPermission
import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.profile.presentation.ProfilePresenter
import chat.rocket.android.profile.presentation.ProfileView
import chat.rocket.android.util.extension.asObservable
import chat.rocket.android.util.extension.dispatchImageSelection
import chat.rocket.android.util.extension.dispatchTakePicture
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.extensions.ui
import chat.rocket.android.util.invalidateFirebaseToken
import chat.rocket.common.model.UserStatus
import chat.rocket.common.model.userStatusOf
import com.facebook.drawee.backends.pipeline.Fresco
import com.google.android.material.snackbar.Snackbar
import dagger.android.support.AndroidSupportInjection
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.Observables
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.avatar_profile.*
import kotlinx.android.synthetic.main.fragment_profile.*
import kotlinx.android.synthetic.main.fragment_profile.view_dim
import kotlinx.android.synthetic.main.fragment_profile.view_loading
import kotlinx.android.synthetic.main.update_avatar_options.*
import javax.inject.Inject
internal const val TAG_PROFILE_FRAGMENT = "ProfileFragment"
private const val REQUEST_CODE_FOR_PERFORM_SAF = 1
private const val REQUEST_CODE_FOR_PERFORM_CAMERA = 2
fun newInstance() = ProfileFragment()
class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
@Inject
lateinit var presenter: ProfilePresenter
@Inject
lateinit var analyticsManager: AnalyticsManager
private var currentStatus = ""
private var currentName = ""
private var currentUsername = ""
private var currentEmail = ""
private var actionMode: ActionMode? = null
private val editTextsDisposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = container?.inflate(R.layout.fragment_profile)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar()
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
presenter.loadUserProfile()
setupListeners()
subscribeEditTexts()
analyticsManager.logScreenView(ScreenViewEvent.Profile)
}
override fun onDestroyView() {
super.onDestroyView()
unsubscribeEditTexts()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
resultData?.run {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_CODE_FOR_PERFORM_SAF) {
data?.let { presenter.updateAvatar(it) }
} else if (requestCode == REQUEST_CODE_FOR_PERFORM_CAMERA) {
extras?.get("data")?.let { presenter.preparePhotoAndUpdateAvatar(it as Bitmap) }
}
}
}
}
override fun onPrepareOptionsMenu(menu: Menu) {
if (actionMode != null) {
menu.clear()
}
super.onPrepareOptionsMenu(menu)
}
override fun showProfile(
status: String,
avatarUrl: String,
name: String,
username: String,
email: String?
) {
ui {
text_status.text = getString(R.string.status, status.capitalize())
image_avatar.setImageURI(avatarUrl)
text_name.textContent = name
text_username.textContent = username
text_email.textContent = email ?: ""
currentStatus = status
currentName = name
currentUsername = username
currentEmail = email ?: ""
profile_container.isVisible = true
}
}
override fun reloadUserAvatar(avatarUrl: String) {
Fresco.getImagePipeline().evictFromCache(avatarUrl.toUri())
image_avatar.setImageURI(avatarUrl)
}
override fun showProfileUpdateSuccessfullyMessage() {
showMessage(getString(R.string.msg_profile_updated_successfully))
}
override fun invalidateToken(token: String) = invalidateFirebaseToken(token)
override fun showLoading() {
enableUserInput(false)
ui { view_loading.isVisible = true }
}
override fun hideLoading() {
ui {
if (view_loading != null) {
view_loading.isVisible = false
}
}
enableUserInput(true)
}
override fun showMessage(resId: Int) {
ui { showToast(resId) }
}
override fun showMessage(message: String) {
ui { showToast(message) }
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.menuInflater.inflate(R.menu.action_mode_profile, menu)
mode.title = getString(R.string.title_update_profile)
return true
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean = false
override fun onActionItemClicked(mode: ActionMode, menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.action_update_profile -> {
presenter.updateUserProfile(
text_email.textContent,
text_name.textContent,
text_username.textContent
)
mode.finish()
true
}
else -> {
false
}
}
}
override fun onDestroyActionMode(mode: ActionMode) {
actionMode = null
}
private fun setupToolbar() {
with((activity as AppCompatActivity)) {
with(toolbar) {
setSupportActionBar(this)
title = getString(R.string.title_profile)
setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
setNavigationOnClickListener { activity?.onBackPressed() }
}
}
}
private fun setupListeners() {
text_status.setOnClickListener { showStatusDialog(currentStatus) }
image_avatar.setOnClickListener { showUpdateAvatarOptions() }
view_dim.setOnClickListener { hideUpdateAvatarOptions() }
button_open_gallery.setOnClickListener {
dispatchImageSelection(REQUEST_CODE_FOR_PERFORM_SAF)
hideUpdateAvatarOptions()
}
button_take_a_photo.setOnClickListener {
context?.let {
if (hasCameraPermission(it)) {
dispatchTakePicture(REQUEST_CODE_FOR_PERFORM_CAMERA)
} else {
getCameraPermission(this)
}
}
hideUpdateAvatarOptions()
}
button_reset_avatar.setOnClickListener {
hideUpdateAvatarOptions()
presenter.resetAvatar()
}
button_view_profile_photo.setOnClickListener {
hideUpdateAvatarOptions()
presenter.toProfileImage()
}
}
private fun showUpdateAvatarOptions() {
view_dim.isVisible = true
layout_update_avatar_options.isVisible = true
}
private fun hideUpdateAvatarOptions() {
layout_update_avatar_options.isVisible = false
view_dim.isVisible = false
}
private fun tintEditTextDrawableStart() {
(activity as MainActivity).apply {
val personDrawable =
DrawableHelper.getDrawableFromId(R.drawable.ic_person_black_20dp, this)
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_20dp, this)
val emailDrawable =
DrawableHelper.getDrawableFromId(R.drawable.ic_email_black_20dp, this)
val drawables = arrayOf(personDrawable, atDrawable, emailDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, this, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(
arrayOf(text_name, text_username, text_email), drawables
)
}
}
private fun subscribeEditTexts() {
editTextsDisposable.add(Observables.combineLatest(
text_name.asObservable(),
text_username.asObservable(),
text_email.asObservable()
) { text_name, text_username, text_email ->
return@combineLatest (text_name.toString() != currentName ||
text_username.toString() != currentUsername ||
text_email.toString() != currentEmail)
}.subscribe { isValid ->
activity?.invalidateOptionsMenu()
if (isValid) {
startActionMode()
} else {
finishActionMode()
}
})
}
private fun unsubscribeEditTexts() = editTextsDisposable.clear()
private fun startActionMode() {
if (actionMode == null) {
actionMode = (activity as MainActivity).startSupportActionMode(this)
}
}
private fun finishActionMode() = actionMode?.finish()
private fun enableUserInput(value: Boolean) {
ui {
text_username.isEnabled = value
text_username.isEnabled = value
text_email.isEnabled = value
}
}
private fun showStatusDialog(currentStatus: String) {
val dialogLayout = layoutInflater.inflate(R.layout.dialog_status, null)
val radioGroup = dialogLayout.findViewById<RadioGroup>(R.id.radio_group_status)
radioGroup.check(
when (userStatusOf(currentStatus)) {
is UserStatus.Online -> R.id.radio_button_online
is UserStatus.Away -> R.id.radio_button_away
is UserStatus.Busy -> R.id.radio_button_busy
else -> R.id.radio_button_invisible
}
)
var newStatus: UserStatus = userStatusOf(currentStatus)
radioGroup.setOnCheckedChangeListener { _, checkId ->
when (checkId) {
R.id.radio_button_online -> newStatus = UserStatus.Online()
R.id.radio_button_away -> newStatus = UserStatus.Away()
R.id.radio_button_busy -> newStatus = UserStatus.Busy()
else -> newStatus = UserStatus.Offline()
}
}
context?.let {
AlertDialog.Builder(it)
.setView(dialogLayout)
.setPositiveButton(R.string.msg_change_status) { dialog, _ ->
presenter.updateStatus(newStatus)
text_status.text = getString(R.string.status, newStatus.toString().capitalize())
this.currentStatus = newStatus.toString()
dialog.dismiss()
}.show()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
AndroidPermissionsHelper.CAMERA_CODE -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted
dispatchTakePicture(REQUEST_CODE_FOR_PERFORM_CAMERA)
} else {
// permission denied
Snackbar.make(
relative_layout,
R.string.msg_camera_permission_denied,
Snackbar.LENGTH_SHORT
).show()
}
return
}
}
}
}