mirror of
https://github.com/element-hq/element-android
synced 2024-11-25 02:45:37 +03:00
Add camera and gallery options to set room avatar.
This commit is contained in:
parent
1f30cf468a
commit
7f2ce91c82
6 changed files with 411 additions and 3 deletions
|
@ -0,0 +1,216 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.roomprofile
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Pair
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewAnimationUtils
|
||||||
|
import android.view.animation.Animation
|
||||||
|
import android.view.animation.AnimationSet
|
||||||
|
import android.view.animation.OvershootInterpolator
|
||||||
|
import android.view.animation.ScaleAnimation
|
||||||
|
import android.view.animation.TranslateAnimation
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.ImageButton
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.PopupWindow
|
||||||
|
import androidx.core.view.doOnNextLayout
|
||||||
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
|
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.getMeasurements
|
||||||
|
import im.vector.riotx.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||||
|
import im.vector.riotx.core.utils.PERMISSIONS_FOR_WRITING_FILES
|
||||||
|
import im.vector.riotx.features.roomprofile.AvatarSelectorView.Callback
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
private const val ANIMATION_DURATION = 250
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is the view presenting choices for picking avatar.
|
||||||
|
* It will return result through [Callback].
|
||||||
|
*/
|
||||||
|
class AvatarSelectorView(context: Context,
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
var callback: Callback?)
|
||||||
|
: PopupWindow(context) {
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onTypeSelected(type: Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val iconColorGenerator = ColorGenerator.MATERIAL
|
||||||
|
|
||||||
|
private var galleryButton: ImageButton
|
||||||
|
private var cameraButton: ImageButton
|
||||||
|
|
||||||
|
private var anchor: View? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
val root = FrameLayout(context)
|
||||||
|
val layout = inflater.inflate(R.layout.view_avatar_selector, root, true)
|
||||||
|
galleryButton = layout.findViewById<ImageButton>(R.id.avatarGalleryButton).configure(Type.GALLERY)
|
||||||
|
cameraButton = layout.findViewById<ImageButton>(R.id.avatarCameraButton).configure(Type.CAMERA)
|
||||||
|
contentView = root
|
||||||
|
width = LinearLayout.LayoutParams.MATCH_PARENT
|
||||||
|
height = LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
animationStyle = 0
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
setBackgroundDrawable(BitmapDrawable())
|
||||||
|
inputMethodMode = INPUT_METHOD_NOT_NEEDED
|
||||||
|
isFocusable = true
|
||||||
|
isTouchable = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show(anchor: View, isKeyboardOpen: Boolean) {
|
||||||
|
this.anchor = anchor
|
||||||
|
val anchorCoordinates = IntArray(2)
|
||||||
|
anchor.getLocationOnScreen(anchorCoordinates)
|
||||||
|
if (isKeyboardOpen) {
|
||||||
|
showAtLocation(anchor, Gravity.NO_GRAVITY, 0, anchorCoordinates[1] + anchor.height)
|
||||||
|
} else {
|
||||||
|
val contentViewHeight = if (contentView.height == 0) {
|
||||||
|
contentView.getMeasurements().second
|
||||||
|
} else {
|
||||||
|
contentView.height
|
||||||
|
}
|
||||||
|
showAtLocation(anchor, Gravity.NO_GRAVITY, 0, anchorCoordinates[1] - contentViewHeight)
|
||||||
|
}
|
||||||
|
contentView.doOnNextLayout {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
animateWindowInCircular(anchor, contentView)
|
||||||
|
} else {
|
||||||
|
animateWindowInTranslate(contentView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
animateButtonIn(galleryButton, ANIMATION_DURATION / 2)
|
||||||
|
animateButtonIn(cameraButton, ANIMATION_DURATION / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dismiss() {
|
||||||
|
val capturedAnchor = anchor
|
||||||
|
if (capturedAnchor != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
animateWindowOutCircular(capturedAnchor, contentView)
|
||||||
|
} else {
|
||||||
|
animateWindowOutTranslate(contentView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun animateButtonIn(button: View, delay: Int) {
|
||||||
|
val animation = AnimationSet(true)
|
||||||
|
val scale = ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.0f)
|
||||||
|
animation.addAnimation(scale)
|
||||||
|
animation.interpolator = OvershootInterpolator(1f)
|
||||||
|
animation.duration = ANIMATION_DURATION.toLong()
|
||||||
|
animation.startOffset = delay.toLong()
|
||||||
|
button.startAnimation(animation)
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
private fun animateWindowInCircular(anchor: View, contentView: View) {
|
||||||
|
val coordinates = getClickCoordinates(anchor, contentView)
|
||||||
|
val animator = ViewAnimationUtils.createCircularReveal(contentView,
|
||||||
|
coordinates.first,
|
||||||
|
coordinates.second,
|
||||||
|
0f,
|
||||||
|
max(contentView.width, contentView.height).toFloat())
|
||||||
|
animator.duration = ANIMATION_DURATION.toLong()
|
||||||
|
animator.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun animateWindowInTranslate(contentView: View) {
|
||||||
|
val animation = TranslateAnimation(0f, 0f, contentView.height.toFloat(), 0f)
|
||||||
|
animation.duration = ANIMATION_DURATION.toLong()
|
||||||
|
getContentView().startAnimation(animation)
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
private fun animateWindowOutCircular(anchor: View, contentView: View) {
|
||||||
|
val coordinates = getClickCoordinates(anchor, contentView)
|
||||||
|
val animator = ViewAnimationUtils.createCircularReveal(getContentView(),
|
||||||
|
coordinates.first,
|
||||||
|
coordinates.second,
|
||||||
|
max(getContentView().width, getContentView().height).toFloat(),
|
||||||
|
0f)
|
||||||
|
|
||||||
|
animator.duration = ANIMATION_DURATION.toLong()
|
||||||
|
animator.addListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
|
super@AvatarSelectorView.dismiss()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
animator.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun animateWindowOutTranslate(contentView: View) {
|
||||||
|
val animation = TranslateAnimation(0f, 0f, 0f, (contentView.top + contentView.height).toFloat())
|
||||||
|
animation.duration = ANIMATION_DURATION.toLong()
|
||||||
|
animation.setAnimationListener(object : Animation.AnimationListener {
|
||||||
|
override fun onAnimationStart(animation: Animation) {}
|
||||||
|
|
||||||
|
override fun onAnimationEnd(animation: Animation) {
|
||||||
|
super@AvatarSelectorView.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationRepeat(animation: Animation) {}
|
||||||
|
})
|
||||||
|
|
||||||
|
getContentView().startAnimation(animation)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getClickCoordinates(anchor: View, contentView: View): Pair<Int, Int> {
|
||||||
|
val anchorCoordinates = IntArray(2)
|
||||||
|
anchor.getLocationOnScreen(anchorCoordinates)
|
||||||
|
val contentCoordinates = IntArray(2)
|
||||||
|
contentView.getLocationOnScreen(contentCoordinates)
|
||||||
|
val x = anchorCoordinates[0] - contentCoordinates[0] + anchor.width / 2
|
||||||
|
val y = anchorCoordinates[1] - contentCoordinates[1]
|
||||||
|
return Pair(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ImageButton.configure(type: Type): ImageButton {
|
||||||
|
this.background = TextDrawable.builder().buildRound("", iconColorGenerator.getColor(type.ordinal))
|
||||||
|
this.setOnClickListener(TypeClickListener(type))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class TypeClickListener(private val type: Type) : View.OnClickListener {
|
||||||
|
|
||||||
|
override fun onClick(v: View) {
|
||||||
|
dismiss()
|
||||||
|
callback?.onTypeSelected(type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The all possible types to pick with their required permissions.
|
||||||
|
*/
|
||||||
|
enum class Type(val permissionsBit: Int) {
|
||||||
|
CAMERA(PERMISSIONS_FOR_TAKING_PHOTO),
|
||||||
|
GALLERY(PERMISSIONS_FOR_WRITING_FILES),
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,11 +17,13 @@
|
||||||
|
|
||||||
package im.vector.riotx.features.roomprofile
|
package im.vector.riotx.features.roomprofile
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
||||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
sealed class RoomProfileAction: VectorViewModelAction {
|
sealed class RoomProfileAction: VectorViewModelAction {
|
||||||
object LeaveRoom: RoomProfileAction()
|
object LeaveRoom: RoomProfileAction()
|
||||||
data class ChangeRoomNotificationState(val notificationState: RoomNotificationState) : RoomProfileAction()
|
data class ChangeRoomNotificationState(val notificationState: RoomNotificationState) : RoomProfileAction()
|
||||||
|
data class ChangeRoomAvatar(val uri: Uri, val fileName: String?) : RoomProfileAction()
|
||||||
object ShareRoomProfile : RoomProfileAction()
|
object ShareRoomProfile : RoomProfileAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,15 +17,23 @@
|
||||||
|
|
||||||
package im.vector.riotx.features.roomprofile
|
package im.vector.riotx.features.roomprofile
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
|
import com.yalantis.ucrop.UCrop
|
||||||
|
import com.yalantis.ucrop.UCropActivity
|
||||||
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
||||||
import im.vector.matrix.android.api.util.MatrixItem
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.matrix.android.api.util.toMatrixItem
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
|
@ -36,7 +44,9 @@ import im.vector.riotx.core.extensions.cleanup
|
||||||
import im.vector.riotx.core.extensions.configureWith
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.extensions.exhaustive
|
import im.vector.riotx.core.extensions.exhaustive
|
||||||
import im.vector.riotx.core.extensions.setTextOrHide
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
|
import im.vector.riotx.core.intent.getFilenameFromUri
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
import im.vector.riotx.core.utils.copyToClipboard
|
import im.vector.riotx.core.utils.copyToClipboard
|
||||||
import im.vector.riotx.core.utils.startSharePlainTextIntent
|
import im.vector.riotx.core.utils.startSharePlainTextIntent
|
||||||
import im.vector.riotx.features.crypto.util.toImageRes
|
import im.vector.riotx.features.crypto.util.toImageRes
|
||||||
|
@ -45,10 +55,13 @@ import im.vector.riotx.features.home.room.list.actions.RoomListActionsArgs
|
||||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
||||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedAction
|
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedAction
|
||||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||||
|
import im.vector.riotx.multipicker.MultiPicker
|
||||||
|
import im.vector.riotx.multipicker.entity.MultiPickerImageType
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.android.synthetic.main.fragment_matrix_profile.*
|
import kotlinx.android.synthetic.main.fragment_matrix_profile.*
|
||||||
import kotlinx.android.synthetic.main.view_stub_room_profile_header.*
|
import kotlinx.android.synthetic.main.view_stub_room_profile_header.*
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
@ -59,8 +72,9 @@ data class RoomProfileArgs(
|
||||||
class RoomProfileFragment @Inject constructor(
|
class RoomProfileFragment @Inject constructor(
|
||||||
private val roomProfileController: RoomProfileController,
|
private val roomProfileController: RoomProfileController,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
val roomProfileViewModelFactory: RoomProfileViewModel.Factory
|
val roomProfileViewModelFactory: RoomProfileViewModel.Factory,
|
||||||
) : VectorBaseFragment(), RoomProfileController.Callback {
|
val colorProvider: ColorProvider
|
||||||
|
) : VectorBaseFragment(), RoomProfileController.Callback, AvatarSelectorView.Callback {
|
||||||
|
|
||||||
private val roomProfileArgs: RoomProfileArgs by args()
|
private val roomProfileArgs: RoomProfileArgs by args()
|
||||||
private lateinit var roomListQuickActionsSharedActionViewModel: RoomListQuickActionsSharedActionViewModel
|
private lateinit var roomListQuickActionsSharedActionViewModel: RoomListQuickActionsSharedActionViewModel
|
||||||
|
@ -69,6 +83,8 @@ class RoomProfileFragment @Inject constructor(
|
||||||
|
|
||||||
private var appBarStateChangeListener: AppBarStateChangeListener? = null
|
private var appBarStateChangeListener: AppBarStateChangeListener? = null
|
||||||
|
|
||||||
|
private lateinit var avatarSelector: AvatarSelectorView
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_matrix_profile
|
override fun getLayoutResId() = R.layout.fragment_matrix_profile
|
||||||
|
|
||||||
override fun getMenuRes() = R.menu.vector_room_profile
|
override fun getMenuRes() = R.menu.vector_room_profile
|
||||||
|
@ -96,6 +112,7 @@ class RoomProfileFragment @Inject constructor(
|
||||||
is RoomProfileViewEvents.Failure -> showFailure(it.throwable)
|
is RoomProfileViewEvents.Failure -> showFailure(it.throwable)
|
||||||
is RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom()
|
is RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom()
|
||||||
is RoomProfileViewEvents.ShareRoomProfile -> onShareRoomProfile(it.permalink)
|
is RoomProfileViewEvents.ShareRoomProfile -> onShareRoomProfile(it.permalink)
|
||||||
|
RoomProfileViewEvents.OnChangeAvatarSuccess -> dismissLoadingDialog()
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
roomListQuickActionsSharedActionViewModel
|
roomListQuickActionsSharedActionViewModel
|
||||||
|
@ -222,6 +239,97 @@ class RoomProfileFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onAvatarClicked(view: View, matrixItem: MatrixItem.RoomItem) {
|
private fun onAvatarClicked(view: View, matrixItem: MatrixItem.RoomItem) {
|
||||||
|
if (matrixItem.avatarUrl.isNullOrEmpty()) {
|
||||||
|
showAvatarSelector()
|
||||||
|
} else {
|
||||||
navigator.openBigImageViewer(requireActivity(), view, matrixItem)
|
navigator.openBigImageViewer(requireActivity(), view, matrixItem)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showAvatarSelector() {
|
||||||
|
if (!::avatarSelector.isInitialized) {
|
||||||
|
avatarSelector = AvatarSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@RoomProfileFragment)
|
||||||
|
}
|
||||||
|
avatarSelector.show(vector_coordinator_layout, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var avatarCameraUri: Uri? = null
|
||||||
|
override fun onTypeSelected(type: AvatarSelectorView.Type) {
|
||||||
|
when (type) {
|
||||||
|
AvatarSelectorView.Type.CAMERA -> {
|
||||||
|
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this)
|
||||||
|
}
|
||||||
|
AvatarSelectorView.Type.GALLERY -> {
|
||||||
|
MultiPicker.get(MultiPicker.IMAGE).single().startWith(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onRoomAvatarSelected(image: MultiPickerImageType) {
|
||||||
|
val destinationFile = File(requireContext().cacheDir, "${image.displayName}_edited_image_${System.currentTimeMillis()}")
|
||||||
|
val uri = image.contentUri
|
||||||
|
UCrop.of(uri, destinationFile.toUri())
|
||||||
|
.withOptions(
|
||||||
|
UCrop.Options()
|
||||||
|
.apply {
|
||||||
|
setAllowedGestures(
|
||||||
|
/* tabScale = */ UCropActivity.SCALE,
|
||||||
|
/* tabRotate = */ UCropActivity.ALL,
|
||||||
|
/* tabAspectRatio = */ UCropActivity.SCALE
|
||||||
|
)
|
||||||
|
setToolbarTitle(image.displayName)
|
||||||
|
// Disable freestyle crop, usability was not easy
|
||||||
|
// setFreeStyleCropEnabled(true)
|
||||||
|
// Color used for toolbar icon and text
|
||||||
|
setToolbarColor(colorProvider.getColorFromAttribute(R.attr.riotx_background))
|
||||||
|
setToolbarWidgetColor(colorProvider.getColorFromAttribute(R.attr.vctr_toolbar_primary_text_color))
|
||||||
|
// Background
|
||||||
|
setRootViewBackgroundColor(colorProvider.getColorFromAttribute(R.attr.riotx_background))
|
||||||
|
// Status bar color (pb in dark mode, icon of the status bar are dark)
|
||||||
|
setStatusBarColor(colorProvider.getColorFromAttribute(R.attr.riotx_header_panel_background))
|
||||||
|
// Known issue: there is still orange color used by the lib
|
||||||
|
// https://github.com/Yalantis/uCrop/issues/602
|
||||||
|
setActiveControlsWidgetColor(colorProvider.getColor(R.color.riotx_accent))
|
||||||
|
// Hide the logo (does not work)
|
||||||
|
setLogoColor(Color.TRANSPARENT)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.start(requireContext(), this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
when (requestCode) {
|
||||||
|
MultiPicker.REQUEST_CODE_TAKE_PHOTO -> {
|
||||||
|
avatarCameraUri?.let { uri ->
|
||||||
|
MultiPicker.get(MultiPicker.CAMERA)
|
||||||
|
.getTakenPhoto(requireContext(), requestCode, resultCode, uri)
|
||||||
|
?.let {
|
||||||
|
onRoomAvatarSelected(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MultiPicker.REQUEST_CODE_PICK_IMAGE -> {
|
||||||
|
MultiPicker
|
||||||
|
.get(MultiPicker.IMAGE)
|
||||||
|
.getSelectedFiles(requireContext(), requestCode, resultCode, data)
|
||||||
|
.firstOrNull()?.let {
|
||||||
|
// TODO. UCrop library cannot read from Gallery. For now, we will set avatar as it is.
|
||||||
|
// onRoomAvatarSelected(it)
|
||||||
|
onAvatarCropped(it.contentUri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onAvatarCropped(uri: Uri?) {
|
||||||
|
if (uri != null) {
|
||||||
|
roomProfileViewModel.handle(RoomProfileAction.ChangeRoomAvatar(uri, getFilenameFromUri(context, uri)))
|
||||||
|
} else {
|
||||||
|
Toast.makeText(requireContext(), "Cannot retrieve cropped value", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,5 +26,6 @@ sealed class RoomProfileViewEvents : VectorViewEvents {
|
||||||
data class Failure(val throwable: Throwable) : RoomProfileViewEvents()
|
data class Failure(val throwable: Throwable) : RoomProfileViewEvents()
|
||||||
|
|
||||||
object OnLeaveRoomSuccess : RoomProfileViewEvents()
|
object OnLeaveRoomSuccess : RoomProfileViewEvents()
|
||||||
|
object OnChangeAvatarSuccess : RoomProfileViewEvents()
|
||||||
data class ShareRoomProfile(val permalink: String) : RoomProfileViewEvents()
|
data class ShareRoomProfile(val permalink: String) : RoomProfileViewEvents()
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.matrix.rx.unwrap
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
class RoomProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomProfileViewState,
|
class RoomProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomProfileViewState,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
|
@ -68,6 +69,7 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted private val ini
|
||||||
RoomProfileAction.LeaveRoom -> handleLeaveRoom()
|
RoomProfileAction.LeaveRoom -> handleLeaveRoom()
|
||||||
is RoomProfileAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
is RoomProfileAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
||||||
is RoomProfileAction.ShareRoomProfile -> handleShareRoomProfile()
|
is RoomProfileAction.ShareRoomProfile -> handleShareRoomProfile()
|
||||||
|
is RoomProfileAction.ChangeRoomAvatar -> handleChangeAvatar(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeNotificationMode(action: RoomProfileAction.ChangeRoomNotificationState) {
|
private fun handleChangeNotificationMode(action: RoomProfileAction.ChangeRoomNotificationState) {
|
||||||
|
@ -96,4 +98,15 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted private val ini
|
||||||
_viewEvents.post(RoomProfileViewEvents.ShareRoomProfile(permalink))
|
_viewEvents.post(RoomProfileViewEvents.ShareRoomProfile(permalink))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleChangeAvatar(action: RoomProfileAction.ChangeRoomAvatar) {
|
||||||
|
_viewEvents.post(RoomProfileViewEvents.Loading())
|
||||||
|
room.rx().updateAvatar(action.uri, action.fileName ?: UUID.randomUUID().toString())
|
||||||
|
.subscribe({
|
||||||
|
_viewEvents.post(RoomProfileViewEvents.OnChangeAvatarSuccess)
|
||||||
|
}, {
|
||||||
|
_viewEvents.post(RoomProfileViewEvents.Failure(it))
|
||||||
|
})
|
||||||
|
.disposeOnClear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
68
vector/src/main/res/layout/view_avatar_selector.xml
Normal file
68
vector/src/main/res/layout/view_avatar_selector.xml
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_attachment_type_selector"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:baselineAligned="false"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:weightSum="2">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/avatarCameraButton"
|
||||||
|
style="@style/AttachmentTypeSelectorButton"
|
||||||
|
android:src="@drawable/ic_attachment_camera_white_24dp"
|
||||||
|
android:contentDescription="@string/attachment_type_camera"
|
||||||
|
tools:background="@color/colorAccent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/AttachmentTypeSelectorLabel"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:text="@string/attachment_type_camera" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/avatarGalleryButton"
|
||||||
|
style="@style/AttachmentTypeSelectorButton"
|
||||||
|
android:src="@drawable/ic_attachment_gallery_white_24dp"
|
||||||
|
android:contentDescription="@string/attachment_type_gallery"
|
||||||
|
tools:background="@color/colorAccent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/AttachmentTypeSelectorLabel"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:text="@string/attachment_type_gallery" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
Loading…
Reference in a new issue