Merge branch 'feature/invites' into develop

This commit is contained in:
ganfra
2019-05-17 17:38:09 +02:00
56 changed files with 665 additions and 219 deletions

View File

@ -27,9 +27,8 @@ sealed class RoomDetailActions {
object IsDisplayed : RoomDetailActions()
data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions()
data class LoadMore(val direction: Timeline.Direction) : RoomDetailActions()
data class SendReaction(val reaction: String, val targetEventId: String) : RoomDetailActions()
object AcceptInvite : RoomDetailActions()
object RejectInvite : RoomDetailActions()
}

View File

@ -53,7 +53,12 @@ import com.otaliastudios.autocomplete.Autocomplete
import com.otaliastudios.autocomplete.AutocompleteCallback
import com.otaliastudios.autocomplete.CharPolicy
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotredesign.R
@ -64,7 +69,15 @@ import im.vector.riotredesign.core.extensions.observeEvent
import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.core.utils.*
import im.vector.riotredesign.core.utils.LiveEvent
import im.vector.riotredesign.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA
import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA
import im.vector.riotredesign.core.utils.checkPermissions
import im.vector.riotredesign.core.utils.copyToClipboard
import im.vector.riotredesign.core.utils.openCamera
import im.vector.riotredesign.core.utils.shareMedia
import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter
import im.vector.riotredesign.features.autocomplete.command.CommandAutocompletePolicy
import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter
@ -82,6 +95,7 @@ import im.vector.riotredesign.features.home.room.detail.timeline.action.MessageM
import im.vector.riotredesign.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotredesign.features.html.PillImageSpan
import im.vector.riotredesign.features.invite.VectorInviteView
import im.vector.riotredesign.features.media.ImageContentRenderer
import im.vector.riotredesign.features.media.ImageMediaViewerActivity
import im.vector.riotredesign.features.media.VideoContentRenderer
@ -112,7 +126,8 @@ private const val REACTION_SELECT_REQUEST_CODE = 2
class RoomDetailFragment :
VectorBaseFragment(),
TimelineEventController.Callback,
AutocompleteUserPresenter.Callback {
AutocompleteUserPresenter.Callback,
VectorInviteView.Callback {
companion object {
@ -169,6 +184,7 @@ class RoomDetailFragment :
setupToolbar()
setupComposer()
setupAttachmentButton()
setupInviteView()
roomDetailViewModel.subscribe { renderState(it) }
textComposerViewModel.subscribe { renderTextComposerState(it) }
roomDetailViewModel.sendMessageResultLiveData.observeEvent(this) { renderSendMessageResult(it) }
@ -183,11 +199,13 @@ class RoomDetailFragment :
if (resultCode == RESULT_OK && data != null) {
when (requestCode) {
REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data)
REACTION_SELECT_REQUEST_CODE -> {
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID) ?: return
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT) ?: return
REACTION_SELECT_REQUEST_CODE -> {
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID)
?: return
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT)
?: return
//TODO check if already reacted with that?
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction,eventId))
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId))
}
}
}
@ -342,27 +360,31 @@ class RoomDetailFragment :
}
}
private fun setupInviteView() {
inviteView.callback = this
}
private fun onSendChoiceClicked(dialogListItem: DialogListItem) {
Timber.v("On send choice clicked: $dialogListItem")
when (dialogListItem) {
is DialogListItem.SendFile -> {
is DialogListItem.SendFile -> {
// launchFileIntent
}
is DialogListItem.SendVoice -> {
is DialogListItem.SendVoice -> {
//launchAudioRecorderIntent()
}
is DialogListItem.SendSticker -> {
is DialogListItem.SendSticker -> {
//startStickerPickerActivity()
}
is DialogListItem.TakePhotoVideo ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
// launchCamera()
}
is DialogListItem.TakePhoto ->
is DialogListItem.TakePhoto ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA)) {
openCamera(requireActivity(), CAMERA_VALUE_TITLE, TAKE_IMAGE_REQUEST_CODE)
}
is DialogListItem.TakeVideo ->
is DialogListItem.TakeVideo ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA)) {
// launchNativeVideoRecorder()
}
@ -376,7 +398,17 @@ class RoomDetailFragment :
private fun renderState(state: RoomDetailViewState) {
renderRoomSummary(state)
timelineEventController.setTimeline(state.timeline)
val summary = state.asyncRoomSummary()
val inviter = state.inviter()
if (summary?.membership == Membership.JOIN) {
timelineEventController.setTimeline(state.timeline)
inviteView.visibility = View.GONE
} else if (summary?.membership == Membership.INVITE && inviter != null) {
inviteView.visibility = View.VISIBLE
inviteView.render(inviter, VectorInviteView.Mode.LARGE)
} else {
//TODO : close the screen
}
}
private fun renderRoomSummary(state: RoomDetailViewState) {
@ -399,20 +431,20 @@ class RoomDetailFragment :
private fun renderSendMessageResult(sendMessageResult: SendMessageResult) {
when (sendMessageResult) {
is SendMessageResult.MessageSent,
is SendMessageResult.SlashCommandHandled -> {
is SendMessageResult.SlashCommandHandled -> {
// Clear composer
composerEditText.text = null
}
is SendMessageResult.SlashCommandError -> {
is SendMessageResult.SlashCommandError -> {
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
}
is SendMessageResult.SlashCommandUnknown -> {
is SendMessageResult.SlashCommandUnknown -> {
displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command))
}
is SendMessageResult.SlashCommandResultOk -> {
is SendMessageResult.SlashCommandResultOk -> {
// Ignore
}
is SendMessageResult.SlashCommandResultError -> {
is SendMessageResult.SlashCommandResultError -> {
displayCommandError(sendMessageResult.throwable.localizedMessage)
}
is SendMessageResult.SlashCommandNotImplemented -> {
@ -487,7 +519,7 @@ class RoomDetailFragment :
override fun onClickOnReactionPill(informationData: MessageInformationData, reaction: String, on: Boolean) {
if (on) {
//we should test the current real state of reaction on this event
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction,informationData.eventId))
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, informationData.eventId))
} else {
//TODO it's an undo :/
}
@ -503,18 +535,18 @@ class RoomDetailFragment :
it?.getContentIfNotHandled()?.let { actionData ->
when (actionData.actionId) {
MessageMenuViewModel.ACTION_ADD_REACTION -> {
MessageMenuViewModel.ACTION_ADD_REACTION -> {
val eventId = actionData.data?.toString() ?: return
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), eventId), REACTION_SELECT_REQUEST_CODE)
}
MessageMenuViewModel.ACTION_COPY -> {
MessageMenuViewModel.ACTION_COPY -> {
//I need info about the current selected message :/
copyToClipboard(requireContext(), actionData.data?.toString() ?: "", false)
val snack = Snackbar.make(view!!, requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
snack.view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.notification_accent_color))
snack.show()
}
MessageMenuViewModel.ACTION_SHARE -> {
MessageMenuViewModel.ACTION_SHARE -> {
//TODO current data communication is too limited
//Need to now the media type
actionData.data?.toString()?.let {
@ -557,12 +589,12 @@ class RoomDetailFragment :
.setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() }
.show()
}
MessageMenuViewModel.ACTION_QUICK_REACT -> {
MessageMenuViewModel.ACTION_QUICK_REACT -> {
(actionData.data as? Pair<String, String>)?.let { pairData ->
roomDetailViewModel.process(RoomDetailActions.SendReaction(pairData.second, pairData.first))
}
}
else -> {
else -> {
Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show()
}
}
@ -615,4 +647,13 @@ class RoomDetailFragment :
}
}
// VectorInviteView.Callback
override fun onAcceptInvite() {
roomDetailViewModel.process(RoomDetailActions.AcceptInvite)
}
override fun onRejectInvite() {
roomDetailViewModel.process(RoomDetailActions.RejectInvite)
}
}

View File

@ -19,11 +19,13 @@ package im.vector.riotredesign.features.home.room.detail
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.VectorViewModel
@ -62,6 +64,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
init {
observeRoomSummary()
observeEventDisplayedActions()
observeInvitationState()
room.loadRoomMembersIfNeeded()
timeline.start()
setState { copy(timeline = this@RoomDetailViewModel.timeline) }
@ -69,12 +72,14 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
fun process(action: RoomDetailActions) {
when (action) {
is RoomDetailActions.SendMessage -> handleSendMessage(action)
is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
is RoomDetailActions.SendMedia -> handleSendMedia(action)
is RoomDetailActions.SendMessage -> handleSendMessage(action)
is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
is RoomDetailActions.SendMedia -> handleSendMedia(action)
is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action)
is RoomDetailActions.LoadMore -> handleLoadMore(action)
is RoomDetailActions.SendReaction -> handleSendReaction(action)
is RoomDetailActions.LoadMore -> handleLoadMore(action)
is RoomDetailActions.SendReaction -> handleSendReaction(action)
is RoomDetailActions.AcceptInvite -> handleAcceptInvite()
is RoomDetailActions.RejectInvite -> handleRejectInvite()
}
}
@ -89,63 +94,63 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
val slashCommandResult = CommandParser.parseSplashCommand(action.text)
when (slashCommandResult) {
is ParsedCommand.ErrorNotACommand -> {
is ParsedCommand.ErrorNotACommand -> {
// Send the text message to the room
room.sendTextMessage(action.text)
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
}
is ParsedCommand.ErrorSyntax -> {
is ParsedCommand.ErrorSyntax -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command)))
}
is ParsedCommand.ErrorEmptySlashCommand -> {
is ParsedCommand.ErrorEmptySlashCommand -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown("/")))
}
is ParsedCommand.ErrorUnknownSlashCommand -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand)))
}
is ParsedCommand.Invite -> {
is ParsedCommand.Invite -> {
handleInviteSlashCommand(slashCommandResult)
}
is ParsedCommand.SetUserPowerLevel -> {
is ParsedCommand.SetUserPowerLevel -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.ClearScalarToken -> {
is ParsedCommand.ClearScalarToken -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.SetMarkdown -> {
is ParsedCommand.SetMarkdown -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.UnbanUser -> {
is ParsedCommand.UnbanUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.BanUser -> {
is ParsedCommand.BanUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.KickUser -> {
is ParsedCommand.KickUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.JoinRoom -> {
is ParsedCommand.JoinRoom -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.PartRoom -> {
is ParsedCommand.PartRoom -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.SendEmote -> {
is ParsedCommand.SendEmote -> {
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
}
is ParsedCommand.ChangeTopic -> {
is ParsedCommand.ChangeTopic -> {
handleChangeTopicSlashCommand(slashCommandResult)
}
is ParsedCommand.ChangeDisplayName -> {
is ParsedCommand.ChangeDisplayName -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
@ -182,7 +187,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
private fun handleSendReaction(action: RoomDetailActions.SendReaction) {
room.sendReaction(action.reaction,action.targetEventId)
room.sendReaction(action.reaction, action.targetEventId)
}
private fun handleSendMedia(action: RoomDetailActions.SendMedia) {
@ -214,6 +219,14 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
timeline.paginate(action.direction, PAGINATION_COUNT)
}
private fun handleRejectInvite() {
room.leave(object : MatrixCallback<Unit> {})
}
private fun handleAcceptInvite() {
room.join(object : MatrixCallback<Unit> {})
}
private fun observeEventDisplayedActions() {
// We are buffering scroll events for one second
// and keep the most recent one to set the read receipt on.
@ -236,6 +249,18 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
}
}
private fun observeInvitationState() {
asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary ->
if (summary.membership == Membership.INVITE) {
summary.lastMessage?.sender?.let { senderId ->
session.getUser(senderId)
}?.also {
setState { copy(inviter = Success(it)) }
}
}
}
}
override fun onCleared() {
timeline.dispose()
super.onCleared()

View File

@ -22,11 +22,13 @@ import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineData
import im.vector.matrix.android.api.session.user.model.User
data class RoomDetailViewState(
val roomId: String,
val eventId: String?,
val timeline: Timeline? = null,
val inviter: Async<User> = Uninitialized,
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
val asyncTimelineData: Async<TimelineData> = Uninitialized
) : MvRxState {

View File

@ -24,6 +24,7 @@ import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.rx.rx
@ -153,6 +154,7 @@ class RoomListViewModel(initialState: RoomListViewState,
}
private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
val invites = ArrayList<RoomSummary>()
val favourites = ArrayList<RoomSummary>()
val directChats = ArrayList<RoomSummary>()
val groupRooms = ArrayList<RoomSummary>()
@ -160,8 +162,10 @@ class RoomListViewModel(initialState: RoomListViewState,
val serverNotices = ArrayList<RoomSummary>()
for (room in rooms) {
if (room.membership.isLeft()) continue
val tags = room.tags.map { it.name }
when {
room.membership == Membership.INVITE -> invites.add(room)
tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room)
tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favourites.add(room)
tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room)
@ -171,6 +175,7 @@ class RoomListViewModel(initialState: RoomListViewState,
}
return RoomSummaries().apply {
put(RoomCategory.INVITE, invites.sortedWith(roomSummaryComparator))
put(RoomCategory.FAVOURITE, favourites.sortedWith(roomSummaryComparator))
put(RoomCategory.DIRECT, directChats.sortedWith(roomSummaryComparator))
put(RoomCategory.GROUP, groupRooms.sortedWith(roomSummaryComparator))

View File

@ -26,15 +26,17 @@ import im.vector.riotredesign.R
data class RoomListViewState(
val asyncRooms: Async<RoomSummaries> = Uninitialized,
val visibleRoomId: String? = null,
val isInviteExpanded: Boolean = true,
val isFavouriteRoomsExpanded: Boolean = true,
val isDirectRoomsExpanded: Boolean = false,
val isGroupRoomsExpanded: Boolean = false,
val isLowPriorityRoomsExpanded: Boolean = false,
val isServerNoticeRoomsExpanded: Boolean = false
val isDirectRoomsExpanded: Boolean = true,
val isGroupRoomsExpanded: Boolean = true,
val isLowPriorityRoomsExpanded: Boolean = true,
val isServerNoticeRoomsExpanded: Boolean = true
) : MvRxState {
fun isCategoryExpanded(roomCategory: RoomCategory): Boolean {
return when (roomCategory) {
RoomCategory.INVITE -> isInviteExpanded
RoomCategory.FAVOURITE -> isFavouriteRoomsExpanded
RoomCategory.DIRECT -> isDirectRoomsExpanded
RoomCategory.GROUP -> isGroupRoomsExpanded
@ -45,6 +47,7 @@ data class RoomListViewState(
fun toggle(roomCategory: RoomCategory): RoomListViewState {
return when (roomCategory) {
RoomCategory.INVITE -> copy(isInviteExpanded = !isInviteExpanded)
RoomCategory.FAVOURITE -> copy(isFavouriteRoomsExpanded = !isFavouriteRoomsExpanded)
RoomCategory.DIRECT -> copy(isDirectRoomsExpanded = !isDirectRoomsExpanded)
RoomCategory.GROUP -> copy(isGroupRoomsExpanded = !isGroupRoomsExpanded)
@ -57,6 +60,7 @@ data class RoomListViewState(
typealias RoomSummaries = LinkedHashMap<RoomCategory, List<RoomSummary>>
enum class RoomCategory(@StringRes val titleRes: Int) {
INVITE(R.string.invitations_header),
FAVOURITE(R.string.bottom_action_favourites),
DIRECT(R.string.bottom_action_people),
GROUP(R.string.bottom_action_rooms),

View File

@ -0,0 +1,68 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.features.invite
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.setPadding
import androidx.core.view.updateLayoutParams
import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotredesign.R
import im.vector.riotredesign.features.home.AvatarRenderer
import kotlinx.android.synthetic.main.vector_invite_view.view.*
class VectorInviteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
: ConstraintLayout(context, attrs, defStyle) {
interface Callback {
fun onAcceptInvite()
fun onRejectInvite()
}
enum class Mode {
LARGE,
SMALL
}
var callback: Callback? = null
init {
View.inflate(context, R.layout.vector_invite_view, this)
setBackgroundColor(Color.WHITE)
inviteRejectView.setOnClickListener { callback?.onRejectInvite() }
inviteAcceptView.setOnClickListener { callback?.onAcceptInvite() }
}
fun render(sender: User, mode: Mode = Mode.LARGE) {
if (mode == Mode.LARGE) {
updateLayoutParams { height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT }
AvatarRenderer.render(sender.avatarUrl, sender.userId, sender.displayName, inviteAvatarView)
inviteIdentifierView.text = sender.userId
inviteNameView.text = sender.displayName
inviteLabelView.text = context.getString(R.string.send_you_invite)
} else {
updateLayoutParams { height = ConstraintLayout.LayoutParams.WRAP_CONTENT }
inviteAvatarView.visibility = View.GONE
inviteIdentifierView.visibility = View.GONE
inviteNameView.visibility = View.GONE
inviteLabelView.text = context.getString(R.string.invited_by, sender.userId)
}
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019 New Vector Ltd
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent"/>
<stroke android:width="1dp" android:color="?attr/colorAccent" />
</shape>
</item>
</selector>

View File

@ -139,4 +139,16 @@
</RelativeLayout>
<im.vector.riotredesign.features.invite.VectorInviteView
android:id="@+id/inviteView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintVertical_bias="1.0" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<ImageView
android:id="@+id/inviteAvatarView"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="86dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/inviteNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:textAppearance="@style/TextAppearance.Vector.Title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/inviteAvatarView"
tools:text="Matthew" />
<TextView
android:id="@+id/inviteIdentifierView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/TextAppearance.Vector.Subtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/inviteNameView"
tools:text="\@matthew:matrix.org" />
<TextView
android:id="@+id/inviteLabelView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="@string/send_you_invite"
android:textAppearance="@style/TextAppearance.Vector.Subtitle2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/inviteIdentifierView" />
<Button
android:id="@+id/inviteRejectView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginRight="4dp"
android:text="@string/reject"
app:layout_constraintEnd_toStartOf="@+id/inviteAcceptView"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/inviteLabelView" />
<Button
android:id="@+id/inviteAcceptView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:text="@string/accept"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@+id/inviteRejectView"
app:layout_constraintTop_toTopOf="@id/inviteRejectView" />
<Space
android:layout_height="16dp"
android:layout_width="match_parent"
app:layout_constraintTop_toBottomOf="@id/inviteAcceptView"/>
</merge>

View File

@ -3,7 +3,6 @@
<color name="pale_grey">#f2f5f8</color>
<color name="dark">#2e3649</color>
<color name="pale_teal">#7ac9a1</color>
<color name="black">#212121</color>
<color name="deep_sky_blue">#007aff</color>
<color name="rosy_pink">#f56679</color>
<color name="bluey_grey">#a5a5a6</color>
@ -18,4 +17,9 @@
<color name="brown_grey">#a5a5a5</color>
<color name="grey_lynch">#61708B</color>
<color name="black">#000000</color>
<color name="black_87">#de000000</color>
<color name="black_38">#61000000</color>
<color name="black_37">#5d000000</color>
</resources>

View File

@ -4,6 +4,8 @@
<!-- Strings not defined in Riot -->
<string name="global_retry">"Retry"</string>
<string name="room_list_empty">"Join a room to start using the app."</string>
<string name="send_you_invite">"Sent you an invitation"</string>
<string name="invited_by">Invited by %s</string>
<string name="title_activity_emoji_reaction_picker">Reactions</string>

View File

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Widget.Button" parent="Widget.AppCompat.Button">
<style name="Widget.Vector.Button" parent="Widget.AppCompat.Button">
<item name="android:minHeight">48dp</item>
<item name="android:background">?attr/colorAccent</item>
<item name="android:textColor">@android:color/white</item>
<item name="android:minWidth">128dp</item>
<item name="android:paddingRight">8dp</item>
<item name="android:paddingLeft">8dp</item>
<item name="android:textAllCaps">true</item>
</style>
</resources>

View File

@ -1,5 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019 New Vector Ltd
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="TextAppearance.Vector.Title" parent="TextAppearance.AppCompat">
<item name="android:textSize">16sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">@color/black_87</item>
</style>
<style name="TextAppearance.Vector.Subtitle" parent="TextAppearance.AppCompat">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">@color/black_37</item>
</style>
<style name="TextAppearance.Vector.Subtitle2" parent="TextAppearance.AppCompat">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">@color/black_38</item>
</style>
</resources>

View File

@ -6,12 +6,14 @@
<style name="AppTheme.Base.Light" parent="Theme.MaterialComponents.Light.NoActionBar.Bridge">
<item name="colorPrimaryDark">@color/primary_color_dark_light</item>
<item name="colorPrimary">@color/primary_color_light</item>
<item name="colorPrimaryVariant">@color/primary_color_dark_light</item>
<item name="colorSecondary">@color/accent_color_light</item>
<item name="colorOnSecondary">@android:color/white</item>
<item name="colorAccent">@color/accent_color_light</item>
<item name="android:textColorPrimary">@color/primary_text_color_selector_light</item>
<item name="android:textColorSecondary">@color/riot_secondary_text_color_light</item>
<!-- Default color for text View -->
<item name="android:textColorTertiary">@color/riot_tertiary_text_color_light</item>
<item name="android:textColorPrimary">@color/black_87</item>
<item name="android:textColorSecondary">@color/black_38</item>
<item name="android:textColorTertiary">@color/black_37</item>
<item name="android:textColorLink">@color/link_color_light</item>
@ -22,6 +24,9 @@
<item name="android:colorBackground">@color/riot_primary_background_color_light</item>
<item name="vctr_bottom_nav_background_color">#FFF3F8FD</item>
<!-- default button -->
<item name="android:buttonStyle">@style/Widget.Vector.Button</item>
<!-- waiting view background -->
<item name="vctr_waiting_background_color">#AAAAAAAA</item>

View File

@ -24,6 +24,8 @@
<item name="vctr_bottom_nav_background_color">@color/riot_primary_background_color_status
</item>
<item name="buttonStyle">@style/Widget.Vector.Button</item>
<!-- waiting view background -->
<item name="vctr_waiting_background_color">#AAAAAAAA</item>