Merge pull request #6073 from vector-im/feature/eric/improve-back-navigation
Adds up navigation in spaces
This commit is contained in:
commit
b8c0c61a4c
1
changelog.d/6073.feature
Normal file
1
changelog.d/6073.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Adds up navigation in spaces
|
@ -72,6 +72,8 @@ class AppStateHandler @Inject constructor(
|
|||||||
|
|
||||||
val selectedRoomGroupingFlow = selectedSpaceDataSource.stream()
|
val selectedRoomGroupingFlow = selectedSpaceDataSource.stream()
|
||||||
|
|
||||||
|
private val spaceBackstack = ArrayDeque<String?>()
|
||||||
|
|
||||||
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? {
|
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? {
|
||||||
// XXX we should somehow make it live :/ just a work around
|
// XXX we should somehow make it live :/ just a work around
|
||||||
// For example just after creating a space and switching to it the
|
// For example just after creating a space and switching to it the
|
||||||
@ -87,12 +89,16 @@ class AppStateHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCurrentSpace(spaceId: String?, session: Session? = null, persistNow: Boolean = false) {
|
fun setCurrentSpace(spaceId: String?, session: Session? = null, persistNow: Boolean = false, isForwardNavigation: Boolean = true) {
|
||||||
|
val currentSpace = (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.BySpace)?.space()
|
||||||
val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
|
val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
|
||||||
if (selectedSpaceDataSource.currentValue?.orNull() is RoomGroupingMethod.BySpace &&
|
if (currentSpace != null && spaceId == currentSpace.roomId) return
|
||||||
spaceId == selectedSpaceDataSource.currentValue?.orNull()?.space()?.roomId) return
|
|
||||||
val spaceSum = spaceId?.let { uSession.getRoomSummary(spaceId) }
|
val spaceSum = spaceId?.let { uSession.getRoomSummary(spaceId) }
|
||||||
|
|
||||||
|
if (isForwardNavigation) {
|
||||||
|
spaceBackstack.addLast(currentSpace?.roomId)
|
||||||
|
}
|
||||||
|
|
||||||
if (persistNow) {
|
if (persistNow) {
|
||||||
uiStateRepository.storeGroupingMethod(true, uSession.sessionId)
|
uiStateRepository.storeGroupingMethod(true, uSession.sessionId)
|
||||||
uiStateRepository.storeSelectedSpace(spaceSum?.roomId, uSession.sessionId)
|
uiStateRepository.storeSelectedSpace(spaceSum?.roomId, uSession.sessionId)
|
||||||
@ -151,6 +157,8 @@ class AppStateHandler @Inject constructor(
|
|||||||
}.launchIn(session.coroutineScope)
|
}.launchIn(session.coroutineScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSpaceBackstack() = spaceBackstack
|
||||||
|
|
||||||
fun safeActiveSpaceId(): String? {
|
fun safeActiveSpaceId(): String? {
|
||||||
return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.BySpace)?.spaceSummary?.roomId
|
return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.BySpace)?.spaceSummary?.roomId
|
||||||
}
|
}
|
||||||
|
@ -199,43 +199,13 @@ class HomeActivity :
|
|||||||
when (sharedAction) {
|
when (sharedAction) {
|
||||||
is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START)
|
is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START)
|
||||||
is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START)
|
is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START)
|
||||||
is HomeActivitySharedAction.OpenGroup -> {
|
is HomeActivitySharedAction.OpenGroup -> openGroup(sharedAction.shouldClearFragment)
|
||||||
views.drawerLayout.closeDrawer(GravityCompat.START)
|
is HomeActivitySharedAction.OpenSpacePreview -> startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId))
|
||||||
|
is HomeActivitySharedAction.AddSpace -> createSpaceResultLauncher.launch(SpaceCreationActivity.newIntent(this))
|
||||||
// Temporary
|
is HomeActivitySharedAction.ShowSpaceSettings -> showSpaceSettings(sharedAction.spaceId)
|
||||||
// When switching from space to group or group to space, we need to reload the fragment
|
is HomeActivitySharedAction.OpenSpaceInvite -> openSpaceInvite(sharedAction.spaceId)
|
||||||
// To be removed when dropping legacy groups
|
HomeActivitySharedAction.SendSpaceFeedBack -> bugReporter.openBugReportScreen(this, ReportType.SPACE_BETA_FEEDBACK)
|
||||||
if (sharedAction.clearFragment) {
|
HomeActivitySharedAction.CloseGroup -> closeGroup()
|
||||||
replaceFragment(views.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true)
|
|
||||||
} else {
|
|
||||||
// nop
|
|
||||||
}
|
|
||||||
// we might want to delay that to avoid having the drawer animation lagging
|
|
||||||
// would be probably better to let the drawer do that? in the on closed callback?
|
|
||||||
}
|
|
||||||
is HomeActivitySharedAction.OpenSpacePreview -> {
|
|
||||||
startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId))
|
|
||||||
}
|
|
||||||
is HomeActivitySharedAction.AddSpace -> {
|
|
||||||
createSpaceResultLauncher.launch(SpaceCreationActivity.newIntent(this))
|
|
||||||
}
|
|
||||||
is HomeActivitySharedAction.ShowSpaceSettings -> {
|
|
||||||
// open bottom sheet
|
|
||||||
SpaceSettingsMenuBottomSheet
|
|
||||||
.newInstance(sharedAction.spaceId, object : SpaceSettingsMenuBottomSheet.InteractionListener {
|
|
||||||
override fun onShareSpaceSelected(spaceId: String) {
|
|
||||||
ShareSpaceBottomSheet.show(supportFragmentManager, spaceId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.show(supportFragmentManager, "SPACE_SETTINGS")
|
|
||||||
}
|
|
||||||
is HomeActivitySharedAction.OpenSpaceInvite -> {
|
|
||||||
SpaceInviteBottomSheet.newInstance(sharedAction.spaceId)
|
|
||||||
.show(supportFragmentManager, "SPACE_INVITE")
|
|
||||||
}
|
|
||||||
HomeActivitySharedAction.SendSpaceFeedBack -> {
|
|
||||||
bugReporter.openBugReportScreen(this, ReportType.SPACE_BETA_FEEDBACK)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.launchIn(lifecycleScope)
|
.launchIn(lifecycleScope)
|
||||||
@ -272,6 +242,37 @@ class HomeActivity :
|
|||||||
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
|
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun openGroup(shouldClearFragment: Boolean) {
|
||||||
|
views.drawerLayout.closeDrawer(GravityCompat.START)
|
||||||
|
|
||||||
|
// When switching from space to group or group to space, we need to reload the fragment
|
||||||
|
if (shouldClearFragment) {
|
||||||
|
replaceFragment(views.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true)
|
||||||
|
} else {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showSpaceSettings(spaceId: String) {
|
||||||
|
// open bottom sheet
|
||||||
|
SpaceSettingsMenuBottomSheet
|
||||||
|
.newInstance(spaceId, object : SpaceSettingsMenuBottomSheet.InteractionListener {
|
||||||
|
override fun onShareSpaceSelected(spaceId: String) {
|
||||||
|
ShareSpaceBottomSheet.show(supportFragmentManager, spaceId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.show(supportFragmentManager, "SPACE_SETTINGS")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openSpaceInvite(spaceId: String) {
|
||||||
|
SpaceInviteBottomSheet.newInstance(spaceId)
|
||||||
|
.show(supportFragmentManager, "SPACE_INVITE")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun closeGroup() {
|
||||||
|
views.drawerLayout.openDrawer(GravityCompat.START)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleShowAnalyticsOptIn() {
|
private fun handleShowAnalyticsOptIn() {
|
||||||
navigator.openAnalyticsOptIn(this)
|
navigator.openAnalyticsOptIn(this)
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,8 @@ import im.vector.app.core.platform.VectorSharedAction
|
|||||||
sealed class HomeActivitySharedAction : VectorSharedAction {
|
sealed class HomeActivitySharedAction : VectorSharedAction {
|
||||||
object OpenDrawer : HomeActivitySharedAction()
|
object OpenDrawer : HomeActivitySharedAction()
|
||||||
object CloseDrawer : HomeActivitySharedAction()
|
object CloseDrawer : HomeActivitySharedAction()
|
||||||
data class OpenGroup(val clearFragment: Boolean) : HomeActivitySharedAction()
|
data class OpenGroup(val shouldClearFragment: Boolean) : HomeActivitySharedAction()
|
||||||
|
object CloseGroup : HomeActivitySharedAction()
|
||||||
object AddSpace : HomeActivitySharedAction()
|
object AddSpace : HomeActivitySharedAction()
|
||||||
data class OpenSpacePreview(val spaceId: String) : HomeActivitySharedAction()
|
data class OpenSpacePreview(val spaceId: String) : HomeActivitySharedAction()
|
||||||
data class OpenSpaceInvite(val spaceId: String) : HomeActivitySharedAction()
|
data class OpenSpaceInvite(val spaceId: String) : HomeActivitySharedAction()
|
||||||
|
@ -33,6 +33,7 @@ import im.vector.app.R
|
|||||||
import im.vector.app.RoomGroupingMethod
|
import im.vector.app.RoomGroupingMethod
|
||||||
import im.vector.app.core.extensions.commitTransaction
|
import im.vector.app.core.extensions.commitTransaction
|
||||||
import im.vector.app.core.extensions.toMvRxBundle
|
import im.vector.app.core.extensions.toMvRxBundle
|
||||||
|
import im.vector.app.core.platform.OnBackPressed
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
@ -69,7 +70,8 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
private val appStateHandler: AppStateHandler
|
private val appStateHandler: AppStateHandler
|
||||||
) : VectorBaseFragment<FragmentHomeDetailBinding>(),
|
) : VectorBaseFragment<FragmentHomeDetailBinding>(),
|
||||||
KeysBackupBanner.Delegate,
|
KeysBackupBanner.Delegate,
|
||||||
CurrentCallsView.Callback {
|
CurrentCallsView.Callback,
|
||||||
|
OnBackPressed {
|
||||||
|
|
||||||
private val viewModel: HomeDetailViewModel by fragmentViewModel()
|
private val viewModel: HomeDetailViewModel by fragmentViewModel()
|
||||||
private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel()
|
private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel()
|
||||||
@ -130,12 +132,8 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
|
|
||||||
viewModel.onEach(HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod ->
|
viewModel.onEach(HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod ->
|
||||||
when (roomGroupingMethod) {
|
when (roomGroupingMethod) {
|
||||||
is RoomGroupingMethod.ByLegacyGroup -> {
|
is RoomGroupingMethod.ByLegacyGroup -> onGroupChange(roomGroupingMethod.groupSummary)
|
||||||
onGroupChange(roomGroupingMethod.groupSummary)
|
is RoomGroupingMethod.BySpace -> onSpaceChange(roomGroupingMethod.spaceSummary)
|
||||||
}
|
|
||||||
is RoomGroupingMethod.BySpace -> {
|
|
||||||
onSpaceChange(roomGroupingMethod.spaceSummary)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +155,6 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
|
|
||||||
unknownDeviceDetectorSharedViewModel.onEach { state ->
|
unknownDeviceDetectorSharedViewModel.onEach { state ->
|
||||||
state.unknownSessions.invoke()?.let { unknownDevices ->
|
state.unknownSessions.invoke()?.let { unknownDevices ->
|
||||||
// Timber.v("## Detector Triggerred in fragment - ${unknownDevices.firstOrNull()}")
|
|
||||||
if (unknownDevices.firstOrNull()?.currentSessionTrust == true) {
|
if (unknownDevices.firstOrNull()?.currentSessionTrust == true) {
|
||||||
val uid = "review_login"
|
val uid = "review_login"
|
||||||
alertManager.cancelAlert(uid)
|
alertManager.cancelAlert(uid)
|
||||||
@ -190,6 +187,27 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun navigateBack() {
|
||||||
|
try {
|
||||||
|
val lastSpace = appStateHandler.getSpaceBackstack().removeLast()
|
||||||
|
setCurrentSpace(lastSpace)
|
||||||
|
} catch (e: NoSuchElementException) {
|
||||||
|
navigateUpOneSpace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setCurrentSpace(spaceId: String?) {
|
||||||
|
appStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false)
|
||||||
|
sharedActionViewModel.post(HomeActivitySharedAction.CloseGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateUpOneSpace() {
|
||||||
|
val parentId = getCurrentSpace()?.flattenParentIds?.lastOrNull()
|
||||||
|
setCurrentSpace(parentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCurrentSpace() = (appStateHandler.getCurrentRoomGroupingMethod() as? RoomGroupingMethod.BySpace)?.spaceSummary
|
||||||
|
|
||||||
private fun handleCallStarted() {
|
private fun handleCallStarted() {
|
||||||
dismissLoadingDialog()
|
dismissLoadingDialog()
|
||||||
val fragmentTag = HomeTab.DialPad.toFragmentTag()
|
val fragmentTag = HomeTab.DialPad.toFragmentTag()
|
||||||
@ -203,20 +221,16 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
// update notification tab if needed
|
|
||||||
updateTabVisibilitySafely(R.id.bottom_action_notification, vectorPreferences.labAddNotificationTab())
|
updateTabVisibilitySafely(R.id.bottom_action_notification, vectorPreferences.labAddNotificationTab())
|
||||||
callManager.checkForProtocolsSupportIfNeeded()
|
callManager.checkForProtocolsSupportIfNeeded()
|
||||||
|
refreshSpaceState()
|
||||||
|
}
|
||||||
|
|
||||||
// Current space/group is not live so at least refresh toolbar on resume
|
private fun refreshSpaceState() {
|
||||||
appStateHandler.getCurrentRoomGroupingMethod()?.let { roomGroupingMethod ->
|
when (val roomGroupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) {
|
||||||
when (roomGroupingMethod) {
|
is RoomGroupingMethod.ByLegacyGroup -> onGroupChange(roomGroupingMethod.groupSummary)
|
||||||
is RoomGroupingMethod.ByLegacyGroup -> {
|
is RoomGroupingMethod.BySpace -> onSpaceChange(roomGroupingMethod.spaceSummary)
|
||||||
onGroupChange(roomGroupingMethod.groupSummary)
|
else -> Unit
|
||||||
}
|
|
||||||
is RoomGroupingMethod.BySpace -> {
|
|
||||||
onSpaceChange(roomGroupingMethod.spaceSummary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,12 +274,12 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
|
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
|
||||||
colorInt = colorProvider.getColorFromAttribute(R.attr.colorPrimary)
|
colorInt = colorProvider.getColorFromAttribute(R.attr.colorPrimary)
|
||||||
contentAction = Runnable {
|
contentAction = Runnable {
|
||||||
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
|
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let { activity ->
|
||||||
// mark as ignored to avoid showing it again
|
// mark as ignored to avoid showing it again
|
||||||
unknownDeviceDetectorSharedViewModel.handle(
|
unknownDeviceDetectorSharedViewModel.handle(
|
||||||
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(oldUnverified.mapNotNull { it.deviceId })
|
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(oldUnverified.mapNotNull { it.deviceId })
|
||||||
)
|
)
|
||||||
it.navigator.openSettings(it, EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS)
|
activity.navigator.openSettings(activity, EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dismissedAction = Runnable {
|
dismissedAction = Runnable {
|
||||||
@ -324,11 +338,11 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
withState(viewModel) {
|
withState(viewModel) {
|
||||||
when (it.roomGroupingMethod) {
|
when (it.roomGroupingMethod) {
|
||||||
is RoomGroupingMethod.ByLegacyGroup -> {
|
is RoomGroupingMethod.ByLegacyGroup -> {
|
||||||
// nothing do far
|
// do nothing
|
||||||
}
|
}
|
||||||
is RoomGroupingMethod.BySpace -> {
|
is RoomGroupingMethod.BySpace -> {
|
||||||
it.roomGroupingMethod.spaceSummary?.let {
|
it.roomGroupingMethod.spaceSummary?.let { spaceSummary ->
|
||||||
sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(it.roomId))
|
sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(spaceSummary.roomId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -348,17 +362,6 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
viewModel.handle(HomeDetailAction.SwitchTab(tab))
|
viewModel.handle(HomeDetailAction.SwitchTab(tab))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
// val menuView = bottomNavigationView.getChildAt(0) as BottomNavigationMenuView
|
|
||||||
|
|
||||||
// bottomNavigationView.getOrCreateBadge()
|
|
||||||
// menuView.forEachIndexed { index, view ->
|
|
||||||
// val itemView = view as BottomNavigationItemView
|
|
||||||
// val badgeLayout = LayoutInflater.from(requireContext()).inflate(R.layout.vector_home_badge_unread_layout, menuView, false)
|
|
||||||
// val unreadCounterBadgeView: UnreadCounterBadgeView = badgeLayout.findViewById(R.id.actionUnreadCounterBadgeView)
|
|
||||||
// itemView.addView(badgeLayout)
|
|
||||||
// unreadCounterBadgeViews.add(index, unreadCounterBadgeView)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateUIForTab(tab: HomeTab) {
|
private fun updateUIForTab(tab: HomeTab) {
|
||||||
@ -436,7 +439,6 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) {
|
override fun invalidate() = withState(viewModel) {
|
||||||
// Timber.v(it.toString())
|
|
||||||
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople)
|
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople)
|
||||||
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
|
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
|
||||||
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)
|
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)
|
||||||
@ -496,4 +498,11 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed(toolbarButton: Boolean) = if (getCurrentSpace() != null) {
|
||||||
|
navigateBack()
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,4 +153,4 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:menu="@menu/home_bottom_navigation" />
|
app:menu="@menu/home_bottom_navigation" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
Loading…
Reference in New Issue
Block a user