feat: 话题榜

This commit is contained in:
HuanCheng65 2023-01-05 17:16:08 +08:00
parent a6c57037e8
commit 876ae60140
No known key found for this signature in database
GPG Key ID: E9031EF91A805148
16 changed files with 819 additions and 316 deletions

View File

@ -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>
/**
* 吧页面
*

View File

@ -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?

View File

@ -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,

View File

@ -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)
)
}
}
}

View File

@ -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

View File

@ -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)
)
}
}

View File

@ -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
)
}
}

View File

@ -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
)
}
}
}

View File

@ -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(),

View File

@ -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)
)
}
}
}

View File

@ -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("不支持该类型")

View File

@ -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()
}
}
}

View File

@ -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))
}
)
}

View File

@ -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)
)
}
}
}

View File

@ -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;
}

View File

@ -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>