This commit is contained in:
2024-10-08 17:25:27 +08:00
parent dcdd42d6b0
commit 43c27b7e0b
11 changed files with 134 additions and 26 deletions

View File

@@ -6,8 +6,6 @@ import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.icu.util.Calendar
import android.icu.util.TimeZone
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@@ -32,12 +30,7 @@ import com.aiosman.riderpro.ui.Navigation
import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.navigateToPost import com.aiosman.riderpro.ui.navigateToPost
import com.aiosman.riderpro.ui.post.NewPostViewModel import com.aiosman.riderpro.ui.post.NewPostViewModel
import com.aiosman.riderpro.utils.Utils
import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.FirebaseAnalytics
import com.tencent.imsdk.v2.V2TIMCallback
import com.tencent.imsdk.v2.V2TIMLogListener
import com.tencent.imsdk.v2.V2TIMManager
import com.tencent.imsdk.v2.V2TIMSDKConfig
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@@ -105,6 +105,9 @@ data class NoticePost(
it.copy( it.copy(
url = "${ApiClient.BASE_SERVER}${it.url}", url = "${ApiClient.BASE_SERVER}${it.url}",
thumbnail = "${ApiClient.BASE_SERVER}${it.thumbnail}", thumbnail = "${ApiClient.BASE_SERVER}${it.thumbnail}",
blurHash = it.blurHash,
width = it.width,
height = it.height
) )
}, },
time = ApiClient.dateFromApiString(time) time = ApiClient.dateFromApiString(time)

View File

@@ -52,12 +52,14 @@ data class Moment(
url = "${ApiClient.BASE_SERVER}${it.url}", url = "${ApiClient.BASE_SERVER}${it.url}",
thumbnail = "${ApiClient.BASE_SERVER}${it.thumbnail}", thumbnail = "${ApiClient.BASE_SERVER}${it.thumbnail}",
id = it.id, id = it.id,
blurHash = it.blurHash blurHash = it.blurHash,
width = it.width,
height = it.height
) )
}, },
authorId = user.id.toInt(), authorId = user.id.toInt(),
liked = isLiked, liked = isLiked,
isFavorite = isFavorite isFavorite = isFavorite,
) )
} }
} }
@@ -70,7 +72,11 @@ data class Image(
@SerializedName("thumbnail") @SerializedName("thumbnail")
val thumbnail: String, val thumbnail: String,
@SerializedName("blurHash") @SerializedName("blurHash")
val blurHash: String? val blurHash: String?,
@SerializedName("width")
val width: Int?,
@SerializedName("height")
val height: Int?
) )
data class User( data class User(

View File

@@ -230,7 +230,11 @@ data class MomentImageEntity(
// 缩略图URL // 缩略图URL
val thumbnail: String, val thumbnail: String,
// 图片BlurHash // 图片BlurHash
val blurHash: String? = null val blurHash: String? = null,
// 宽度
var width: Int? = null,
// 高度
var height: Int? = null
) )
/** /**

View File

@@ -501,7 +501,6 @@ fun ProfileV3(
Spacer(modifier = Modifier.height(120.dp)) Spacer(modifier = Modifier.height(120.dp))
} }
} }
1 -> 1 ->
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier

View File

@@ -21,15 +21,25 @@ import com.aiosman.riderpro.ui.navigateToPost
fun GalleryItem( fun GalleryItem(
moment: MomentEntity, moment: MomentEntity,
idx: Int = 0 idx: Int = 0
){ ) {
val navController = LocalNavController.current val navController = LocalNavController.current
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.aspectRatio( .let {
if (idx % 3 == 0) 1.5f else 1f val firstImage = moment.images.firstOrNull()
) if (firstImage?.width != null &&
firstImage.height != null &&
firstImage.width!! > 0 &&
firstImage.height!! > 0
) {
val ratio = firstImage.width!!.toFloat() / firstImage.height!!.toFloat()
return@let it.aspectRatio(ratio.coerceIn(0.7f, 1.5f))
} else {
return@let it.aspectRatio(if (idx % 3 == 0) 1.5f else 1f)
}
}
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
.noRippleClickable { .noRippleClickable {
navController.navigateToPost( navController.navigateToPost(

View File

@@ -1,6 +1,5 @@
package com.aiosman.riderpro.ui.index.tabs.search package com.aiosman.riderpro.ui.index.tabs.search
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -50,7 +49,7 @@ import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.aiosman.riderpro.ui.navigateToPost import com.aiosman.riderpro.ui.navigateToPost
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class) @OptIn( ExperimentalMaterialApi::class)
@Preview @Preview
@Composable @Composable
fun DiscoverScreen() { fun DiscoverScreen() {
@@ -82,6 +81,7 @@ fun DiscoverScreen() {
.fillMaxWidth() .fillMaxWidth()
.padding(top = 16.dp, start = 24.dp, end = 24.dp), .padding(top = 16.dp, start = 24.dp, end = 24.dp),
) { ) {
SearchViewModel.requestFocus = true
navController.navigate(NavigationRoute.Search.route) navController.navigate(NavigationRoute.Search.route)
} }
} }
@@ -150,7 +150,6 @@ fun DiscoverView() {
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(1f) .aspectRatio(1f)
.padding(2.dp) .padding(2.dp)
.noRippleClickable { .noRippleClickable {
navController.navigateToPost( navController.navigateToPost(
id = momentItem.id, id = momentItem.id,

View File

@@ -4,6 +4,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
@@ -51,9 +52,11 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.AppState
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.entity.AccountProfileEntity import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.ui.composables.ActionButton
import com.aiosman.riderpro.ui.composables.CustomAsyncImage import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.index.tabs.moment.MomentCard import com.aiosman.riderpro.ui.index.tabs.moment.MomentCard
import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.modifiers.noRippleClickable
@@ -82,7 +85,10 @@ fun SearchScreen() {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = true) systemUiController.setStatusBarColor(Color.Transparent, darkIcons = true)
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
focusRequester.requestFocus() if (model.requestFocus) {
focusRequester.requestFocus()
model.requestFocus = false
}
} }
Column( Column(
@@ -246,7 +252,8 @@ fun MomentResultTab() {
val momentItem = moments[idx] ?: return@items val momentItem = moments[idx] ?: return@items
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth().background(Color.White) .fillMaxWidth()
.background(Color.White)
) { ) {
MomentCard(momentEntity = momentItem, hideAction = true) MomentCard(momentEntity = momentItem, hideAction = true)
} }
@@ -260,6 +267,7 @@ fun MomentResultTab() {
fun UserResultTab() { fun UserResultTab() {
val model = SearchViewModel val model = SearchViewModel
val users = model.usersFlow.collectAsLazyPagingItems() val users = model.usersFlow.collectAsLazyPagingItems()
val scope = rememberCoroutineScope()
Box( Box(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
@@ -268,14 +276,25 @@ fun UserResultTab() {
) { ) {
items(users.itemCount) { idx -> items(users.itemCount) { idx ->
val userItem = users[idx] ?: return@items val userItem = users[idx] ?: return@items
UserItem(userItem) UserItem(userItem) {
scope.launch {
if (userItem.isFollowing) {
model.unfollowUser(userItem.id)
} else {
model.followUser(userItem.id)
}
}
}
} }
} }
} }
} }
@Composable @Composable
fun UserItem(accountProfile: AccountProfileEntity) { fun UserItem(
accountProfile: AccountProfileEntity,
onFollow: (AccountProfileEntity) -> Unit = {},
) {
val context = LocalContext.current val context = LocalContext.current
val navController = LocalNavController.current val navController = LocalNavController.current
Row( Row(
@@ -291,11 +310,52 @@ fun UserItem(accountProfile: AccountProfileEntity) {
context, context,
imageUrl = accountProfile.avatar, imageUrl = accountProfile.avatar,
modifier = Modifier modifier = Modifier
.size(64.dp) .size(48.dp)
.clip(CircleShape), .clip(CircleShape),
contentDescription = null contentDescription = null
) )
Spacer(modifier = Modifier.padding(16.dp)) Spacer(modifier = Modifier.padding(8.dp))
Text(text = accountProfile.nickName, fontSize = 18.sp, fontWeight = FontWeight.Bold) Row(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(text = accountProfile.nickName, fontSize = 16.sp, fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.padding(2.dp))
Text(
text = stringResource(
R.string.search_user_item_follower_count,
accountProfile.followerCount
), fontSize = 14.sp, color = Color(0xFF9E9E9E)
)
}
Spacer(modifier = Modifier.padding(8.dp))
if (accountProfile.id != AppState.UserId) {
if (accountProfile.isFollowing) {
ActionButton(
text = stringResource(R.string.following_upper),
backgroundColor = Color(0xFF9E9E9E),
contentPadding = PaddingValues(vertical = 4.dp, horizontal = 8.dp),
color = Color.White
) {
onFollow(accountProfile)
}
} else {
ActionButton(
text = stringResource(R.string.follow_upper),
backgroundColor = Color(0xffda3832),
contentPadding = PaddingValues(vertical = 4.dp, horizontal = 8.dp),
color = Color.White
) {
onFollow(accountProfile)
}
}
}
}
} }
} }

View File

@@ -9,6 +9,7 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.cachedIn import androidx.paging.cachedIn
import androidx.paging.map
import com.aiosman.riderpro.entity.AccountPagingSource import com.aiosman.riderpro.entity.AccountPagingSource
import com.aiosman.riderpro.entity.AccountProfileEntity import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.entity.MomentPagingSource import com.aiosman.riderpro.entity.MomentPagingSource
@@ -17,6 +18,7 @@ import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.entity.MomentServiceImpl import com.aiosman.riderpro.entity.MomentServiceImpl
import com.aiosman.riderpro.data.UserServiceImpl import com.aiosman.riderpro.data.UserServiceImpl
import com.aiosman.riderpro.entity.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@@ -32,7 +34,11 @@ object SearchViewModel : ViewModel() {
private val _usersFlow = MutableStateFlow<PagingData<AccountProfileEntity>>(PagingData.empty()) private val _usersFlow = MutableStateFlow<PagingData<AccountProfileEntity>>(PagingData.empty())
val usersFlow = _usersFlow.asStateFlow() val usersFlow = _usersFlow.asStateFlow()
var showResult by mutableStateOf(false) var showResult by mutableStateOf(false)
var requestFocus by mutableStateOf(false)
fun search() { fun search() {
if (searchText.isEmpty()) {
return
}
viewModelScope.launch { viewModelScope.launch {
Pager( Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false), config = PagingConfig(pageSize = 5, enablePlaceholders = false),
@@ -62,6 +68,32 @@ object SearchViewModel : ViewModel() {
showResult = true showResult = true
} }
suspend fun followUser(id:Int){
userService.followUser(id.toString())
val currentPagingData = _usersFlow.value
val updatedPagingData = currentPagingData.map { userItem ->
if (userItem.id == id) {
userItem.copy(isFollowing = true, followerCount = userItem.followerCount + 1)
} else {
userItem
}
}
_usersFlow.value = updatedPagingData
}
suspend fun unfollowUser(id:Int){
userService.unFollowUser(id.toString())
val currentPagingData = _usersFlow.value
val updatedPagingData = currentPagingData.map { userItem ->
if (userItem.id == id) {
userItem.copy(isFollowing = false, followerCount = userItem.followerCount - 1)
} else {
userItem
}
}
_usersFlow.value = updatedPagingData
}
fun ResetModel(){ fun ResetModel(){
_momentsFlow.value = PagingData.empty() _momentsFlow.value = PagingData.empty()
_usersFlow.value = PagingData.empty() _usersFlow.value = PagingData.empty()

View File

@@ -86,4 +86,5 @@
<string name="refresh">刷新</string> <string name="refresh">刷新</string>
<string name="clear">清除</string> <string name="clear">清除</string>
<string name="incorrect_captcha_please_try_again">验证码错误,请重试</string> <string name="incorrect_captcha_please_try_again">验证码错误,请重试</string>
<string name="search_user_item_follower_count">%d 粉丝</string>
</resources> </resources>

View File

@@ -85,4 +85,5 @@
<string name="refresh">Refresh</string> <string name="refresh">Refresh</string>
<string name="clear">Clear</string> <string name="clear">Clear</string>
<string name="incorrect_captcha_please_try_again">incorrect captcha,please try again</string> <string name="incorrect_captcha_please_try_again">incorrect captcha,please try again</string>
<string name="search_user_item_follower_count">%d followers</string>
</resources> </resources>