Post Creation Share flow

This commit is contained in:
Valere 2021-02-25 18:22:16 +01:00
parent 7d2d7b411e
commit a901e1d179
19 changed files with 454 additions and 131 deletions

View File

@ -77,6 +77,7 @@ import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet
import im.vector.app.features.share.IncomingShareActivity import im.vector.app.features.share.IncomingShareActivity
import im.vector.app.features.signout.soft.SoftLogoutActivity import im.vector.app.features.signout.soft.SoftLogoutActivity
import im.vector.app.features.spaces.ShareSpaceBottomSheet
import im.vector.app.features.spaces.SpaceCreationActivity import im.vector.app.features.spaces.SpaceCreationActivity
import im.vector.app.features.terms.ReviewTermsActivity import im.vector.app.features.terms.ReviewTermsActivity
import im.vector.app.features.ui.UiStateRepository import im.vector.app.features.ui.UiStateRepository
@ -175,6 +176,7 @@ interface ScreenComponent {
fun inject(bottomSheet: CallControlsBottomSheet) fun inject(bottomSheet: CallControlsBottomSheet)
fun inject(bottomSheet: SignOutBottomSheetDialogFragment) fun inject(bottomSheet: SignOutBottomSheetDialogFragment)
fun inject(bottomSheet: MatrixToBottomSheet) fun inject(bottomSheet: MatrixToBottomSheet)
fun inject(bottomSheet: ShareSpaceBottomSheet)
/* ========================================================================================== /* ==========================================================================================
* Others * Others

View File

@ -22,6 +22,7 @@ import android.util.TypedValue
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R import im.vector.app.R
@ -48,12 +49,14 @@ abstract class HomeSpaceSummaryItem : VectorEpoxyModel<HomeSpaceSummaryItem.Hold
holder.avatarImageView.background = ContextCompat.getDrawable(holder.view.context, R.drawable.space_home_background) holder.avatarImageView.background = ContextCompat.getDrawable(holder.view.context, R.drawable.space_home_background)
holder.avatarImageView.setImageResource(R.drawable.ic_space_home) holder.avatarImageView.setImageResource(R.drawable.ic_space_home)
holder.avatarImageView.scaleType = ImageView.ScaleType.CENTER_INSIDE holder.avatarImageView.scaleType = ImageView.ScaleType.CENTER_INSIDE
holder.leaveView.isVisible = false
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {
val avatarImageView by bind<ImageView>(R.id.groupAvatarImageView) val avatarImageView by bind<ImageView>(R.id.groupAvatarImageView)
val groupNameView by bind<TextView>(R.id.groupNameView) val groupNameView by bind<TextView>(R.id.groupNameView)
val rootView by bind<CheckableConstraintLayout>(R.id.itemGroupLayout) val rootView by bind<CheckableConstraintLayout>(R.id.itemGroupLayout)
val leaveView by bind<ImageView>(R.id.groupTmpLeave)
} }
fun dpToPx(resources: Resources, dp: Int): Int { fun dpToPx(resources: Resources, dp: Int): Int {

View File

@ -111,14 +111,10 @@ class HomeActivity :
val defaultRoomsId = activityResult.data?.extras?.getString(SpaceCreationActivity.RESULT_DATA_DEFAULT_ROOM_ID) val defaultRoomsId = activityResult.data?.extras?.getString(SpaceCreationActivity.RESULT_DATA_DEFAULT_ROOM_ID)
views.drawerLayout.closeDrawer(GravityCompat.START) views.drawerLayout.closeDrawer(GravityCompat.START)
// Here we want to change current space to the newly created one, and then immediatly open the default room // Here we want to change current space to the newly created one, and then immediately open the default room
if (spaceId != null) { if (spaceId != null) {
navigator.switchToSpace(this, spaceId, defaultRoomsId) navigator.switchToSpace(this, spaceId, defaultRoomsId, true)
} }
// Also we should show the share space bottomsheet
} else {
// viewModel.handle(CrossSigningSettingsAction.ReAuthCancelled)
} }
} }

View File

@ -91,7 +91,9 @@ import im.vector.app.core.intent.getMimeTypeFromUri
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.platform.showOptimizedSnackbar import im.vector.app.core.platform.showOptimizedSnackbar
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.ui.views.ActiveConferenceView
import im.vector.app.core.ui.views.CurrentCallsView import im.vector.app.core.ui.views.CurrentCallsView
import im.vector.app.core.ui.views.JumpToReadMarkerView
import im.vector.app.core.ui.views.KnownCallsViewHolder import im.vector.app.core.ui.views.KnownCallsViewHolder
import im.vector.app.core.ui.views.ActiveConferenceView import im.vector.app.core.ui.views.ActiveConferenceView
import im.vector.app.core.ui.views.FailedMessagesWarningView import im.vector.app.core.ui.views.FailedMessagesWarningView
@ -163,6 +165,7 @@ import im.vector.app.features.roomprofile.RoomProfileActivity
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.share.SharedData import im.vector.app.features.share.SharedData
import im.vector.app.features.spaces.ShareSpaceBottomSheet
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetActivity
import im.vector.app.features.widgets.WidgetArgs import im.vector.app.features.widgets.WidgetArgs
@ -210,7 +213,8 @@ import javax.inject.Inject
data class RoomDetailArgs( data class RoomDetailArgs(
val roomId: String, val roomId: String,
val eventId: String? = null, val eventId: String? = null,
val sharedData: SharedData? = null val sharedData: SharedData? = null,
val openShareSpaceForId: String? = null
) : Parcelable ) : Parcelable
class RoomDetailFragment @Inject constructor( class RoomDetailFragment @Inject constructor(
@ -293,7 +297,7 @@ class RoomDetailFragment @Inject constructor(
private lateinit var attachmentsHelper: AttachmentsHelper private lateinit var attachmentsHelper: AttachmentsHelper
private lateinit var keyboardStateUtils: KeyboardStateUtils private lateinit var keyboardStateUtils: KeyboardStateUtils
private lateinit var callActionsHandler : StartCallActionsHandler private lateinit var callActionsHandler: StartCallActionsHandler
private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView
@ -358,9 +362,9 @@ class RoomDetailFragment @Inject constructor(
} }
when (mode) { when (mode) {
is SendMode.REGULAR -> renderRegularMode(mode.text) is SendMode.REGULAR -> renderRegularMode(mode.text)
is SendMode.EDIT -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text) is SendMode.EDIT -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text)
is SendMode.QUOTE -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text) is SendMode.QUOTE -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text)
is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text)
} }
} }
@ -384,29 +388,30 @@ class RoomDetailFragment @Inject constructor(
RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager() RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager()
is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it) is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it)
is RoomDetailViewEvents.DisplayEnableIntegrationsWarning -> displayDisabledIntegrationDialog() is RoomDetailViewEvents.DisplayEnableIntegrationsWarning -> displayDisabledIntegrationDialog()
is RoomDetailViewEvents.OpenIntegrationManager -> openIntegrationManager() is RoomDetailViewEvents.OpenIntegrationManager -> openIntegrationManager()
is RoomDetailViewEvents.OpenFile -> startOpenFileIntent(it) is RoomDetailViewEvents.OpenFile -> startOpenFileIntent(it)
RoomDetailViewEvents.OpenActiveWidgetBottomSheet -> onViewWidgetsClicked() RoomDetailViewEvents.OpenActiveWidgetBottomSheet -> onViewWidgetsClicked()
is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message) is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message)
is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo) is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo)
RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView() RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView()
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView() RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it) is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it) is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it)
RoomDetailViewEvents.OpenInvitePeople -> navigator.openInviteUsersToRoom(requireContext(), roomDetailArgs.roomId) RoomDetailViewEvents.OpenInvitePeople -> navigator.openInviteUsersToRoom(requireContext(), roomDetailArgs.roomId)
RoomDetailViewEvents.OpenSetRoomAvatarDialog -> galleryOrCameraDialogHelper.show() RoomDetailViewEvents.OpenSetRoomAvatarDialog -> galleryOrCameraDialogHelper.show()
RoomDetailViewEvents.OpenRoomSettings -> handleOpenRoomSettings() RoomDetailViewEvents.OpenRoomSettings -> handleOpenRoomSettings()
is RoomDetailViewEvents.ShowRoomAvatarFullScreen -> it.matrixItem?.let { item -> is RoomDetailViewEvents.ShowRoomAvatarFullScreen -> it.matrixItem?.let { item ->
navigator.openBigImageViewer(requireActivity(), it.view, item) navigator.openBigImageViewer(requireActivity(), it.view, item)
} }
is RoomDetailViewEvents.StartChatEffect -> handleChatEffect(it.type) is RoomDetailViewEvents.StartChatEffect -> handleChatEffect(it.type)
RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects() RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects()
is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it) is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it)
}.exhaustive }.exhaustive
} }
if (savedInstanceState == null) { if (savedInstanceState == null) {
handleShareData() handleShareData()
handleSpaceShare()
} }
} }
@ -419,7 +424,7 @@ class RoomDetailFragment @Inject constructor(
startActivity(intent) startActivity(intent)
} }
private fun handleChatEffect(chatEffect: ChatEffect) { private fun handleChatEffect(chatEffect: ChatEffect) {
when (chatEffect) { when (chatEffect) {
ChatEffect.CONFETTI -> { ChatEffect.CONFETTI -> {
views.viewKonfetti.isVisible = true views.viewKonfetti.isVisible = true
@ -434,7 +439,7 @@ class RoomDetailFragment @Inject constructor(
.setPosition(-50f, views.viewKonfetti.width + 50f, -50f, -50f) .setPosition(-50f, views.viewKonfetti.width + 50f, -50f, -50f)
.streamFor(150, 3000L) .streamFor(150, 3000L)
} }
ChatEffect.SNOW -> { ChatEffect.SNOW -> {
views.viewSnowFall.isVisible = true views.viewSnowFall.isVisible = true
views.viewSnowFall.restartFalling() views.viewSnowFall.restartFalling()
} }
@ -627,17 +632,26 @@ class RoomDetailFragment @Inject constructor(
private fun handleShareData() { private fun handleShareData() {
when (val sharedData = roomDetailArgs.sharedData) { when (val sharedData = roomDetailArgs.sharedData) {
is SharedData.Text -> { is SharedData.Text -> {
roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true)) roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true))
} }
is SharedData.Attachments -> { is SharedData.Attachments -> {
// open share edition // open share edition
onContentAttachmentsReady(sharedData.attachmentData) onContentAttachmentsReady(sharedData.attachmentData)
} }
null -> Timber.v("No share data to process") null -> Timber.v("No share data to process")
}.exhaustive }.exhaustive
} }
private fun handleSpaceShare() {
roomDetailArgs.openShareSpaceForId?.let { spaceId ->
ShareSpaceBottomSheet.show(childFragmentManager, spaceId)
view?.post {
handleChatEffect(ChatEffect.CONFETTI)
}
}
}
override fun onDestroyView() { override fun onDestroyView() {
timelineEventController.callback = null timelineEventController.callback = null
timelineEventController.removeModelBuildListener(modelBuildListener) timelineEventController.removeModelBuildListener(modelBuildListener)
@ -760,8 +774,8 @@ class RoomDetailFragment @Inject constructor(
withState(roomDetailViewModel) { state -> withState(roomDetailViewModel) { state ->
// Set the visual state of the call buttons (voice/video) to enabled/disabled according to user permissions // Set the visual state of the call buttons (voice/video) to enabled/disabled according to user permissions
val callButtonsEnabled = when (state.asyncRoomSummary.invoke()?.joinedMembersCount) { val callButtonsEnabled = when (state.asyncRoomSummary.invoke()?.joinedMembersCount) {
1 -> false 1 -> false
2 -> state.isAllowedToStartWebRTCCall 2 -> state.isAllowedToStartWebRTCCall
else -> state.isAllowedToManageWidgets else -> state.isAllowedToManageWidgets
} }
setOf(R.id.voice_call, R.id.video_call).forEach { setOf(R.id.voice_call, R.id.video_call).forEach {
@ -791,7 +805,7 @@ class RoomDetailFragment @Inject constructor(
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.invite -> { R.id.invite -> {
navigator.openInviteUsersToRoom(requireActivity(), roomDetailArgs.roomId) navigator.openInviteUsersToRoom(requireActivity(), roomDetailArgs.roomId)
true true
} }
@ -807,19 +821,19 @@ class RoomDetailFragment @Inject constructor(
callActionsHandler.onVoiceCallClicked() callActionsHandler.onVoiceCallClicked()
true true
} }
R.id.video_call -> { R.id.video_call -> {
callActionsHandler.onVideoCallClicked() callActionsHandler.onVideoCallClicked()
true true
} }
R.id.hangup_call -> { R.id.hangup_call -> {
roomDetailViewModel.handle(RoomDetailAction.EndCall) roomDetailViewModel.handle(RoomDetailAction.EndCall)
true true
} }
R.id.search -> { R.id.search -> {
handleSearchAction() handleSearchAction()
true true
} }
R.id.dev_tools -> { R.id.dev_tools -> {
navigator.openDevTools(requireContext(), roomDetailArgs.roomId) navigator.openDevTools(requireContext(), roomDetailArgs.roomId)
true true
} }
@ -921,9 +935,9 @@ class RoomDetailFragment @Inject constructor(
when (roomDetailPendingAction) { when (roomDetailPendingAction) {
is RoomDetailPendingAction.JumpToReadReceipt -> is RoomDetailPendingAction.JumpToReadReceipt ->
roomDetailViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId)) roomDetailViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId))
is RoomDetailPendingAction.MentionUser -> is RoomDetailPendingAction.MentionUser ->
insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId) insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId)
is RoomDetailPendingAction.OpenOrCreateDm -> is RoomDetailPendingAction.OpenOrCreateDm ->
roomDetailViewModel.handle(RoomDetailAction.OpenOrCreateDm(roomDetailPendingAction.userId)) roomDetailViewModel.handle(RoomDetailAction.OpenOrCreateDm(roomDetailPendingAction.userId))
}.exhaustive }.exhaustive
} }
@ -1078,9 +1092,9 @@ class RoomDetailFragment @Inject constructor(
withState(roomDetailViewModel) { withState(roomDetailViewModel) {
val showJumpToUnreadBanner = when (it.unreadState) { val showJumpToUnreadBanner = when (it.unreadState) {
UnreadState.Unknown, UnreadState.Unknown,
UnreadState.HasNoUnread -> false UnreadState.HasNoUnread -> false
is UnreadState.ReadMarkerNotLoaded -> true is UnreadState.ReadMarkerNotLoaded -> true
is UnreadState.HasUnread -> { is UnreadState.HasUnread -> {
if (it.canShowJumpToReadMarker) { if (it.canShowJumpToReadMarker) {
val lastVisibleItem = layoutManager.findLastVisibleItemPosition() val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
val positionOfReadMarker = timelineEventController.getPositionOfReadMarker() val positionOfReadMarker = timelineEventController.getPositionOfReadMarker()
@ -1268,7 +1282,7 @@ class RoomDetailFragment @Inject constructor(
navigator.openRoom(vectorBaseActivity, async()) navigator.openRoom(vectorBaseActivity, async())
vectorBaseActivity.finish() vectorBaseActivity.finish()
} }
is Fail -> { is Fail -> {
vectorBaseActivity.hideWaitingView() vectorBaseActivity.hideWaitingView()
vectorBaseActivity.toast(errorFormatter.toHumanReadable(async.error)) vectorBaseActivity.toast(errorFormatter.toHumanReadable(async.error))
} }
@ -1277,19 +1291,19 @@ class RoomDetailFragment @Inject constructor(
private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) { private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) {
when (sendMessageResult) { when (sendMessageResult) {
is RoomDetailViewEvents.SlashCommandHandled -> { is RoomDetailViewEvents.SlashCommandHandled -> {
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
} }
is RoomDetailViewEvents.SlashCommandError -> { is RoomDetailViewEvents.SlashCommandError -> {
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
} }
is RoomDetailViewEvents.SlashCommandUnknown -> { is RoomDetailViewEvents.SlashCommandUnknown -> {
displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command))
} }
is RoomDetailViewEvents.SlashCommandResultOk -> { is RoomDetailViewEvents.SlashCommandResultOk -> {
updateComposerText("") updateComposerText("")
} }
is RoomDetailViewEvents.SlashCommandResultError -> { is RoomDetailViewEvents.SlashCommandResultError -> {
displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable)) displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable))
} }
is RoomDetailViewEvents.SlashCommandNotImplemented -> { is RoomDetailViewEvents.SlashCommandNotImplemented -> {
@ -1311,7 +1325,7 @@ class RoomDetailFragment @Inject constructor(
private fun displayE2eError(withHeldCode: WithHeldCode?) { private fun displayE2eError(withHeldCode: WithHeldCode?) {
val msgId = when (withHeldCode) { val msgId = when (withHeldCode) {
WithHeldCode.BLACKLISTED -> R.string.crypto_error_withheld_blacklisted WithHeldCode.BLACKLISTED -> R.string.crypto_error_withheld_blacklisted
WithHeldCode.UNVERIFIED -> R.string.crypto_error_withheld_unverified WithHeldCode.UNVERIFIED -> R.string.crypto_error_withheld_unverified
WithHeldCode.UNAUTHORISED, WithHeldCode.UNAUTHORISED,
WithHeldCode.UNAVAILABLE -> R.string.crypto_error_withheld_generic WithHeldCode.UNAVAILABLE -> R.string.crypto_error_withheld_generic
else -> R.string.notice_crypto_unable_to_decrypt_friendly_desc else -> R.string.notice_crypto_unable_to_decrypt_friendly_desc
@ -1362,7 +1376,7 @@ class RoomDetailFragment @Inject constructor(
private fun displayRoomDetailActionSuccess(result: RoomDetailViewEvents.ActionSuccess) { private fun displayRoomDetailActionSuccess(result: RoomDetailViewEvents.ActionSuccess) {
when (val data = result.action) { when (val data = result.action) {
is RoomDetailAction.ReportContent -> { is RoomDetailAction.ReportContent -> {
when { when {
data.spam -> { data.spam -> {
AlertDialog.Builder(requireActivity()) AlertDialog.Builder(requireActivity())
@ -1399,7 +1413,7 @@ class RoomDetailFragment @Inject constructor(
} }
} }
} }
is RoomDetailAction.RequestVerification -> { is RoomDetailAction.RequestVerification -> {
Timber.v("## SAS RequestVerification action") Timber.v("## SAS RequestVerification action")
VerificationBottomSheet.withArgs( VerificationBottomSheet.withArgs(
roomDetailArgs.roomId, roomDetailArgs.roomId,
@ -1414,7 +1428,7 @@ class RoomDetailFragment @Inject constructor(
data.transactionId data.transactionId
).show(parentFragmentManager, "REQ") ).show(parentFragmentManager, "REQ")
} }
is RoomDetailAction.ResumeVerification -> { is RoomDetailAction.ResumeVerification -> {
val otherUserId = data.otherUserId ?: return val otherUserId = data.otherUserId ?: return
VerificationBottomSheet().apply { VerificationBottomSheet().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
@ -1557,11 +1571,11 @@ class RoomDetailFragment @Inject constructor(
is MessageVerificationRequestContent -> { is MessageVerificationRequestContent -> {
roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId, null)) roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId, null))
} }
is MessageWithAttachmentContent -> { is MessageWithAttachmentContent -> {
val action = RoomDetailAction.DownloadOrOpen(informationData.eventId, informationData.senderId, messageContent) val action = RoomDetailAction.DownloadOrOpen(informationData.eventId, informationData.senderId, messageContent)
roomDetailViewModel.handle(action) roomDetailViewModel.handle(action)
} }
is EncryptedEventContent -> { is EncryptedEventContent -> {
roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId)) roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId))
} }
} }
@ -1724,75 +1738,75 @@ class RoomDetailFragment @Inject constructor(
private fun handleActions(action: EventSharedAction) { private fun handleActions(action: EventSharedAction) {
when (action) { when (action) {
is EventSharedAction.OpenUserProfile -> { is EventSharedAction.OpenUserProfile -> {
openRoomMemberProfile(action.userId) openRoomMemberProfile(action.userId)
} }
is EventSharedAction.AddReaction -> { is EventSharedAction.AddReaction -> {
emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), action.eventId)) emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), action.eventId))
} }
is EventSharedAction.ViewReactions -> { is EventSharedAction.ViewReactions -> {
ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData) ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData)
.show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS")
} }
is EventSharedAction.Copy -> { is EventSharedAction.Copy -> {
// I need info about the current selected message :/ // I need info about the current selected message :/
copyToClipboard(requireContext(), action.content, false) copyToClipboard(requireContext(), action.content, false)
showSnackWithMessage(getString(R.string.copied_to_clipboard)) showSnackWithMessage(getString(R.string.copied_to_clipboard))
} }
is EventSharedAction.Redact -> { is EventSharedAction.Redact -> {
promptConfirmationToRedactEvent(action) promptConfirmationToRedactEvent(action)
} }
is EventSharedAction.Share -> { is EventSharedAction.Share -> {
onShareActionClicked(action) onShareActionClicked(action)
} }
is EventSharedAction.Save -> { is EventSharedAction.Save -> {
onSaveActionClicked(action) onSaveActionClicked(action)
} }
is EventSharedAction.ViewEditHistory -> { is EventSharedAction.ViewEditHistory -> {
onEditedDecorationClicked(action.messageInformationData) onEditedDecorationClicked(action.messageInformationData)
} }
is EventSharedAction.ViewSource -> { is EventSharedAction.ViewSource -> {
JSonViewerDialog.newInstance( JSonViewerDialog.newInstance(
action.content, action.content,
-1, -1,
createJSonViewerStyleProvider(colorProvider) createJSonViewerStyleProvider(colorProvider)
).show(childFragmentManager, "JSON_VIEWER") ).show(childFragmentManager, "JSON_VIEWER")
} }
is EventSharedAction.ViewDecryptedSource -> { is EventSharedAction.ViewDecryptedSource -> {
JSonViewerDialog.newInstance( JSonViewerDialog.newInstance(
action.content, action.content,
-1, -1,
createJSonViewerStyleProvider(colorProvider) createJSonViewerStyleProvider(colorProvider)
).show(childFragmentManager, "JSON_VIEWER") ).show(childFragmentManager, "JSON_VIEWER")
} }
is EventSharedAction.QuickReact -> { is EventSharedAction.QuickReact -> {
// eventId,ClickedOn,Add // eventId,ClickedOn,Add
roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add)) roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
} }
is EventSharedAction.Edit -> { is EventSharedAction.Edit -> {
roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, views.composerLayout.text.toString())) roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, views.composerLayout.text.toString()))
} }
is EventSharedAction.Quote -> { is EventSharedAction.Quote -> {
roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, views.composerLayout.text.toString())) roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, views.composerLayout.text.toString()))
} }
is EventSharedAction.Reply -> { is EventSharedAction.Reply -> {
roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, views.composerLayout.text.toString())) roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, views.composerLayout.text.toString()))
} }
is EventSharedAction.CopyPermalink -> { is EventSharedAction.CopyPermalink -> {
val permalink = session.permalinkService().createPermalink(roomDetailArgs.roomId, action.eventId) val permalink = session.permalinkService().createPermalink(roomDetailArgs.roomId, action.eventId)
copyToClipboard(requireContext(), permalink, false) copyToClipboard(requireContext(), permalink, false)
showSnackWithMessage(getString(R.string.copied_to_clipboard)) showSnackWithMessage(getString(R.string.copied_to_clipboard))
} }
is EventSharedAction.Resend -> { is EventSharedAction.Resend -> {
roomDetailViewModel.handle(RoomDetailAction.ResendMessage(action.eventId)) roomDetailViewModel.handle(RoomDetailAction.ResendMessage(action.eventId))
} }
is EventSharedAction.Remove -> { is EventSharedAction.Remove -> {
roomDetailViewModel.handle(RoomDetailAction.RemoveFailedEcho(action.eventId)) roomDetailViewModel.handle(RoomDetailAction.RemoveFailedEcho(action.eventId))
} }
is EventSharedAction.Cancel -> { is EventSharedAction.Cancel -> {
handleCancelSend(action) handleCancelSend(action)
} }
is EventSharedAction.ReportContentSpam -> { is EventSharedAction.ReportContentSpam -> {
roomDetailViewModel.handle(RoomDetailAction.ReportContent( roomDetailViewModel.handle(RoomDetailAction.ReportContent(
action.eventId, action.senderId, "This message is spam", spam = true)) action.eventId, action.senderId, "This message is spam", spam = true))
} }
@ -1800,22 +1814,22 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.ReportContent( roomDetailViewModel.handle(RoomDetailAction.ReportContent(
action.eventId, action.senderId, "This message is inappropriate", inappropriate = true)) action.eventId, action.senderId, "This message is inappropriate", inappropriate = true))
} }
is EventSharedAction.ReportContentCustom -> { is EventSharedAction.ReportContentCustom -> {
promptReasonToReportContent(action) promptReasonToReportContent(action)
} }
is EventSharedAction.IgnoreUser -> { is EventSharedAction.IgnoreUser -> {
action.senderId?.let { askConfirmationToIgnoreUser(it) } action.senderId?.let { askConfirmationToIgnoreUser(it) }
} }
is EventSharedAction.OnUrlClicked -> { is EventSharedAction.OnUrlClicked -> {
onUrlClicked(action.url, action.title) onUrlClicked(action.url, action.title)
} }
is EventSharedAction.OnUrlLongClicked -> { is EventSharedAction.OnUrlLongClicked -> {
onUrlLongClicked(action.url) onUrlLongClicked(action.url)
} }
is EventSharedAction.ReRequestKey -> { is EventSharedAction.ReRequestKey -> {
roomDetailViewModel.handle(RoomDetailAction.ReRequestKeys(action.eventId)) roomDetailViewModel.handle(RoomDetailAction.ReRequestKeys(action.eventId))
} }
is EventSharedAction.UseKeyBackup -> { is EventSharedAction.UseKeyBackup -> {
context?.let { context?.let {
startActivity(KeysBackupRestoreActivity.intent(it)) startActivity(KeysBackupRestoreActivity.intent(it))
} }
@ -1955,10 +1969,10 @@ class RoomDetailFragment @Inject constructor(
private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) {
when (type) { when (type) {
AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher) AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher)
AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher)
AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentImageActivityResultLauncher) AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentImageActivityResultLauncher)
AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher) AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher)
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher)
AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment) AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
}.exhaustive }.exhaustive

View File

@ -70,6 +70,7 @@ import im.vector.app.features.roomprofile.RoomProfileActivity
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.share.SharedData import im.vector.app.features.share.SharedData
import im.vector.app.features.spaces.SpacePreviewActivity
import im.vector.app.features.terms.ReviewTermsActivity import im.vector.app.features.terms.ReviewTermsActivity
import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetActivity
import im.vector.app.features.widgets.WidgetArgsBuilder import im.vector.app.features.widgets.WidgetArgsBuilder
@ -102,7 +103,7 @@ class DefaultNavigator @Inject constructor(
startActivity(context, intent, buildTask) startActivity(context, intent, buildTask)
} }
override fun switchToSpace(context: Context, spaceId: String, roomId: String?) { override fun switchToSpace(context: Context, spaceId: String, roomId: String?, openShareSheet: Boolean) {
if (sessionHolder.getSafeActiveSession()?.spaceService()?.getSpace(spaceId) == null) { if (sessionHolder.getSafeActiveSession()?.spaceService()?.getSpace(spaceId) == null) {
fatalError("Trying to open an unknown space $spaceId", vectorPreferences.failFast()) fatalError("Trying to open an unknown space $spaceId", vectorPreferences.failFast())
return return
@ -115,10 +116,16 @@ class DefaultNavigator @Inject constructor(
Timber.d("## Nav: Failed to switch to space $spaceId") Timber.d("## Nav: Failed to switch to space $spaceId")
} }
if (roomId != null) { if (roomId != null) {
openRoom(context, roomId) val args = RoomDetailArgs(roomId, eventId = null, openShareSpaceForId = spaceId.takeIf { openShareSheet })
val intent = RoomDetailActivity.newIntent(context, args)
startActivity(context, intent, false)
} }
} }
override fun openSpacePreview(context: Context, spaceId: String) {
startActivity(context, SpacePreviewActivity.newIntent(context, spaceId), false)
}
override fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) { override fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) {
val session = sessionHolder.getSafeActiveSession() ?: return val session = sessionHolder.getSafeActiveSession() ?: return
val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId) val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId)

View File

@ -38,7 +38,9 @@ interface Navigator {
fun openRoom(context: Context, roomId: String, eventId: String? = null, buildTask: Boolean = false) fun openRoom(context: Context, roomId: String, eventId: String? = null, buildTask: Boolean = false)
fun switchToSpace(context: Context, spaceId: String, roomId: String?) fun switchToSpace(context: Context, spaceId: String, roomId: String?, openShareSheet: Boolean)
fun openSpacePreview(context: Context, spaceId: String)
fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String)

View File

@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.permalinks.PermalinkData 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.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.rx
@ -147,33 +148,43 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
val membership = roomSummary?.membership val membership = roomSummary?.membership
val eventId = permalinkData.eventId val eventId = permalinkData.eventId
val roomAlias = permalinkData.getRoomAliasOrNull() val roomAlias = permalinkData.getRoomAliasOrNull()
val isSpace = roomSummary?.roomType == RoomType.SPACE
return when { return when {
membership == Membership.BAN -> context.toast(R.string.error_opening_banned_room) membership == Membership.BAN -> context.toast(R.string.error_opening_banned_room)
membership?.isActive().orFalse() -> { membership?.isActive().orFalse() -> {
navigator.openRoom(context, roomId, eventId, buildTask) if (isSpace) {
// navigator.switchToSpace(context, roomId, null)
navigator.openSpacePreview(context, roomId)
} else {
navigator.openRoom(context, roomId, eventId, buildTask)
}
} }
else -> { else -> {
if (roomSummary == null) { if (isSpace) {
// we don't know this room, try to peek navigator.openSpacePreview(context, roomId)
val roomPreviewData = RoomPreviewData(
roomId = roomId,
roomAlias = roomAlias,
peekFromServer = true,
buildTask = buildTask,
homeServers = permalinkData.viaParameters
)
navigator.openRoomPreview(context, roomPreviewData)
} else { } else {
val roomPreviewData = RoomPreviewData( if (roomSummary == null) {
roomId = roomId, // we don't know this room, try to peek
eventId = eventId, val roomPreviewData = RoomPreviewData(
roomAlias = roomAlias ?: roomSummary.canonicalAlias, roomId = roomId,
roomName = roomSummary.displayName, roomAlias = roomAlias,
avatarUrl = roomSummary.avatarUrl, peekFromServer = true,
buildTask = buildTask, buildTask = buildTask,
homeServers = permalinkData.viaParameters homeServers = permalinkData.viaParameters
) )
navigator.openRoomPreview(context, roomPreviewData) navigator.openRoomPreview(context, roomPreviewData)
} else {
val roomPreviewData = RoomPreviewData(
roomId = roomId,
eventId = eventId,
roomAlias = roomAlias ?: roomSummary.canonicalAlias,
roomName = roomSummary.displayName,
avatarUrl = roomSummary.avatarUrl,
buildTask = buildTask,
homeServers = permalinkData.viaParameters
)
navigator.openRoomPreview(context, roomPreviewData)
}
} }
} }
} }

View File

@ -0,0 +1,107 @@
/*
* Copyright (c) 2021 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.spaces
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentManager
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.BottomSheetSpaceInviteBinding
import im.vector.app.features.invite.InviteUsersToRoomActivity
import kotlinx.parcelize.Parcelize
import javax.inject.Inject
class ShareSpaceBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetSpaceInviteBinding>() {
@Parcelize
data class Args(
val spaceId: String
) : Parcelable
override val showExpanded = true
@Inject
lateinit var activeSessionHolder: ActiveSessionHolder
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetSpaceInviteBinding {
return BottomSheetSpaceInviteBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Not going for full view model for now, as it may change
val args: Args = arguments?.getParcelable(EXTRA_ARGS)
?: return Unit.also { dismiss() }
val summary = activeSessionHolder.getSafeActiveSession()?.spaceService()?.getSpace(args.spaceId)?.spaceSummary()
val spaceName = summary?.roomSummary?.name
views.descriptionText.text = getString(R.string.invite_people_to_your_space_desc, spaceName)
views.inviteByMailButton.debouncedClicks {
}
views.inviteByMxidButton.debouncedClicks {
val intent = InviteUsersToRoomActivity.getIntent(requireContext(), args.spaceId)
startActivity(intent)
}
views.inviteByLinkButton.debouncedClicks {
activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createRoomPermalink(args.spaceId)?.let { permalink ->
startSharePlainTextIntent(
fragment = this,
activityResultLauncher = null,
chooserTitle = getString(R.string.share_by_text),
text = getString(R.string.share_space_link_message, spaceName, permalink),
extraTitle = getString(R.string.share_space_link_message, spaceName, permalink)
)
}
}
views.skipButton.debouncedClicks {
dismiss()
}
}
companion object {
const val EXTRA_ARGS = "EXTRA_ARGS"
fun show(fragmentManager: FragmentManager, spaceId: String): ShareSpaceBottomSheet {
return ShareSpaceBottomSheet().apply {
isCancelable = true
arguments = Bundle().apply {
this.putParcelable(EXTRA_ARGS, ShareSpaceBottomSheet.Args(spaceId = spaceId))
}
}.also {
it.show(fragmentManager, ShareSpaceBottomSheet::class.java.name)
}
}
}
}

View File

@ -80,6 +80,10 @@ class SpaceListFragment @Inject constructor(
viewModel.handle(SpaceListAction.SelectSpace(spaceSummary)) viewModel.handle(SpaceListAction.SelectSpace(spaceSummary))
} }
override fun onLeaveSpace(spaceSummary: SpaceSummary) {
viewModel.handle(SpaceListAction.LeaveSpace(spaceSummary))
}
override fun onAddSpaceSelected() { override fun onAddSpaceSelected() {
viewModel.handle(SpaceListAction.AddSpace) viewModel.handle(SpaceListAction.AddSpace)
} }

View File

@ -71,6 +71,7 @@ class SpaceSummaryController @Inject constructor(
matrixItem(it.toMatrixItem()) matrixItem(it.toMatrixItem())
selected(false) selected(false)
listener { callback?.onSpaceSelected(it) } listener { callback?.onSpaceSelected(it) }
// lea { callback?.onSpaceSelected(it) }
} }
} }
genericFooterItem { genericFooterItem {
@ -101,6 +102,7 @@ class SpaceSummaryController @Inject constructor(
id(groupSummary.spaceId) id(groupSummary.spaceId)
matrixItem(groupSummary.toMatrixItem()) matrixItem(groupSummary.toMatrixItem())
selected(isSelected) selected(isSelected)
onLeave { callback?.onLeaveSpace(groupSummary) }
listener { callback?.onSpaceSelected(groupSummary) } listener { callback?.onSpaceSelected(groupSummary) }
} }
} }
@ -118,6 +120,7 @@ class SpaceSummaryController @Inject constructor(
interface Callback { interface Callback {
fun onSpaceSelected(spaceSummary: SpaceSummary) fun onSpaceSelected(spaceSummary: SpaceSummary)
fun onLeaveSpace(spaceSummary: SpaceSummary)
fun onAddSpaceSelected() fun onAddSpaceSelected()
} }
} }

View File

@ -18,12 +18,14 @@ package im.vector.app.features.spaces
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass 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.core.platform.CheckableConstraintLayout import im.vector.app.core.platform.CheckableConstraintLayout
import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
@ -34,12 +36,22 @@ abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() {
@EpoxyAttribute lateinit var matrixItem: MatrixItem @EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var selected: Boolean = false @EpoxyAttribute var selected: Boolean = false
@EpoxyAttribute var listener: (() -> Unit)? = null @EpoxyAttribute var listener: (() -> Unit)? = null
@EpoxyAttribute var onLeave: (() -> Unit)? = null
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
holder.rootView.setOnClickListener { listener?.invoke() } holder.rootView.setOnClickListener { listener?.invoke() }
holder.groupNameView.text = matrixItem.displayName holder.groupNameView.text = matrixItem.displayName
holder.rootView.isChecked = selected holder.rootView.isChecked = selected
if (onLeave != null) {
holder.leaveView.setOnClickListener(
DebouncedClickListener({ _ ->
onLeave?.invoke()
})
)
} else {
holder.leaveView.isVisible = false
}
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView) avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
} }
@ -52,5 +64,6 @@ abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() {
val avatarImageView by bind<ImageView>(R.id.groupAvatarImageView) val avatarImageView by bind<ImageView>(R.id.groupAvatarImageView)
val groupNameView by bind<TextView>(R.id.groupNameView) val groupNameView by bind<TextView>(R.id.groupNameView)
val rootView by bind<CheckableConstraintLayout>(R.id.itemGroupLayout) val rootView by bind<CheckableConstraintLayout>(R.id.itemGroupLayout)
val leaveView by bind<ImageView>(R.id.groupTmpLeave)
} }
} }

View File

@ -16,6 +16,7 @@
package im.vector.app.features.spaces package im.vector.app.features.spaces
import androidx.lifecycle.viewModelScope
import arrow.core.Option import arrow.core.Option
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
@ -34,18 +35,22 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.features.grouplist.SelectedSpaceDataSource import im.vector.app.features.grouplist.SelectedSpaceDataSource
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.functions.BiFunction import io.reactivex.functions.BiFunction
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.session.space.SpaceSummary
import org.matrix.android.sdk.internal.util.awaitCallback
import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.rx
const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID" const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID"
sealed class SpaceListAction : VectorViewModelAction { sealed class SpaceListAction : VectorViewModelAction {
data class SelectSpace(val spaceSummary: SpaceSummary) : SpaceListAction() data class SelectSpace(val spaceSummary: SpaceSummary) : SpaceListAction()
data class LeaveSpace(val spaceSummary: SpaceSummary) : SpaceListAction()
object AddSpace : SpaceListAction() object AddSpace : SpaceListAction()
} }
@ -88,13 +93,18 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
init { init {
observeSpaceSummaries() observeSpaceSummaries()
observeSelectionState() observeSelectionState()
selectedSpaceDataSource.observe().execute { selectedSpaceDataSource
if (this.selectedSpace != it.invoke()?.orNull()) { .observe()
copy( .subscribe {
selectedSpace = it.invoke()?.orNull() if (currentGroupId != it.orNull()?.spaceId) {
) setState {
} else this copy(
} selectedSpace = it.orNull()
)
}
}
}
.disposeOnClear()
} }
private fun observeSelectionState() { private fun observeSelectionState() {
@ -119,11 +129,12 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
override fun handle(action: SpaceListAction) { override fun handle(action: SpaceListAction) {
when (action) { when (action) {
is SpaceListAction.SelectSpace -> handleSelectSpace(action) is SpaceListAction.SelectSpace -> handleSelectSpace(action)
else -> handleAddSpace() is SpaceListAction.LeaveSpace -> handleLeaveSpace(action)
SpaceListAction.AddSpace -> handleAddSpace()
} }
} }
// PRIVATE METHODS ***************************************************************************** // PRIVATE METHODS *****************************************************************************
private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state -> private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state ->
// get uptodate version of the space // get uptodate version of the space
@ -146,6 +157,16 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
} }
} }
private fun handleLeaveSpace(action: SpaceListAction.LeaveSpace) {
viewModelScope.launch {
awaitCallback {
tryOrNull("Failed to leave space ${action.spaceSummary.spaceId}") {
session.spaceService().getSpace(action.spaceSummary.spaceId)?.asRoom()?.leave(null, it)
}
}
}
}
private fun handleAddSpace() { private fun handleAddSpace() {
_viewEvents.post(SpaceListViewEvents.AddSpace) _viewEvents.post(SpaceListViewEvents.AddSpace)
} }

View File

@ -17,6 +17,7 @@
package im.vector.app.features.spaces.create package im.vector.app.features.spaces.create
import android.content.Context import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.util.AttributeSet import android.util.AttributeSet
@ -25,6 +26,7 @@ import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.withStyledAttributes import androidx.core.content.withStyledAttributes
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.databinding.ViewSpaceTypeButtonBinding import im.vector.app.databinding.ViewSpaceTypeButtonBinding
class WizardButtonView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) class WizardButtonView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
@ -36,7 +38,7 @@ class WizardButtonView @JvmOverloads constructor(context: Context, attrs: Attrib
set(value) { set(value) {
if (value != title) { if (value != title) {
field = value field = value
views.title.text = value views.title.setTextOrHide(value)
} }
} }
@ -44,7 +46,7 @@ class WizardButtonView @JvmOverloads constructor(context: Context, attrs: Attrib
set(value) { set(value) {
if (value != subTitle) { if (value != subTitle) {
field = value field = value
views.subTitle.text = value views.subTitle.setTextOrHide(value)
} }
} }
@ -56,15 +58,13 @@ class WizardButtonView @JvmOverloads constructor(context: Context, attrs: Attrib
} }
} }
// private var tint: Int? = null private var tint: Int? = null
// set(value) { set(value) {
// field = value field = value
// if (value != null) { if (value != null) {
// views.buttonImageView.imageTintList = ColorStateList.valueOf(value) views.buttonImageView.imageTintList = ColorStateList.valueOf(value)
// } else { }
// views.buttonImageView.clearColorFilter() }
// }
// }
// var action: (() -> Unit)? = null // var action: (() -> Unit)? = null
@ -72,16 +72,19 @@ class WizardButtonView @JvmOverloads constructor(context: Context, attrs: Attrib
inflate(context, R.layout.view_space_type_button, this) inflate(context, R.layout.view_space_type_button, this)
views = ViewSpaceTypeButtonBinding.bind(this) views = ViewSpaceTypeButtonBinding.bind(this)
views.subTitle.setTextOrHide(null)
if (isInEditMode) { if (isInEditMode) {
title = "Title" title = "Title"
subTitle = "This is doing something" subTitle = "This is doing something"
} }
context.withStyledAttributes(attrs, R.styleable.WizardButtonView) { context.withStyledAttributes(attrs, R.styleable.WizardButtonView) {
title = getString(R.styleable.WizardButtonView_title) ?: "" title = getString(R.styleable.WizardButtonView_title)
subTitle = getString(R.styleable.WizardButtonView_subTitle) ?: "" subTitle = getString(R.styleable.WizardButtonView_subTitle)
icon = getDrawable(R.styleable.WizardButtonView_icon) icon = getDrawable(R.styleable.WizardButtonView_icon)
// tint = getColor(R.styleable.WizardButtonView_iconTint, ThemeUtils.getColor(context, R.attr.riotx_text_primary)) tint = getColor(R.styleable.WizardButtonView_iconTint, -1)
.takeIf { it != -1 }
} }
val outValue = TypedValue() val outValue = TypedValue()

View File

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M4,4H20C21.1,4 22,4.9 22,6V18C22,19.1 21.1,20 20,20H4C2.9,20 2,19.1 2,18V6C2,4.9 2.9,4 4,4Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#737D8C"
android:strokeLineCap="round"/>
<path
android:pathData="M22,6L12,13L2,6"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#737D8C"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12.5285,6.5409L13.0273,6.0421C14.4052,4.6643 16.6259,4.651 17.9874,6.0125C19.349,7.374 19.3357,9.5947 17.9579,10.9725L15.5878,13.3425C14.21,14.7203 11.9893,14.7335 10.6277,13.372M11.4717,17.4589L10.9727,17.9579C9.5948,19.3357 7.3741,19.349 6.0126,17.9875C4.651,16.626 4.6643,14.4053 6.0421,13.0275L8.412,10.6577C9.7899,9.2799 12.0106,9.2667 13.3721,10.6281"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#737D8C"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:id="@+id/callControlsWrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?riotx_bottom_sheet_background"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/headerText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:text="@string/invite_people_to_your_space"
android:textColor="?riotx_text_primary"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/descriptionText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:gravity="center"
android:textColor="?riotx_text_secondary"
android:textSize="18sp"
app:layout_constraintBottom_toTopOf="@id/joinInfoHelpText"
app:layout_constraintTop_toBottomOf="@id/headerText"
app:layout_constraintVertical_bias="1"
tools:text="@string/invite_people_to_your_space_desc" />
<im.vector.app.features.spaces.create.WizardButtonView
android:id="@+id/inviteByMailButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:icon="@drawable/ic_mail"
app:iconTint="?riotx_text_secondary"
app:title="@string/invite_by_email" />
<im.vector.app.features.spaces.create.WizardButtonView
android:id="@+id/inviteByMxidButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:icon="@drawable/ic_add_people"
app:iconTint="?riotx_text_secondary"
app:title="@string/invite_by_mxid" />
<im.vector.app.features.spaces.create.WizardButtonView
android:id="@+id/inviteByLinkButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:icon="@drawable/ic_share_link"
app:iconTint="?riotx_text_secondary"
app:title="@string/invite_by_link">
</im.vector.app.features.spaces.create.WizardButtonView>
<com.google.android.material.button.MaterialButton
android:id="@+id/skipButton"
style="@style/VectorButtonStyleOutlined"
android:textAllCaps="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:textColor="?colorAccent"
android:text="@string/skip_for_now"/>
</LinearLayout>

View File

@ -35,11 +35,26 @@
android:textSize="15sp" android:textSize="15sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/groupBottomSeparator" app:layout_constraintBottom_toTopOf="@+id/groupBottomSeparator"
app:layout_constraintEnd_toStartOf="@+id/groupAvatarChevron" app:layout_constraintEnd_toStartOf="@+id/groupTmpLeave"
app:layout_constraintStart_toEndOf="@+id/groupAvatarImageView" app:layout_constraintStart_toEndOf="@+id/groupAvatarImageView"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem/random" /> tools:text="@tools:sample/lorem/random" />
<ImageView
android:id="@+id/groupTmpLeave"
android:clickable="true"
android:background="?selectableItemBackground"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="4dp"
android:layout_marginEnd="4dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_room_actions_leave"
android:tint="@color/riotx_destructive_accent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/groupAvatarChevron"
app:layout_constraintTop_toTopOf="parent" />
<ImageView <ImageView
android:id="@+id/groupAvatarChevron" android:id="@+id/groupAvatarChevron"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -2,10 +2,10 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/rounded_rect_shape_8" android:background="@drawable/rounded_rect_shape_8"
android:id="@+id/content"
android:padding="4dp"> android:padding="4dp">
<ImageView <ImageView
@ -34,6 +34,7 @@
app:layout_constraintEnd_toStartOf="@id/rightChevron" app:layout_constraintEnd_toStartOf="@id/rightChevron"
app:layout_constraintStart_toEndOf="@id/buttonImageView" app:layout_constraintStart_toEndOf="@id/buttonImageView"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginBottom="0dp"
tools:text="Public" /> tools:text="Public" />
@ -53,7 +54,8 @@
app:layout_constraintStart_toEndOf="@id/buttonImageView" app:layout_constraintStart_toEndOf="@id/buttonImageView"
app:layout_constraintTop_toBottomOf="@id/title" app:layout_constraintTop_toBottomOf="@id/title"
app:layout_goneMarginBottom="8dp" app:layout_goneMarginBottom="8dp"
tools:text="Open to anyone, best for communities" /> tools:text="Open to anyone, best for communities"
tools:visibility="visible" />
<ImageView <ImageView
android:id="@+id/rightChevron" android:id="@+id/rightChevron"

View File

@ -3277,6 +3277,14 @@
<string name="create_spaces_room_public_header_desc">Well create rooms for them, and auto-join everyone. You can add more later too.</string> <string name="create_spaces_room_public_header_desc">Well create rooms for them, and auto-join everyone. You can add more later too.</string>
<string name="create_spaces_default_public_room_name">General</string> <string name="create_spaces_default_public_room_name">General</string>
<string name="create_spaces_loading_message">Creating Space…</string> <string name="create_spaces_loading_message">Creating Space…</string>
<string name="invite_people_to_your_space">Invite people to your space</string>
<string name="invite_people_to_your_space_desc">Its just you at the moment. %s will be even better with others.</string>
<string name="invite_by_email">Invite by email</string>
<string name="invite_by_mxid">Invite by username</string>
<string name="invite_by_link">Share link</string>
<!-- First one is the space name, and the second one is the matrix.to link -->
<string name="share_space_link_message">Join my space %1$s %2$s</string>
<string name="skip_for_now">Skip for now</string>
</resources> </resources>