Room list actions: start creating all the components
This commit is contained in:
parent
b17b54d218
commit
9762d5be40
@ -45,6 +45,7 @@ import im.vector.riotx.features.home.room.detail.timeline.action.*
|
|||||||
import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
|
import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
|
import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
|
||||||
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
||||||
|
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
||||||
import im.vector.riotx.features.home.room.list.RoomListFragment
|
import im.vector.riotx.features.home.room.list.RoomListFragment
|
||||||
import im.vector.riotx.features.invite.VectorInviteView
|
import im.vector.riotx.features.invite.VectorInviteView
|
||||||
import im.vector.riotx.features.link.LinkHandlerActivity
|
import im.vector.riotx.features.link.LinkHandlerActivity
|
||||||
@ -186,6 +187,8 @@ interface ScreenComponent {
|
|||||||
|
|
||||||
fun inject(incomingShareActivity: IncomingShareActivity)
|
fun inject(incomingShareActivity: IncomingShareActivity)
|
||||||
|
|
||||||
|
fun inject(roomListActionsBottomSheet: RoomListQuickActionsBottomSheet)
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(vectorComponent: VectorComponent,
|
fun create(vectorComponent: VectorComponent,
|
||||||
|
@ -39,6 +39,7 @@ import im.vector.riotx.core.extensions.observeEventFirstThrottle
|
|||||||
import im.vector.riotx.core.platform.OnBackPressed
|
import im.vector.riotx.core.platform.OnBackPressed
|
||||||
import im.vector.riotx.core.platform.StateView
|
import im.vector.riotx.core.platform.StateView
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
||||||
import im.vector.riotx.features.home.room.list.widget.FabMenuView
|
import im.vector.riotx.features.home.room.list.widget.FabMenuView
|
||||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||||
import im.vector.riotx.features.share.SharedData
|
import im.vector.riotx.features.share.SharedData
|
||||||
@ -298,10 +299,17 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
|
|||||||
|
|
||||||
// RoomSummaryController.Callback **************************************************************
|
// RoomSummaryController.Callback **************************************************************
|
||||||
|
|
||||||
override fun onRoomSelected(room: RoomSummary) {
|
override fun onRoomClicked(room: RoomSummary) {
|
||||||
roomListViewModel.accept(RoomListActions.SelectRoom(room))
|
roomListViewModel.accept(RoomListActions.SelectRoom(room))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onRoomLongClicked(room: RoomSummary): Boolean {
|
||||||
|
RoomListQuickActionsBottomSheet
|
||||||
|
.newInstance(room.roomId)
|
||||||
|
.show(requireActivity().supportFragmentManager, "ROOM_LIST_QUICK_ACTIONS")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
override fun onAcceptRoomInvitation(room: RoomSummary) {
|
override fun onAcceptRoomInvitation(room: RoomSummary) {
|
||||||
notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId)
|
notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId)
|
||||||
roomListViewModel.accept(RoomListActions.AcceptInvitation(room))
|
roomListViewModel.accept(RoomListActions.AcceptInvitation(room))
|
||||||
|
@ -138,7 +138,8 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
|||||||
|
|
||||||
interface Listener : FilteredRoomFooterItem.FilteredRoomFooterItemListener {
|
interface Listener : FilteredRoomFooterItem.FilteredRoomFooterItemListener {
|
||||||
fun onToggleRoomCategory(roomCategory: RoomCategory)
|
fun onToggleRoomCategory(roomCategory: RoomCategory)
|
||||||
fun onRoomSelected(room: RoomSummary)
|
fun onRoomClicked(room: RoomSummary)
|
||||||
|
fun onRoomLongClicked(room: RoomSummary): Boolean
|
||||||
fun onRejectRoomInvitation(room: RoomSummary)
|
fun onRejectRoomInvitation(room: RoomSummary)
|
||||||
fun onAcceptRoomInvitation(room: RoomSummary)
|
fun onAcceptRoomInvitation(room: RoomSummary)
|
||||||
}
|
}
|
||||||
|
@ -41,11 +41,13 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
|
|||||||
@EpoxyAttribute var hasUnreadMessage: Boolean = false
|
@EpoxyAttribute var hasUnreadMessage: Boolean = false
|
||||||
@EpoxyAttribute var hasDraft: Boolean = false
|
@EpoxyAttribute var hasDraft: Boolean = false
|
||||||
@EpoxyAttribute var showHighlighted: Boolean = false
|
@EpoxyAttribute var showHighlighted: Boolean = false
|
||||||
@EpoxyAttribute var listener: (() -> Unit)? = null
|
@EpoxyAttribute var itemLongClickListener: View.OnLongClickListener? = null
|
||||||
|
@EpoxyAttribute var itemClickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
holder.rootView.setOnClickListener { listener?.invoke() }
|
holder.rootView.setOnClickListener(itemClickListener)
|
||||||
|
holder.rootView.setOnLongClickListener(itemLongClickListener)
|
||||||
holder.titleView.text = roomName
|
holder.titleView.text = roomName
|
||||||
holder.lastEventTimeView.text = lastEventTime
|
holder.lastEventTimeView.text = lastEventTime
|
||||||
holder.lastEventView.text = lastFormattedEvent
|
holder.lastEventView.text = lastFormattedEvent
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.riotx.features.home.room.list
|
package im.vector.riotx.features.home.room.list
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
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.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
@ -28,6 +29,7 @@ import im.vector.riotx.core.extensions.localDateTime
|
|||||||
import im.vector.riotx.core.resources.ColorProvider
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
import im.vector.riotx.core.resources.DateProvider
|
import im.vector.riotx.core.resources.DateProvider
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
|
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
|
import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
|
||||||
@ -79,7 +81,7 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
|
|||||||
.rejectListener { listener?.onRejectRoomInvitation(roomSummary) }
|
.rejectListener { listener?.onRejectRoomInvitation(roomSummary) }
|
||||||
.roomName(roomSummary.displayName)
|
.roomName(roomSummary.displayName)
|
||||||
.avatarUrl(roomSummary.avatarUrl)
|
.avatarUrl(roomSummary.avatarUrl)
|
||||||
.listener { listener?.onRoomSelected(roomSummary) }
|
.listener { listener?.onRoomClicked(roomSummary) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRoomItem(roomSummary: RoomSummary, listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
private fun createRoomItem(roomSummary: RoomSummary, listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
||||||
@ -134,6 +136,13 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
|
|||||||
.unreadNotificationCount(unreadCount)
|
.unreadNotificationCount(unreadCount)
|
||||||
.hasUnreadMessage(roomSummary.hasUnreadMessages)
|
.hasUnreadMessage(roomSummary.hasUnreadMessages)
|
||||||
.hasDraft(roomSummary.userDrafts.isNotEmpty())
|
.hasDraft(roomSummary.userDrafts.isNotEmpty())
|
||||||
.listener { listener?.onRoomSelected(roomSummary) }
|
.itemLongClickListener { _ ->
|
||||||
|
listener?.onRoomLongClicked(roomSummary) ?: false
|
||||||
|
}
|
||||||
|
.itemClickListener(
|
||||||
|
DebouncedClickListener(View.OnClickListener { _ ->
|
||||||
|
listener?.onRoomClicked(roomSummary)
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotx.features.home.room.list.actions
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
|
||||||
|
sealed class RoomListQuickActions(@StringRes val titleRes: Int, @DrawableRes val iconResId: Int) {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotx.features.home.room.list.actions
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.ButterKnife
|
||||||
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class RoomListActionsArgs(
|
||||||
|
val roomId: String
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bottom sheet fragment that shows room information with list of contextual actions
|
||||||
|
*/
|
||||||
|
class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomListQuickActionsEpoxyController.Listener {
|
||||||
|
|
||||||
|
@Inject lateinit var roomListActionsViewModelFactory: RoomListActionsViewModel.Factory
|
||||||
|
@Inject lateinit var roomListActionsEpoxyController: RoomListQuickActionsEpoxyController
|
||||||
|
|
||||||
|
private val viewModel: RoomListActionsViewModel by fragmentViewModel(RoomListActionsViewModel::class)
|
||||||
|
|
||||||
|
@BindView(R.id.bottomSheetRecyclerView)
|
||||||
|
lateinit var recyclerView: RecyclerView
|
||||||
|
|
||||||
|
override val showExpanded = true
|
||||||
|
|
||||||
|
override fun injectWith(screenComponent: ScreenComponent) {
|
||||||
|
screenComponent.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = inflater.inflate(R.layout.bottom_sheet_generic_list, container, false)
|
||||||
|
ButterKnife.bind(this, view)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
recyclerView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
|
||||||
|
recyclerView.adapter = roomListActionsEpoxyController.adapter
|
||||||
|
// Disable item animation
|
||||||
|
recyclerView.itemAnimator = null
|
||||||
|
roomListActionsEpoxyController.listener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) {
|
||||||
|
roomListActionsEpoxyController.setData(it)
|
||||||
|
super.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun didSelectMenuAction(quickActions: RoomListQuickActions) {
|
||||||
|
vectorBaseActivity.notImplemented("RoomListQuickActions")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance(roomId: String): RoomListQuickActionsBottomSheet {
|
||||||
|
return RoomListQuickActionsBottomSheet().apply {
|
||||||
|
setArguments(RoomListActionsArgs(roomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotx.features.home.room.list.actions
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import im.vector.riotx.EmojiCompatFontProvider
|
||||||
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.bottomSheetItemAction
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.bottomSheetItemSeparator
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Epoxy controller for room list actions
|
||||||
|
*/
|
||||||
|
class RoomListQuickActionsEpoxyController @Inject constructor(private val stringProvider: StringProvider,
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val fontProvider: EmojiCompatFontProvider) : TypedEpoxyController<RoomListQuickActionsState>() {
|
||||||
|
|
||||||
|
var listener: Listener? = null
|
||||||
|
|
||||||
|
override fun buildModels(state: RoomListQuickActionsState) {
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
bottomSheetItemSeparator {
|
||||||
|
id("actions_separator")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
state.quickActions()?.forEachIndexed { index, action ->
|
||||||
|
bottomSheetItemAction {
|
||||||
|
id("action_$index")
|
||||||
|
iconRes(action.iconResId)
|
||||||
|
textRes(action.titleRes)
|
||||||
|
listener(View.OnClickListener { listener?.didSelectMenuAction(action) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun didSelectMenuAction(quickActions: RoomListQuickActions)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotx.features.home.room.list.actions
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
|
data class RoomListQuickActionsState(
|
||||||
|
val roomId: String,
|
||||||
|
val quickActions: Async<List<RoomListQuickActions>> = Uninitialized,
|
||||||
|
val timelineEvent: Async<RoomSummary> = Uninitialized
|
||||||
|
) : MvRxState {
|
||||||
|
|
||||||
|
constructor(args: RoomListActionsArgs) : this(roomId = args.roomId)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotx.features.home.room.list.actions
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.*
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import dagger.Lazy
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
|
||||||
|
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||||
|
|
||||||
|
class RoomListActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
|
initialState: RoomListQuickActionsState,
|
||||||
|
private val eventHtmlRenderer: Lazy<EventHtmlRenderer>,
|
||||||
|
private val session: Session,
|
||||||
|
private val noticeEventFormatter: NoticeEventFormatter,
|
||||||
|
private val stringProvider: StringProvider
|
||||||
|
) : VectorViewModel<RoomListQuickActionsState>(initialState) {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: RoomListQuickActionsState): RoomListActionsViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<RoomListActionsViewModel, RoomListQuickActionsState> {
|
||||||
|
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: RoomListQuickActionsState): RoomListActionsViewModel? {
|
||||||
|
val fragment: RoomListQuickActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
|
return fragment.roomListActionsViewModelFactory.create(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user