mirror of
				https://github.com/KokaKiwi/BarInsta
				synced 2025-11-04 05:25:35 +00:00 
			
		
		
		
	Update ProfileFragmentViewModel
This commit is contained in:
		
							parent
							
								
									b1628492f5
								
							
						
					
					
						commit
						1ebf7a2e4b
					
				@ -192,6 +192,7 @@ dependencies {
 | 
			
		||||
    // Lifecycle
 | 
			
		||||
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
 | 
			
		||||
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
 | 
			
		||||
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
 | 
			
		||||
 | 
			
		||||
    // Room
 | 
			
		||||
    def room_version = "2.3.0"
 | 
			
		||||
@ -244,6 +245,7 @@ dependencies {
 | 
			
		||||
    testImplementation "androidx.test:core-ktx:1.3.0"
 | 
			
		||||
    testImplementation "androidx.arch.core:core-testing:2.1.0"
 | 
			
		||||
    testImplementation "org.robolectric:robolectric:4.5.1"
 | 
			
		||||
    testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.0'
 | 
			
		||||
 | 
			
		||||
    androidTestImplementation 'org.junit.jupiter:junit-jupiter:5.7.2'
 | 
			
		||||
    androidTestImplementation 'androidx.test:core:1.3.0'
 | 
			
		||||
 | 
			
		||||
@ -6,11 +6,12 @@ import androidx.savedstate.SavedStateRegistryOwner
 | 
			
		||||
import awais.instagrabber.db.repositories.AccountRepository
 | 
			
		||||
import awais.instagrabber.db.repositories.FavoriteRepository
 | 
			
		||||
import awais.instagrabber.managers.DirectMessagesManager
 | 
			
		||||
import awais.instagrabber.models.enums.BroadcastItemType
 | 
			
		||||
import awais.instagrabber.models.Resource
 | 
			
		||||
import awais.instagrabber.repositories.responses.User
 | 
			
		||||
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
 | 
			
		||||
import awais.instagrabber.webservices.*
 | 
			
		||||
import kotlinx.coroutines.CoroutineDispatcher
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
 | 
			
		||||
class ProfileFragmentViewModel(
 | 
			
		||||
    state: SavedStateHandle,
 | 
			
		||||
@ -21,12 +22,61 @@ class ProfileFragmentViewModel(
 | 
			
		||||
    graphQLRepository: GraphQLRepository,
 | 
			
		||||
    accountRepository: AccountRepository,
 | 
			
		||||
    favoriteRepository: FavoriteRepository,
 | 
			
		||||
    ioDispatcher: CoroutineDispatcher,
 | 
			
		||||
) : ViewModel() {
 | 
			
		||||
    private val _profile = MutableLiveData<Resource<User?>>(Resource.loading(null))
 | 
			
		||||
    private val _isLoggedIn = MutableLiveData(false)
 | 
			
		||||
    private val _currentUser = MutableLiveData<Resource<User?>>(Resource.loading(null))
 | 
			
		||||
    private var messageManager: DirectMessagesManager? = null
 | 
			
		||||
 | 
			
		||||
    val profile: LiveData<Resource<User?>> = _profile
 | 
			
		||||
    val currentUser: LiveData<Resource<User?>> = _currentUser
 | 
			
		||||
    val isLoggedIn: LiveData<Boolean> = currentUser.map { it.data != null }
 | 
			
		||||
 | 
			
		||||
    private val currentUserAndStateUsernameLiveData: LiveData<Pair<Resource<User?>, Resource<String?>>> =
 | 
			
		||||
        object : MediatorLiveData<Pair<Resource<User?>, Resource<String?>>>() {
 | 
			
		||||
            var user: Resource<User?> = Resource.loading(null)
 | 
			
		||||
            var stateUsername: Resource<String?> = Resource.loading(null)
 | 
			
		||||
 | 
			
		||||
            init {
 | 
			
		||||
                addSource(currentUser) { currentUser ->
 | 
			
		||||
                    this.user = currentUser
 | 
			
		||||
                    value = currentUser to stateUsername
 | 
			
		||||
                }
 | 
			
		||||
                addSource(state.getLiveData<String?>("username")) { username ->
 | 
			
		||||
                    this.stateUsername = Resource.success(username)
 | 
			
		||||
                    value = user to this.stateUsername
 | 
			
		||||
                }
 | 
			
		||||
                // trigger currentUserAndStateUsernameLiveData switch map with a state username success resource
 | 
			
		||||
                if (!state.contains("username")) {
 | 
			
		||||
                    this.stateUsername = Resource.success(null)
 | 
			
		||||
                    value = user to this.stateUsername
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    val profile: LiveData<Resource<User?>> = currentUserAndStateUsernameLiveData.switchMap {
 | 
			
		||||
        val (userResource, stateUsernameResource) = it
 | 
			
		||||
        liveData<Resource<User?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
 | 
			
		||||
            if (userResource.status == Resource.Status.LOADING || stateUsernameResource.status == Resource.Status.LOADING) {
 | 
			
		||||
                emit(Resource.loading(null))
 | 
			
		||||
                return@liveData
 | 
			
		||||
            }
 | 
			
		||||
            val user = userResource.data
 | 
			
		||||
            val stateUsername = stateUsernameResource.data
 | 
			
		||||
            if (stateUsername.isNullOrBlank()) {
 | 
			
		||||
                emit(Resource.success(user))
 | 
			
		||||
                return@liveData
 | 
			
		||||
            }
 | 
			
		||||
            try {
 | 
			
		||||
                val fetchedUser = if (user != null) {
 | 
			
		||||
                    userRepository.getUsernameInfo(stateUsername) // logged in
 | 
			
		||||
                } else {
 | 
			
		||||
                    graphQLRepository.fetchUser(stateUsername) // anonymous
 | 
			
		||||
                }
 | 
			
		||||
                emit(Resource.success(fetchedUser))
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
                emit(Resource.error(e.message, null))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Username of profile without '`@`'
 | 
			
		||||
@ -37,30 +87,12 @@ class ProfileFragmentViewModel(
 | 
			
		||||
            Resource.Status.SUCCESS -> it.data?.username ?: ""
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    val isLoggedIn: LiveData<Boolean> = _isLoggedIn
 | 
			
		||||
 | 
			
		||||
    var currentUser: Resource<User?>? = null
 | 
			
		||||
        set(value) {
 | 
			
		||||
            _isLoggedIn.postValue(value?.data != null)
 | 
			
		||||
            // if no profile, and value is valid, set it as profile
 | 
			
		||||
            val profileValue = profile.value
 | 
			
		||||
            if (
 | 
			
		||||
                profileValue?.status != Resource.Status.LOADING
 | 
			
		||||
                && profileValue?.data == null
 | 
			
		||||
                && value?.status == Resource.Status.SUCCESS
 | 
			
		||||
                && value.data != null
 | 
			
		||||
            ) {
 | 
			
		||||
                _profile.postValue(Resource.success(value.data))
 | 
			
		||||
            }
 | 
			
		||||
            field = value
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        // Log.d(TAG, "${state.keys()} $userRepository $friendshipRepository $storiesRepository $mediaRepository")
 | 
			
		||||
        val usernameFromState = state.get<String?>("username")
 | 
			
		||||
        if (usernameFromState.isNullOrBlank()) {
 | 
			
		||||
            _profile.postValue(Resource.success(null))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun setCurrentUser(currentUser: Resource<User?>) {
 | 
			
		||||
        _currentUser.postValue(currentUser)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun shareDm(result: RankedRecipient) {
 | 
			
		||||
@ -104,6 +136,7 @@ class ProfileFragmentViewModelFactory(
 | 
			
		||||
            graphQLRepository,
 | 
			
		||||
            accountRepository,
 | 
			
		||||
            favoriteRepository,
 | 
			
		||||
            Dispatchers.IO,
 | 
			
		||||
        ) as T
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ import org.json.JSONObject
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GraphQLRepository(private val service: GraphQLService) {
 | 
			
		||||
open class GraphQLRepository(private val service: GraphQLService) {
 | 
			
		||||
 | 
			
		||||
    // TODO convert string response to a response class
 | 
			
		||||
    private suspend fun fetch(
 | 
			
		||||
@ -176,7 +176,7 @@ class GraphQLRepository(private val service: GraphQLService) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO convert string response to a response class
 | 
			
		||||
    suspend fun fetchUser(
 | 
			
		||||
    open suspend fun fetchUser(
 | 
			
		||||
        username: String,
 | 
			
		||||
    ): User {
 | 
			
		||||
        val response = service.getUser(username)
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,84 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 Google LLC
 | 
			
		||||
 *
 | 
			
		||||
 * 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 awais.instagrabber
 | 
			
		||||
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
 | 
			
		||||
import kotlinx.coroutines.test.TestCoroutineDispatcher
 | 
			
		||||
import kotlinx.coroutines.test.TestCoroutineScope
 | 
			
		||||
import kotlinx.coroutines.test.resetMain
 | 
			
		||||
import kotlinx.coroutines.test.setMain
 | 
			
		||||
import org.junit.rules.TestWatcher
 | 
			
		||||
import org.junit.runner.Description
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * MainCoroutineRule installs a TestCoroutineDispatcher for Disptachers.Main.
 | 
			
		||||
 *
 | 
			
		||||
 * Since it extends TestCoroutineScope, you can directly launch coroutines on the MainCoroutineRule
 | 
			
		||||
 * as a [CoroutineScope]:
 | 
			
		||||
 *
 | 
			
		||||
 * ```
 | 
			
		||||
 * mainCoroutineRule.launch { aTestCoroutine() }
 | 
			
		||||
 * ```
 | 
			
		||||
 *
 | 
			
		||||
 * All coroutines started on [MainCoroutineScopeRule] must complete (including timeouts) before the test
 | 
			
		||||
 * finishes, or it will throw an exception.
 | 
			
		||||
 *
 | 
			
		||||
 * When using MainCoroutineRule you should always invoke runBlockingTest on it to avoid creating two
 | 
			
		||||
 * instances of [TestCoroutineDispatcher] or [TestCoroutineScope] in your test:
 | 
			
		||||
 *
 | 
			
		||||
 * ```
 | 
			
		||||
 * @Test
 | 
			
		||||
 * fun usingRunBlockingTest() = mainCoroutineRule.runBlockingTest {
 | 
			
		||||
 *     aTestCoroutine()
 | 
			
		||||
 * }
 | 
			
		||||
 * ```
 | 
			
		||||
 *
 | 
			
		||||
 * You may call [DelayController] methods on [MainCoroutineScopeRule] and they will control the
 | 
			
		||||
 * virtual-clock.
 | 
			
		||||
 *
 | 
			
		||||
 * ```
 | 
			
		||||
 * mainCoroutineRule.pauseDispatcher()
 | 
			
		||||
 * // do some coroutines
 | 
			
		||||
 * mainCoroutineRule.advanceUntilIdle() // run all pending coroutines until the dispatcher is idle
 | 
			
		||||
 * ```
 | 
			
		||||
 *
 | 
			
		||||
 * By default, [MainCoroutineScopeRule] will be in a *resumed* state.
 | 
			
		||||
 *
 | 
			
		||||
 * @param dispatcher if provided, this [TestCoroutineDispatcher] will be used.
 | 
			
		||||
 */
 | 
			
		||||
@ExperimentalCoroutinesApi
 | 
			
		||||
class MainCoroutineScopeRule(val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()) :
 | 
			
		||||
    TestWatcher(),
 | 
			
		||||
    TestCoroutineScope by TestCoroutineScope(dispatcher) {
 | 
			
		||||
    override fun starting(description: Description?) {
 | 
			
		||||
        super.starting(description)
 | 
			
		||||
        // If your codebase allows the injection of other dispatchers like
 | 
			
		||||
        // Dispatchers.Default and Dispatchers.IO, consider injecting all of them here
 | 
			
		||||
        // and renaming this class to `CoroutineScopeRule`
 | 
			
		||||
        //
 | 
			
		||||
        // All injected dispatchers in a test should point to a single instance of
 | 
			
		||||
        // TestCoroutineDispatcher.
 | 
			
		||||
        Dispatchers.setMain(dispatcher)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun finished(description: Description?) {
 | 
			
		||||
        super.finished(description)
 | 
			
		||||
        cleanupTestCoroutines()
 | 
			
		||||
        Dispatchers.resetMain()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -3,6 +3,7 @@ package awais.instagrabber.viewmodels
 | 
			
		||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
 | 
			
		||||
import androidx.lifecycle.SavedStateHandle
 | 
			
		||||
import androidx.test.ext.junit.runners.AndroidJUnit4
 | 
			
		||||
import awais.instagrabber.MainCoroutineScopeRule
 | 
			
		||||
import awais.instagrabber.common.*
 | 
			
		||||
import awais.instagrabber.db.datasources.AccountDataSource
 | 
			
		||||
import awais.instagrabber.db.datasources.FavoriteDataSource
 | 
			
		||||
@ -12,6 +13,7 @@ import awais.instagrabber.getOrAwaitValue
 | 
			
		||||
import awais.instagrabber.models.Resource
 | 
			
		||||
import awais.instagrabber.repositories.responses.User
 | 
			
		||||
import awais.instagrabber.webservices.*
 | 
			
		||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
 | 
			
		||||
import org.junit.Rule
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
import org.junit.jupiter.api.Assertions.assertEquals
 | 
			
		||||
@ -21,12 +23,22 @@ import org.junit.runner.RunWith
 | 
			
		||||
@RunWith(AndroidJUnit4::class)
 | 
			
		||||
internal class ProfileFragmentViewModelTest {
 | 
			
		||||
 | 
			
		||||
    private val testPublicUser = User(
 | 
			
		||||
        pk = 100,
 | 
			
		||||
        username = "test",
 | 
			
		||||
        fullName = "Test user"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @get:Rule
 | 
			
		||||
    var instantExecutorRule = InstantTaskExecutorRule()
 | 
			
		||||
 | 
			
		||||
    @ExperimentalCoroutinesApi
 | 
			
		||||
    @get:Rule
 | 
			
		||||
    val coroutineScope = MainCoroutineScopeRule()
 | 
			
		||||
 | 
			
		||||
    @ExperimentalCoroutinesApi
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testNoUsernameNoCurrentUser() {
 | 
			
		||||
        val accountDataSource = AccountDataSource(AccountDaoAdapter())
 | 
			
		||||
        val viewModel = ProfileFragmentViewModel(
 | 
			
		||||
            SavedStateHandle(),
 | 
			
		||||
            UserRepository(UserServiceAdapter()),
 | 
			
		||||
@ -34,46 +46,75 @@ internal class ProfileFragmentViewModelTest {
 | 
			
		||||
            StoriesRepository(StoriesServiceAdapter()),
 | 
			
		||||
            MediaRepository(MediaServiceAdapter()),
 | 
			
		||||
            GraphQLRepository(GraphQLServiceAdapter()),
 | 
			
		||||
            AccountRepository(accountDataSource),
 | 
			
		||||
            FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter()))
 | 
			
		||||
            AccountRepository(AccountDataSource(AccountDaoAdapter())),
 | 
			
		||||
            FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
 | 
			
		||||
            coroutineScope.dispatcher,
 | 
			
		||||
        )
 | 
			
		||||
        assertEquals(false, viewModel.isLoggedIn.getOrAwaitValue())
 | 
			
		||||
        viewModel.setCurrentUser(Resource.success(null))
 | 
			
		||||
        assertNull(viewModel.profile.getOrAwaitValue().data)
 | 
			
		||||
        assertEquals("", viewModel.username.getOrAwaitValue())
 | 
			
		||||
        viewModel.currentUser = Resource.success(null)
 | 
			
		||||
        viewModel.setCurrentUser(Resource.success(null))
 | 
			
		||||
        assertEquals(false, viewModel.isLoggedIn.getOrAwaitValue())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @ExperimentalCoroutinesApi
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testNoUsernameWithCurrentUser() {
 | 
			
		||||
        // val state = SavedStateHandle(
 | 
			
		||||
        //     mutableMapOf<String, Any?>(
 | 
			
		||||
        //         "username" to "test"
 | 
			
		||||
        //     )
 | 
			
		||||
        // )
 | 
			
		||||
        val userRepository = UserRepository(UserServiceAdapter())
 | 
			
		||||
        val friendshipRepository = FriendshipRepository(FriendshipServiceAdapter())
 | 
			
		||||
        val storiesRepository = StoriesRepository(StoriesServiceAdapter())
 | 
			
		||||
        val mediaRepository = MediaRepository(MediaServiceAdapter())
 | 
			
		||||
        val graphQLRepository = GraphQLRepository(GraphQLServiceAdapter())
 | 
			
		||||
        val accountDataSource = AccountDataSource(AccountDaoAdapter())
 | 
			
		||||
        val accountRepository = AccountRepository(accountDataSource)
 | 
			
		||||
        val favoriteRepository = FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter()))
 | 
			
		||||
        val viewModel = ProfileFragmentViewModel(
 | 
			
		||||
            SavedStateHandle(),
 | 
			
		||||
            userRepository,
 | 
			
		||||
            friendshipRepository,
 | 
			
		||||
            storiesRepository,
 | 
			
		||||
            mediaRepository,
 | 
			
		||||
            graphQLRepository,
 | 
			
		||||
            accountRepository,
 | 
			
		||||
            favoriteRepository
 | 
			
		||||
            UserRepository(UserServiceAdapter()),
 | 
			
		||||
            FriendshipRepository(FriendshipServiceAdapter()),
 | 
			
		||||
            StoriesRepository(StoriesServiceAdapter()),
 | 
			
		||||
            MediaRepository(MediaServiceAdapter()),
 | 
			
		||||
            GraphQLRepository(GraphQLServiceAdapter()),
 | 
			
		||||
            AccountRepository(AccountDataSource(AccountDaoAdapter())),
 | 
			
		||||
            FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
 | 
			
		||||
            coroutineScope.dispatcher,
 | 
			
		||||
        )
 | 
			
		||||
        assertEquals(false, viewModel.isLoggedIn.getOrAwaitValue())
 | 
			
		||||
        assertNull(viewModel.profile.getOrAwaitValue().data)
 | 
			
		||||
        val user = User()
 | 
			
		||||
        viewModel.currentUser = Resource.success(user)
 | 
			
		||||
        viewModel.setCurrentUser(Resource.success(user))
 | 
			
		||||
        assertEquals(true, viewModel.isLoggedIn.getOrAwaitValue())
 | 
			
		||||
        assertEquals(user, viewModel.profile.getOrAwaitValue().data)
 | 
			
		||||
        var profile = viewModel.profile.getOrAwaitValue()
 | 
			
		||||
        while (profile.status == Resource.Status.LOADING) {
 | 
			
		||||
            profile = viewModel.profile.getOrAwaitValue()
 | 
			
		||||
        }
 | 
			
		||||
        assertEquals(user, profile.data)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @ExperimentalCoroutinesApi
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testPublicUsernameWithNoCurrentUser() {
 | 
			
		||||
        // username without `@`
 | 
			
		||||
        val state = SavedStateHandle(
 | 
			
		||||
            mutableMapOf<String, Any?>(
 | 
			
		||||
                "username" to testPublicUser.username
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        val graphQLRepository = object : GraphQLRepository(GraphQLServiceAdapter()) {
 | 
			
		||||
            override suspend fun fetchUser(username: String): User {
 | 
			
		||||
                return testPublicUser
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        val viewModel = ProfileFragmentViewModel(
 | 
			
		||||
            state,
 | 
			
		||||
            UserRepository(UserServiceAdapter()),
 | 
			
		||||
            FriendshipRepository(FriendshipServiceAdapter()),
 | 
			
		||||
            StoriesRepository(StoriesServiceAdapter()),
 | 
			
		||||
            MediaRepository(MediaServiceAdapter()),
 | 
			
		||||
            graphQLRepository,
 | 
			
		||||
            AccountRepository(AccountDataSource(AccountDaoAdapter())),
 | 
			
		||||
            FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
 | 
			
		||||
            coroutineScope.dispatcher,
 | 
			
		||||
        )
 | 
			
		||||
        viewModel.setCurrentUser(Resource.success(null))
 | 
			
		||||
        assertEquals(false, viewModel.isLoggedIn.getOrAwaitValue())
 | 
			
		||||
        var profile = viewModel.profile.getOrAwaitValue()
 | 
			
		||||
        while (profile.status == Resource.Status.LOADING) {
 | 
			
		||||
            profile = viewModel.profile.getOrAwaitValue()
 | 
			
		||||
        }
 | 
			
		||||
        assertEquals(testPublicUser, profile.data)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user