feat: 话题榜
This commit is contained in:
parent
a6c57037e8
commit
876ae60140
|
|
@ -7,6 +7,7 @@ import com.huanchengfly.tieba.post.api.models.*
|
|||
import com.huanchengfly.tieba.post.api.models.protos.frsPage.FrsPageResponse
|
||||
import com.huanchengfly.tieba.post.api.models.protos.hotThreadList.HotThreadListResponse
|
||||
import com.huanchengfly.tieba.post.api.models.protos.personalized.PersonalizedResponse
|
||||
import com.huanchengfly.tieba.post.api.models.protos.topicList.TopicListResponse
|
||||
import com.huanchengfly.tieba.post.api.models.protos.userLike.UserLikeResponse
|
||||
import com.huanchengfly.tieba.post.api.models.web.ForumBean
|
||||
import com.huanchengfly.tieba.post.api.models.web.ForumHome
|
||||
|
|
@ -1084,6 +1085,11 @@ interface ITiebaApi {
|
|||
tabCode: String
|
||||
): Flow<HotThreadListResponse>
|
||||
|
||||
/**
|
||||
* 话题榜
|
||||
*/
|
||||
fun topicListFlow(): Flow<TopicListResponse>
|
||||
|
||||
/**
|
||||
* 吧页面
|
||||
*
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ import com.huanchengfly.tieba.post.api.models.protos.hotThreadList.HotThreadList
|
|||
import com.huanchengfly.tieba.post.api.models.protos.personalized.PersonalizedRequest
|
||||
import com.huanchengfly.tieba.post.api.models.protos.personalized.PersonalizedRequestData
|
||||
import com.huanchengfly.tieba.post.api.models.protos.personalized.PersonalizedResponse
|
||||
import com.huanchengfly.tieba.post.api.models.protos.topicList.TopicListRequest
|
||||
import com.huanchengfly.tieba.post.api.models.protos.topicList.TopicListRequestData
|
||||
import com.huanchengfly.tieba.post.api.models.protos.topicList.TopicListResponse
|
||||
import com.huanchengfly.tieba.post.api.models.protos.userLike.UserLikeRequest
|
||||
import com.huanchengfly.tieba.post.api.models.protos.userLike.UserLikeRequestData
|
||||
import com.huanchengfly.tieba.post.api.models.protos.userLike.UserLikeResponse
|
||||
|
|
@ -798,6 +801,22 @@ object MixedTiebaApiImpl : ITiebaApi {
|
|||
)
|
||||
}
|
||||
|
||||
override fun topicListFlow(): Flow<TopicListResponse> {
|
||||
return RetrofitTiebaApi.OFFICIAL_PROTOBUF_TIEBA_API.topicListFlow(
|
||||
buildProtobufRequestBody(
|
||||
TopicListRequest(
|
||||
TopicListRequestData(
|
||||
common = buildCommonRequest(),
|
||||
call_from = "newbang",
|
||||
list_type = "all",
|
||||
need_tab_list = "0",
|
||||
fid = 0L
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun frsPage(
|
||||
forumName: String,
|
||||
goodClassifyId: Int?
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.huanchengfly.tieba.post.api.retrofit.interfaces
|
|||
import com.huanchengfly.tieba.post.api.models.protos.frsPage.FrsPageResponse
|
||||
import com.huanchengfly.tieba.post.api.models.protos.hotThreadList.HotThreadListResponse
|
||||
import com.huanchengfly.tieba.post.api.models.protos.personalized.PersonalizedResponse
|
||||
import com.huanchengfly.tieba.post.api.models.protos.topicList.TopicListResponse
|
||||
import com.huanchengfly.tieba.post.api.models.protos.userLike.UserLikeResponse
|
||||
import com.huanchengfly.tieba.post.api.retrofit.body.MyMultipartBody
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -25,6 +26,11 @@ interface OfficialProtobufTiebaApi {
|
|||
@Body body: MyMultipartBody,
|
||||
): Flow<HotThreadListResponse>
|
||||
|
||||
@POST("/c/f/recommend/topicList?cmd=309289")
|
||||
fun topicListFlow(
|
||||
@Body body: MyMultipartBody,
|
||||
): Flow<TopicListResponse>
|
||||
|
||||
@POST("/c/f/frs/page?cmd=301001")
|
||||
fun frsPageFlow(
|
||||
@Body body: MyMultipartBody,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,230 @@
|
|||
package com.huanchengfly.tieba.post.ui.page.hottopic.list
|
||||
|
||||
import android.graphics.Typeface
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.google.accompanist.insets.ui.Scaffold
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.api.models.protos.topicList.NewTopicList
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
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
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.RedA700
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.Shapes
|
||||
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.widgets.compose.BackNavigationIcon
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.NetworkImage
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.TitleCentredToolbar
|
||||
import com.huanchengfly.tieba.post.utils.StringUtil.getShortNumString
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
|
||||
@Composable
|
||||
private fun TopicImage(
|
||||
index: Int,
|
||||
imageUri: String
|
||||
) {
|
||||
val boxModifier = if (index < 3) {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(2.39f)
|
||||
.clip(Shapes.medium)
|
||||
} else {
|
||||
Modifier
|
||||
.size(Sizes.Medium)
|
||||
.aspectRatio(1f)
|
||||
.clip(Shapes.small)
|
||||
}
|
||||
Box(
|
||||
modifier = boxModifier
|
||||
) {
|
||||
NetworkImage(
|
||||
imageUri = imageUri,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
Text(
|
||||
text = "${index + 1}",
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colors.background,
|
||||
fontFamily = FontFamily(
|
||||
Typeface.createFromAsset(
|
||||
LocalContext.current.assets,
|
||||
"bebas.ttf"
|
||||
)
|
||||
),
|
||||
modifier = Modifier
|
||||
.background(
|
||||
when (index) {
|
||||
0 -> RedA700
|
||||
1 -> OrangeA700
|
||||
2 -> Yellow
|
||||
else -> MaterialTheme.colors.onBackground.copy(ContentAlpha.medium)
|
||||
}
|
||||
)
|
||||
.padding(4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TopicBody(
|
||||
index: Int,
|
||||
item: NewTopicList
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(text = item.topic_name, style = MaterialTheme.typography.subtitle1)
|
||||
when (item.topic_tag) {
|
||||
2 -> Text(
|
||||
text = stringResource(id = R.string.topic_tag_hot),
|
||||
fontSize = 10.sp,
|
||||
color = White,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.background(RedA700)
|
||||
.padding(vertical = 2.dp, horizontal = 4.dp)
|
||||
)
|
||||
|
||||
1 -> Text(
|
||||
text = stringResource(id = R.string.topic_tag_new),
|
||||
fontSize = 10.sp,
|
||||
color = White,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.background(OrangeA700)
|
||||
.padding(vertical = 2.dp, horizontal = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = item.topic_desc,
|
||||
maxLines = if (index < 3) 3 else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.body2
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.hot_num, item.discuss_num.getShortNumString()),
|
||||
style = MaterialTheme.typography.caption,
|
||||
color = ExtendedTheme.colors.textSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun HotTopicListPage(
|
||||
viewModel: HotTopicListViewModel = pageViewModel<HotTopicListUiIntent, HotTopicListViewModel>(
|
||||
listOf(HotTopicListUiIntent.Load)
|
||||
),
|
||||
navigator: DestinationsNavigator,
|
||||
) {
|
||||
val isRefreshing by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = HotTopicListUiState::isRefreshing,
|
||||
initial = true
|
||||
)
|
||||
val topicList by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = HotTopicListUiState::topicList,
|
||||
initial = emptyList()
|
||||
)
|
||||
Scaffold(
|
||||
backgroundColor = Color.Transparent,
|
||||
topBar = {
|
||||
TitleCentredToolbar(
|
||||
title = stringResource(id = R.string.title_hot_message_list),
|
||||
navigationIcon = {
|
||||
BackNavigationIcon(onBackPressed = { navigator.navigateUp() })
|
||||
}
|
||||
)
|
||||
},
|
||||
) { contentPaddings ->
|
||||
val pullRefreshState = rememberPullRefreshState(
|
||||
refreshing = isRefreshing,
|
||||
onRefresh = { viewModel.send(HotTopicListUiIntent.Load) }
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(contentPaddings)
|
||||
.pullRefresh(pullRefreshState)
|
||||
) {
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
contentPadding = PaddingValues(16.dp)
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = topicList,
|
||||
key = { _, item -> item.topic_id },
|
||||
) { index, item ->
|
||||
if (index < 3) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
TopicImage(index = index, imageUri = item.topic_image)
|
||||
TopicBody(index = index, item = item)
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
TopicImage(index = index, imageUri = item.topic_image)
|
||||
TopicBody(index = index, item = item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = isRefreshing,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.huanchengfly.tieba.post.ui.page.hottopic.list
|
||||
|
||||
import com.huanchengfly.tieba.post.api.TiebaApi
|
||||
import com.huanchengfly.tieba.post.api.models.protos.topicList.NewTopicList
|
||||
import com.huanchengfly.tieba.post.api.models.protos.topicList.TopicListResponse
|
||||
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.FlowPreview
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.flatMapConcat
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class HotTopicListViewModel @Inject constructor() :
|
||||
BaseViewModel<HotTopicListUiIntent, HotTopicListPartialChange, HotTopicListUiState, UiEvent>() {
|
||||
override fun createInitialState(): HotTopicListUiState = HotTopicListUiState()
|
||||
|
||||
override fun createPartialChangeProducer(): PartialChangeProducer<HotTopicListUiIntent, HotTopicListPartialChange, HotTopicListUiState> =
|
||||
HotTopicListPartialChangeProducer
|
||||
|
||||
private object HotTopicListPartialChangeProducer :
|
||||
PartialChangeProducer<HotTopicListUiIntent, HotTopicListPartialChange, HotTopicListUiState> {
|
||||
@OptIn(FlowPreview::class)
|
||||
override fun toPartialChangeFlow(intentFlow: Flow<HotTopicListUiIntent>): Flow<HotTopicListPartialChange> =
|
||||
merge(
|
||||
intentFlow.filterIsInstance<HotTopicListUiIntent.Load>()
|
||||
.flatMapConcat { produceLoadPartialChange() }
|
||||
)
|
||||
|
||||
private fun produceLoadPartialChange(): Flow<HotTopicListPartialChange.Load> =
|
||||
TiebaApi.getInstance().topicListFlow()
|
||||
.map<TopicListResponse, HotTopicListPartialChange.Load> {
|
||||
HotTopicListPartialChange.Load.Success(it.data_?.topic_list ?: emptyList())
|
||||
}
|
||||
.onStart { emit(HotTopicListPartialChange.Load.Start) }
|
||||
.catch { emit(HotTopicListPartialChange.Load.Failure(it)) }
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface HotTopicListUiIntent : UiIntent {
|
||||
object Load : HotTopicListUiIntent
|
||||
}
|
||||
|
||||
sealed interface HotTopicListPartialChange : PartialChange<HotTopicListUiState> {
|
||||
sealed class Load : HotTopicListPartialChange {
|
||||
override fun reduce(oldState: HotTopicListUiState): HotTopicListUiState = when (this) {
|
||||
Start -> oldState.copy(isRefreshing = true)
|
||||
is Success -> oldState.copy(isRefreshing = false, topicList = topicList)
|
||||
is Failure -> oldState.copy(isRefreshing = false)
|
||||
}
|
||||
|
||||
object Start : Load()
|
||||
|
||||
data class Success(
|
||||
val topicList: List<NewTopicList>
|
||||
) : Load()
|
||||
|
||||
data class Failure(
|
||||
val error: Throwable
|
||||
) : Load()
|
||||
}
|
||||
}
|
||||
|
||||
data class HotTopicListUiState(
|
||||
val isRefreshing: Boolean = true,
|
||||
val topicList: List<NewTopicList> = emptyList()
|
||||
) : UiState
|
||||
|
|
@ -8,22 +8,25 @@ import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
|||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.itemsIndexed
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||
import com.huanchengfly.tieba.post.activities.ThreadActivity
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.page.main.explore.FeedCard
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun ConcernPage(
|
||||
viewModel: ConcernViewModel = pageViewModel()
|
||||
|
|
@ -49,10 +52,11 @@ fun ConcernPage(
|
|||
prop1 = ConcernUiState::data,
|
||||
initial = emptyList()
|
||||
)
|
||||
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isRefreshing)
|
||||
SwipeRefresh(
|
||||
state = swipeRefreshState,
|
||||
onRefresh = { viewModel.send(ConcernUiIntent.Refresh) }
|
||||
val pullRefreshState = rememberPullRefreshState(
|
||||
refreshing = isRefreshing,
|
||||
onRefresh = { viewModel.send(ConcernUiIntent.Refresh) })
|
||||
Box(
|
||||
modifier = Modifier.pullRefresh(pullRefreshState)
|
||||
) {
|
||||
LoadMoreLayout(
|
||||
isLoading = isLoadingMore,
|
||||
|
|
@ -100,5 +104,11 @@ fun ConcernPage(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = isRefreshing,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -21,16 +21,21 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
|||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.KeyboardArrowRight
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
|
|
@ -40,8 +45,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.google.accompanist.placeholder.material.placeholder
|
||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
|
|
@ -51,11 +54,18 @@ import com.huanchengfly.tieba.post.ui.common.theme.compose.OrangeA700
|
|||
import com.huanchengfly.tieba.post.ui.common.theme.compose.RedA700
|
||||
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.widgets.compose.*
|
||||
import com.huanchengfly.tieba.post.ui.page.LocalNavigator
|
||||
import com.huanchengfly.tieba.post.ui.page.destinations.HotTopicListPageDestination
|
||||
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
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalGrid
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun HotPage(
|
||||
|
|
@ -65,6 +75,7 @@ fun HotPage(
|
|||
viewModel.send(HotUiIntent.Load)
|
||||
viewModel.initialized = true
|
||||
}
|
||||
val navigator = LocalNavigator.current
|
||||
val isLoading by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = HotUiState::isRefreshing,
|
||||
initial = true
|
||||
|
|
@ -89,8 +100,10 @@ fun HotPage(
|
|||
prop1 = HotUiState::isLoadingThreadList,
|
||||
initial = false
|
||||
)
|
||||
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isLoading)
|
||||
SwipeRefresh(state = swipeRefreshState, onRefresh = { viewModel.send(HotUiIntent.Load) }) {
|
||||
val pullRefreshState = rememberPullRefreshState(
|
||||
refreshing = isLoading,
|
||||
onRefresh = { viewModel.send(HotUiIntent.Load) })
|
||||
Box(modifier = Modifier.pullRefresh(pullRefreshState)) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
|
|
@ -170,6 +183,9 @@ fun HotPage(
|
|||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
navigator.navigate(HotTopicListPageDestination)
|
||||
}
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
Text(
|
||||
|
|
@ -250,6 +266,12 @@ fun HotPage(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = isLoading,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -344,7 +366,8 @@ private fun ThreadListItem(
|
|||
NetworkImage(
|
||||
imageUri = item.media.first().dynamicPic,
|
||||
contentDescription = null,
|
||||
modifier = heightModifier.aspectRatio(16f / 9)
|
||||
modifier = heightModifier.aspectRatio(16f / 9),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,158 @@
|
|||
package com.huanchengfly.tieba.post.ui.page.main.explore.personalized
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import com.huanchengfly.tieba.post.R
|
||||
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.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ClickMenu
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalGrid
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.items
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberMenuState
|
||||
|
||||
@Composable
|
||||
fun Dislike(
|
||||
personalized: ThreadPersonalized,
|
||||
onDislike: (clickTime: Long, reasons: List<DislikeReason>) -> Unit,
|
||||
) {
|
||||
var clickTime by remember { mutableStateOf(0L) }
|
||||
val selectedReasons = remember { mutableStateListOf<DislikeReason>() }
|
||||
val menuState = rememberMenuState()
|
||||
ClickMenu(
|
||||
menuState = menuState,
|
||||
menuContent = {
|
||||
DisposableEffect(personalized) {
|
||||
clickTime = System.currentTimeMillis()
|
||||
onDispose {
|
||||
selectedReasons.clear()
|
||||
}
|
||||
}
|
||||
ConstraintLayout(
|
||||
modifier = Modifier.padding(vertical = 8.dp)
|
||||
) {
|
||||
val (title, grid) = createRefs()
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.constrainAs(title) {
|
||||
start.linkTo(parent.start)
|
||||
end.linkTo(parent.end)
|
||||
top.linkTo(parent.top)
|
||||
}
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.title_dislike),
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(32.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.button_submit_dislike),
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.background(color = ExtendedTheme.colors.accent)
|
||||
.clickable {
|
||||
dismiss()
|
||||
onDislike(clickTime, selectedReasons)
|
||||
}
|
||||
.padding(vertical = 4.dp, horizontal = 8.dp),
|
||||
color = ExtendedTheme.colors.onAccent,
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = MaterialTheme.typography.subtitle2,
|
||||
)
|
||||
}
|
||||
VerticalGrid(
|
||||
column = 2,
|
||||
modifier = Modifier
|
||||
.constrainAs(grid) {
|
||||
start.linkTo(title.start)
|
||||
end.linkTo(title.end)
|
||||
top.linkTo(title.bottom, 16.dp)
|
||||
bottom.linkTo(parent.bottom)
|
||||
}
|
||||
.padding(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
items(
|
||||
items = personalized.dislikeResource,
|
||||
span = { if (it.dislikeId == 7) 2 else 1 }
|
||||
) {
|
||||
val backgroundColor by animateColorAsState(
|
||||
targetValue = if (selectedReasons.contains(it)) ExtendedTheme.colors.accent else ExtendedTheme.colors.chip
|
||||
)
|
||||
val contentColor by animateColorAsState(
|
||||
targetValue = if (selectedReasons.contains(it)) ExtendedTheme.colors.onAccent else ExtendedTheme.colors.onChip
|
||||
)
|
||||
Text(
|
||||
text = it.dislikeReason,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.background(color = backgroundColor)
|
||||
.clickable {
|
||||
if (selectedReasons.contains(it)) {
|
||||
selectedReasons.remove(it)
|
||||
} else {
|
||||
selectedReasons.add(it)
|
||||
}
|
||||
}
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||
color = contentColor,
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = MaterialTheme.typography.subtitle2,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { menuState.expanded = true },
|
||||
modifier = Modifier.size(24.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.KeyboardArrowDown,
|
||||
contentDescription = null,
|
||||
tint = ExtendedTheme.colors.textSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +1,64 @@
|
|||
package com.huanchengfly.tieba.post.ui.page.main.explore.personalized
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Refresh
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.activities.ThreadActivity
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.page.main.explore.Dislike
|
||||
import com.huanchengfly.tieba.post.ui.page.main.explore.FeedCard
|
||||
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 kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun PersonalizedPage(
|
||||
viewModel: PersonalizedViewModel = pageViewModel()
|
||||
|
|
@ -76,7 +96,10 @@ fun PersonalizedPage(
|
|||
prop1 = PersonalizedUiState::hiddenThreadIds,
|
||||
initial = emptyList()
|
||||
)
|
||||
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isRefreshing)
|
||||
val pullRefreshState = rememberPullRefreshState(
|
||||
refreshing = isRefreshing,
|
||||
onRefresh = { viewModel.send(PersonalizedUiIntent.Refresh) }
|
||||
)
|
||||
val lazyStaggeredGridState = rememberLazyStaggeredGridState()
|
||||
var refreshCount by remember {
|
||||
mutableStateOf(0)
|
||||
|
|
@ -99,103 +122,107 @@ fun PersonalizedPage(
|
|||
}
|
||||
}
|
||||
|
||||
Box {
|
||||
SwipeRefresh(
|
||||
state = swipeRefreshState,
|
||||
onRefresh = { viewModel.send(PersonalizedUiIntent.Refresh) },
|
||||
Box(modifier = Modifier.pullRefresh(pullRefreshState)) {
|
||||
LoadMoreLayout(
|
||||
isLoading = isLoadingMore,
|
||||
loadEnd = false,
|
||||
onLoadMore = { viewModel.send(PersonalizedUiIntent.LoadMore(currentPage + 1)) },
|
||||
) {
|
||||
LoadMoreLayout(
|
||||
isLoading = isLoadingMore,
|
||||
loadEnd = false,
|
||||
onLoadMore = { viewModel.send(PersonalizedUiIntent.LoadMore(currentPage + 1)) },
|
||||
LazyVerticalStaggeredGrid(
|
||||
columns = StaggeredGridCells.Adaptive(240.dp),
|
||||
state = lazyStaggeredGridState
|
||||
) {
|
||||
LazyVerticalStaggeredGrid(
|
||||
columns = StaggeredGridCells.Adaptive(240.dp),
|
||||
state = lazyStaggeredGridState
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = data,
|
||||
key = { _, item -> "${item.id}" },
|
||||
contentType = { _, item ->
|
||||
when {
|
||||
item.videoInfo != null -> "Video"
|
||||
item.media.isNotEmpty() -> "Media"
|
||||
else -> "PlainText"
|
||||
}
|
||||
itemsIndexed(
|
||||
items = data,
|
||||
key = { _, item -> "${item.id}" },
|
||||
contentType = { _, item ->
|
||||
when {
|
||||
item.videoInfo != null -> "Video"
|
||||
item.media.isNotEmpty() -> "Media"
|
||||
else -> "PlainText"
|
||||
}
|
||||
) { index, item ->
|
||||
Column {
|
||||
AnimatedVisibility(
|
||||
visible = !hiddenThreadIds.contains(item.threadId),
|
||||
enter = EnterTransition.None,
|
||||
exit = shrinkVertically() + fadeOut()
|
||||
}
|
||||
) { index, item ->
|
||||
Column {
|
||||
AnimatedVisibility(
|
||||
visible = !hiddenThreadIds.contains(item.threadId),
|
||||
enter = EnterTransition.None,
|
||||
exit = shrinkVertically() + fadeOut()
|
||||
) {
|
||||
FeedCard(
|
||||
item = item,
|
||||
onClick = {
|
||||
ThreadActivity.launch(context, item.threadId.toString())
|
||||
},
|
||||
onAgree = {
|
||||
viewModel.send(
|
||||
PersonalizedUiIntent.Agree(
|
||||
item.threadId,
|
||||
item.firstPostId,
|
||||
item.agree?.hasAgree ?: 0
|
||||
)
|
||||
)
|
||||
},
|
||||
) {
|
||||
FeedCard(
|
||||
item = item,
|
||||
onClick = {
|
||||
ThreadActivity.launch(context, item.threadId.toString())
|
||||
},
|
||||
onAgree = {
|
||||
Dislike(
|
||||
personalized = threadPersonalizedData[index],
|
||||
onDislike = { clickTime, reasons ->
|
||||
viewModel.send(
|
||||
PersonalizedUiIntent.Agree(
|
||||
PersonalizedUiIntent.Dislike(
|
||||
item.forumInfo?.id ?: 0,
|
||||
item.threadId,
|
||||
item.firstPostId,
|
||||
item.agree?.hasAgree ?: 0
|
||||
reasons,
|
||||
clickTime
|
||||
)
|
||||
)
|
||||
},
|
||||
) {
|
||||
Dislike(
|
||||
personalized = threadPersonalizedData[index],
|
||||
onDislike = { clickTime, reasons ->
|
||||
viewModel.send(
|
||||
PersonalizedUiIntent.Dislike(
|
||||
item.forumInfo?.id ?: 0,
|
||||
item.threadId,
|
||||
reasons,
|
||||
clickTime
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
if (!hiddenThreadIds.contains(item.threadId)) {
|
||||
if ((refreshPosition == 0 || index + 1 != refreshPosition) && index < data.size - 1) {
|
||||
Divider(
|
||||
color = ExtendedTheme.colors.divider,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
thickness = 2.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!hiddenThreadIds.contains(item.threadId)) {
|
||||
if ((refreshPosition == 0 || index + 1 != refreshPosition) && index < data.size - 1) {
|
||||
Divider(
|
||||
color = ExtendedTheme.colors.divider,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
thickness = 2.dp
|
||||
)
|
||||
}
|
||||
if (refreshPosition != 0 && index + 1 == refreshPosition) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { viewModel.send(PersonalizedUiIntent.Refresh) }
|
||||
.padding(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Refresh,
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(text = stringResource(id = R.string.tip_refresh), style = MaterialTheme.typography.subtitle1)
|
||||
}
|
||||
}
|
||||
if (refreshPosition != 0 && index + 1 == refreshPosition) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { viewModel.send(PersonalizedUiIntent.Refresh) }
|
||||
.padding(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Refresh,
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.tip_refresh),
|
||||
style = MaterialTheme.typography.subtitle1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(data.firstOrNull()?.id) {
|
||||
//delay(50)
|
||||
lazyStaggeredGridState.scrollToItem(0, 0)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(data.firstOrNull()?.id) {
|
||||
//delay(50)
|
||||
lazyStaggeredGridState.scrollToItem(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = isRefreshing,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter)
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = showRefreshTip,
|
||||
enter = fadeIn() + slideInVertically(),
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
|||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Surface
|
||||
|
|
@ -33,6 +34,9 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.outlined.ViewAgenda
|
||||
import androidx.compose.material.icons.rounded.Check
|
||||
import androidx.compose.material.icons.rounded.Search
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
|
|
@ -40,6 +44,7 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.Center
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -55,8 +60,6 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import com.google.accompanist.placeholder.placeholder
|
||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.activities.ForumActivity
|
||||
import com.huanchengfly.tieba.post.activities.NewSearchActivity
|
||||
|
|
@ -69,6 +72,7 @@ import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
|||
import com.huanchengfly.tieba.post.ui.widgets.Chip
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.AccountNavIconIfCompact
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ActionItem
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ConfirmDialog
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LongClickMenu
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Toolbar
|
||||
|
|
@ -307,30 +311,7 @@ private fun ForumItem(
|
|||
) {
|
||||
AnimatedVisibility(visible = showAvatar) {
|
||||
Row {
|
||||
coil.compose.AsyncImage(
|
||||
model = item.avatar,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.size(40.dp)
|
||||
.align(CenterVertically),
|
||||
)
|
||||
// AsyncImage(
|
||||
// imageUri = item.avatar,
|
||||
// contentDescription = null,
|
||||
// modifier = Modifier
|
||||
// .clip(CircleShape)
|
||||
// .size(40.dp)
|
||||
// .align(CenterVertically),
|
||||
// ) {
|
||||
// resultCachePolicy(CachePolicy.DISABLED)
|
||||
// memoryCachePolicy(CachePolicy.DISABLED)
|
||||
// disallowReuseBitmap()
|
||||
// }
|
||||
// {
|
||||
// placeholder(ImageUtil.getPlaceHolder(context, 0))
|
||||
// crossfade()
|
||||
// }
|
||||
Avatar(data = item.avatar, size = 40.dp, contentDescription = null)
|
||||
Spacer(modifier = Modifier.width(14.dp))
|
||||
}
|
||||
}
|
||||
|
|
@ -382,6 +363,7 @@ private fun ForumItem(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun HomePage(
|
||||
viewModel: HomeViewModel = pageViewModel<HomeUiIntent, HomeViewModel>(
|
||||
|
|
@ -431,11 +413,12 @@ fun HomePage(
|
|||
},
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) { contentPaddings ->
|
||||
SwipeRefresh(
|
||||
state = rememberSwipeRefreshState(isRefreshing = isLoading),
|
||||
onRefresh = { viewModel.send(HomeUiIntent.Refresh) },
|
||||
modifier = Modifier.padding(contentPaddings)
|
||||
) {
|
||||
val pullRefreshState = rememberPullRefreshState(
|
||||
refreshing = isLoading,
|
||||
onRefresh = { viewModel.send(HomeUiIntent.Refresh) })
|
||||
Box(modifier = Modifier
|
||||
.padding(contentPaddings)
|
||||
.pullRefresh(pullRefreshState)) {
|
||||
val gridState = rememberLazyGridState()
|
||||
LazyVerticalGrid(
|
||||
state = gridState,
|
||||
|
|
@ -512,7 +495,10 @@ fun HomePage(
|
|||
Header(
|
||||
text = stringResource(id = R.string.forum_list_title),
|
||||
invert = true,
|
||||
modifier = Modifier.placeholder(visible = true, color = ExtendedTheme.colors.chip)
|
||||
modifier = Modifier.placeholder(
|
||||
visible = true,
|
||||
color = ExtendedTheme.colors.chip
|
||||
)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
|
@ -522,6 +508,12 @@ fun HomePage(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = isLoading,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,8 @@ import androidx.compose.ui.layout.ContentScale
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.github.panpf.sketch.compose.AsyncImage
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import com.huanchengfly.tieba.post.utils.ImageUtil
|
||||
|
||||
|
|
@ -83,16 +84,22 @@ fun Avatar(
|
|||
|
||||
is String? -> {
|
||||
AsyncImage(
|
||||
imageUri = data,
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(data)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = contentDescription,
|
||||
placeholder = rememberDrawablePainter(
|
||||
drawable = ImageUtil.getPlaceHolder(
|
||||
context,
|
||||
0
|
||||
)
|
||||
),
|
||||
modifier = modifier
|
||||
.size(size)
|
||||
.clip(CircleShape),
|
||||
contentScale = ContentScale.Crop
|
||||
) {
|
||||
placeholder(ImageUtil.getPlaceHolder(context, 0))
|
||||
crossfade()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("不支持该类型")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.huanchengfly.tieba.post.ui.page.main.explore
|
||||
package com.huanchengfly.tieba.post.ui.widgets.compose
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -16,23 +16,17 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.LocalContentColor
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Favorite
|
||||
import androidx.compose.material.icons.rounded.FavoriteBorder
|
||||
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.rounded.PhotoSizeSelectActual
|
||||
import androidx.compose.material.icons.rounded.SwapCalls
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
|
@ -42,13 +36,10 @@ import androidx.compose.ui.layout.ContentScale
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import cn.jzvd.Jzvd
|
||||
import com.github.panpf.sketch.displayImage
|
||||
import com.huanchengfly.tieba.post.R
|
||||
|
|
@ -56,19 +47,9 @@ import com.huanchengfly.tieba.post.activities.ForumActivity
|
|||
import com.huanchengfly.tieba.post.activities.UserActivity
|
||||
import com.huanchengfly.tieba.post.api.models.protos.Media
|
||||
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.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.utils.getPhotoViewData
|
||||
import com.huanchengfly.tieba.post.ui.widgets.VideoPlayerStandard
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ClickMenu
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.NetworkImage
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.UserHeader
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalGrid
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.items
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberMenuState
|
||||
import com.huanchengfly.tieba.post.utils.DateTimeUtils
|
||||
import com.huanchengfly.tieba.post.utils.ImageUtil
|
||||
import com.huanchengfly.tieba.post.utils.StringUtil
|
||||
|
|
@ -79,142 +60,6 @@ private val Media.url: String
|
|||
@Composable get() =
|
||||
ImageUtil.getUrl(LocalContext.current, true, originPic, dynamicPic, bigPic, srcPic)
|
||||
|
||||
@Composable
|
||||
fun VideoPlayer(
|
||||
videoUrl: String,
|
||||
thumbnailUrl: String,
|
||||
title: String = "",
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
VideoPlayerStandard(context)
|
||||
},
|
||||
modifier = modifier
|
||||
) {
|
||||
it.setUp(videoUrl, title)
|
||||
it.posterImageView.displayImage(thumbnailUrl)
|
||||
}
|
||||
DisposableEffect(videoUrl) {
|
||||
onDispose {
|
||||
Jzvd.releaseAllVideos()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Dislike(
|
||||
personalized: ThreadPersonalized,
|
||||
onDislike: (clickTime: Long, reasons: List<DislikeReason>) -> Unit,
|
||||
) {
|
||||
var clickTime by remember { mutableStateOf(0L) }
|
||||
val selectedReasons = remember { mutableStateListOf<DislikeReason>() }
|
||||
val menuState = rememberMenuState()
|
||||
ClickMenu(
|
||||
menuState = menuState,
|
||||
menuContent = {
|
||||
DisposableEffect(personalized) {
|
||||
clickTime = System.currentTimeMillis()
|
||||
onDispose {
|
||||
selectedReasons.clear()
|
||||
}
|
||||
}
|
||||
ConstraintLayout(
|
||||
modifier = Modifier.padding(vertical = 8.dp)
|
||||
) {
|
||||
val (title, grid) = createRefs()
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.constrainAs(title) {
|
||||
start.linkTo(parent.start)
|
||||
end.linkTo(parent.end)
|
||||
top.linkTo(parent.top)
|
||||
}
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.title_dislike),
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(32.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.button_submit_dislike),
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.background(color = ExtendedTheme.colors.accent)
|
||||
.clickable {
|
||||
dismiss()
|
||||
onDislike(clickTime, selectedReasons)
|
||||
}
|
||||
.padding(vertical = 4.dp, horizontal = 8.dp),
|
||||
color = ExtendedTheme.colors.onAccent,
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = MaterialTheme.typography.subtitle2,
|
||||
)
|
||||
}
|
||||
VerticalGrid(
|
||||
column = 2,
|
||||
modifier = Modifier
|
||||
.constrainAs(grid) {
|
||||
start.linkTo(title.start)
|
||||
end.linkTo(title.end)
|
||||
top.linkTo(title.bottom, 16.dp)
|
||||
bottom.linkTo(parent.bottom)
|
||||
}
|
||||
.padding(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
items(
|
||||
items = personalized.dislikeResource,
|
||||
span = { if (it.dislikeId == 7) 2 else 1 }
|
||||
) {
|
||||
val backgroundColor by animateColorAsState(
|
||||
targetValue = if (selectedReasons.contains(it)) ExtendedTheme.colors.accent else ExtendedTheme.colors.chip
|
||||
)
|
||||
val contentColor by animateColorAsState(
|
||||
targetValue = if (selectedReasons.contains(it)) ExtendedTheme.colors.onAccent else ExtendedTheme.colors.onChip
|
||||
)
|
||||
Text(
|
||||
text = it.dislikeReason,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.background(color = backgroundColor)
|
||||
.clickable {
|
||||
if (selectedReasons.contains(it)) {
|
||||
selectedReasons.remove(it)
|
||||
} else {
|
||||
selectedReasons.add(it)
|
||||
}
|
||||
}
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||
color = contentColor,
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = MaterialTheme.typography.subtitle2,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { menuState.expanded = true },
|
||||
modifier = Modifier.size(24.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.KeyboardArrowDown,
|
||||
contentDescription = null,
|
||||
tint = ExtendedTheme.colors.textSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FeedCard(
|
||||
item: ThreadInfo,
|
||||
|
|
@ -432,4 +277,27 @@ private fun ActionBtn(
|
|||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(text = text, style = MaterialTheme.typography.caption, color = animatedColor)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun VideoPlayer(
|
||||
videoUrl: String,
|
||||
thumbnailUrl: String,
|
||||
title: String = "",
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
VideoPlayerStandard(context)
|
||||
},
|
||||
modifier = modifier
|
||||
) {
|
||||
it.setUp(videoUrl, title)
|
||||
it.posterImageView.displayImage(thumbnailUrl)
|
||||
}
|
||||
DisposableEffect(videoUrl) {
|
||||
onDispose {
|
||||
Jzvd.releaseAllVideos()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,9 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.github.panpf.sketch.compose.AsyncImage
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import com.huanchengfly.tieba.post.goToActivity
|
||||
import com.huanchengfly.tieba.post.models.protos.PhotoViewData
|
||||
import com.huanchengfly.tieba.post.ui.page.photoview.PhotoViewActivity
|
||||
|
|
@ -37,11 +39,13 @@ fun NetworkImage(
|
|||
}
|
||||
} else Modifier
|
||||
AsyncImage(
|
||||
imageUri = imageUri,
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(imageUri)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = contentDescription,
|
||||
placeholder = rememberDrawablePainter(drawable = ImageUtil.getPlaceHolder(context, 0)),
|
||||
modifier = modifier.then(clickableModifier),
|
||||
contentScale = contentScale,
|
||||
) {
|
||||
placeholder(ImageUtil.getPlaceHolder(context, 0))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -42,7 +42,6 @@ import androidx.compose.ui.res.vectorResource
|
|||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.github.panpf.sketch.compose.AsyncImage
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.activities.LoginActivity
|
||||
|
|
@ -52,7 +51,6 @@ import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
|||
import com.huanchengfly.tieba.post.ui.common.windowsizeclass.WindowWidthSizeClass.Companion.Compact
|
||||
import com.huanchengfly.tieba.post.utils.AccountUtil
|
||||
import com.huanchengfly.tieba.post.utils.AccountUtil.LocalAccount
|
||||
import com.huanchengfly.tieba.post.utils.ImageUtil
|
||||
import com.huanchengfly.tieba.post.utils.StringUtil
|
||||
import com.huanchengfly.tieba.post.utils.compose.calcStatusBarColor
|
||||
|
||||
|
|
@ -136,16 +134,11 @@ fun AccountNavIcon(
|
|||
onClick = onClick,
|
||||
shape = CircleShape
|
||||
) {
|
||||
AsyncImage(
|
||||
imageUri = StringUtil.getAvatarUrl(currentAccount.portrait),
|
||||
contentDescription = stringResource(id = R.string.title_switch_account_long_press),
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.size(size),
|
||||
) {
|
||||
placeholder(ImageUtil.getPlaceHolder(context, 0))
|
||||
crossfade()
|
||||
}
|
||||
Avatar(
|
||||
data = StringUtil.getAvatarUrl(currentAccount.portrait),
|
||||
size = size,
|
||||
contentDescription = stringResource(id = R.string.title_switch_account_long_press)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package protos;
|
||||
|
||||
option java_package = "com.huanchengfly.tieba.post.api.models.protos.topicList";
|
||||
|
||||
import "Common.proto";
|
||||
import "CommonRequest.proto";
|
||||
import "ThreadInfo.proto";
|
||||
|
||||
message TopicListRequestData {
|
||||
CommonRequest common = 1;
|
||||
string call_from = 2;
|
||||
string list_type = 3;
|
||||
string need_tab_list = 4;
|
||||
int64 fid = 5;
|
||||
}
|
||||
|
||||
message TopicListRequest {
|
||||
TopicListRequestData data = 1;
|
||||
}
|
||||
|
||||
message TopicListModule {
|
||||
string module_title = 1;
|
||||
repeated TopicList topic_list = 2;
|
||||
string tips = 3;
|
||||
string rule_jump_url = 4;
|
||||
}
|
||||
|
||||
message MediaTopic {
|
||||
uint64 topic_id = 1;
|
||||
string topic_name = 2;
|
||||
VideoInfo video_info = 3;
|
||||
string pic_url = 4;
|
||||
}
|
||||
|
||||
message TabList {
|
||||
string tab_name = 1;
|
||||
string tab_type = 2;
|
||||
string share_pic = 3;
|
||||
string share_title = 4;
|
||||
string share_desc = 5;
|
||||
string share_url = 6;
|
||||
}
|
||||
|
||||
message TopicList {
|
||||
uint64 topic_id = 1;
|
||||
string topic_name = 2;
|
||||
uint64 discuss_num = 3;
|
||||
int32 tag = 4;
|
||||
string topic_desc = 5;
|
||||
string topic_pic = 6;
|
||||
int64 update_time = 7;
|
||||
string topic_user_name = 8;
|
||||
repeated Media media = 9;
|
||||
int64 topic_tid = 10;
|
||||
string topic_h5_url = 11;
|
||||
VideoInfo video_info = 12;
|
||||
int32 topic_thread_types = 13;
|
||||
}
|
||||
|
||||
message NewTopicList {
|
||||
int64 topic_id = 1;
|
||||
string topic_name = 2;
|
||||
string topic_desc = 3;
|
||||
int64 discuss_num = 4;
|
||||
string topic_image = 5;
|
||||
int32 topic_tag = 6;
|
||||
}
|
||||
|
||||
message TopicListResponseData {
|
||||
TopicListModule topic_bang = 1;
|
||||
TopicListModule topic_manual = 2;
|
||||
MediaTopic media_topic = 3;
|
||||
repeated TabList tab_list = 6;
|
||||
repeated TopicList frs_tab_topic = 7;
|
||||
repeated NewTopicList topic_list = 8;
|
||||
}
|
||||
|
||||
message TopicListResponse {
|
||||
Error error = 1;
|
||||
TopicListResponseData data = 2;
|
||||
}
|
||||
|
|
@ -276,7 +276,7 @@
|
|||
<string name="title_dialog_unfollow_forum">确定要取消关注%s吧吗?</string>
|
||||
<string name="menu_save_audio">保存语音</string>
|
||||
<string name="title_hot_message">贴吧话题</string>
|
||||
<string name="title_hot_message_list">热议话题</string>
|
||||
<string name="title_hot_message_list">话题榜</string>
|
||||
<string name="desc_hot_message_list">(每小时更新一次,以实时讨论量排序)</string>
|
||||
<string name="toast_photo_saved">图片保存在 %1$s</string>
|
||||
<string name="title_settings_default_sort_type">吧默认排序方式</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue