forked from GitHub-Mirror/riotX-android
Merge branch 'develop' into feature/Perf
This commit is contained in:
@ -29,7 +29,7 @@ import im.vector.riotredesign.features.themes.ThemeUtils
|
||||
/**
|
||||
* Set a text in the TextView, or set visibility to GONE if the text is null
|
||||
*/
|
||||
fun TextView.setTextOrHide(newText: String?, hideWhenBlank: Boolean = true) {
|
||||
fun TextView.setTextOrHide(newText: CharSequence?, hideWhenBlank: Boolean = true) {
|
||||
if (newText == null
|
||||
|| (newText.isBlank() && hideWhenBlank)) {
|
||||
isVisible = false
|
||||
|
@ -19,12 +19,15 @@ package im.vector.riotredesign.features.autocomplete.user
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.riotredesign.features.autocomplete.AutocompleteClickListener
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
import javax.inject.Inject
|
||||
|
||||
class AutocompleteUserController @Inject constructor(): TypedEpoxyController<List<User>>() {
|
||||
|
||||
var listener: AutocompleteClickListener<User>? = null
|
||||
|
||||
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||
|
||||
override fun buildModels(data: List<User>?) {
|
||||
if (data.isNullOrEmpty()) {
|
||||
return
|
||||
@ -35,6 +38,7 @@ class AutocompleteUserController @Inject constructor(): TypedEpoxyController<Lis
|
||||
userId(user.userId)
|
||||
name(user.displayName)
|
||||
avatarUrl(user.avatarUrl)
|
||||
avatarRenderer(avatarRenderer)
|
||||
clickListener { _ ->
|
||||
listener?.onItemClick(user)
|
||||
}
|
||||
|
@ -81,7 +81,10 @@ import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandP
|
||||
import im.vector.riotredesign.features.autocomplete.command.CommandAutocompletePolicy
|
||||
import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter
|
||||
import im.vector.riotredesign.features.command.Command
|
||||
import im.vector.riotredesign.features.home.*
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
import im.vector.riotredesign.features.home.NavigateToRoomInterceptor
|
||||
import im.vector.riotredesign.features.home.PermalinkHandler
|
||||
import im.vector.riotredesign.features.home.getColorFromUserId
|
||||
import im.vector.riotredesign.features.home.room.detail.composer.TextComposerActions
|
||||
import im.vector.riotredesign.features.home.room.detail.composer.TextComposerView
|
||||
import im.vector.riotredesign.features.home.room.detail.composer.TextComposerViewModel
|
||||
@ -509,6 +512,9 @@ class RoomDetailFragment :
|
||||
} else if (summary?.membership == Membership.INVITE && inviter != null) {
|
||||
inviteView.visibility = View.VISIBLE
|
||||
inviteView.render(inviter, VectorInviteView.Mode.LARGE)
|
||||
|
||||
// Intercept click event
|
||||
inviteView.setOnClickListener { }
|
||||
} else if (state.asyncInviter.complete) {
|
||||
vectorBaseActivity.finish()
|
||||
}
|
||||
|
@ -504,7 +504,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
private fun observeInvitationState() {
|
||||
asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary ->
|
||||
if (summary.membership == Membership.INVITE) {
|
||||
summary.lastMessage?.senderId?.let { senderId ->
|
||||
summary.latestEvent?.root?.senderId?.let { senderId ->
|
||||
session.getUser(senderId)
|
||||
}?.also {
|
||||
setState { copy(asyncInviter = Success(it)) }
|
||||
|
@ -25,14 +25,14 @@ class ChronologicalRoomComparator @Inject constructor() : Comparator<RoomSummary
|
||||
var rightTimestamp = 0L
|
||||
var leftTimestamp = 0L
|
||||
if (null != leftRoomSummary) {
|
||||
leftTimestamp = leftRoomSummary.lastMessage?.originServerTs ?: 0
|
||||
leftTimestamp = leftRoomSummary.latestEvent?.root?.originServerTs ?: 0
|
||||
}
|
||||
if (null != rightRoomSummary) {
|
||||
rightTimestamp = rightRoomSummary.lastMessage?.originServerTs ?: 0
|
||||
rightTimestamp = rightRoomSummary.latestEvent?.root?.originServerTs ?: 0
|
||||
}
|
||||
return if (rightRoomSummary?.lastMessage == null) {
|
||||
return if (rightRoomSummary?.latestEvent?.root == null) {
|
||||
-1
|
||||
} else if (leftRoomSummary?.lastMessage == null) {
|
||||
} else if (leftRoomSummary?.latestEvent?.root == null) {
|
||||
1
|
||||
} else {
|
||||
val deltaTimestamp = rightTimestamp - leftTimestamp
|
||||
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.home.room.list
|
||||
|
||||
import android.view.ViewGroup
|
||||
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.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotredesign.core.extensions.setTextOrHide
|
||||
import im.vector.riotredesign.core.platform.ButtonStateView
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_room_invitation)
|
||||
abstract class RoomInvitationItem : VectorEpoxyModel<RoomInvitationItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||
@EpoxyAttribute lateinit var roomName: CharSequence
|
||||
@EpoxyAttribute lateinit var roomId: String
|
||||
@EpoxyAttribute var secondLine: CharSequence? = null
|
||||
@EpoxyAttribute var avatarUrl: String? = null
|
||||
@EpoxyAttribute var listener: (() -> Unit)? = null
|
||||
@EpoxyAttribute var invitationAcceptInProgress: Boolean = false
|
||||
@EpoxyAttribute var invitationAcceptInError: Boolean = false
|
||||
@EpoxyAttribute var invitationRejectInProgress: Boolean = false
|
||||
@EpoxyAttribute var invitationRejectInError: Boolean = false
|
||||
@EpoxyAttribute var acceptListener: (() -> Unit)? = null
|
||||
@EpoxyAttribute var rejectListener: (() -> Unit)? = null
|
||||
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.rootView.setOnClickListener { listener?.invoke() }
|
||||
|
||||
// When a request is in progress (accept or reject), we only use the accept State button
|
||||
val requestInProgress = invitationAcceptInProgress || invitationRejectInProgress
|
||||
|
||||
when {
|
||||
requestInProgress -> holder.acceptView.render(ButtonStateView.State.Loading)
|
||||
invitationAcceptInError -> holder.acceptView.render(ButtonStateView.State.Error)
|
||||
else -> holder.acceptView.render(ButtonStateView.State.Button)
|
||||
}
|
||||
// ButtonStateView.State.Loaded not used because roomSummary will not be displayed as a room invitation anymore
|
||||
|
||||
|
||||
holder.acceptView.callback = object : ButtonStateView.Callback {
|
||||
override fun onButtonClicked() {
|
||||
acceptListener?.invoke()
|
||||
}
|
||||
|
||||
override fun onRetryClicked() {
|
||||
acceptListener?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
holder.rejectView.isVisible = !requestInProgress
|
||||
|
||||
when {
|
||||
invitationRejectInError -> holder.rejectView.render(ButtonStateView.State.Error)
|
||||
else -> holder.rejectView.render(ButtonStateView.State.Button)
|
||||
}
|
||||
|
||||
holder.rejectView.callback = object : ButtonStateView.Callback {
|
||||
override fun onButtonClicked() {
|
||||
rejectListener?.invoke()
|
||||
}
|
||||
|
||||
override fun onRetryClicked() {
|
||||
rejectListener?.invoke()
|
||||
}
|
||||
}
|
||||
holder.titleView.text = roomName
|
||||
holder.subtitleView.setTextOrHide(secondLine)
|
||||
avatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val titleView by bind<TextView>(R.id.roomInvitationNameView)
|
||||
val subtitleView by bind<TextView>(R.id.roomInvitationSubTitle)
|
||||
val acceptView by bind<ButtonStateView>(R.id.roomInvitationAccept)
|
||||
val rejectView by bind<ButtonStateView>(R.id.roomInvitationReject)
|
||||
val avatarImageView by bind<ImageView>(R.id.roomInvitationAvatarImageView)
|
||||
val rootView by bind<ViewGroup>(R.id.itemRoomInvitationLayout)
|
||||
}
|
||||
|
||||
}
|
@ -22,8 +22,10 @@ sealed class RoomListActions {
|
||||
|
||||
data class SelectRoom(val roomSummary: RoomSummary) : RoomListActions()
|
||||
|
||||
data class FilterRooms(val roomName: CharSequence? = null) : RoomListActions()
|
||||
|
||||
data class ToggleCategory(val category: RoomCategory) : RoomListActions()
|
||||
|
||||
data class AcceptInvitation(val roomSummary: RoomSummary) : RoomListActions()
|
||||
|
||||
data class RejectInvitation(val roomSummary: RoomSummary) : RoomListActions()
|
||||
|
||||
}
|
@ -24,12 +24,15 @@ import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.mvrx.*
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.di.ScreenComponent
|
||||
import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer
|
||||
import im.vector.riotredesign.core.error.ErrorFormatter
|
||||
import im.vector.riotredesign.core.extensions.observeEvent
|
||||
import im.vector.riotredesign.core.extensions.observeEventDebounced
|
||||
import im.vector.riotredesign.core.platform.OnBackPressed
|
||||
import im.vector.riotredesign.core.platform.StateView
|
||||
@ -45,7 +48,7 @@ data class RoomListParams(
|
||||
) : Parcelable
|
||||
|
||||
|
||||
class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, OnBackPressed, FabMenuView.Listener {
|
||||
class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener {
|
||||
|
||||
enum class DisplayMode(@StringRes val titleRes: Int) {
|
||||
HOME(R.string.bottom_action_home),
|
||||
@ -64,6 +67,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
|
||||
private val roomListParams: RoomListParams by args()
|
||||
@Inject lateinit var roomController: RoomSummaryController
|
||||
@Inject lateinit var roomListViewModelFactory: RoomListViewModel.Factory
|
||||
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||
private val roomListViewModel: RoomListViewModel by fragmentViewModel()
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_room_list
|
||||
@ -82,6 +86,13 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
|
||||
}
|
||||
|
||||
createChatFabMenu.listener = this
|
||||
|
||||
roomListViewModel.invitationAnswerErrorLiveData.observeEvent(this) { throwable ->
|
||||
vectorBaseActivity.coordinatorLayout?.let {
|
||||
Snackbar.make(it, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCreateRoomButton() {
|
||||
@ -135,7 +146,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
|
||||
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
||||
epoxyRecyclerView.layoutManager = layoutManager
|
||||
epoxyRecyclerView.itemAnimator = RoomListAnimator()
|
||||
roomController.callback = this
|
||||
roomController.listener = this
|
||||
roomController.addModelBuildListener { it.dispatchTo(stateRestorer) }
|
||||
stateView.contentView = epoxyRecyclerView
|
||||
epoxyRecyclerView.setController(roomController)
|
||||
@ -233,6 +244,14 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
|
||||
roomListViewModel.accept(RoomListActions.SelectRoom(room))
|
||||
}
|
||||
|
||||
override fun onAcceptRoomInvitation(room: RoomSummary) {
|
||||
roomListViewModel.accept(RoomListActions.AcceptInvitation(room))
|
||||
}
|
||||
|
||||
override fun onRejectRoomInvitation(room: RoomSummary) {
|
||||
roomListViewModel.accept(RoomListActions.RejectInvitation(room))
|
||||
}
|
||||
|
||||
override fun onToggleRoomCategory(roomCategory: RoomCategory) {
|
||||
roomListViewModel.accept(RoomListActions.ToggleCategory(roomCategory))
|
||||
}
|
||||
|
@ -18,13 +18,12 @@ package im.vector.riotredesign.features.home.room.list
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import arrow.core.Option
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
@ -32,8 +31,7 @@ import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
||||
import im.vector.riotredesign.core.platform.VectorViewModel
|
||||
import im.vector.riotredesign.core.utils.LiveEvent
|
||||
import im.vector.riotredesign.features.home.HomeRoomListObservableStore
|
||||
|
||||
typealias RoomListFilterName = CharSequence
|
||||
import timber.log.Timber
|
||||
|
||||
class RoomListViewModel @AssistedInject constructor(@Assisted initialState: RoomListViewState,
|
||||
private val session: Session,
|
||||
@ -57,22 +55,25 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
|
||||
}
|
||||
|
||||
private val displayMode = initialState.displayMode
|
||||
private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode)
|
||||
private val roomListFilter = BehaviorRelay.createDefault<Option<RoomListFilterName>>(Option.empty())
|
||||
|
||||
private val _openRoomLiveData = MutableLiveData<LiveEvent<String>>()
|
||||
val openRoomLiveData: LiveData<LiveEvent<String>>
|
||||
get() = _openRoomLiveData
|
||||
|
||||
private val _invitationAnswerErrorLiveData = MutableLiveData<LiveEvent<Throwable>>()
|
||||
val invitationAnswerErrorLiveData: LiveData<LiveEvent<Throwable>>
|
||||
get() = _invitationAnswerErrorLiveData
|
||||
|
||||
init {
|
||||
observeRoomSummaries()
|
||||
}
|
||||
|
||||
fun accept(action: RoomListActions) {
|
||||
when (action) {
|
||||
is RoomListActions.SelectRoom -> handleSelectRoom(action)
|
||||
is RoomListActions.FilterRooms -> handleFilterRooms(action)
|
||||
is RoomListActions.ToggleCategory -> handleToggleCategory(action)
|
||||
is RoomListActions.SelectRoom -> handleSelectRoom(action)
|
||||
is RoomListActions.ToggleCategory -> handleToggleCategory(action)
|
||||
is RoomListActions.AcceptInvitation -> handleAcceptInvitation(action)
|
||||
is RoomListActions.RejectInvitation -> handleRejectInvitation(action)
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,11 +83,6 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
|
||||
_openRoomLiveData.postValue(LiveEvent(action.roomSummary.roomId))
|
||||
}
|
||||
|
||||
private fun handleFilterRooms(action: RoomListActions.FilterRooms) {
|
||||
val optionalFilter = Option.fromNullable(action.roomName)
|
||||
roomListFilter.accept(optionalFilter)
|
||||
}
|
||||
|
||||
private fun handleToggleCategory(action: RoomListActions.ToggleCategory) = setState {
|
||||
this.toggle(action.category)
|
||||
}
|
||||
@ -106,6 +102,78 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAcceptInvitation(action: RoomListActions.AcceptInvitation) = withState { state ->
|
||||
val roomId = action.roomSummary.roomId
|
||||
|
||||
if (state.joiningRoomsIds.contains(roomId) || state.rejectingRoomsIds.contains(roomId)) {
|
||||
// Request already sent, should not happen
|
||||
Timber.w("Try to join an already joining room. Should not happen")
|
||||
return@withState
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { add(roomId) },
|
||||
rejectingErrorRoomsIds = rejectingErrorRoomsIds.toMutableSet().apply { remove(roomId) }
|
||||
)
|
||||
}
|
||||
|
||||
session.getRoom(roomId)?.join(object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
// We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
|
||||
// Instead, we wait for the room to be joined
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
// Notify the user
|
||||
_invitationAnswerErrorLiveData.postValue(LiveEvent(failure))
|
||||
|
||||
setState {
|
||||
copy(
|
||||
joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { remove(roomId) },
|
||||
joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { add(roomId) }
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun handleRejectInvitation(action: RoomListActions.RejectInvitation) = withState { state ->
|
||||
val roomId = action.roomSummary.roomId
|
||||
|
||||
if (state.joiningRoomsIds.contains(roomId) || state.rejectingRoomsIds.contains(roomId)) {
|
||||
// Request already sent, should not happen
|
||||
Timber.w("Try to reject an already rejecting room. Should not happen")
|
||||
return@withState
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
rejectingRoomsIds = rejectingRoomsIds.toMutableSet().apply { add(roomId) },
|
||||
joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { remove(roomId) }
|
||||
)
|
||||
}
|
||||
|
||||
session.getRoom(roomId)?.leave(object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
// We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
|
||||
// Instead, we wait for the room to be joined
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
// Notify the user
|
||||
_invitationAnswerErrorLiveData.postValue(LiveEvent(failure))
|
||||
|
||||
setState {
|
||||
copy(
|
||||
rejectingRoomsIds = rejectingRoomsIds.toMutableSet().apply { remove(roomId) },
|
||||
rejectingErrorRoomsIds = rejectingErrorRoomsIds.toMutableSet().apply { add(roomId) }
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
|
||||
val invites = ArrayList<RoomSummary>()
|
||||
val favourites = ArrayList<RoomSummary>()
|
||||
|
@ -27,6 +27,14 @@ data class RoomListViewState(
|
||||
val displayMode: RoomListFragment.DisplayMode,
|
||||
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
|
||||
val asyncFilteredRooms: Async<RoomSummaries> = Uninitialized,
|
||||
// List of roomIds that the user wants to join
|
||||
val joiningRoomsIds: Set<String> = emptySet(),
|
||||
// List of roomIds that the user wants to join, but an error occurred
|
||||
val joiningErrorRoomsIds: Set<String> = emptySet(),
|
||||
// List of roomIds that the user wants to join
|
||||
val rejectingRoomsIds: Set<String> = emptySet(),
|
||||
// List of roomIds that the user wants to reject, but an error occurred
|
||||
val rejectingErrorRoomsIds: Set<String> = emptySet(),
|
||||
val isInviteExpanded: Boolean = true,
|
||||
val isFavouriteRoomsExpanded: Boolean = true,
|
||||
val isDirectRoomsExpanded: Boolean = true,
|
||||
|
@ -18,25 +18,15 @@ package im.vector.riotredesign.features.home.room.list
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.riotredesign.core.extensions.localDateTime
|
||||
import im.vector.riotredesign.core.resources.DateProvider
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomSummaryController @Inject constructor(private val stringProvider: StringProvider,
|
||||
private val eventFormatter: NoticeEventFormatter,
|
||||
private val timelineDateFormatter: TimelineDateFormatter,
|
||||
private val avatarRenderer: AvatarRenderer
|
||||
private val roomSummaryItemFactory: RoomSummaryItemFactory
|
||||
) : TypedEpoxyController<RoomListViewState>() {
|
||||
|
||||
var callback: Callback? = null
|
||||
var listener: Listener? = null
|
||||
|
||||
override fun buildModels(viewState: RoomListViewState) {
|
||||
val roomSummaries = viewState.asyncFilteredRooms()
|
||||
@ -46,10 +36,14 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
||||
} else {
|
||||
val isExpanded = viewState.isCategoryExpanded(category)
|
||||
buildRoomCategory(viewState, summaries, category.titleRes, viewState.isCategoryExpanded(category)) {
|
||||
callback?.onToggleRoomCategory(category)
|
||||
listener?.onToggleRoomCategory(category)
|
||||
}
|
||||
if (isExpanded) {
|
||||
buildRoomModels(summaries)
|
||||
buildRoomModels(summaries,
|
||||
viewState.joiningRoomsIds,
|
||||
viewState.joiningErrorRoomsIds,
|
||||
viewState.rejectingRoomsIds,
|
||||
viewState.rejectingErrorRoomsIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -83,52 +77,23 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildRoomModels(summaries: List<RoomSummary>) {
|
||||
private fun buildRoomModels(summaries: List<RoomSummary>,
|
||||
joiningRoomsIds: Set<String>,
|
||||
joiningErrorRoomsIds: Set<String>,
|
||||
rejectingRoomsIds: Set<String>,
|
||||
rejectingErrorRoomsIds: Set<String>) {
|
||||
summaries.forEach { roomSummary ->
|
||||
val unreadCount = roomSummary.notificationCount
|
||||
val showHighlighted = roomSummary.highlightCount > 0
|
||||
|
||||
var lastMessageFormatted: CharSequence = ""
|
||||
var lastMessageTime: CharSequence = ""
|
||||
val lastMessage = roomSummary.lastMessage
|
||||
if (lastMessage != null) {
|
||||
val date = lastMessage.localDateTime()
|
||||
val currentData = DateProvider.currentLocalDateTime()
|
||||
val isSameDay = date.toLocalDate() == currentData.toLocalDate()
|
||||
//TODO: get formatted
|
||||
if (lastMessage.type == EventType.MESSAGE) {
|
||||
val content = lastMessage.content?.toModel<MessageContent>()
|
||||
lastMessageFormatted = content?.body ?: ""
|
||||
} else {
|
||||
lastMessageFormatted = lastMessage.type
|
||||
}
|
||||
lastMessageTime = if (isSameDay) {
|
||||
timelineDateFormatter.formatMessageHour(date)
|
||||
} else {
|
||||
//TODO: change this
|
||||
timelineDateFormatter.formatMessageDay(date)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
roomSummaryItem {
|
||||
avatarRenderer(avatarRenderer)
|
||||
id(roomSummary.roomId)
|
||||
roomId(roomSummary.roomId)
|
||||
lastEventTime(lastMessageTime)
|
||||
lastFormattedEvent(lastMessageFormatted)
|
||||
roomName(roomSummary.displayName)
|
||||
avatarUrl(roomSummary.avatarUrl)
|
||||
showHighlighted(showHighlighted)
|
||||
unreadCount(unreadCount)
|
||||
listener { callback?.onRoomSelected(roomSummary) }
|
||||
}
|
||||
roomSummaryItemFactory
|
||||
.create(roomSummary, joiningRoomsIds, joiningErrorRoomsIds, rejectingRoomsIds, rejectingErrorRoomsIds, listener)
|
||||
.addTo(this)
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
interface Listener {
|
||||
fun onToggleRoomCategory(roomCategory: RoomCategory)
|
||||
fun onRoomSelected(room: RoomSummary)
|
||||
fun onRejectRoomInvitation(room: RoomSummary)
|
||||
fun onAcceptRoomInvitation(room: RoomSummary)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.home.room.list
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
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.message.MessageContent
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotredesign.core.extensions.localDateTime
|
||||
import im.vector.riotredesign.core.resources.ColorProvider
|
||||
import im.vector.riotredesign.core.resources.DateProvider
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName
|
||||
import me.gujun.android.span.span
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatter: NoticeEventFormatter,
|
||||
private val timelineDateFormatter: TimelineDateFormatter,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val stringProvider: StringProvider,
|
||||
private val avatarRenderer: AvatarRenderer) {
|
||||
|
||||
fun create(roomSummary: RoomSummary,
|
||||
joiningRoomsIds: Set<String>,
|
||||
joiningErrorRoomsIds: Set<String>,
|
||||
rejectingRoomsIds: Set<String>,
|
||||
rejectingErrorRoomsIds: Set<String>,
|
||||
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
||||
return when (roomSummary.membership) {
|
||||
Membership.INVITE -> createInvitationItem(roomSummary, joiningRoomsIds, joiningErrorRoomsIds, rejectingRoomsIds, rejectingErrorRoomsIds, listener)
|
||||
else -> createRoomItem(roomSummary, listener)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createInvitationItem(roomSummary: RoomSummary,
|
||||
joiningRoomsIds: Set<String>,
|
||||
joiningErrorRoomsIds: Set<String>,
|
||||
rejectingRoomsIds: Set<String>,
|
||||
rejectingErrorRoomsIds: Set<String>,
|
||||
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
||||
val secondLine = if (roomSummary.isDirect) {
|
||||
roomSummary.latestEvent?.root?.senderId
|
||||
} else {
|
||||
roomSummary.latestEvent?.root?.senderId?.let {
|
||||
stringProvider.getString(R.string.invited_by, it)
|
||||
}
|
||||
}
|
||||
|
||||
return RoomInvitationItem_()
|
||||
.id(roomSummary.roomId)
|
||||
.avatarRenderer(avatarRenderer)
|
||||
.roomId(roomSummary.roomId)
|
||||
.secondLine(secondLine)
|
||||
.invitationAcceptInProgress(joiningRoomsIds.contains(roomSummary.roomId))
|
||||
.invitationAcceptInError(joiningErrorRoomsIds.contains(roomSummary.roomId))
|
||||
.invitationRejectInProgress(rejectingRoomsIds.contains(roomSummary.roomId))
|
||||
.invitationRejectInError(rejectingErrorRoomsIds.contains(roomSummary.roomId))
|
||||
.acceptListener { listener?.onAcceptRoomInvitation(roomSummary) }
|
||||
.rejectListener { listener?.onRejectRoomInvitation(roomSummary) }
|
||||
.roomName(roomSummary.displayName)
|
||||
.avatarUrl(roomSummary.avatarUrl)
|
||||
.listener { listener?.onRoomSelected(roomSummary) }
|
||||
}
|
||||
|
||||
private fun createRoomItem(roomSummary: RoomSummary, listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
||||
val unreadCount = roomSummary.notificationCount
|
||||
val showHighlighted = roomSummary.highlightCount > 0
|
||||
|
||||
var latestFormattedEvent: CharSequence = ""
|
||||
var latestEventTime: CharSequence = ""
|
||||
val latestEvent = roomSummary.latestEvent
|
||||
if (latestEvent != null) {
|
||||
val date = latestEvent.root.localDateTime()
|
||||
val currentDate = DateProvider.currentLocalDateTime()
|
||||
val isSameDay = date.toLocalDate() == currentDate.toLocalDate()
|
||||
latestFormattedEvent = if (latestEvent.root.getClearType() == EventType.MESSAGE) {
|
||||
val senderName = latestEvent.senderName() ?: latestEvent.root.senderId
|
||||
val content = latestEvent.root.getClearContent()?.toModel<MessageContent>()
|
||||
val message = content?.body ?: ""
|
||||
if (roomSummary.isDirect.not() && senderName != null) {
|
||||
span {
|
||||
text = senderName
|
||||
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)
|
||||
}
|
||||
.append(" - ")
|
||||
.append(message)
|
||||
} else {
|
||||
message
|
||||
}
|
||||
} else {
|
||||
span {
|
||||
text = noticeEventFormatter.format(latestEvent) ?: ""
|
||||
textStyle = "italic"
|
||||
}
|
||||
}
|
||||
latestEventTime = if (isSameDay) {
|
||||
timelineDateFormatter.formatMessageHour(date)
|
||||
} else {
|
||||
//TODO: change this
|
||||
timelineDateFormatter.formatMessageDay(date)
|
||||
}
|
||||
}
|
||||
return RoomSummaryItem_()
|
||||
.id(roomSummary.roomId)
|
||||
.avatarRenderer(avatarRenderer)
|
||||
.roomId(roomSummary.roomId)
|
||||
.lastEventTime(latestEventTime)
|
||||
.lastFormattedEvent(latestFormattedEvent)
|
||||
.roomName(roomSummary.displayName)
|
||||
.avatarUrl(roomSummary.avatarUrl)
|
||||
.showHighlighted(showHighlighted)
|
||||
.unreadCount(unreadCount)
|
||||
.listener { listener?.onRoomSelected(roomSummary) }
|
||||
}
|
||||
|
||||
}
|
@ -20,13 +20,13 @@ import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.Person
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||
import im.vector.riotredesign.BuildConfig
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.di.ActiveSessionHolder
|
||||
import im.vector.riotredesign.core.utils.SecretStoringUtils
|
||||
import im.vector.riotredesign.features.settings.PreferencesManager
|
||||
import me.gujun.android.span.span
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
@ -275,9 +275,32 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||
}
|
||||
|
||||
try {
|
||||
val summaryLine = context.resources.getQuantityString(
|
||||
R.plurals.notification_compat_summary_line_for_room, events.size, roomName, events.size)
|
||||
summaryInboxStyle.addLine(summaryLine)
|
||||
if (events.size == 1) {
|
||||
val event = events[0]
|
||||
if (roomEventGroupInfo.isDirect) {
|
||||
val line = span {
|
||||
span {
|
||||
textStyle = "bold"
|
||||
+String.format("%s: ", event.senderName)
|
||||
}
|
||||
+(event.description ?: "")
|
||||
}
|
||||
summaryInboxStyle.addLine(line)
|
||||
} else {
|
||||
val line = span {
|
||||
span {
|
||||
textStyle = "bold"
|
||||
+String.format("%s: %s ", roomName, event.senderName)
|
||||
}
|
||||
+(event.description ?: "")
|
||||
}
|
||||
summaryInboxStyle.addLine(line)
|
||||
}
|
||||
} else {
|
||||
val summaryLine = context.resources.getQuantityString(
|
||||
R.plurals.notification_compat_summary_line_for_room, events.size, roomName, events.size)
|
||||
summaryInboxStyle.addLine(summaryLine)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
//String not found or bad format
|
||||
Timber.v("%%%%%%%% REFRESH NOTIFICATION DRAWER failed to resolve string")
|
||||
@ -343,6 +366,11 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||
val sumTitle = context.resources.getQuantityString(
|
||||
R.plurals.notification_compat_summary_title, nbEvents, nbEvents)
|
||||
summaryInboxStyle.setBigContentTitle(sumTitle)
|
||||
//TODO get latest event?
|
||||
.setSummaryText(
|
||||
context.resources
|
||||
.getQuantityString(R.plurals.notification_unread_notified_messages, nbEvents, nbEvents))
|
||||
|
||||
NotificationUtils.buildSummaryListNotification(
|
||||
context,
|
||||
summaryInboxStyle,
|
||||
|
@ -606,7 +606,7 @@ object NotificationUtils {
|
||||
* Build the summary notification
|
||||
*/
|
||||
fun buildSummaryListNotification(context: Context,
|
||||
style: NotificationCompat.Style,
|
||||
style: NotificationCompat.InboxStyle,
|
||||
compatSummary: String,
|
||||
noisy: Boolean,
|
||||
lastMessageTimestamp: Long): Notification? {
|
||||
|
@ -203,7 +203,7 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
||||
var userId = "undefined"
|
||||
var olmVersion = "undefined"
|
||||
|
||||
activeSessionHolder.getActiveSession().let { session ->
|
||||
activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||
userId = session.sessionParams.credentials.userId
|
||||
deviceId = session.sessionParams.credentials.deviceId ?: "undefined"
|
||||
olmVersion = session.getCryptoVersion(context, true)
|
||||
|
@ -28,11 +28,11 @@ data class PublicRoomsViewState(
|
||||
val asyncPublicRoomsRequest: Async<List<PublicRoom>> = Uninitialized,
|
||||
// True if more result are available server side
|
||||
val hasMore: Boolean = false,
|
||||
// List of roomIds that the user wants to join
|
||||
val joiningRoomsIds: List<String> = emptyList(),
|
||||
// List of roomIds that the user wants to join, but an error occurred
|
||||
val joiningErrorRoomsIds: List<String> = emptyList(),
|
||||
// List of joined roomId,
|
||||
val joinedRoomsIds: List<String> = emptyList(),
|
||||
// Set of roomIds that the user wants to join
|
||||
val joiningRoomsIds: Set<String> = emptySet(),
|
||||
// Set of roomIds that the user wants to join, but an error occurred
|
||||
val joiningErrorRoomsIds: Set<String> = emptySet(),
|
||||
// Set of joined roomId,
|
||||
val joinedRoomsIds: Set<String> = emptySet(),
|
||||
val roomDirectoryDisplayName: String? = null
|
||||
) : MvRxState
|
@ -18,13 +18,7 @@ package im.vector.riotredesign.features.roomdirectory
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.airbnb.mvrx.appendAt
|
||||
import com.airbnb.mvrx.*
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
@ -95,19 +89,19 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
||||
.liveRoomSummaries()
|
||||
.subscribe { list ->
|
||||
val joinedRoomIds = list
|
||||
// Keep only joined room
|
||||
?.filter { it.membership == Membership.JOIN }
|
||||
?.map { it.roomId }
|
||||
?.toList()
|
||||
?: emptyList()
|
||||
// Keep only joined room
|
||||
?.filter { it.membership == Membership.JOIN }
|
||||
?.map { it.roomId }
|
||||
?.toSet()
|
||||
?: emptySet()
|
||||
|
||||
setState {
|
||||
copy(
|
||||
joinedRoomsIds = joinedRoomIds,
|
||||
// Remove (newly) joined room id from the joining room list
|
||||
joiningRoomsIds = joiningRoomsIds.toMutableList().apply { removeAll(joinedRoomIds) },
|
||||
joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { removeAll(joinedRoomIds) },
|
||||
// Remove (newly) joined room id from the joining room list in error
|
||||
joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableList().apply { removeAll(joinedRoomIds) }
|
||||
joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { removeAll(joinedRoomIds) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -166,39 +160,39 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
||||
|
||||
private fun load() {
|
||||
currentTask = session.getPublicRooms(roomDirectoryData.homeServer,
|
||||
PublicRoomsParams(
|
||||
limit = PUBLIC_ROOMS_LIMIT,
|
||||
filter = PublicRoomsFilter(searchTerm = currentFilter),
|
||||
includeAllNetworks = roomDirectoryData.includeAllNetworks,
|
||||
since = since,
|
||||
thirdPartyInstanceId = roomDirectoryData.thirdPartyInstanceId
|
||||
),
|
||||
object : MatrixCallback<PublicRoomsResponse> {
|
||||
override fun onSuccess(data: PublicRoomsResponse) {
|
||||
currentTask = null
|
||||
PublicRoomsParams(
|
||||
limit = PUBLIC_ROOMS_LIMIT,
|
||||
filter = PublicRoomsFilter(searchTerm = currentFilter),
|
||||
includeAllNetworks = roomDirectoryData.includeAllNetworks,
|
||||
since = since,
|
||||
thirdPartyInstanceId = roomDirectoryData.thirdPartyInstanceId
|
||||
),
|
||||
object : MatrixCallback<PublicRoomsResponse> {
|
||||
override fun onSuccess(data: PublicRoomsResponse) {
|
||||
currentTask = null
|
||||
|
||||
since = data.nextBatch
|
||||
since = data.nextBatch
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncPublicRoomsRequest = Success(data.chunk!!),
|
||||
// It's ok to append at the end of the list, so I use publicRooms.size()
|
||||
publicRooms = publicRooms.appendAt(data.chunk!!, publicRooms.size),
|
||||
hasMore = since != null
|
||||
)
|
||||
}
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
asyncPublicRoomsRequest = Success(data.chunk!!),
|
||||
// It's ok to append at the end of the list, so I use publicRooms.size()
|
||||
publicRooms = publicRooms.appendAt(data.chunk!!, publicRooms.size),
|
||||
hasMore = since != null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
currentTask = null
|
||||
override fun onFailure(failure: Throwable) {
|
||||
currentTask = null
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncPublicRoomsRequest = Fail(failure)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
setState {
|
||||
copy(
|
||||
asyncPublicRoomsRequest = Fail(failure)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun joinRoom(publicRoom: PublicRoom) = withState { state ->
|
||||
@ -210,7 +204,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
||||
|
||||
setState {
|
||||
copy(
|
||||
joiningRoomsIds = joiningRoomsIds.toMutableList().apply { add(publicRoom.roomId) }
|
||||
joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { add(publicRoom.roomId) }
|
||||
)
|
||||
}
|
||||
|
||||
@ -226,8 +220,8 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
||||
|
||||
setState {
|
||||
copy(
|
||||
joiningRoomsIds = joiningRoomsIds.toMutableList().apply { remove(publicRoom.roomId) },
|
||||
joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableList().apply { add(publicRoom.roomId) }
|
||||
joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { remove(publicRoom.roomId) },
|
||||
joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { add(publicRoom.roomId) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
tools:openDrawer="start">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/coordinatorLayout"
|
||||
android:id="@+id/vector_coordinator_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
@ -93,6 +93,7 @@
|
||||
android:id="@+id/inviteView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?riotx_background"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
117
vector/src/main/res/layout/item_room_invitation.xml
Normal file
117
vector/src/main/res/layout/item_room_invitation.xml
Normal file
@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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/itemRoomInvitationLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?riotx_background"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/roomInvitationAvatarImageView"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="12dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<!-- Margin bottom does not work, so I use space -->
|
||||
<Space
|
||||
android:id="@+id/roomInvitationAvatarBottomSpace"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="12dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/roomInvitationAvatarImageView"
|
||||
tools:layout_marginStart="20dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/roomInvitationNameView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginLeft="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginRight="20dp"
|
||||
android:drawableEnd="@drawable/ic_arrow_right"
|
||||
android:drawablePadding="8dp"
|
||||
android:duplicateParentState="true"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/roomInvitationAvatarImageView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@sample/matrix.json/data/displayName" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/roomInvitationSubTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="@+id/roomInvitationNameView"
|
||||
app:layout_constraintStart_toStartOf="@+id/roomInvitationNameView"
|
||||
app:layout_constraintTop_toBottomOf="@+id/roomInvitationNameView"
|
||||
tools:text="@sample/matrix.json/data/message" />
|
||||
|
||||
<!-- Margin bottom does not work, so I use space -->
|
||||
<Space
|
||||
android:id="@+id/roomLastEventBottomSpace"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="7dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/roomInvitationSubTitle"
|
||||
tools:layout_marginStart="120dp" />
|
||||
|
||||
<im.vector.riotredesign.core.platform.ButtonStateView
|
||||
android:id="@+id/roomInvitationAccept"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:minWidth="122dp"
|
||||
app:bsv_button_text="@string/accept"
|
||||
app:bsv_loaded_image_src="@drawable/ic_tick"
|
||||
app:bsv_use_flat_button="false"
|
||||
app:layout_constraintEnd_toEndOf="@+id/roomInvitationNameView"
|
||||
app:layout_constraintTop_toBottomOf="@+id/roomLastEventBottomSpace" />
|
||||
|
||||
<im.vector.riotredesign.core.platform.ButtonStateView
|
||||
android:id="@+id/roomInvitationReject"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/layout_vertical_margin"
|
||||
android:minWidth="122dp"
|
||||
app:bsv_button_text="@string/reject"
|
||||
app:bsv_loaded_image_src="@drawable/ic_tick"
|
||||
app:bsv_use_flat_button="true"
|
||||
app:layout_constraintEnd_toStartOf="@+id/roomInvitationAccept"
|
||||
app:layout_constraintTop_toTopOf="@+id/roomInvitationAccept" />
|
||||
|
||||
<View
|
||||
android:id="@+id/roomInvitationDividerView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:background="?riotx_header_panel_border_mobile"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/roomInvitationAccept" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name" translatable="false">"Riot X"</string>
|
||||
<string name="app_name" translatable="false">"RiotX"</string>
|
||||
|
||||
<!-- server urls -->
|
||||
<string name="vector_im_server_url" translatable="false">https://vector.im</string>
|
||||
|
@ -142,6 +142,16 @@
|
||||
<item name="colorControlHighlight">?colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="VectorButtonStyleOutlined" parent="Widget.MaterialComponents.Button.OutlinedButton">
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:background">@null</item>
|
||||
<!--item name="android:textColor">?colorAccent</item-->
|
||||
<item name="colorControlHighlight">?colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AlerterButton" parent="Widget.AppCompat.Button.Borderless.Colored">
|
||||
<item name="colorAccent">@android:color/white</item>
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
|
Reference in New Issue
Block a user