Use simple dialog for avatar selection.

This commit is contained in:
onurays 2020-06-29 10:20:59 +03:00 committed by Benoit Marty
parent 512e4f0ce3
commit ad084e1fec
4 changed files with 56 additions and 267 deletions

View File

@ -17,6 +17,7 @@
package im.vector.riotx.features.media
import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.net.Uri
@ -31,19 +32,19 @@ import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.features.roomprofile.AvatarSelectorView
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.multipicker.MultiPicker
import im.vector.riotx.multipicker.entity.MultiPickerImageType
import kotlinx.android.synthetic.main.activity_big_image_viewer.*
import java.io.File
import javax.inject.Inject
class BigImageViewerActivity : VectorBaseActivity(), AvatarSelectorView.Callback {
class BigImageViewerActivity : VectorBaseActivity() {
@Inject lateinit var sessionHolder: ActiveSessionHolder
@Inject lateinit var colorProvider: ColorProvider
@Inject lateinit var stringProvider: StringProvider
private var uri: Uri? = null
private lateinit var avatarSelector: AvatarSelectorView
override fun getMenuRes() = R.menu.vector_big_avatar_viewer
@ -91,23 +92,24 @@ class BigImageViewerActivity : VectorBaseActivity(), AvatarSelectorView.Callback
return uri != null && intent.getBooleanExtra(EXTRA_CAN_EDIT_IMAGE, false)
}
private fun showAvatarSelector() {
if (!::avatarSelector.isInitialized) {
avatarSelector = AvatarSelectorView(this, layoutInflater, this)
}
avatarSelector.show(bigImageViewerToolbar, false)
}
private var avatarCameraUri: Uri? = null
override fun onTypeSelected(type: AvatarSelectorView.Type) {
when (type) {
AvatarSelectorView.Type.CAMERA -> {
private fun showAvatarSelector() {
AlertDialog
.Builder(this)
.setItems(arrayOf(
stringProvider.getString(R.string.attachment_type_camera),
stringProvider.getString(R.string.attachment_type_gallery)
)) { dialog, which ->
dialog.cancel()
when (which) {
0 -> {
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this)
}
AvatarSelectorView.Type.GALLERY -> {
1 -> {
MultiPicker.get(MultiPicker.IMAGE).single().startWith(this)
}
}
}.show()
}
private fun onRoomAvatarSelected(image: MultiPickerImageType) {

View File

@ -1,216 +0,0 @@
/*
* 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),
}
}

View File

@ -47,6 +47,9 @@ 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.resources.ColorProvider
import im.vector.riotx.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
import im.vector.riotx.core.utils.checkPermissions
import im.vector.riotx.core.utils.copyToClipboard
import im.vector.riotx.core.utils.startSharePlainTextIntent
import im.vector.riotx.features.crypto.util.toImageRes
@ -76,7 +79,7 @@ class RoomProfileFragment @Inject constructor(
private val avatarRenderer: AvatarRenderer,
val roomProfileViewModelFactory: RoomProfileViewModel.Factory,
val colorProvider: ColorProvider
) : VectorBaseFragment(), RoomProfileController.Callback, AvatarSelectorView.Callback {
) : VectorBaseFragment(), RoomProfileController.Callback {
private val roomProfileArgs: RoomProfileArgs by args()
private lateinit var roomListQuickActionsSharedActionViewModel: RoomListQuickActionsSharedActionViewModel
@ -85,8 +88,6 @@ class RoomProfileFragment @Inject constructor(
private var appBarStateChangeListener: AppBarStateChangeListener? = null
private lateinit var avatarSelector: AvatarSelectorView
override fun getLayoutResId() = R.layout.fragment_matrix_profile
override fun getMenuRes() = R.menu.vector_room_profile
@ -251,23 +252,27 @@ class RoomProfileFragment @Inject constructor(
}
private fun showAvatarSelector() {
if (!::avatarSelector.isInitialized) {
avatarSelector = AvatarSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@RoomProfileFragment)
}
avatarSelector.show(vector_coordinator_layout, false)
AlertDialog
.Builder(requireContext())
.setItems(arrayOf(
getString(R.string.attachment_type_camera),
getString(R.string.attachment_type_gallery)
)) { dialog, which ->
dialog.cancel()
onAvatarTypeSelected(isCamera = (which == 0))
}.show()
}
private var avatarCameraUri: Uri? = null
override fun onTypeSelected(type: AvatarSelectorView.Type) {
when (type) {
AvatarSelectorView.Type.CAMERA -> {
private fun onAvatarTypeSelected(isCamera: Boolean) {
if (isCamera) {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this)
}
AvatarSelectorView.Type.GALLERY -> {
} else {
MultiPicker.get(MultiPicker.IMAGE).single().startWith(this)
}
}
}
private fun onRoomAvatarSelected(image: MultiPickerImageType) {
val destinationFile = File(requireContext().cacheDir, "${image.displayName}_edited_image_${System.currentTimeMillis()}")

View File

@ -64,7 +64,6 @@ import im.vector.riotx.core.utils.toast
import im.vector.riotx.features.MainActivity
import im.vector.riotx.features.MainActivityArgs
import im.vector.riotx.features.media.createUCropWithDefaultSettings
import im.vector.riotx.features.roomprofile.AvatarSelectorView
import im.vector.riotx.features.themes.ThemeUtils
import im.vector.riotx.features.workers.signout.SignOutUiWorker
import im.vector.riotx.multipicker.MultiPicker
@ -76,7 +75,7 @@ import kotlinx.coroutines.withContext
import java.io.File
import java.util.UUID
class VectorSettingsGeneralFragment : VectorSettingsBaseFragment(), AvatarSelectorView.Callback {
class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
override var titleRes = R.string.settings_general_title
override val preferenceXmlRes = R.xml.vector_settings_general
@ -84,7 +83,6 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment(), AvatarSelect
private var mDisplayedEmails = ArrayList<String>()
private var mDisplayedPhoneNumber = ArrayList<String>()
private lateinit var avatarSelector: AvatarSelectorView
private var avatarCameraUri: Uri? = null
private val mUserSettingsCategory by lazy {
@ -296,7 +294,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment(), AvatarSelect
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
if (allGranted(grantResults)) {
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
onTypeSelected(AvatarSelectorView.Type.CAMERA)
onAvatarTypeSelected(true)
}
}
}
@ -404,26 +402,26 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment(), AvatarSelect
* Update the avatar.
*/
private fun onUpdateAvatarClick() {
if (!::avatarSelector.isInitialized) {
avatarSelector = AvatarSelectorView(activity!!, activity!!.layoutInflater, this)
}
mUserAvatarPreference.mAvatarView?.let {
avatarSelector.show(it, false)
}
AlertDialog
.Builder(requireContext())
.setItems(arrayOf(
getString(R.string.attachment_type_camera),
getString(R.string.attachment_type_gallery)
)) { dialog, which ->
dialog.cancel()
onAvatarTypeSelected(isCamera = (which == 0))
}.show()
}
override fun onTypeSelected(type: AvatarSelectorView.Type) {
when (type) {
AvatarSelectorView.Type.CAMERA -> {
private fun onAvatarTypeSelected(isCamera: Boolean) {
if (isCamera) {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this)
}
}
AvatarSelectorView.Type.GALLERY -> {
} else {
MultiPicker.get(MultiPicker.IMAGE).single().startWith(this)
}
}
}
private fun onAvatarSelected(image: MultiPickerImageType) {
val destinationFile = File(requireContext().cacheDir, "${image.displayName}_edited_image_${System.currentTimeMillis()}")