上传
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -27,9 +27,19 @@ fun GalleryItem(
|
|||||||
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(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
if (model.requestFocus) {
|
||||||
focusRequester.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user