feat: 新版收藏页面

This commit is contained in:
HuanCheng65 2023-01-07 22:07:50 +08:00
parent 0a834c81ab
commit 257dd0ee38
No known key found for this signature in database
GPG Key ID: E9031EF91A805148
13 changed files with 587 additions and 28 deletions

View File

@ -158,7 +158,6 @@
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="forum"
android:scheme="tblite" />
</intent-filter>

View File

@ -499,6 +499,19 @@ interface ITiebaApi {
pageSize: Int = 20
): Call<ThreadStoreBean>
/**
* 查看收藏贴列表
*
* **需登录**
*
* @param page 分页页码 0 开始
* @param pageSize 每页贴数默认 20
*/
fun threadStoreFlow(
page: Int = 0,
pageSize: Int = 20
): Flow<ThreadStoreBean>
/**
* 移除收藏
*
@ -512,6 +525,17 @@ interface ITiebaApi {
tbs: String
): Call<CommonResponse>
/**
* 移除收藏
*
* **需登录**
*
* @param threadId 贴子 ID
*/
fun removeStoreFlow(
threadId: String
): Flow<CommonResponse>
/**
* 添加/更新收藏
*

View File

@ -375,9 +375,18 @@ object MixedTiebaApiImpl : ITiebaApi {
AccountUtil.getUid()
)
override fun threadStoreFlow(page: Int, pageSize: Int): Flow<ThreadStoreBean> =
RetrofitTiebaApi.OFFICIAL_TIEBA_API.threadStoreFlow(
pageSize,
pageSize * page
)
override fun removeStore(threadId: String, tbs: String): Call<CommonResponse> =
RetrofitTiebaApi.NEW_TIEBA_API.removeStore(threadId, tbs)
override fun removeStoreFlow(threadId: String): Flow<CommonResponse> =
RetrofitTiebaApi.OFFICIAL_TIEBA_API.removeStoreFlow(threadId)
override fun addStore(threadId: String, postId: String, tbs: String): Call<CommonResponse> =
RetrofitTiebaApi.NEW_TIEBA_API.addStore(
listOf(

View File

@ -311,4 +311,41 @@ interface OfficialTiebaApi {
@retrofit2.http.Header(Header.USER_AGENT) user_agent: String = "bdtb for Android $client_version",
@Field("stoken") stoken: String? = AccountUtil.getSToken(),
): Flow<CommonResponse>
@POST("/c/f/post/threadstore")
@FormUrlEncoded
@Headers(
"${Header.FORCE_LOGIN}: ${Header.FORCE_LOGIN_TRUE}",
"${Header.COOKIE}: ka=open",
"${Header.DROP_HEADERS}: ${Header.CHARSET},${Header.CLIENT_TYPE}",
"${Header.NO_COMMON_PARAMS}: ${Param.OAID}",
)
fun threadStoreFlow(
@Field("rn") pageSize: Int,
@Field("offset") offset: Int,
@retrofit2.http.Header("client_user_token") client_user_token: String? = AccountUtil.getUid(),
@Field("_client_version") client_version: String = "11.10.8.6",
@retrofit2.http.Header(Header.USER_AGENT) user_agent: String = "bdtb for Android $client_version",
@Field("stoken") stoken: String? = AccountUtil.getSToken(),
@Field("user_id") user_id: String? = AccountUtil.getUid(),
): Flow<ThreadStoreBean>
@POST("/c/c/post/rmstore")
@FormUrlEncoded
@Headers(
"${Header.FORCE_LOGIN}: ${Header.FORCE_LOGIN_TRUE}",
"${Header.COOKIE}: ka=open",
"${Header.DROP_HEADERS}: ${Header.CHARSET},${Header.CLIENT_TYPE}",
"${Header.NO_COMMON_PARAMS}: ${Param.OAID}",
)
fun removeStoreFlow(
@Field("tid") threadId: String,
@Field("tbs") tbs: String = AccountUtil.getLoginInfo()!!.tbs,
@Field("stoken") stoken: String = AccountUtil.getSToken()!!,
@Field("user_id") user_id: String? = AccountUtil.getUid(),
@Field("fid") fid: String = "null",
@Field("_client_version") client_version: String = "11.10.8.6",
@retrofit2.http.Header(Header.USER_AGENT) user_agent: String = "bdtb for Android $client_version",
@retrofit2.http.Header("client_user_token") client_user_token: String? = user_id,
): Flow<CommonResponse>
}

View File

@ -184,7 +184,7 @@ class ThreadStoreFragment : BaseFragment() {
val storeInfoList = data!!.storeThread ?: return
threadStoreAdapter.reset()
threadStoreAdapter.setData(storeInfoList)
hasMore = storeInfoList.size > 0
hasMore = storeInfoList.isNotEmpty()
}
override fun onFailure(call: Call<ThreadStoreBean?>, t: Throwable) {

View File

@ -144,7 +144,13 @@ private fun Header(
invert: Boolean = false,
modifier: Modifier = Modifier
) {
Chip(text = text, modifier = modifier.padding(start = 16.dp), invertColor = invert)
Chip(
text = text,
modifier = Modifier
.padding(start = 16.dp)
.then(modifier),
invertColor = invert
)
}
@Composable

View File

@ -120,10 +120,25 @@ fun NotificationsListPage(
contentDescription = null
)
},
name = { Text(text = it.replyer.nameShow ?: it.replyer.name ?: "") },
desc = { Text(text = DateTimeUtils.getRelativeTimeString(LocalContext.current, it.time!!)) },
name = {
Text(
text = it.replyer.nameShow ?: it.replyer.name ?: ""
)
},
onClick = {
UserActivity.launch(context, it.replyer.id!!, StringUtil.getAvatarUrl(it.replyer.portrait))
UserActivity.launch(
context,
it.replyer.id!!,
StringUtil.getAvatarUrl(it.replyer.portrait)
)
},
desc = {
Text(
text = DateTimeUtils.getRelativeTimeString(
LocalContext.current,
it.time!!
)
)
},
) {}
}

View File

@ -49,7 +49,6 @@ import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.activities.AppThemeActivity
import com.huanchengfly.tieba.post.activities.HistoryActivity
import com.huanchengfly.tieba.post.activities.UserActivity
import com.huanchengfly.tieba.post.activities.UserCollectActivity
import com.huanchengfly.tieba.post.activities.WebViewActivity
import com.huanchengfly.tieba.post.arch.collectPartialAsState
import com.huanchengfly.tieba.post.arch.pageViewModel
@ -59,6 +58,7 @@ import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
import com.huanchengfly.tieba.post.ui.page.LocalNavigator
import com.huanchengfly.tieba.post.ui.page.destinations.AboutPageDestination
import com.huanchengfly.tieba.post.ui.page.destinations.SettingsPageDestination
import com.huanchengfly.tieba.post.ui.page.destinations.ThreadStorePageDestination
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
import com.huanchengfly.tieba.post.ui.widgets.compose.HorizontalDivider
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
@ -312,7 +312,7 @@ fun UserPage(
icon = ImageVector.vectorResource(id = R.drawable.ic_favorite),
text = stringResource(id = R.string.title_my_collect),
onClick = {
context.goToActivity<UserCollectActivity>()
navigator.navigate(ThreadStorePageDestination)
}
)
ListMenuItem(

View File

@ -1,2 +1,280 @@
package com.huanchengfly.tieba.post.ui.page.threadstore
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.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.ExperimentalMaterialApi
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.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.TextMeasurer
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.activities.ThreadActivity
import com.huanchengfly.tieba.post.activities.UserActivity
import com.huanchengfly.tieba.post.api.models.ThreadStoreBean
import com.huanchengfly.tieba.post.arch.collectPartialAsState
import com.huanchengfly.tieba.post.arch.onEvent
import com.huanchengfly.tieba.post.arch.pageViewModel
import com.huanchengfly.tieba.post.dpToPxFloat
import com.huanchengfly.tieba.post.pxToSp
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
import com.huanchengfly.tieba.post.ui.widgets.compose.BackNavigationIcon
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
import com.huanchengfly.tieba.post.ui.widgets.compose.LongClickMenu
import com.huanchengfly.tieba.post.ui.widgets.compose.MyScaffold
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
import com.huanchengfly.tieba.post.ui.widgets.compose.TitleCentredToolbar
import com.huanchengfly.tieba.post.ui.widgets.compose.UserHeader
import com.huanchengfly.tieba.post.utils.StringUtil
import com.huanchengfly.tieba.post.utils.StringUtil.getUsernameAnnotatedString
import com.huanchengfly.tieba.post.utils.appPreferences
import com.ramcosta.composedestinations.annotation.DeepLink
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
private val UpdateTipTextStyle = TextStyle(fontWeight = FontWeight.Bold, fontSize = 10.sp)
@OptIn(ExperimentalMaterialApi::class, ExperimentalTextApi::class)
@Destination(
deepLinks = [
DeepLink(uriPattern = "tblite://favorite")
]
)
@Composable
fun ThreadStorePage(
navigator: DestinationsNavigator,
viewModel: ThreadStoreViewModel = pageViewModel()
) {
LazyLoad(loaded = viewModel.initialized) {
viewModel.send(ThreadStoreUiIntent.Refresh)
viewModel.initialized = true
}
val isRefreshing by viewModel.uiState.collectPartialAsState(
prop1 = ThreadStoreUiState::isRefreshing,
initial = false
)
val isLoadingMore by viewModel.uiState.collectPartialAsState(
prop1 = ThreadStoreUiState::isLoadingMore,
initial = false
)
val hasMore by viewModel.uiState.collectPartialAsState(
prop1 = ThreadStoreUiState::hasMore,
initial = true
)
val currentPage by viewModel.uiState.collectPartialAsState(
prop1 = ThreadStoreUiState::currentPage,
initial = 0
)
val data by viewModel.uiState.collectPartialAsState(
prop1 = ThreadStoreUiState::data,
initial = emptyList()
)
val context = LocalContext.current
val scaffoldState = rememberScaffoldState()
val pullRefreshState = rememberPullRefreshState(
refreshing = isRefreshing,
onRefresh = { viewModel.send(ThreadStoreUiIntent.Refresh) })
LaunchedEffect(null) {
onEvent<ThreadStoreUiEvent.Delete.Failure>(viewModel) {
scaffoldState.snackbarHostState.showSnackbar(
context.getString(
R.string.delete_store_failure,
it.errorMsg
)
)
}
onEvent<ThreadStoreUiEvent.Delete.Success>(viewModel) {
scaffoldState.snackbarHostState.showSnackbar(context.getString(R.string.delete_store_success))
}
}
MyScaffold(
scaffoldState = scaffoldState,
topBar = {
TitleCentredToolbar(
title = stringResource(id = R.string.title_my_collect),
navigationIcon = {
BackNavigationIcon(onBackPressed = { navigator.navigateUp() })
}
)
}
) { paddingValues ->
val textMeasurer = rememberTextMeasurer()
Box(modifier = Modifier.pullRefresh(pullRefreshState)) {
LoadMoreLayout(
isLoading = isLoadingMore,
onLoadMore = { viewModel.send(ThreadStoreUiIntent.LoadMore(currentPage + 1)) },
loadEnd = !hasMore
) {
LazyColumn(contentPadding = paddingValues) {
items(
items = data,
key = { it.threadId }
) { info ->
LongClickMenu(
menuContent = {
DropdownMenuItem(onClick = {
viewModel.send(
ThreadStoreUiIntent.Delete(
info.threadId
)
)
}) {
Text(text = stringResource(id = R.string.title_collect_on))
}
},
onClick = {
ThreadActivity.launch(
context,
info.threadId,
info.markPid,
context.appPreferences.collectThreadSeeLz,
"collect",
info.maxPid
)
}
) {
StoreItem(
info = info,
onUserClick = {
info.author.lzUid?.let {
UserActivity.launch(
context,
it, StringUtil.getAvatarUrl(info.author.userPortrait)
)
}
},
textMeasurer = textMeasurer
)
}
}
}
}
PullRefreshIndicator(
refreshing = isRefreshing,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter)
)
}
}
}
@OptIn(ExperimentalTextApi::class)
@Composable
private fun StoreItem(
info: ThreadStoreBean.ThreadStoreInfo,
onUserClick: () -> Unit,
modifier: Modifier = Modifier,
textMeasurer: TextMeasurer = rememberTextMeasurer()
) {
val hasUpdate = info.count != "0" && info.postNo != "0"
var width = 0
var height = 0
Column(
modifier = modifier
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
UserHeader(
avatar = {
Avatar(
data = StringUtil.getAvatarUrl(info.author.userPortrait),
size = Sizes.Small,
contentDescription = null
)
},
name = {
Text(
text = getUsernameAnnotatedString(
info.author.name ?: "",
info.author.nameShow
)
)
},
onClick = onUserClick,
)
val title = buildAnnotatedString {
append(info.title)
if (hasUpdate) {
append(" ")
appendInlineContent("Update", info.postNo)
}
}
val updateTip = stringResource(
id = R.string.tip_thread_store_update,
info.postNo
)
if (hasUpdate) {
val result = textMeasurer.measure(
AnnotatedString(updateTip),
style = UpdateTipTextStyle
)
width =
result.size.width.pxToSp() + 12F.dpToPxFloat().pxToSp() * 2 + 1
height = result.size.height.pxToSp() + 4F.dpToPxFloat().pxToSp() * 2
}
Text(
text = title,
fontSize = 15.sp,
color = ExtendedTheme.colors.text,
inlineContent = mapOf(
"Update" to InlineTextContent(
placeholder = Placeholder(
width = width.sp,
height = height.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter
),
children = {
Box(
modifier = Modifier
.fillMaxSize()
.background(
color = ExtendedTheme.colors.chip,
shape = RoundedCornerShape(3.dp)
)
.padding(vertical = 4.dp, horizontal = 12.dp)
) {
Text(
text = updateTip,
style = UpdateTipTextStyle,
color = ExtendedTheme.colors.onChip,
modifier = Modifier.align(Alignment.Center)
)
}
}
)
)
)
}
}

View File

@ -0,0 +1,186 @@
package com.huanchengfly.tieba.post.ui.page.threadstore
import com.huanchengfly.tieba.post.api.TiebaApi
import com.huanchengfly.tieba.post.api.models.CommonResponse
import com.huanchengfly.tieba.post.api.models.ThreadStoreBean
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorCode
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage
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 ThreadStoreViewModel @Inject constructor() :
BaseViewModel<ThreadStoreUiIntent, ThreadStorePartialChange, ThreadStoreUiState, ThreadStoreUiEvent>() {
override fun createInitialState(): ThreadStoreUiState = ThreadStoreUiState()
override fun createPartialChangeProducer(): PartialChangeProducer<ThreadStoreUiIntent, ThreadStorePartialChange, ThreadStoreUiState> =
ThreadStorePartialChangeProducer
override fun dispatchEvent(partialChange: ThreadStorePartialChange): UiEvent? {
return when (partialChange) {
is ThreadStorePartialChange.Delete.Success -> ThreadStoreUiEvent.Delete.Success
is ThreadStorePartialChange.Delete.Failure -> ThreadStoreUiEvent.Delete.Failure(
partialChange.error.getErrorCode(),
partialChange.error.getErrorMessage()
)
else -> null
}
}
private object ThreadStorePartialChangeProducer :
PartialChangeProducer<ThreadStoreUiIntent, ThreadStorePartialChange, ThreadStoreUiState> {
@OptIn(FlowPreview::class)
override fun toPartialChangeFlow(intentFlow: Flow<ThreadStoreUiIntent>): Flow<ThreadStorePartialChange> =
merge(
intentFlow.filterIsInstance<ThreadStoreUiIntent.Refresh>()
.flatMapConcat { produceRefreshPartialChange() },
intentFlow.filterIsInstance<ThreadStoreUiIntent.LoadMore>()
.flatMapConcat { it.producePartialChange() },
intentFlow.filterIsInstance<ThreadStoreUiIntent.Delete>()
.flatMapConcat { it.producePartialChange() },
)
private fun produceRefreshPartialChange() =
TiebaApi.getInstance()
.threadStoreFlow()
.map {
if (it.storeThread != null) ThreadStorePartialChange.Refresh.Success(
it.storeThread,
it.storeThread.isNotEmpty()
)
else ThreadStorePartialChange.Refresh.Failure(NullPointerException("未知错误"))
}
.onStart { emit(ThreadStorePartialChange.Refresh.Start) }
.catch { emit(ThreadStorePartialChange.Refresh.Failure(it)) }
private fun ThreadStoreUiIntent.LoadMore.producePartialChange() =
TiebaApi.getInstance()
.threadStoreFlow(page)
.map {
if (it.storeThread != null) ThreadStorePartialChange.LoadMore.Success(
it.storeThread,
it.storeThread.isNotEmpty(),
page
)
else ThreadStorePartialChange.LoadMore.Failure(NullPointerException("未知错误"))
}
.onStart { emit(ThreadStorePartialChange.LoadMore.Start) }
.catch { emit(ThreadStorePartialChange.LoadMore.Failure(it)) }
private fun ThreadStoreUiIntent.Delete.producePartialChange() =
TiebaApi.getInstance()
.removeStoreFlow(threadId)
.map<CommonResponse, ThreadStorePartialChange.Delete> {
ThreadStorePartialChange.Delete.Success(threadId)
}
.catch { emit(ThreadStorePartialChange.Delete.Failure(it)) }
}
}
sealed interface ThreadStoreUiIntent : UiIntent {
object Refresh : ThreadStoreUiIntent
data class LoadMore(val page: Int) : ThreadStoreUiIntent
data class Delete(val threadId: String) : ThreadStoreUiIntent
}
sealed interface ThreadStorePartialChange : PartialChange<ThreadStoreUiState> {
sealed class Refresh : ThreadStorePartialChange {
override fun reduce(oldState: ThreadStoreUiState): ThreadStoreUiState = when (this) {
is Failure -> oldState.copy(isRefreshing = false)
Start -> oldState.copy(isRefreshing = true)
is Success -> oldState.copy(
isRefreshing = false,
data = data,
currentPage = 0,
hasMore = hasMore
)
}
object Start : Refresh()
data class Success(
val data: List<ThreadStoreBean.ThreadStoreInfo>,
val hasMore: Boolean
) : Refresh()
data class Failure(
val error: Throwable
) : Refresh()
}
sealed class LoadMore : ThreadStorePartialChange {
override fun reduce(oldState: ThreadStoreUiState): ThreadStoreUiState = when (this) {
is Failure -> oldState.copy(isLoadingMore = false)
Start -> oldState.copy(isLoadingMore = true)
is Success -> oldState.copy(
isLoadingMore = false,
data = oldState.data + data,
currentPage = currentPage,
hasMore = hasMore
)
}
object Start : LoadMore()
data class Success(
val data: List<ThreadStoreBean.ThreadStoreInfo>,
val hasMore: Boolean,
val currentPage: Int
) : LoadMore()
data class Failure(
val error: Throwable
) : LoadMore()
}
sealed class Delete : ThreadStorePartialChange {
override fun reduce(oldState: ThreadStoreUiState): ThreadStoreUiState = when (this) {
is Failure -> oldState
is Success -> oldState.copy(data = oldState.data.filterNot { it.threadId == threadId })
}
data class Success(
val threadId: String
) : Delete()
data class Failure(
val error: Throwable
) : Delete()
}
}
data class ThreadStoreUiState(
val isRefreshing: Boolean = false,
val isLoadingMore: Boolean = false,
val hasMore: Boolean = true,
val currentPage: Int = 0,
val data: List<ThreadStoreBean.ThreadStoreInfo> = emptyList()
) : UiState
sealed interface ThreadStoreUiEvent : UiEvent {
sealed interface Delete : ThreadStoreUiEvent {
object Success : Delete
data class Failure(
val errorCode: Int,
val errorMsg: String
) : Delete
}
}

View File

@ -96,6 +96,13 @@ private fun DefaultUserHeader(
color = ExtendedTheme.colors.text
)
},
onClick = {
UserActivity.launch(
context,
user.id.toString(),
StringUtil.getAvatarUrl(user.portrait)
)
},
desc = {
Text(
text = DateTimeUtils.getRelativeTimeString(
@ -104,13 +111,6 @@ private fun DefaultUserHeader(
)
)
},
onClick = {
UserActivity.launch(
context,
user.id.toString(),
StringUtil.getAvatarUrl(user.portrait)
)
},
content = content
)
}

View File

@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ProvideTextStyle
@ -26,9 +25,9 @@ import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
fun UserHeader(
avatar: @Composable () -> Unit,
name: @Composable () -> Unit,
desc: @Composable () -> Unit,
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
desc: @Composable (() -> Unit)? = null,
content: @Composable (RowScope.() -> Unit)? = null,
) {
val clickableModifier = if (onClick != null) {
@ -49,7 +48,10 @@ fun UserHeader(
avatar()
}
Spacer(modifier = Modifier.width(8.dp))
Column(modifier = Modifier.weight(1f)) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
Box(modifier = clickableModifier) {
ProvideTextStyle(
value = MaterialTheme.typography.subtitle1.merge(
@ -62,7 +64,7 @@ fun UserHeader(
name()
}
}
Spacer(modifier = Modifier.height(2.dp))
if (desc != null) {
Box(modifier = clickableModifier) {
ProvideTextStyle(
value = MaterialTheme.typography.caption.merge(
@ -76,6 +78,7 @@ fun UserHeader(
}
}
}
}
content?.invoke(this)
}
}

View File

@ -624,4 +624,6 @@
<string name="launcher_not_support_pin_shortcut">启动器不支持创建快捷方式</string>
<string name="load_shortcut_icon_fail">加载图标失败</string>
<string name="sort_menu">排序菜单</string>
<string name="delete_store_success">取消收藏成功</string>
<string name="delete_store_failure">取消收藏失败 %s</string>
</resources>