mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-20 12:09:45 +01:00
Data loading improvements
Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
parent
8a3008ef25
commit
f99dac5f45
@ -143,6 +143,7 @@ ext {
|
||||
work_version = "1.0.1"
|
||||
koin_version = "2.1.0-alpha-1"
|
||||
lifecycle_version = "2.1.0"
|
||||
coil_version = "0.7.0"
|
||||
}
|
||||
|
||||
|
||||
@ -269,6 +270,10 @@ dependencies {
|
||||
implementation 'com.github.mario.fresco:animated-gif:111'
|
||||
implementation 'com.github.mario.fresco:imagepipeline-okhttp3:111'
|
||||
|
||||
implementation "io.coil-kt:coil:${coil_version}"
|
||||
implementation "io.coil-kt:coil-gif:${coil_version}"
|
||||
implementation "io.coil-kt:coil-svg:${coil_version}"
|
||||
|
||||
implementation 'com.github.natario1:Autocomplete:v1.1.0'
|
||||
|
||||
implementation 'com.github.cotechde.hwsecurity:hwsecurity-fido:2.4.5'
|
||||
|
@ -151,7 +151,6 @@ class NextcloudTalkApplication : Application(), LifecycleObserver {
|
||||
Security.insertProviderAt(Conscrypt.newProvider(), 1)
|
||||
|
||||
ClosedInterfaceImpl().providerInstallerInstallIfNeededAsync()
|
||||
DeviceUtils.ignoreSpecialBatteryFeatures()
|
||||
|
||||
val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java).build()
|
||||
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
|
||||
|
@ -48,6 +48,7 @@ import com.nextcloud.talk.jobs.CapabilitiesWorker;
|
||||
import com.nextcloud.talk.jobs.PushRegistrationWorker;
|
||||
import com.nextcloud.talk.jobs.SignalingSettingsWorker;
|
||||
import com.nextcloud.talk.models.database.UserEntity;
|
||||
import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListView;
|
||||
import com.nextcloud.talk.utils.ApiUtils;
|
||||
import com.nextcloud.talk.utils.ClosedInterfaceImpl;
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
||||
@ -407,7 +408,7 @@ public class AccountVerificationController extends BaseController {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (userUtils.getUsers().size() == 1) {
|
||||
getRouter().setRoot(RouterTransaction.with(new
|
||||
ConversationsListController())
|
||||
ConversationsListView())
|
||||
.pushChangeHandler(new HorizontalChangeHandler())
|
||||
.popChangeHandler(new HorizontalChangeHandler()));
|
||||
} else {
|
||||
@ -474,7 +475,7 @@ public class AccountVerificationController extends BaseController {
|
||||
|
||||
} else {
|
||||
if (userUtils.anyUserExists()) {
|
||||
getRouter().setRoot(RouterTransaction.with(new ConversationsListController())
|
||||
getRouter().setRoot(RouterTransaction.with(new ConversationsListView())
|
||||
.pushChangeHandler(new HorizontalChangeHandler())
|
||||
.popChangeHandler(new HorizontalChangeHandler()));
|
||||
} else {
|
||||
|
@ -142,9 +142,6 @@ public class ConversationsListController extends BaseController implements Searc
|
||||
@BindView(R.id.progressBar)
|
||||
ProgressBar progressBarView;
|
||||
|
||||
@BindView(R.id.emptyLayout)
|
||||
RelativeLayout emptyLayoutView;
|
||||
|
||||
@BindView(R.id.fast_scroller)
|
||||
FastScroller fastScroller;
|
||||
|
||||
@ -319,7 +316,7 @@ public class ConversationsListController extends BaseController implements Searc
|
||||
progressBarView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (roomsOverall.getOcs().getData().size() > 0) {
|
||||
/*if (roomsOverall.getOcs().getData().size() > 0) {
|
||||
if (emptyLayoutView.getVisibility() != View.GONE) {
|
||||
emptyLayoutView.setVisibility(View.GONE);
|
||||
}
|
||||
@ -335,7 +332,7 @@ public class ConversationsListController extends BaseController implements Searc
|
||||
if (swipeRefreshLayout.getVisibility() != View.GONE) {
|
||||
swipeRefreshLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
Conversation conversation;
|
||||
for (int i = 0; i < roomsOverall.getOcs().getData().size(); i++) {
|
||||
@ -386,14 +383,14 @@ public class ConversationsListController extends BaseController implements Searc
|
||||
HttpException exception = (HttpException) throwable;
|
||||
switch (exception.code()) {
|
||||
case 401:
|
||||
if (getParentController() != null &&
|
||||
/*if (getParentController() != null &&
|
||||
getParentController().getRouter() != null) {
|
||||
getParentController().getRouter().pushController((RouterTransaction.with
|
||||
(new WebViewLoginController(currentUser.getBaseUrl(),
|
||||
true))
|
||||
.pushChangeHandler(new VerticalChangeHandler())
|
||||
.popChangeHandler(new VerticalChangeHandler())));
|
||||
}
|
||||
}*/
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -432,7 +429,7 @@ public class ConversationsListController extends BaseController implements Searc
|
||||
swipeRefreshLayout.setOnRefreshListener(() -> fetchData(false));
|
||||
swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
|
||||
|
||||
emptyLayoutView.setOnClickListener(v -> showNewConversationsScreen());
|
||||
//emptyLayoutView.setOnClickListener(v -> showNewConversationsScreen());
|
||||
floatingActionButton.setOnClickListener(v -> {
|
||||
showNewConversationsScreen();
|
||||
});
|
||||
|
@ -251,3 +251,9 @@ fun createNextcloudTalkRepository(apiService: ApiService): NextcloudTalkReposito
|
||||
return NextcloudTalkRepositoryImpl(apiService)
|
||||
}
|
||||
|
||||
fun createGetConversationsUseCase(
|
||||
nextcloudTalkRepository: NextcloudTalkRepository,
|
||||
apiErrorHandler: ApiErrorHandler
|
||||
): GetConversationsUseCase {
|
||||
return GetConversationsUseCase(nextcloudTalkRepository, apiErrorHandler)
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ package com.nextcloud.talk.newarch.features.conversationsList
|
||||
|
||||
import android.app.SearchManager
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@ -60,6 +61,7 @@ import com.nextcloud.talk.models.json.conversations.Conversation
|
||||
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState.FAILED
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED_EMPTY
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADING
|
||||
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
@ -72,12 +74,14 @@ import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemClickListener
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemLongClickListener
|
||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import kotlinx.android.synthetic.main.controller_conversations_rv.view.emptyLayout
|
||||
import kotlinx.android.synthetic.main.controller_conversations_rv.view.dataStateView
|
||||
import kotlinx.android.synthetic.main.controller_conversations_rv.view.floatingActionButton
|
||||
import kotlinx.android.synthetic.main.controller_conversations_rv.view.progressBar
|
||||
import kotlinx.android.synthetic.main.controller_conversations_rv.view.recyclerView
|
||||
import kotlinx.android.synthetic.main.controller_conversations_rv.view.swipeRefreshLayoutView
|
||||
import kotlinx.android.synthetic.main.fast_scroller.view.fast_scroller
|
||||
import kotlinx.android.synthetic.main.view_states.view.errorStateImageView
|
||||
import kotlinx.android.synthetic.main.view_states.view.errorStateTextView
|
||||
import kotlinx.android.synthetic.main.view_states.view.loadingStateView
|
||||
import kotlinx.android.synthetic.main.view_states.view.stateWithMessageView
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.parceler.Parcels
|
||||
import java.util.ArrayList
|
||||
@ -178,18 +182,36 @@ class ConversationsListView() : BaseView(), OnQueryTextListener,
|
||||
viewState.observe(this@ConversationsListView, Observer { value ->
|
||||
when (value) {
|
||||
LOADING -> {
|
||||
view?.recyclerView?.visibility = View.GONE
|
||||
view?.emptyLayout?.visibility = View.GONE
|
||||
view?.swipeRefreshLayoutView?.visibility = View.GONE
|
||||
view?.progressBar?.visibility = View.VISIBLE
|
||||
view?.loadingStateView?.visibility = View.VISIBLE
|
||||
view?.stateWithMessageView?.visibility = View.GONE
|
||||
view?.dataStateView?.visibility = View.GONE
|
||||
view?.floatingActionButton?.visibility = View.GONE
|
||||
searchItem?.setVisible(false)
|
||||
searchItem?.isVisible = false
|
||||
}
|
||||
LOADED, FAILED -> {
|
||||
view?.recyclerView?.visibility = View.VISIBLE
|
||||
// The rest is handled in an actual network call
|
||||
view?.progressBar?.visibility = View.GONE
|
||||
view?.floatingActionButton?.visibility = View.VISIBLE
|
||||
LOADED -> {
|
||||
view?.loadingStateView?.visibility = View.GONE
|
||||
view?.stateWithMessageView?.visibility = View.GONE
|
||||
view!!.dataStateView.visibility = View.VISIBLE
|
||||
view?.floatingActionButton?.visibility = View.GONE
|
||||
searchItem?.isVisible = true
|
||||
}
|
||||
LOADED_EMPTY, FAILED -> {
|
||||
view?.loadingStateView?.visibility = View.GONE
|
||||
view?.dataStateView?.visibility = View.VISIBLE
|
||||
view?.floatingActionButton?.visibility = View.GONE
|
||||
searchItem?.isVisible = false
|
||||
|
||||
if (value.equals(FAILED)) {
|
||||
view?.stateWithMessageView?.errorStateTextView?.text = messageData
|
||||
view?.stateWithMessageView?.errorStateImageView?.setImageResource(
|
||||
R.drawable.ic_announcement_white_24dp
|
||||
)
|
||||
} else {
|
||||
view?.stateWithMessageView?.errorStateTextView?.text = resources?.getText(R.string.nc_conversations_empty)
|
||||
view?.stateWithMessageView?.errorStateImageView?.setImageResource(R.drawable.ic_logo)
|
||||
}
|
||||
|
||||
view?.stateWithMessageView?.visibility = View.VISIBLE
|
||||
}
|
||||
else -> {
|
||||
// We should not be here
|
||||
@ -208,16 +230,6 @@ class ConversationsListView() : BaseView(), OnQueryTextListener,
|
||||
}
|
||||
|
||||
recyclerViewAdapter.updateDataSet(newConversations as List<IFlexible<ViewHolder>>?)
|
||||
|
||||
if (it.isNotEmpty()) {
|
||||
view?.emptyLayout?.visibility = View.GONE
|
||||
view?.swipeRefreshLayoutView?.visibility = View.VISIBLE
|
||||
searchItem?.setVisible(true)
|
||||
} else {
|
||||
view?.emptyLayout?.visibility = View.VISIBLE
|
||||
view?.swipeRefreshLayoutView?.visibility = View.GONE
|
||||
searchItem?.setVisible(false)
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
@ -243,7 +255,8 @@ class ConversationsListView() : BaseView(), OnQueryTextListener,
|
||||
dataSource.subscribe(object : BaseBitmapDataSubscriber() {
|
||||
override fun onNewResultImpl(bitmap: Bitmap?) {
|
||||
if (bitmap != null && resources != null) {
|
||||
val roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(resources!!, bitmap)
|
||||
val roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(resources as Resources,
|
||||
bitmap)
|
||||
roundedBitmapDrawable.isCircular = true
|
||||
roundedBitmapDrawable.setAntiAlias(true)
|
||||
menuItem.icon = roundedBitmapDrawable
|
||||
@ -261,8 +274,19 @@ class ConversationsListView() : BaseView(), OnQueryTextListener,
|
||||
return R.layout.controller_conversations_rv
|
||||
}
|
||||
|
||||
@OnClick(R.id.floatingActionButton, R.id.emptyLayout)
|
||||
@OnClick(R.id.floatingActionButton)
|
||||
fun onFloatingActionButtonClick() {
|
||||
openNewConversationScreen()
|
||||
}
|
||||
|
||||
@OnClick(R.id.stateWithMessageView)
|
||||
fun onStateWithMessageViewClick() {
|
||||
if (viewModel.viewState.equals(LOADED_EMPTY)) {
|
||||
openNewConversationScreen()
|
||||
}
|
||||
}
|
||||
|
||||
private fun openNewConversationScreen() {
|
||||
val bundle = Bundle()
|
||||
bundle.putBoolean(BundleKeys.KEY_NEW_CONVERSATION, true)
|
||||
router.pushController(
|
||||
@ -273,7 +297,7 @@ class ConversationsListView() : BaseView(), OnQueryTextListener,
|
||||
}
|
||||
|
||||
override fun getTitle(): String? {
|
||||
return resources!!.getString(R.string.nc_app_name)
|
||||
return resources?.getString(R.string.nc_app_name)
|
||||
}
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
|
@ -30,6 +30,7 @@ import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState.FAILED
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED_EMPTY
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADING
|
||||
import com.nextcloud.talk.utils.database.user.UserUtils
|
||||
import org.apache.commons.lang3.builder.CompareToBuilder
|
||||
@ -41,7 +42,7 @@ class ConversationsListViewModel constructor(
|
||||
|
||||
val conversationsListData = MutableLiveData<List<Conversation>>()
|
||||
val viewState = MutableLiveData<ViewState>(LOADING)
|
||||
val messageData = MutableLiveData<String>()
|
||||
var messageData : String? = null
|
||||
val searchQuery = MutableLiveData<String>()
|
||||
lateinit var currentUser: UserEntity
|
||||
|
||||
@ -66,11 +67,12 @@ class ConversationsListViewModel constructor(
|
||||
})
|
||||
|
||||
conversationsListData.value = newConversations
|
||||
viewState.value = LOADED
|
||||
viewState.value = if (newConversations.isNotEmpty()) LOADED else LOADED_EMPTY
|
||||
|
||||
}
|
||||
|
||||
override fun onError(errorModel: ErrorModel?) {
|
||||
messageData.value = errorModel?.message
|
||||
messageData = errorModel?.message
|
||||
viewState.value = FAILED
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ package com.nextcloud.talk.newarch.mvvm
|
||||
|
||||
enum class ViewState {
|
||||
LOADING,
|
||||
LOADED_EMPTY,
|
||||
LOADED,
|
||||
FAILED
|
||||
}
|
@ -47,7 +47,7 @@ class NetworkUtils {
|
||||
.header("OCS-APIRequest", "true")
|
||||
.method(original.method, original.body)
|
||||
.build()
|
||||
|
||||
|
||||
val response = chain.proceed(request)
|
||||
|
||||
if (request.url.encodedPath.contains("/avatar/")) {
|
||||
|
25
app/src/main/res/drawable/ic_announcement_white_24dp.xml
Normal file
25
app/src/main/res/drawable/ic_announcement_white_24dp.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Mario Danic
|
||||
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM13,11h-2L11,5h2v6zM13,15h-2v-2h2v2z"/>
|
||||
</vector>
|
@ -25,54 +25,20 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="@dimen/item_height"
|
||||
android:layout_height="@dimen/item_height"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginLeft="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/activity_horizontal_margin"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/colorPrimary"
|
||||
android:indeterminateTintMode="src_in" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/emptyLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/noConversationsImageView"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:src="@drawable/ic_logo"
|
||||
android:tint="@color/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sendHiTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/noConversationsImageView"
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/nc_conversations_empty"
|
||||
android:textAlignment="center"
|
||||
android:textSize="20sp" />
|
||||
</RelativeLayout>
|
||||
<include layout="@layout/view_states"/>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayoutView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
android:visibility="visible"
|
||||
app:layout_behavior="com.nextcloud.talk.utils.FABAwareScrollingViewBehavior">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/dataStateView"
|
||||
android:visibility="invisible">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
|
66
app/src/main/res/layout/view_states.xml
Normal file
66
app/src/main/res/layout/view_states.xml
Normal file
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Mario Danic
|
||||
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loadingStateView"
|
||||
android:layout_width="@dimen/item_height"
|
||||
android:layout_height="@dimen/item_height"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="@dimen/activity_horizontal_margin"
|
||||
android:indeterminate="true"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminateTint="@color/colorPrimary"
|
||||
android:indeterminateTintMode="src_in"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/stateWithMessageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/errorStateImageView"
|
||||
android:layout_width="@dimen/item_height"
|
||||
android:layout_height="@dimen/item_height"
|
||||
android:layout_above="@id/errorStateTextView"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:src="@drawable/ic_announcement_white_24dp"
|
||||
android:tint="@color/colorPrimary"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errorStateTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_margin="8dp"
|
||||
android:textAlignment="center"
|
||||
android:textSize="20sp"
|
||||
/>
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
Loading…
Reference in New Issue
Block a user