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.share.IncomingShareActivity
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.terms.ReviewTermsActivity
import im.vector.app.features.ui.UiStateRepository
@ -175,6 +176,7 @@ interface ScreenComponent {
fun inject(bottomSheet: CallControlsBottomSheet)
fun inject(bottomSheet: SignOutBottomSheetDialogFragment)
fun inject(bottomSheet: MatrixToBottomSheet)
fun inject(bottomSheet: ShareSpaceBottomSheet)
/* ==========================================================================================
* Others

View File

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

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.PermalinkParser
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.toOptional
import org.matrix.android.sdk.rx.rx
@ -147,33 +148,43 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
val membership = roomSummary?.membership
val eventId = permalinkData.eventId
val roomAlias = permalinkData.getRoomAliasOrNull()
val isSpace = roomSummary?.roomType == RoomType.SPACE
return when {
membership == Membership.BAN -> context.toast(R.string.error_opening_banned_room)
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 -> {
if (roomSummary == null) {
// we don't know this room, try to peek
val roomPreviewData = RoomPreviewData(
roomId = roomId,
roomAlias = roomAlias,
peekFromServer = true,
buildTask = buildTask,
homeServers = permalinkData.viaParameters
)
navigator.openRoomPreview(context, roomPreviewData)
if (isSpace) {
navigator.openSpacePreview(context, roomId)
} 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)
if (roomSummary == null) {
// we don't know this room, try to peek
val roomPreviewData = RoomPreviewData(
roomId = roomId,
roomAlias = roomAlias,
peekFromServer = true,
buildTask = buildTask,
homeServers = permalinkData.viaParameters
)
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))
}
override fun onLeaveSpace(spaceSummary: SpaceSummary) {
viewModel.handle(SpaceListAction.LeaveSpace(spaceSummary))
}
override fun onAddSpaceSelected() {
viewModel.handle(SpaceListAction.AddSpace)
}

View File

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

View File

@ -18,12 +18,14 @@ package im.vector.app.features.spaces
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.platform.CheckableConstraintLayout
import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.util.MatrixItem
@ -34,12 +36,22 @@ abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() {
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var selected: Boolean = false
@EpoxyAttribute var listener: (() -> Unit)? = null
@EpoxyAttribute var onLeave: (() -> Unit)? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.rootView.setOnClickListener { listener?.invoke() }
holder.groupNameView.text = matrixItem.displayName
holder.rootView.isChecked = selected
if (onLeave != null) {
holder.leaveView.setOnClickListener(
DebouncedClickListener({ _ ->
onLeave?.invoke()
})
)
} else {
holder.leaveView.isVisible = false
}
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
}
@ -52,5 +64,6 @@ abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() {
val avatarImageView by bind<ImageView>(R.id.groupAvatarImageView)
val groupNameView by bind<TextView>(R.id.groupNameView)
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
import androidx.lifecycle.viewModelScope
import arrow.core.Option
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.FragmentViewModelContext
@ -34,18 +35,22 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.features.grouplist.SelectedSpaceDataSource
import io.reactivex.Observable
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.session.Session
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.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.space.SpaceSummary
import org.matrix.android.sdk.internal.util.awaitCallback
import org.matrix.android.sdk.rx.rx
const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID"
sealed class SpaceListAction : VectorViewModelAction {
data class SelectSpace(val spaceSummary: SpaceSummary) : SpaceListAction()
data class LeaveSpace(val spaceSummary: SpaceSummary) : SpaceListAction()
object AddSpace : SpaceListAction()
}
@ -88,13 +93,18 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
init {
observeSpaceSummaries()
observeSelectionState()
selectedSpaceDataSource.observe().execute {
if (this.selectedSpace != it.invoke()?.orNull()) {
copy(
selectedSpace = it.invoke()?.orNull()
)
} else this
}
selectedSpaceDataSource
.observe()
.subscribe {
if (currentGroupId != it.orNull()?.spaceId) {
setState {
copy(
selectedSpace = it.orNull()
)
}
}
}
.disposeOnClear()
}
private fun observeSelectionState() {
@ -119,11 +129,12 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
override fun handle(action: SpaceListAction) {
when (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 ->
// 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() {
_viewEvents.post(SpaceListViewEvents.AddSpace)
}

View File

@ -17,6 +17,7 @@
package im.vector.app.features.spaces.create
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.os.Build
import android.util.AttributeSet
@ -25,6 +26,7 @@ import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.withStyledAttributes
import im.vector.app.R
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.databinding.ViewSpaceTypeButtonBinding
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) {
if (value != title) {
field = value
views.title.text = value
views.title.setTextOrHide(value)
}
}
@ -44,7 +46,7 @@ class WizardButtonView @JvmOverloads constructor(context: Context, attrs: Attrib
set(value) {
if (value != subTitle) {
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
// set(value) {
// field = value
// if (value != null) {
// views.buttonImageView.imageTintList = ColorStateList.valueOf(value)
// } else {
// views.buttonImageView.clearColorFilter()
// }
// }
private var tint: Int? = null
set(value) {
field = value
if (value != null) {
views.buttonImageView.imageTintList = ColorStateList.valueOf(value)
}
}
// 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)
views = ViewSpaceTypeButtonBinding.bind(this)
views.subTitle.setTextOrHide(null)
if (isInEditMode) {
title = "Title"
subTitle = "This is doing something"
}
context.withStyledAttributes(attrs, R.styleable.WizardButtonView) {
title = getString(R.styleable.WizardButtonView_title) ?: ""
subTitle = getString(R.styleable.WizardButtonView_subTitle) ?: ""
title = getString(R.styleable.WizardButtonView_title)
subTitle = getString(R.styleable.WizardButtonView_subTitle)
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()

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:textStyle="bold"
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_constraintTop_toTopOf="parent"
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
android:id="@+id/groupAvatarChevron"
android:layout_width="wrap_content"

View File

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