mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-08 12:42:44 -05:00
fix floating bar for 3-button nav + start work for displaying recent searches
This commit is contained in:
@@ -6,6 +6,7 @@ import android.os.Bundle
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
@@ -96,7 +97,10 @@ class MainActivity : MonetCompatActivity() {
|
|||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
val windowSize = rememberWindowSizeClass()
|
val windowSize = rememberWindowSizeClass()
|
||||||
val appNavController = rememberNavController()
|
val appNavController = rememberNavController()
|
||||||
Box(modifier = Modifier.padding(innerPadding.copy(bottom = 0.dp, top = 0.dp))) {
|
Box(modifier = Modifier
|
||||||
|
.padding(innerPadding.copy(bottom = 0.dp, top = 0.dp))
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
AppNavigationHost(
|
AppNavigationHost(
|
||||||
appNavController = appNavController,
|
appNavController = appNavController,
|
||||||
mainNavStartRoute = mainNavStartRoute,
|
mainNavStartRoute = mainNavStartRoute,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class AppPreferences(context: Context) {
|
|||||||
private val SHOW_NEXT_MCU = "show_next_mcu"
|
private val SHOW_NEXT_MCU = "show_next_mcu"
|
||||||
private val STORED_TEST_ROUTE = "stored_test_route"
|
private val STORED_TEST_ROUTE = "stored_test_route"
|
||||||
private val FLOATING_BOTTOM_BAR = "floating_bottom_bar"
|
private val FLOATING_BOTTOM_BAR = "floating_bottom_bar"
|
||||||
|
private val RECENT_SEARCHES = "recent_searches"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val preferences: SharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE)
|
private val preferences: SharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE)
|
||||||
@@ -147,6 +148,11 @@ class AppPreferences(context: Context) {
|
|||||||
get() = preferences.getString(STORED_TEST_ROUTE, storedTestRouteDefault) ?: storedTestRouteDefault
|
get() = preferences.getString(STORED_TEST_ROUTE, storedTestRouteDefault) ?: storedTestRouteDefault
|
||||||
set(value) { preferences.put(STORED_TEST_ROUTE, value) }
|
set(value) { preferences.put(STORED_TEST_ROUTE, value) }
|
||||||
|
|
||||||
|
/******** General Storage ********/
|
||||||
|
var recentSearches: Set<String>
|
||||||
|
get() = preferences.getStringSet(RECENT_SEARCHES, emptySet()) ?: emptySet()
|
||||||
|
set(value) { preferences.put(RECENT_SEARCHES, value) }
|
||||||
|
|
||||||
/********* Helpers ********/
|
/********* Helpers ********/
|
||||||
private fun SharedPreferences.put(key: String, value: Any?) {
|
private fun SharedPreferences.put(key: String, value: Any?) {
|
||||||
edit().apply {
|
edit().apply {
|
||||||
@@ -157,6 +163,7 @@ class AppPreferences(context: Context) {
|
|||||||
is Float -> putFloat(key, value)
|
is Float -> putFloat(key, value)
|
||||||
is Double -> putFloat(key, value.toFloat())
|
is Double -> putFloat(key, value.toFloat())
|
||||||
is String -> putString(key, value)
|
is String -> putString(key, value)
|
||||||
|
is Set<*> -> putStringSet(key, value as Set<String>)
|
||||||
else -> throw UnsupportedTypeError()
|
else -> throw UnsupportedTypeError()
|
||||||
}
|
}
|
||||||
apply()
|
apply()
|
||||||
|
|||||||
@@ -231,7 +231,11 @@ fun SearchView(
|
|||||||
onClick = {
|
onClick = {
|
||||||
appNavController.navigate(route)
|
appNavController.navigate(route)
|
||||||
},
|
},
|
||||||
modifier = Modifier.offset(y = if (preferences.floatingBottomBar) -(HomeScreen.FLOATING_NAV_BAR_HEIGHT - HomeScreen.FLOATING_NAV_BAR_OFFSET) else 0.dp)
|
modifier = Modifier
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.offset(
|
||||||
|
y = if (preferences.floatingBottomBar) -(HomeScreen.FLOATING_NAV_BAR_HEIGHT - HomeScreen.FLOATING_NAV_BAR_OFFSET) else 0.dp
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Filled.Search, stringResource(id = R.string.preference_heading_search))
|
Icon(Icons.Filled.Search, stringResource(id = R.string.preference_heading_search))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
@@ -37,6 +38,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.compositeOver
|
import androidx.compose.ui.graphics.compositeOver
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -62,7 +64,7 @@ import org.koin.java.KoinJavaComponent.get
|
|||||||
|
|
||||||
object HomeScreen {
|
object HomeScreen {
|
||||||
val FLOATING_NAV_BAR_HEIGHT = 80.dp
|
val FLOATING_NAV_BAR_HEIGHT = 80.dp
|
||||||
val FLOATING_NAV_BAR_OFFSET = (-24).dp
|
val FLOATING_NAV_BAR_OFFSET = (-12).dp
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -116,7 +118,10 @@ fun HomeScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Box(modifier = Modifier.padding(innerPadding)) {
|
Box(modifier = Modifier
|
||||||
|
.padding(innerPadding)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
MainContent(
|
MainContent(
|
||||||
windowSize = windowSize,
|
windowSize = windowSize,
|
||||||
appNavController = appNavController,
|
appNavController = appNavController,
|
||||||
@@ -167,9 +172,11 @@ private fun FloatingBottomNavBar(
|
|||||||
modifier = modifier.then(
|
modifier = modifier.then(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(HomeScreen.FLOATING_NAV_BAR_HEIGHT)
|
.navigationBarsPadding()
|
||||||
.padding(horizontal = 24.dp)
|
.padding(horizontal = 24.dp)
|
||||||
.offset(y = HomeScreen.FLOATING_NAV_BAR_OFFSET)
|
.padding(bottom = 12.dp)
|
||||||
|
.height(HomeScreen.FLOATING_NAV_BAR_HEIGHT)
|
||||||
|
// .offset(y = HomeScreen.FLOATING_NAV_BAR_OFFSET)
|
||||||
.clip(RoundedCornerShape(50))
|
.clip(RoundedCornerShape(50))
|
||||||
.background(MaterialTheme.colorScheme.defaultNavBarColor())
|
.background(MaterialTheme.colorScheme.defaultNavBarColor())
|
||||||
)
|
)
|
||||||
@@ -181,7 +188,9 @@ private fun FloatingBottomNavBar(
|
|||||||
HomeScreenNavItem.SortedItems.forEach { item ->
|
HomeScreenNavItem.SortedItems.forEach { item ->
|
||||||
val isSelected = currentRoute == item.route
|
val isSelected = currentRoute == item.route
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
modifier = Modifier.clip(RoundedCornerShape(25.dp)),
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(25.dp))
|
||||||
|
.padding(top = 4.dp),
|
||||||
icon = { Icon(imageVector = item.icon, contentDescription = null) },
|
icon = { Icon(imageVector = item.icon, contentDescription = null) },
|
||||||
label = {
|
label = {
|
||||||
if (preferences.showBottomTabLabels) {
|
if (preferences.showBottomTabLabels) {
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
|
@file:Suppress("UNCHECKED_CAST")
|
||||||
|
|
||||||
package com.owenlejeune.tvtime.ui.screens
|
package com.owenlejeune.tvtime.ui.screens
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Clear
|
import androidx.compose.material.icons.filled.Clear
|
||||||
|
import androidx.compose.material.icons.outlined.Restore
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
@@ -17,11 +21,13 @@ import androidx.compose.ui.focus.FocusRequester
|
|||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
|
import androidx.paging.PagingData
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService
|
||||||
@@ -29,6 +35,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService
|
|||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
|
||||||
import com.owenlejeune.tvtime.extensions.getCalendarYear
|
import com.owenlejeune.tvtime.extensions.getCalendarYear
|
||||||
import com.owenlejeune.tvtime.extensions.lazyPagingItems
|
import com.owenlejeune.tvtime.extensions.lazyPagingItems
|
||||||
|
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||||
import com.owenlejeune.tvtime.ui.components.BackButton
|
import com.owenlejeune.tvtime.ui.components.BackButton
|
||||||
import com.owenlejeune.tvtime.ui.components.MediaResultCard
|
import com.owenlejeune.tvtime.ui.components.MediaResultCard
|
||||||
import com.owenlejeune.tvtime.ui.components.PillSegmentedControl
|
import com.owenlejeune.tvtime.ui.components.PillSegmentedControl
|
||||||
@@ -38,6 +45,8 @@ import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
|
|||||||
import com.owenlejeune.tvtime.ui.viewmodel.SearchViewModel
|
import com.owenlejeune.tvtime.ui.viewmodel.SearchViewModel
|
||||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||||
|
import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.koin.java.KoinJavaComponent.get
|
import org.koin.java.KoinJavaComponent.get
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -45,7 +54,8 @@ import org.koin.java.KoinJavaComponent.get
|
|||||||
fun SearchScreen(
|
fun SearchScreen(
|
||||||
appNavController: NavHostController,
|
appNavController: NavHostController,
|
||||||
title: String,
|
title: String,
|
||||||
mediaViewType: MediaViewType
|
mediaViewType: MediaViewType,
|
||||||
|
preferences: AppPreferences = get(AppPreferences::class.java)
|
||||||
) {
|
) {
|
||||||
val searchViewModel = viewModel<SearchViewModel>()
|
val searchViewModel = viewModel<SearchViewModel>()
|
||||||
val applicationViewModel = viewModel<ApplicationViewModel>()
|
val applicationViewModel = viewModel<ApplicationViewModel>()
|
||||||
@@ -104,7 +114,15 @@ fun SearchScreen(
|
|||||||
Divider(thickness = 2.dp, color = MaterialTheme.colorScheme.surfaceVariant)
|
Divider(thickness = 2.dp, color = MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
|
||||||
val searchTypes = listOf(MediaViewType.MOVIE, MediaViewType.TV, MediaViewType.PERSON, MediaViewType.MIXED)
|
val searchTypes = listOf(MediaViewType.MOVIE, MediaViewType.TV, MediaViewType.PERSON, MediaViewType.MIXED)
|
||||||
val selected = remember { mutableStateOf(searchTypes[0]) }
|
val selected = remember {
|
||||||
|
mutableStateOf(
|
||||||
|
if (preferences.multiSearch) {
|
||||||
|
MediaViewType.MIXED
|
||||||
|
} else {
|
||||||
|
mediaViewType
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
PillSegmentedControl(
|
PillSegmentedControl(
|
||||||
@@ -121,9 +139,90 @@ fun SearchScreen(
|
|||||||
onItemSelected = { _, i ->
|
onItemSelected = { _, i ->
|
||||||
selected.value = i
|
selected.value = i
|
||||||
},
|
},
|
||||||
|
defaultSelectedItemIndex = searchTypes.indexOf(selected.value),
|
||||||
modifier = Modifier.padding(start = 12.dp, top = 12.dp, end = 12.dp)
|
modifier = Modifier.padding(start = 12.dp, top = 12.dp, end = 12.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
val results = remember { searchViewModel.produceSearchResultsFor(viewType.value) }
|
||||||
|
results.value?.let {
|
||||||
|
val pagingItems = (results.value as Flow<PagingData<SortableSearchResult>>).collectAsLazyPagingItems()
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = 12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
handleLoadState(context, pagingItems.loadState.refresh)
|
||||||
|
item {
|
||||||
|
if (pagingItems.itemCount == 0) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.no_search_results),
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lazyPagingItems(
|
||||||
|
lazyPagingItems = pagingItems,
|
||||||
|
key = { i -> pagingItems[i]?.id ?: -1 }
|
||||||
|
) { item ->
|
||||||
|
when (item?.mediaType) {
|
||||||
|
MediaViewType.MOVIE -> {
|
||||||
|
MovieSearchResultView(
|
||||||
|
appNavController = appNavController,
|
||||||
|
result = item as SearchResultMovie
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MediaViewType.TV -> {
|
||||||
|
TvSearchResultView(
|
||||||
|
appNavController = appNavController,
|
||||||
|
result = item as SearchResultTv
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MediaViewType.PERSON -> {
|
||||||
|
PeopleSearchResultView(
|
||||||
|
appNavController = appNavController,
|
||||||
|
result = item as SearchResultPerson
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item { Spacer(modifier = Modifier.height(12.dp)) }
|
||||||
|
handleLoadState(context, pagingItems.loadState.refresh)
|
||||||
|
}
|
||||||
|
} ?: run {
|
||||||
|
listOf("Michael Meyers", "Mutant", "Deadpool", "Ryan Reynolds", "Boardwalk Empire")//preferences.recentSearches
|
||||||
|
.forEach {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 12.dp, horizontal = 16.dp)
|
||||||
|
.clickable {
|
||||||
|
searchValue.value = it
|
||||||
|
},
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Restore,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
fontStyle = FontStyle.Italic
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
when (viewType.value) {
|
when (viewType.value) {
|
||||||
MediaViewType.TV -> {
|
MediaViewType.TV -> {
|
||||||
TvResultsView(appNavController = appNavController, searchViewModel = searchViewModel)
|
TvResultsView(appNavController = appNavController, searchViewModel = searchViewModel)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.SearchResultProvider
|
|||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.SearchService
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.SearchService
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Searchable
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Searchable
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SortableSearchResult
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SortableSearchResult
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
|
||||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||||
import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException
|
import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -41,6 +42,16 @@ class SearchViewModel: ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun produceSearchResultsFor(type: MediaViewType): MutableState<out Flow<PagingData<out SortableSearchResult>>?> {
|
||||||
|
return when (type) {
|
||||||
|
MediaViewType.MOVIE -> movieResults
|
||||||
|
MediaViewType.TV -> tvResults
|
||||||
|
MediaViewType.PERSON -> peopleResults
|
||||||
|
MediaViewType.MIXED -> multiResults
|
||||||
|
else -> throw ViewableMediaTypeException(type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun searchForMovies(query: String) {
|
fun searchForMovies(query: String) {
|
||||||
movieResults.value = createPagingSource { service.searchMovies(query, it) }
|
movieResults.value = createPagingSource { service.searchMovies(query, it) }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user