Add ability to share profile by QR code

This commit is contained in:
TR-SLimey 2020-11-06 13:04:21 +00:00 committed by Valere
parent 5b278f704c
commit e8d084b855
22 changed files with 680 additions and 71 deletions

View File

@ -39,7 +39,7 @@ We do not forget all translators, for their work of translating Element into man
Feel free to add your name below, when you contribute to the project! Feel free to add your name below, when you contribute to the project!
Name | Matrix ID | GitHub Name | Matrix ID | GitHub
--------|---------------------|-------------------------------------- ----------|-----------------------------|--------------------------------------
gjpower | @gjpower:matrix.org | [gjpower](https://github.com/gjpower) gjpower | @gjpower:matrix.org | [gjpower](https://github.com/gjpower)
TR_SLimey | @tr_slimey:an-atom-in.space | [TR-SLimey](https://github.com/TR-SLimey)

View File

@ -2,7 +2,7 @@ Changes in Element 1.0.11 (2020-XX-XX)
=================================================== ===================================================
Features ✨: Features ✨:
- - Create DMs with users by scanning their QR code (#2025)
Improvements 🙌: Improvements 🙌:
- New room creation tile with quick action (#2346) - New room creation tile with quick action (#2346)

View File

@ -22,6 +22,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
@ -37,6 +38,8 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.SimpleFragmentActivity import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.platform.WaitingViewData
import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS
import im.vector.app.core.utils.allGranted import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.checkPermissions
@ -72,35 +75,45 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
toolbar.visibility = View.GONE toolbar.visibility = View.GONE
sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
sharedActionViewModel if (intent?.getBooleanExtra(BY_QR_CODE, false)!!) {
.observe() if (isFirstCreation()) { openAddByQrCode() }
.subscribe { sharedAction -> } else {
when (sharedAction) { sharedActionViewModel
UserDirectorySharedAction.OpenUsersDirectory -> .observe()
addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java) .subscribe { sharedAction ->
UserDirectorySharedAction.Close -> finish() when (sharedAction) {
UserDirectorySharedAction.GoBack -> onBackPressed() UserDirectorySharedAction.OpenUsersDirectory ->
is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction) addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java)
UserDirectorySharedAction.OpenPhoneBook -> openPhoneBook() UserDirectorySharedAction.Close -> finish()
}.exhaustive UserDirectorySharedAction.GoBack -> onBackPressed()
} is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction)
.disposeOnDestroy() UserDirectorySharedAction.OpenPhoneBook -> openPhoneBook()
if (isFirstCreation()) { }.exhaustive
addFragment( }
R.id.container, .disposeOnDestroy()
KnownUsersFragment::class.java, if (isFirstCreation()) {
KnownUsersFragmentArgs( addFragment(
title = getString(R.string.fab_menu_create_chat), R.id.container,
menuResId = R.menu.vector_create_direct_room, KnownUsersFragment::class.java,
isCreatingRoom = true KnownUsersFragmentArgs(
) title = getString(R.string.fab_menu_create_chat),
) menuResId = R.menu.vector_create_direct_room,
isCreatingRoom = true
)
)
}
} }
viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) { viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) {
renderCreateAndInviteState(it) renderCreateAndInviteState(it)
} }
} }
private fun openAddByQrCode() {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA, 0)) {
addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
}
}
private fun openPhoneBook() { private fun openPhoneBook() {
// Check permission first // Check permission first
if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH, if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH,
@ -116,6 +129,13 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
if (allGranted(grantResults)) { if (allGranted(grantResults)) {
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) { if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) } doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
} else if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && intent?.getBooleanExtra(BY_QR_CODE, false)!!) {
addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
}
} else {
Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show()
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && intent?.getBooleanExtra(BY_QR_CODE, false)!!) {
finish()
} }
} }
} }
@ -178,8 +198,12 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
} }
companion object { companion object {
fun getIntent(context: Context): Intent { private const val BY_QR_CODE = "BY_QR_CODE"
return Intent(context, CreateDirectRoomActivity::class.java)
fun getIntent(context: Context, byQrCode: Boolean = false): Intent {
return Intent(context, CreateDirectRoomActivity::class.java).apply {
putExtra(BY_QR_CODE, byQrCode)
}
} }
} }
} }

View File

@ -0,0 +1,108 @@
/*
* Copyright 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.app.features.createdirect
import android.widget.Toast
import com.airbnb.mvrx.activityViewModel
import com.google.zxing.Result
import com.google.zxing.ResultMetadataType
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.features.userdirectory.PendingInvitee
import kotlinx.android.synthetic.main.fragment_qr_code_scanner.*
import me.dm7.barcodescanner.zxing.ZXingScannerView
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.user.model.User
import javax.inject.Inject
class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragment(), ZXingScannerView.ResultHandler {
private val viewModel: CreateDirectRoomViewModel by activityViewModel()
override fun getLayoutResId() = R.layout.fragment_qr_code_scanner
override fun onResume() {
super.onResume()
// Register ourselves as a handler for scan results.
scannerView.setResultHandler(null)
// Start camera on resume
scannerView.startCamera()
}
override fun onPause() {
super.onPause()
// Stop camera on pause
scannerView.stopCamera()
}
// Copied from https://github.com/markusfisch/BinaryEye/blob/
// 9d57889b810dcaa1a91d7278fc45c262afba1284/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt#L434
private fun getRawBytes(result: Result): ByteArray? {
val metadata = result.resultMetadata ?: return null
val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
var bytes = ByteArray(0)
@Suppress("UNCHECKED_CAST")
for (seg in segments as Iterable<ByteArray>) {
bytes += seg
}
// byte segments can never be shorter than the text.
// Zxing cuts off content prefixes like "WIFI:"
return if (bytes.size >= result.text.length) bytes else null
}
private fun addByQrCode(value: String) {
val mxid = (PermalinkParser.parse(value) as? PermalinkData.UserLink)?.userId
if (mxid === null) {
Toast.makeText(requireContext(), R.string.invalid_qr_code_uri, Toast.LENGTH_SHORT).show()
requireActivity().finish()
} else {
val existingDm = viewModel.session.getExistingDirectRoomWithUser(mxid)
if (existingDm === null) {
// The following assumes MXIDs are case insensitive
if (mxid.equals(other = viewModel.session.myUserId, ignoreCase = true)) {
Toast.makeText(requireContext(), R.string.cannot_dm_self, Toast.LENGTH_SHORT).show()
requireActivity().finish()
} else {
// Try to get user from known users and fall back to creating a User object from MXID
val qrInvitee = if (viewModel.session.getUser(mxid) != null) viewModel.session.getUser(mxid)!! else User(mxid, null, null)
viewModel.handle(
CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingInvitee.UserPendingInvitee(qrInvitee)))
)
}
} else {
navigator.openRoom(requireContext(), existingDm, null, false)
requireActivity().finish()
}
}
}
override fun handleResult(result: Result?) {
if (result === null) {
Toast.makeText(requireContext(), R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
requireActivity().finish()
} else {
val rawBytes = getRawBytes(result)
val rawBytesStr = rawBytes?.toString(Charsets.ISO_8859_1)
val value = rawBytesStr ?: result.text
addByQrCode(value)
}
}
}

View File

@ -38,7 +38,7 @@ import org.matrix.android.sdk.rx.rx
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
initialState: CreateDirectRoomViewState, initialState: CreateDirectRoomViewState,
private val rawService: RawService, private val rawService: RawService,
private val session: Session) val session: Session)
: VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction, CreateDirectRoomViewEvents>(initialState) { : VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction, CreateDirectRoomViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory

View File

@ -22,7 +22,7 @@ import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.features.home.room.list.widget.FabMenuView import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
@EpoxyModelClass(layout = R.layout.item_room_filter_footer) @EpoxyModelClass(layout = R.layout.item_room_filter_footer)
abstract class FilteredRoomFooterItem : VectorEpoxyModel<FilteredRoomFooterItem.Holder>() { abstract class FilteredRoomFooterItem : VectorEpoxyModel<FilteredRoomFooterItem.Holder>() {
@ -46,7 +46,7 @@ abstract class FilteredRoomFooterItem : VectorEpoxyModel<FilteredRoomFooterItem.
val openRoomDirectory by bind<Button>(R.id.roomFilterFooterOpenRoomDirectory) val openRoomDirectory by bind<Button>(R.id.roomFilterFooterOpenRoomDirectory)
} }
interface FilteredRoomFooterItemListener : FabMenuView.Listener { interface FilteredRoomFooterItemListener : NotifsFabMenuView.Listener {
fun createRoom(initialName: String) fun createRoom(initialName: String)
} }
} }

View File

@ -45,7 +45,8 @@ import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.home.room.list.widget.FabMenuView import im.vector.app.features.home.room.list.widget.DmsFabMenuView
import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.notifications.NotificationDrawerManager
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_list.* import kotlinx.android.synthetic.main.fragment_room_list.*
@ -66,8 +67,7 @@ class RoomListFragment @Inject constructor(
val roomListViewModelFactory: RoomListViewModel.Factory, val roomListViewModelFactory: RoomListViewModel.Factory,
private val notificationDrawerManager: NotificationDrawerManager, private val notificationDrawerManager: NotificationDrawerManager,
private val sharedViewPool: RecyclerView.RecycledViewPool private val sharedViewPool: RecyclerView.RecycledViewPool
) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, DmsFabMenuView.Listener, NotifsFabMenuView.Listener {
) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener {
private var modelBuildListener: OnModelBuildFinishedListener? = null private var modelBuildListener: OnModelBuildFinishedListener? = null
private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel
@ -111,6 +111,7 @@ class RoomListFragment @Inject constructor(
}.exhaustive }.exhaustive
} }
createDmFabMenu.listener = this
createChatFabMenu.listener = this createChatFabMenu.listener = this
sharedActionViewModel sharedActionViewModel
@ -129,6 +130,7 @@ class RoomListFragment @Inject constructor(
roomListView.cleanup() roomListView.cleanup()
roomController.listener = null roomController.listener = null
stateRestorer.clear() stateRestorer.clear()
createDmFabMenu.listener = null
createChatFabMenu.listener = null createChatFabMenu.listener = null
super.onDestroyView() super.onDestroyView()
} }
@ -140,33 +142,32 @@ class RoomListFragment @Inject constructor(
private fun setupCreateRoomButton() { private fun setupCreateRoomButton() {
when (roomListParams.displayMode) { when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.isVisible = true RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.isVisible = true
RoomListDisplayMode.PEOPLE -> createChatRoomButton.isVisible = true RoomListDisplayMode.PEOPLE -> createDmFabMenu.isVisible = true
RoomListDisplayMode.ROOMS -> createGroupRoomButton.isVisible = true RoomListDisplayMode.ROOMS -> createGroupRoomButton.isVisible = true
else -> Unit // No button in this mode else -> Unit // No button in this mode
} }
createChatRoomButton.debouncedClicks {
createDirectChat()
}
createGroupRoomButton.debouncedClicks { createGroupRoomButton.debouncedClicks {
openRoomDirectory() openRoomDirectory()
} }
// Hide FAB when list is scrolling // Hide FABs when list is scrolling
roomListView.addOnScrollListener( roomListView.addOnScrollListener(
object : RecyclerView.OnScrollListener() { object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
createDmFabMenu.removeCallbacks(showFabRunnable)
createChatFabMenu.removeCallbacks(showFabRunnable) createChatFabMenu.removeCallbacks(showFabRunnable)
when (newState) { when (newState) {
RecyclerView.SCROLL_STATE_IDLE -> { RecyclerView.SCROLL_STATE_IDLE -> {
createDmFabMenu.postDelayed(showFabRunnable, 250)
createChatFabMenu.postDelayed(showFabRunnable, 250) createChatFabMenu.postDelayed(showFabRunnable, 250)
} }
RecyclerView.SCROLL_STATE_DRAGGING, RecyclerView.SCROLL_STATE_DRAGGING,
RecyclerView.SCROLL_STATE_SETTLING -> { RecyclerView.SCROLL_STATE_SETTLING -> {
when (roomListParams.displayMode) { when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.hide() RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.hide()
RoomListDisplayMode.PEOPLE -> createChatRoomButton.hide() RoomListDisplayMode.PEOPLE -> createDmFabMenu.hide()
RoomListDisplayMode.ROOMS -> createGroupRoomButton.hide() RoomListDisplayMode.ROOMS -> createGroupRoomButton.hide()
else -> Unit else -> Unit
} }
@ -191,6 +192,10 @@ class RoomListFragment @Inject constructor(
navigator.openCreateDirectRoom(requireActivity()) navigator.openCreateDirectRoom(requireActivity())
} }
override fun createDirectChatByQrCode() {
navigator.openCreateDirectRoom(requireContext(), true)
}
private fun setupRecyclerView() { private fun setupRecyclerView() {
val layoutManager = LinearLayoutManager(context) val layoutManager = LinearLayoutManager(context)
stateRestorer = LayoutManagerStateRestorer(layoutManager).register() stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
@ -209,7 +214,7 @@ class RoomListFragment @Inject constructor(
if (isAdded) { if (isAdded) {
when (roomListParams.displayMode) { when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.show() RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.show()
RoomListDisplayMode.PEOPLE -> createChatRoomButton.show() RoomListDisplayMode.PEOPLE -> createDmFabMenu.show()
RoomListDisplayMode.ROOMS -> createGroupRoomButton.show() RoomListDisplayMode.ROOMS -> createGroupRoomButton.show()
else -> Unit else -> Unit
} }
@ -338,6 +343,9 @@ class RoomListFragment @Inject constructor(
} }
override fun onBackPressed(toolbarButton: Boolean): Boolean { override fun onBackPressed(toolbarButton: Boolean): Boolean {
if (createDmFabMenu.onBackPressed()) {
return true
}
if (createChatFabMenu.onBackPressed()) { if (createChatFabMenu.onBackPressed()) {
return true return true
} }

View File

@ -0,0 +1,102 @@
/*
* Copyright 2019 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.app.features.home.room.list.widget
import android.content.Context
import android.util.AttributeSet
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.view.isVisible
import com.google.android.material.floatingactionbutton.FloatingActionButton
import im.vector.app.R
import kotlinx.android.synthetic.main.motion_dms_fab_menu_merge.view.*
class DmsFabMenuView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : MotionLayout(context, attrs, defStyleAttr) {
var listener: Listener? = null
init {
inflate(context, R.layout.motion_dms_fab_menu_merge, this)
}
override fun onFinishInflate() {
super.onFinishInflate()
listOf(createDmByMxid, createDmByMxidLabel)
.forEach {
it.setOnClickListener {
closeFabMenu()
listener?.createDirectChat()
}
}
listOf(createDmByQrCode, createDmByQrCodeLabel)
.forEach {
it.setOnClickListener {
closeFabMenu()
listener?.createDirectChatByQrCode()
}
}
dmsCreateRoomTouchGuard.setOnClickListener {
closeFabMenu()
}
}
override fun transitionToEnd() {
super.transitionToEnd()
dmsCreateRoomButton.contentDescription = context.getString(R.string.a11y_create_menu_close)
}
override fun transitionToStart() {
super.transitionToStart()
dmsCreateRoomButton.contentDescription = context.getString(R.string.a11y_create_menu_open)
}
fun show() {
isVisible = true
dmsCreateRoomButton.show()
}
fun hide() {
dmsCreateRoomButton.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton?) {
super.onHidden(fab)
isVisible = false
}
})
}
private fun closeFabMenu() {
transitionToStart()
}
fun onBackPressed(): Boolean {
if (currentState == R.id.constraint_set_fab_menu_open) {
closeFabMenu()
return true
}
return false
}
interface Listener {
fun createDirectChat()
fun createDirectChatByQrCode()
}
}

View File

@ -22,15 +22,15 @@ import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import im.vector.app.R import im.vector.app.R
import kotlinx.android.synthetic.main.motion_fab_menu_merge.view.* import kotlinx.android.synthetic.main.motion_notifs_fab_menu_merge.view.*
class FabMenuView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, class NotifsFabMenuView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : MotionLayout(context, attrs, defStyleAttr) { defStyleAttr: Int = 0) : MotionLayout(context, attrs, defStyleAttr) {
var listener: Listener? = null var listener: Listener? = null
init { init {
inflate(context, R.layout.motion_fab_menu_merge, this) inflate(context, R.layout.motion_notifs_fab_menu_merge, this)
} }
override fun onFinishInflate() { override fun onFinishInflate() {

View File

@ -203,8 +203,8 @@ class DefaultNavigator @Inject constructor(
context.startActivity(intent) context.startActivity(intent)
} }
override fun openCreateDirectRoom(context: Context) { override fun openCreateDirectRoom(context: Context, byQrCode: Boolean) {
val intent = CreateDirectRoomActivity.getIntent(context) val intent = CreateDirectRoomActivity.getIntent(context, byQrCode)
context.startActivity(intent) context.startActivity(intent)
} }

View File

@ -56,7 +56,7 @@ interface Navigator {
fun openCreateRoom(context: Context, initialName: String = "") fun openCreateRoom(context: Context, initialName: String = "")
fun openCreateDirectRoom(context: Context) fun openCreateDirectRoom(context: Context, byQrCode: Boolean = false)
fun openInviteUsersToRoom(context: Context, roomId: String) fun openInviteUsersToRoom(context: Context, roomId: String)

View File

@ -62,7 +62,7 @@ abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>()
holder.avatarView.isInvisible = directoryAvatarUrl.isNullOrBlank() && includeAllNetworks holder.avatarView.isInvisible = directoryAvatarUrl.isNullOrBlank() && includeAllNetworks
holder.nameView.text = directoryName holder.nameView.text = directoryName
holder.descritionView.setTextOrHide(directoryDescription) holder.descriptionView.setTextOrHide(directoryDescription)
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {
@ -70,6 +70,6 @@ abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>()
val avatarView by bind<ImageView>(R.id.itemRoomDirectoryAvatar) val avatarView by bind<ImageView>(R.id.itemRoomDirectoryAvatar)
val nameView by bind<TextView>(R.id.itemRoomDirectoryName) val nameView by bind<TextView>(R.id.itemRoomDirectoryName)
val descritionView by bind<TextView>(R.id.itemRoomDirectoryDescription) val descriptionView by bind<TextView>(R.id.itemRoomDirectoryDescription)
} }
} }

View File

@ -294,12 +294,20 @@ class RoomMemberProfileFragment @Inject constructor(
} }
private fun handleShareRoomMemberProfile(permalink: String) { private fun handleShareRoomMemberProfile(permalink: String) {
startSharePlainTextIntent( val view = layoutInflater.inflate(R.layout.dialog_share_qr_code, null)
fragment = this, val qrCode = view.findViewById<im.vector.app.core.ui.views.QrCodeImageView>(R.id.itemShareQrCodeImage)
activityResultLauncher = null, qrCode.setData(permalink)
chooserTitle = null, AlertDialog.Builder(requireContext())
text = permalink .setView(view)
) .setNeutralButton(R.string.ok, null)
.setPositiveButton(R.string.share_by_text) { _, _ ->
startSharePlainTextIntent(
fragment = this,
activityResultLauncher = null,
chooserTitle = null,
text = permalink
)
}.show()
} }
private fun onAvatarClicked(view: View, userMatrixItem: MatrixItem) { private fun onAvatarClicked(view: View, userMatrixItem: MatrixItem) {

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="37dp"
android:height="36dp"
android:viewportWidth="37"
android:viewportHeight="36">
<path
android:pathData="M17.5911,26.2922C15.9951,27.3704 14.0711,28 12,28C9.7488,28 7.6713,27.2561 6,26.0007C3.5711,24.1763 2,21.2716 2,18C2,12.4772 6.4771,8 12,8C17.5228,8 22,12.4772 22,18C22,21.4518 20.2511,24.4951 17.5911,26.2922ZM12,18.5C13.6569,18.5 15,17.0449 15,15.25C15,13.4551 13.6569,12 12,12C10.3431,12 9,13.4551 9,15.25C9,17.0449 10.3431,18.5 12,18.5ZM12,26C14.162,26 16.1236,25.1424 17.5634,23.7488C16.673,21.5506 14.5176,20 12,20C9.4824,20 7.327,21.5506 6.4366,23.7488C7.8763,25.1424 9.838,26 12,26Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<group>
<clip-path
android:pathData="M17.5911,26.2922C15.9951,27.3704 14.0711,28 12,28C9.7488,28 7.6713,27.2561 6,26.0007C3.5711,24.1763 2,21.2716 2,18C2,12.4772 6.4771,8 12,8C17.5228,8 22,12.4772 22,18C22,21.4518 20.2511,24.4951 17.5911,26.2922ZM12,18.5C13.6569,18.5 15,17.0449 15,15.25C15,13.4551 13.6569,12 12,12C10.3431,12 9,13.4551 9,15.25C9,17.0449 10.3431,18.5 12,18.5ZM12,26C14.162,26 16.1236,25.1424 17.5634,23.7488C16.673,21.5506 14.5176,20 12,20C9.4824,20 7.327,21.5506 6.4366,23.7488C7.8763,25.1424 9.838,26 12,26Z"
android:fillType="evenOdd"/>
<path
android:pathData="M17.5911,26.2922L16.4715,24.6349L17.5911,26.2922ZM6,26.0007L4.7989,27.5999L4.7989,27.5999L6,26.0007ZM17.5634,23.7488L18.9544,25.1859L19.9234,24.2479L19.4171,22.998L17.5634,23.7488ZM6.4366,23.7488L4.5829,22.998L4.0766,24.2479L5.0456,25.1859L6.4366,23.7488ZM12,30C14.4825,30 16.7945,29.244 18.7107,27.9494L16.4715,24.6349C15.1957,25.4968 13.6596,26 12,26V30ZM4.7989,27.5999C6.8046,29.1065 9.3008,30 12,30V26C10.1967,26 8.538,25.4058 7.2011,24.4016L4.7989,27.5999ZM0,18C0,21.9273 1.8887,25.414 4.7989,27.5999L7.2011,24.4016C5.2535,22.9387 4,20.616 4,18H0ZM12,6C5.3726,6 0,11.3726 0,18H4C4,13.5817 7.5817,10 12,10V6ZM24,18C24,11.3726 18.6274,6 12,6V10C16.4183,10 20,13.5817 20,18H24ZM18.7107,27.9494C21.8977,25.7963 24,22.144 24,18H20C20,20.7596 18.6045,23.1939 16.4715,24.6349L18.7107,27.9494ZM13,15.25C13,16.0941 12.4046,16.5 12,16.5V20.5C14.9091,20.5 17,17.9958 17,15.25H13ZM12,14C12.4046,14 13,14.4059 13,15.25H17C17,12.5042 14.9091,10 12,10V14ZM11,15.25C11,14.4059 11.5954,14 12,14V10C9.0909,10 7,12.5042 7,15.25H11ZM12,16.5C11.5954,16.5 11,16.0941 11,15.25H7C7,17.9958 9.0909,20.5 12,20.5V16.5ZM16.1724,22.3118C15.0906,23.3588 13.6223,24 12,24V28C14.7017,28 17.1567,26.926 18.9544,25.1859L16.1724,22.3118ZM12,22C13.6752,22 15.1146,23.0305 15.7097,24.4996L19.4171,22.998C18.2314,20.0707 15.3599,18 12,18V22ZM8.2903,24.4996C8.8854,23.0305 10.3248,22 12,22V18C8.6401,18 5.7686,20.0707 4.5829,22.998L8.2903,24.4996ZM12,24C10.3777,24 8.9094,23.3588 7.8276,22.3118L5.0456,25.1859C6.8433,26.926 9.2983,28 12,28V24Z"
android:fillColor="#000000"/>
</group>
<path
android:pathData="M27,18H35"
android:strokeWidth="2.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M31,14L31,22"
android:strokeWidth="2.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="37dp"
android:height="36dp"
android:viewportWidth="37"
android:viewportHeight="36">
<path
android:pathData="M17.5911,26.2922C15.9951,27.3704 14.0711,28 12,28C9.7488,28 7.6713,27.2561 6,26.0007C3.5711,24.1763 2,21.2716 2,18C2,12.4772 6.4771,8 12,8C17.5228,8 22,12.4772 22,18C22,21.4518 20.2511,24.4951 17.5911,26.2922ZM12,18.5C13.6569,18.5 15,17.0449 15,15.25C15,13.4551 13.6569,12 12,12C10.3431,12 9,13.4551 9,15.25C9,17.0449 10.3431,18.5 12,18.5ZM12,26C14.162,26 16.1236,25.1424 17.5634,23.7488C16.673,21.5506 14.5176,20 12,20C9.4824,20 7.327,21.5506 6.4366,23.7488C7.8763,25.1424 9.838,26 12,26Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<group>
<clip-path
android:pathData="M17.5911,26.2922C15.9951,27.3704 14.0711,28 12,28C9.7488,28 7.6713,27.2561 6,26.0007C3.5711,24.1763 2,21.2716 2,18C2,12.4772 6.4771,8 12,8C17.5228,8 22,12.4772 22,18C22,21.4518 20.2511,24.4951 17.5911,26.2922ZM12,18.5C13.6569,18.5 15,17.0449 15,15.25C15,13.4551 13.6569,12 12,12C10.3431,12 9,13.4551 9,15.25C9,17.0449 10.3431,18.5 12,18.5ZM12,26C14.162,26 16.1236,25.1424 17.5634,23.7488C16.673,21.5506 14.5176,20 12,20C9.4824,20 7.327,21.5506 6.4366,23.7488C7.8763,25.1424 9.838,26 12,26Z"
android:fillType="evenOdd"/>
<path
android:pathData="M17.5911,26.2922L16.4715,24.6349L17.5911,26.2922ZM6,26.0007L4.7989,27.5999L4.7989,27.5999L6,26.0007ZM17.5634,23.7488L18.9544,25.1859L19.9234,24.2479L19.4171,22.998L17.5634,23.7488ZM6.4366,23.7488L4.5829,22.998L4.0766,24.2479L5.0456,25.1859L6.4366,23.7488ZM12,30C14.4825,30 16.7945,29.244 18.7107,27.9494L16.4715,24.6349C15.1957,25.4968 13.6596,26 12,26V30ZM4.7989,27.5999C6.8046,29.1065 9.3008,30 12,30V26C10.1967,26 8.538,25.4058 7.2011,24.4016L4.7989,27.5999ZM0,18C0,21.9273 1.8887,25.414 4.7989,27.5999L7.2011,24.4016C5.2535,22.9387 4,20.616 4,18H0ZM12,6C5.3726,6 0,11.3726 0,18H4C4,13.5817 7.5817,10 12,10V6ZM24,18C24,11.3726 18.6274,6 12,6V10C16.4183,10 20,13.5817 20,18H24ZM18.7107,27.9494C21.8977,25.7963 24,22.144 24,18H20C20,20.7596 18.6045,23.1939 16.4715,24.6349L18.7107,27.9494ZM13,15.25C13,16.0941 12.4046,16.5 12,16.5V20.5C14.9091,20.5 17,17.9958 17,15.25H13ZM12,14C12.4046,14 13,14.4059 13,15.25H17C17,12.5042 14.9091,10 12,10V14ZM11,15.25C11,14.4059 11.5954,14 12,14V10C9.0909,10 7,12.5042 7,15.25H11ZM12,16.5C11.5954,16.5 11,16.0941 11,15.25H7C7,17.9958 9.0909,20.5 12,20.5V16.5ZM16.1724,22.3118C15.0906,23.3588 13.6223,24 12,24V28C14.7017,28 17.1567,26.926 18.9544,25.1859L16.1724,22.3118ZM12,22C13.6752,22 15.1146,23.0305 15.7097,24.4996L19.4171,22.998C18.2314,20.0707 15.3599,18 12,18V22ZM8.2903,24.4996C8.8854,23.0305 10.3248,22 12,22V18C8.6401,18 5.7686,20.0707 4.5829,22.998L8.2903,24.4996ZM12,24C10.3777,24 8.9094,23.3588 7.8276,22.3118L5.0456,25.1859C6.8433,26.926 9.2983,28 12,28V24Z"
android:fillColor="#000000"/>
</group>
<path
android:pathData="M27,18H35"
android:strokeWidth="2.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M31,14L31,22"
android:strokeWidth="2.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_margin="8dp">
<im.vector.app.core.ui.views.QrCodeImageView
android:id="@+id/itemShareQrCodeImage"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center_horizontal"
android:contentDescription="@string/a11y_qr_code_for_verification"
tools:src="@color/riotx_header_panel_background_black" />
</FrameLayout>

View File

@ -14,29 +14,25 @@
android:overScrollMode="always" android:overScrollMode="always"
tools:listitem="@layout/item_room" /> tools:listitem="@layout/item_room" />
<im.vector.app.features.home.room.list.widget.FabMenuView <im.vector.app.features.home.room.list.widget.NotifsFabMenuView
android:id="@+id/createChatFabMenu" android:id="@+id/createChatFabMenu"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:visibility="gone" android:visibility="gone"
app:layoutDescription="@xml/motion_scene_fab_menu" app:layoutDescription="@xml/motion_scene_notifs_fab_menu"
tools:showPaths="true" tools:showPaths="true"
tools:visibility="visible" /> tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <im.vector.app.features.home.room.list.widget.DmsFabMenuView
android:id="@+id/createChatRoomButton" android:id="@+id/createDmFabMenu"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:accessibilityTraversalBefore="@+id/roomListView"
android:contentDescription="@string/a11y_create_direct_message" android:contentDescription="@string/a11y_create_direct_message"
android:scaleType="center"
android:src="@drawable/ic_fab_add_chat" android:src="@drawable/ic_fab_add_chat"
android:visibility="gone" android:visibility="gone"
app:maxImageSize="34dp" app:maxImageSize="34dp"
tools:layout_marginEnd="80dp" app:layoutDescription="@xml/motion_scene_dms_fab_menu"
tools:showPaths="true"
tools:visibility="visible" /> tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/motion_scene_dms_fab_menu"
tools:motionProgress="0.65"
tools:parentTag="androidx.constraintlayout.motion.widget.MotionLayout"
tools:showPaths="true">
<View
android:id="@+id/dmsCreateRoomTouchGuard"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_touch_guard_bg"
android:clickable="true"
android:contentDescription="@string/a11y_create_menu_close"
android:focusable="true" />
<!-- Sub menu item 2 -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/createDmByQrCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:accessibilityTraversalBefore="@+id/roomListView"
android:contentDescription="@string/a11y_create_direct_message_by_qr_code"
android:src="@drawable/ic_fab_add_by_qr_code"
app:backgroundTint="#FFFFFF"
app:fabCustomSize="48dp"
app:maxImageSize="26dp"
app:tint="@color/black" />
<TextView
android:id="@+id/createDmByQrCodeLabel"
style="@style/VectorLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:importantForAccessibility="no"
android:text="@string/add_by_qr_code" />
<!-- Sub menu item 1 -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/createDmByMxid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:accessibilityTraversalBefore="@+id/createDmByQrCode"
android:contentDescription="@string/a11y_create_direct_message_by_mxid"
android:src="@drawable/ic_fab_add_by_mxid"
app:backgroundTint="#FFFFFF"
app:fabCustomSize="48dp"
app:maxImageSize="29dp"
app:tint="@color/black" />
<TextView
android:id="@+id/createDmByMxidLabel"
style="@style/VectorLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:importantForAccessibility="no"
android:text="@string/add_by_matrix_id" />
<!-- Menu -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/dmsCreateRoomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:accessibilityTraversalBefore="@+id/createDmByMxid"
android:contentDescription="@string/a11y_create_menu_open"
android:src="@drawable/ic_fab_add"
app:maxImageSize="14dp" />
</merge>

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layoutDescription="@xml/motion_scene_fab_menu" app:layoutDescription="@xml/motion_scene_notifs_fab_menu"
tools:motionProgress="0.65" tools:motionProgress="0.65"
tools:parentTag="androidx.constraintlayout.motion.widget.MotionLayout" tools:parentTag="androidx.constraintlayout.motion.widget.MotionLayout"
tools:showPaths="true"> tools:showPaths="true">

View File

@ -1761,6 +1761,7 @@
<string name="link_copied_to_clipboard">Link copied to clipboard</string> <string name="link_copied_to_clipboard">Link copied to clipboard</string>
<string name="add_by_matrix_id">Add by matrix ID</string> <string name="add_by_matrix_id">Add by matrix ID</string>
<string name="add_by_qr_code">Add by QR code</string>
<string name="creating_direct_room">"Creating room…"</string> <string name="creating_direct_room">"Creating room…"</string>
<string name="direct_room_no_known_users">"No result found, use Add by matrix ID to search on server."</string> <string name="direct_room_no_known_users">"No result found, use Add by matrix ID to search on server."</string>
<string name="direct_room_start_search">"Start typing to get results"</string> <string name="direct_room_start_search">"Start typing to get results"</string>
@ -1828,6 +1829,8 @@
<string name="a11y_create_menu_open">Open the create room menu</string> <string name="a11y_create_menu_open">Open the create room menu</string>
<string name="a11y_create_menu_close">Close the create room menu…</string> <string name="a11y_create_menu_close">Close the create room menu…</string>
<string name="a11y_create_direct_message">Create a new direct conversation</string> <string name="a11y_create_direct_message">Create a new direct conversation</string>
<string name="a11y_create_direct_message_by_mxid">Create a new direct conversation by Matrix ID</string>
<string name="a11y_create_direct_message_by_qr_code">Create a new direct conversation by scanning a QR code</string>
<string name="a11y_create_room">Create a new room</string> <string name="a11y_create_room">Create a new room</string>
<string name="a11y_close_keys_backup_banner">Close keys backup banner</string> <string name="a11y_close_keys_backup_banner">Close keys backup banner</string>
<string name="a11y_show_password">Show password</string> <string name="a11y_show_password">Show password</string>
@ -2674,4 +2677,10 @@
<string name="warning_room_not_created_yet">The room is not yet created. Cancel the room creation?</string> <string name="warning_room_not_created_yet">The room is not yet created. Cancel the room creation?</string>
<string name="warning_unsaved_change">There are unsaved changes. Discard the changes?</string> <string name="warning_unsaved_change">There are unsaved changes. Discard the changes?</string>
<string name="warning_unsaved_change_discard">Discard changes</string> <string name="warning_unsaved_change_discard">Discard changes</string>
<!-- Add by QR code -->
<string name="share_by_text">Share by text</string>
<string name="cannot_dm_self">Cannot DM yourself!</string>
<string name="invalid_qr_code_uri">Invalid QR code (Invalid URI)!</string>
<string name="qr_code_not_scanned">QR code not scanned!</string>
</resources> </resources>

View File

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<!-- Click on main FAB: toggle -->
<Transition
motion:constraintSetEnd="@+id/constraint_set_fab_menu_open"
motion:constraintSetStart="@+id/constraint_set_fab_menu_close"
motion:duration="300"
motion:motionInterpolator="easeInOut">
<OnClick
motion:clickAction="toggle"
motion:targetId="@+id/dmsCreateRoomButton" />
<KeyFrameSet>
<!-- First icon goes up quickly to let room for other-->
<KeyPosition
motion:framePosition="50"
motion:keyPositionType="deltaRelative"
motion:motionTarget="@id/createDmByQrCode"
motion:percentX="0.8"
motion:percentY="0.8" />
<KeyPosition
motion:framePosition="50"
motion:keyPositionType="deltaRelative"
motion:motionTarget="@id/createDmByQrCodeLabel"
motion:percentX="0.9"
motion:percentY="0.8" />
<!-- Delay apparition of labels-->
<KeyAttribute
android:alpha="0.4"
motion:framePosition="80"
motion:motionTarget="@id/createDmByMxidLabel" />
<KeyAttribute
android:alpha="0.4"
motion:framePosition="80"
motion:motionTarget="@id/createDmByQrCodeLabel" />
</KeyFrameSet>
</Transition>
<ConstraintSet android:id="@+id/constraint_set_fab_menu_close">
<Constraint
android:id="@+id/dmsCreateRoomTouchGuard"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_touch_guard_bg"
android:visibility="invisible" />
<!-- Sub menu item 2 -->
<Constraint
android:id="@+id/createDmByQrCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:src="@drawable/ic_fab_add_room"
android:visibility="invisible"
motion:backgroundTint="#FFFFFF"
motion:fabCustomSize="48dp"
motion:layout_constraintBottom_toBottomOf="@+id/dmsCreateRoomButton"
motion:layout_constraintEnd_toEndOf="@+id/dmsCreateRoomButton"
motion:layout_constraintStart_toStartOf="@+id/dmsCreateRoomButton"
motion:layout_constraintTop_toTopOf="@+id/dmsCreateRoomButton"
motion:maxImageSize="26dp"
motion:tint="@color/black" />
<Constraint
android:id="@+id/createDmByQrCodeLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/fab_menu_create_room"
android:visibility="invisible"
motion:layout_constraintBottom_toBottomOf="@+id/createDmByQrCode"
motion:layout_constraintEnd_toEndOf="@+id/createDmByQrCode"
motion:layout_constraintTop_toTopOf="@+id/createDmByQrCode" />
<!-- Sub menu item 1 -->
<Constraint
android:id="@+id/createDmByMxid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:src="@drawable/ic_fab_add_chat"
android:visibility="invisible"
motion:backgroundTint="#FFFFFF"
motion:fabCustomSize="48dp"
motion:layout_constraintBottom_toBottomOf="@+id/dmsCreateRoomButton"
motion:layout_constraintEnd_toEndOf="@+id/dmsCreateRoomButton"
motion:layout_constraintStart_toStartOf="@+id/dmsCreateRoomButton"
motion:layout_constraintTop_toTopOf="@+id/dmsCreateRoomButton"
motion:maxImageSize="29dp"
motion:tint="@color/black" />
<Constraint
android:id="@+id/createDmByMxidLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/fab_menu_create_chat"
android:visibility="invisible"
motion:layout_constraintBottom_toBottomOf="@+id/createDmByMxid"
motion:layout_constraintEnd_toEndOf="@+id/createDmByMxid"
motion:layout_constraintTop_toTopOf="@+id/createDmByMxid" />
<!-- Menu -->
<Constraint
android:id="@+id/dmsCreateRoomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:src="@drawable/ic_fab_add"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:maxImageSize="14dp" />
</ConstraintSet>
<ConstraintSet android:id="@+id/constraint_set_fab_menu_open">
<Constraint
android:id="@+id/dmsCreateRoomTouchGuard"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_touch_guard_bg" />
<!-- Sub menu item 2 -->
<Constraint
android:id="@+id/createDmByQrCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginBottom="14dp"
android:src="@drawable/ic_fab_add_room"
motion:backgroundTint="#FFFFFF"
motion:fabCustomSize="48dp"
motion:layout_constraintBottom_toTopOf="@+id/createDmByMxid"
motion:layout_constraintEnd_toEndOf="@+id/dmsCreateRoomButton"
motion:layout_constraintStart_toStartOf="@+id/dmsCreateRoomButton"
motion:maxImageSize="26dp"
motion:tint="@color/black" />
<Constraint
android:id="@+id/createDmByQrCodeLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/fab_menu_create_room"
motion:layout_constraintBottom_toBottomOf="@+id/createDmByQrCode"
motion:layout_constraintEnd_toStartOf="@+id/createDmByQrCode"
motion:layout_constraintTop_toTopOf="@+id/createDmByQrCode" />
<!-- Sub menu item 1 -->
<Constraint
android:id="@+id/createDmByMxid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginBottom="25dp"
android:src="@drawable/ic_fab_add_chat"
motion:backgroundTint="#FFFFFF"
motion:fabCustomSize="48dp"
motion:layout_constraintBottom_toTopOf="@+id/dmsCreateRoomButton"
motion:layout_constraintEnd_toEndOf="@+id/dmsCreateRoomButton"
motion:layout_constraintStart_toStartOf="@+id/dmsCreateRoomButton"
motion:maxImageSize="29dp"
motion:tint="@color/black" />
<Constraint
android:id="@+id/createDmByMxidLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/fab_menu_create_chat"
motion:layout_constraintBottom_toBottomOf="@+id/createDmByMxid"
motion:layout_constraintEnd_toStartOf="@+id/createDmByMxid"
motion:layout_constraintTop_toTopOf="@+id/createDmByMxid" />
<!-- Menu -->
<Constraint
android:id="@+id/dmsCreateRoomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:rotation="135"
android:src="@drawable/ic_fab_add"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:maxImageSize="14dp" />
</ConstraintSet>
</MotionScene>