feat: 新 UI 双击底栏刷新
This commit is contained in:
parent
18cbbe1b76
commit
37f378f59f
|
|
@ -18,6 +18,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -49,6 +50,7 @@ import com.huanchengfly.tieba.post.utils.appPreferences
|
|||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -56,6 +58,7 @@ import kotlinx.coroutines.launch
|
|||
private fun NavigationWrapper(
|
||||
currentPosition: Int,
|
||||
onChangePosition: (position: Int) -> Unit,
|
||||
onReselected: (position: Int) -> Unit,
|
||||
navigationItems: List<NavigationItem>,
|
||||
navigationType: MainNavigationType,
|
||||
navigationContentPosition: MainNavigationContentPosition,
|
||||
|
|
@ -66,6 +69,7 @@ private fun NavigationWrapper(
|
|||
NavigationDrawerContent(
|
||||
currentPosition = currentPosition,
|
||||
onChangePosition = onChangePosition,
|
||||
onReselected = onReselected,
|
||||
navigationItems = navigationItems,
|
||||
navigationContentPosition = navigationContentPosition
|
||||
)
|
||||
|
|
@ -74,6 +78,7 @@ private fun NavigationWrapper(
|
|||
NavigationRail(
|
||||
currentPosition = currentPosition,
|
||||
onChangePosition = onChangePosition,
|
||||
onReselected = onReselected,
|
||||
navigationItems = navigationItems,
|
||||
navigationContentPosition = navigationContentPosition
|
||||
)
|
||||
|
|
@ -100,6 +105,15 @@ fun MainPage(
|
|||
initial = 0
|
||||
)
|
||||
|
||||
val eventFlows = remember {
|
||||
listOf(
|
||||
MutableSharedFlow<MainUiEvent>(),
|
||||
MutableSharedFlow<MainUiEvent>(),
|
||||
MutableSharedFlow<MainUiEvent>(),
|
||||
MutableSharedFlow<MainUiEvent>(),
|
||||
)
|
||||
}
|
||||
|
||||
val notificationCountFlow = LocalNotificationCountFlow.current
|
||||
LaunchedEffect(null) {
|
||||
notificationCountFlow.collect {
|
||||
|
|
@ -118,13 +132,13 @@ fun MainPage(
|
|||
title = stringResource(id = R.string.title_main),
|
||||
content = {
|
||||
HomePage(
|
||||
canOpenExplore = !LocalContext.current.appPreferences.hideExplore,
|
||||
onOpenExplore = {
|
||||
coroutineScope.launch {
|
||||
pagerState.scrollToPage(1)
|
||||
}
|
||||
eventFlow = eventFlows[0],
|
||||
canOpenExplore = !LocalContext.current.appPreferences.hideExplore
|
||||
) {
|
||||
coroutineScope.launch {
|
||||
pagerState.scrollToPage(1)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
),
|
||||
if (LocalContext.current.appPreferences.hideExplore) null
|
||||
|
|
@ -136,7 +150,7 @@ fun MainPage(
|
|||
},
|
||||
title = stringResource(id = R.string.title_explore),
|
||||
content = {
|
||||
ExplorePage()
|
||||
ExplorePage(eventFlows[1])
|
||||
}
|
||||
),
|
||||
NavigationItem(
|
||||
|
|
@ -187,17 +201,25 @@ fun MainPage(
|
|||
WindowHeightSizeClass.Compact -> {
|
||||
MainNavigationContentPosition.TOP
|
||||
}
|
||||
|
||||
WindowHeightSizeClass.Medium,
|
||||
WindowHeightSizeClass.Expanded -> {
|
||||
MainNavigationContentPosition.CENTER
|
||||
}
|
||||
|
||||
else -> {
|
||||
MainNavigationContentPosition.TOP
|
||||
}
|
||||
}
|
||||
val onReselected: (Int) -> Unit = {
|
||||
coroutineScope.launch {
|
||||
eventFlows[it].emit(MainUiEvent.Refresh)
|
||||
}
|
||||
}
|
||||
NavigationWrapper(
|
||||
currentPosition = pagerState.currentPage,
|
||||
onChangePosition = { coroutineScope.launch { pagerState.scrollToPage(it) } },
|
||||
onReselected = onReselected,
|
||||
navigationItems = navigationItems,
|
||||
navigationType = navigationType,
|
||||
navigationContentPosition = navigationContentPosition
|
||||
|
|
@ -209,7 +231,10 @@ fun MainPage(
|
|||
AnimatedVisibility(visible = navigationType == MainNavigationType.BOTTOM_NAVIGATION) {
|
||||
BottomNavigation(
|
||||
currentPosition = pagerState.currentPage,
|
||||
onChangePosition = { coroutineScope.launch { pagerState.scrollToPage(it) } },
|
||||
onChangePosition = {
|
||||
coroutineScope.launch { pagerState.scrollToPage(it) }
|
||||
},
|
||||
onReselected = onReselected,
|
||||
navigationItems = navigationItems,
|
||||
themeColors = themeColors,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
package com.huanchengfly.tieba.post.ui.page.main
|
||||
|
||||
import com.huanchengfly.tieba.post.arch.*
|
||||
import com.huanchengfly.tieba.post.arch.BaseViewModel
|
||||
import com.huanchengfly.tieba.post.arch.PartialChange
|
||||
import com.huanchengfly.tieba.post.arch.PartialChangeProducer
|
||||
import com.huanchengfly.tieba.post.arch.UiEvent
|
||||
import com.huanchengfly.tieba.post.arch.UiIntent
|
||||
import com.huanchengfly.tieba.post.arch.UiState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
|
|
@ -50,4 +55,6 @@ sealed interface MainPartialChange : PartialChange<MainUiState> {
|
|||
|
||||
data class MainUiState(val messageCount: Int = 0) : UiState
|
||||
|
||||
sealed interface MainUiEvent : UiEvent
|
||||
sealed interface MainUiEvent : UiEvent {
|
||||
object Refresh : MainUiEvent
|
||||
}
|
||||
|
|
@ -129,6 +129,7 @@ fun NavigationDrawerItem(
|
|||
fun NavigationDrawerContent(
|
||||
currentPosition: Int,
|
||||
onChangePosition: (position: Int) -> Unit,
|
||||
onReselected: (position: Int) -> Unit,
|
||||
navigationItems: List<NavigationItem>,
|
||||
navigationContentPosition: MainNavigationContentPosition
|
||||
) {
|
||||
|
|
@ -191,7 +192,13 @@ fun NavigationDrawerContent(
|
|||
navigationItems.forEachIndexed { index, navigationItem ->
|
||||
NavigationDrawerItem(
|
||||
selected = index == currentPosition,
|
||||
onClick = { onChangePosition(index) },
|
||||
onClick = {
|
||||
if (index == currentPosition) {
|
||||
onReselected(index)
|
||||
} else {
|
||||
onChangePosition(index)
|
||||
}
|
||||
},
|
||||
label = { Text(text = navigationItem.title) },
|
||||
icon = {
|
||||
Box {
|
||||
|
|
@ -275,6 +282,7 @@ private fun PositionLayout(
|
|||
fun NavigationRail(
|
||||
currentPosition: Int,
|
||||
onChangePosition: (position: Int) -> Unit,
|
||||
onReselected: (position: Int) -> Unit,
|
||||
navigationItems: List<NavigationItem>,
|
||||
navigationContentPosition: MainNavigationContentPosition
|
||||
) {
|
||||
|
|
@ -296,7 +304,13 @@ fun NavigationRail(
|
|||
navigationItems.forEachIndexed { index, navigationItem ->
|
||||
NavigationRailItem(
|
||||
selected = index == currentPosition,
|
||||
onClick = { onChangePosition(index) },
|
||||
onClick = {
|
||||
if (index == currentPosition) {
|
||||
onReselected(index)
|
||||
} else {
|
||||
onChangePosition(index)
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
Box {
|
||||
Icon(
|
||||
|
|
@ -345,6 +359,7 @@ fun BottomNavigationDivider(
|
|||
fun BottomNavigation(
|
||||
currentPosition: Int,
|
||||
onChangePosition: (position: Int) -> Unit,
|
||||
onReselected: (position: Int) -> Unit,
|
||||
navigationItems: List<NavigationItem>,
|
||||
themeColors: ExtendedColors = ExtendedTheme.colors
|
||||
) {
|
||||
|
|
@ -358,7 +373,11 @@ fun BottomNavigation(
|
|||
BottomNavigationItem(
|
||||
selected = index == currentPosition,
|
||||
onClick = {
|
||||
onChangePosition(index)
|
||||
if (index == currentPosition) {
|
||||
onReselected(index)
|
||||
} else {
|
||||
onChangePosition(index)
|
||||
}
|
||||
navigationItem.onClick?.invoke()
|
||||
},
|
||||
icon = {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.compose.material.Text
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Search
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -21,8 +22,10 @@ import com.google.accompanist.pager.HorizontalPager
|
|||
import com.google.accompanist.pager.rememberPagerState
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.activities.NewSearchActivity
|
||||
import com.huanchengfly.tieba.post.arch.onEvent
|
||||
import com.huanchengfly.tieba.post.goToActivity
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.page.main.MainUiEvent
|
||||
import com.huanchengfly.tieba.post.ui.page.main.explore.concern.ConcernPage
|
||||
import com.huanchengfly.tieba.post.ui.page.main.explore.hot.HotPage
|
||||
import com.huanchengfly.tieba.post.ui.page.main.explore.personalized.PersonalizedPage
|
||||
|
|
@ -31,27 +34,47 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.PagerTabIndicator
|
|||
import com.huanchengfly.tieba.post.ui.widgets.compose.Toolbar
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.accountNavIconIfCompact
|
||||
import com.huanchengfly.tieba.post.utils.AccountUtil.LocalAccount
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun ExplorePage() {
|
||||
fun ExplorePage(
|
||||
eventFlow: Flow<MainUiEvent>,
|
||||
) {
|
||||
val account = LocalAccount.current
|
||||
val context = LocalContext.current
|
||||
|
||||
val eventFlows = remember {
|
||||
listOf(
|
||||
MutableSharedFlow<MainUiEvent>(),
|
||||
MutableSharedFlow<MainUiEvent>(),
|
||||
MutableSharedFlow<MainUiEvent>()
|
||||
)
|
||||
}
|
||||
|
||||
val firstIndex = if (account != null) 0 else -1
|
||||
|
||||
val pages = listOfNotNull<Pair<String, (@Composable () -> Unit)>>(
|
||||
if (account != null) stringResource(id = R.string.title_concern) to @Composable {
|
||||
ConcernPage()
|
||||
ConcernPage(eventFlows[firstIndex + 0])
|
||||
} else null,
|
||||
stringResource(id = R.string.title_personalized) to @Composable {
|
||||
PersonalizedPage()
|
||||
PersonalizedPage(eventFlows[firstIndex + 1])
|
||||
},
|
||||
stringResource(id = R.string.title_hot) to @Composable {
|
||||
HotPage()
|
||||
HotPage(eventFlows[firstIndex + 2])
|
||||
},
|
||||
)
|
||||
val pagerState = rememberPagerState(initialPage = if (account != null) 1 else 0)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
eventFlow.onEvent<MainUiEvent.Refresh> {
|
||||
eventFlows[pagerState.currentPage].emit(it)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
backgroundColor = Color.Transparent,
|
||||
topBar = {
|
||||
|
|
@ -88,7 +111,11 @@ fun ExplorePage() {
|
|||
selected = pagerState.currentPage == index,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
pagerState.animateScrollToPage(index)
|
||||
if (pagerState.currentPage == index) {
|
||||
eventFlows[pagerState.currentPage].emit(MainUiEvent.Refresh)
|
||||
} else {
|
||||
pagerState.animateScrollToPage(index)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,16 +19,20 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.huanchengfly.tieba.post.activities.ThreadActivity
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
import com.huanchengfly.tieba.post.arch.onEvent
|
||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
||||
import com.huanchengfly.tieba.post.ui.page.main.MainUiEvent
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.FeedCard
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalDivider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun ConcernPage(
|
||||
eventFlow: Flow<MainUiEvent>,
|
||||
viewModel: ConcernViewModel = pageViewModel()
|
||||
) {
|
||||
LazyLoad(loaded = viewModel.initialized) {
|
||||
|
|
@ -55,6 +59,11 @@ fun ConcernPage(
|
|||
val pullRefreshState = rememberPullRefreshState(
|
||||
refreshing = isRefreshing,
|
||||
onRefresh = { viewModel.send(ConcernUiIntent.Refresh) })
|
||||
|
||||
eventFlow.onEvent<MainUiEvent.Refresh> {
|
||||
viewModel.send(ConcernUiIntent.Refresh)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.pullRefresh(pullRefreshState)
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import com.google.accompanist.placeholder.material.placeholder
|
|||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
import com.huanchengfly.tieba.post.arch.onEvent
|
||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.OrangeA700
|
||||
|
|
@ -55,6 +56,7 @@ import com.huanchengfly.tieba.post.ui.common.theme.compose.White
|
|||
import com.huanchengfly.tieba.post.ui.common.theme.compose.Yellow
|
||||
import com.huanchengfly.tieba.post.ui.page.LocalNavigator
|
||||
import com.huanchengfly.tieba.post.ui.page.destinations.HotTopicListPageDestination
|
||||
import com.huanchengfly.tieba.post.ui.page.main.MainUiEvent
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.NetworkImage
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ProvideContentColor
|
||||
|
|
@ -64,17 +66,22 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.items
|
|||
import com.huanchengfly.tieba.post.ui.widgets.compose.itemsIndexed
|
||||
import com.huanchengfly.tieba.post.utils.StringUtil.getShortNumString
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun HotPage(
|
||||
eventFlow: Flow<MainUiEvent>,
|
||||
viewModel: HotViewModel = pageViewModel()
|
||||
) {
|
||||
LazyLoad(loaded = viewModel.initialized) {
|
||||
viewModel.send(HotUiIntent.Load)
|
||||
viewModel.initialized = true
|
||||
}
|
||||
eventFlow.onEvent<MainUiEvent.Refresh> {
|
||||
viewModel.send(HotUiIntent.Load)
|
||||
}
|
||||
val navigator = LocalNavigator.current
|
||||
val isLoading by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = HotUiState::isRefreshing,
|
||||
|
|
|
|||
|
|
@ -53,20 +53,22 @@ import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo
|
|||
import com.huanchengfly.tieba.post.api.models.protos.personalized.DislikeReason
|
||||
import com.huanchengfly.tieba.post.api.models.protos.personalized.ThreadPersonalized
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
import com.huanchengfly.tieba.post.arch.onEvent
|
||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.page.main.MainUiEvent
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.FeedCard
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalDivider
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun PersonalizedPage(
|
||||
eventFlow: Flow<MainUiEvent>,
|
||||
viewModel: PersonalizedViewModel = pageViewModel()
|
||||
) {
|
||||
LazyLoad(loaded = viewModel.initialized) {
|
||||
|
|
@ -113,16 +115,15 @@ fun PersonalizedPage(
|
|||
var showRefreshTip by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
launch {
|
||||
viewModel.uiEventFlow
|
||||
.filterIsInstance<PersonalizedUiEvent.RefreshSuccess>()
|
||||
.collect {
|
||||
refreshCount = it.count
|
||||
showRefreshTip = true
|
||||
}
|
||||
}
|
||||
|
||||
eventFlow.onEvent<MainUiEvent.Refresh> {
|
||||
viewModel.send(PersonalizedUiIntent.Refresh)
|
||||
}
|
||||
viewModel.onEvent<PersonalizedUiEvent.RefreshSuccess> {
|
||||
refreshCount = it.count
|
||||
showRefreshTip = true
|
||||
}
|
||||
|
||||
if (showRefreshTip) {
|
||||
LaunchedEffect(Unit) {
|
||||
delay(2000)
|
||||
|
|
|
|||
|
|
@ -70,11 +70,13 @@ import com.huanchengfly.tieba.post.R
|
|||
import com.huanchengfly.tieba.post.activities.LoginActivity
|
||||
import com.huanchengfly.tieba.post.activities.NewSearchActivity
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
import com.huanchengfly.tieba.post.arch.onEvent
|
||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||
import com.huanchengfly.tieba.post.goToActivity
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.page.LocalNavigator
|
||||
import com.huanchengfly.tieba.post.ui.page.destinations.ForumPageDestination
|
||||
import com.huanchengfly.tieba.post.ui.page.main.MainUiEvent
|
||||
import com.huanchengfly.tieba.post.ui.widgets.Chip
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ActionItem
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
||||
|
|
@ -93,6 +95,7 @@ import com.huanchengfly.tieba.post.utils.AccountUtil.LocalAccount
|
|||
import com.huanchengfly.tieba.post.utils.ImageUtil
|
||||
import com.huanchengfly.tieba.post.utils.TiebaUtil
|
||||
import com.huanchengfly.tieba.post.utils.appPreferences
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
private fun getGridCells(context: Context, listSingle: Boolean = context.appPreferences.listSingle): GridCells {
|
||||
return if (listSingle) {
|
||||
|
|
@ -370,6 +373,7 @@ private fun ForumItem(
|
|||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun HomePage(
|
||||
eventFlow: Flow<MainUiEvent>,
|
||||
viewModel: HomeViewModel = pageViewModel<HomeUiIntent, HomeViewModel>(
|
||||
listOf(
|
||||
HomeUiIntent.Refresh
|
||||
|
|
@ -400,6 +404,10 @@ fun HomePage(
|
|||
var listSingle by remember { mutableStateOf(context.appPreferences.listSingle) }
|
||||
val gridCells by remember { derivedStateOf { getGridCells(context, listSingle) } }
|
||||
|
||||
eventFlow.onEvent<MainUiEvent.Refresh> {
|
||||
viewModel.send(HomeUiIntent.Refresh)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
backgroundColor = Color.Transparent,
|
||||
topBar = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue