更新
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
package com.aiosman.riderpro.data
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.aiosman.riderpro.AppStore
|
||||
import com.aiosman.riderpro.data.api.ApiClient
|
||||
import com.aiosman.riderpro.data.api.ChangePasswordRequestBody
|
||||
import com.aiosman.riderpro.data.api.LoginUserRequestBody
|
||||
import com.aiosman.riderpro.data.api.RegisterRequestBody
|
||||
import com.aiosman.riderpro.model.MomentEntity
|
||||
import com.aiosman.riderpro.test.TestDatabase
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.MultipartBody
|
||||
@@ -12,6 +15,7 @@ import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
data class AccountProfileEntity(
|
||||
val id: Int,
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.aiosman.riderpro.AppStore
|
||||
import com.aiosman.riderpro.R
|
||||
import com.aiosman.riderpro.data.api.ApiClient
|
||||
import com.aiosman.riderpro.model.MomentEntity
|
||||
import com.aiosman.riderpro.model.MomentImageEntity
|
||||
import com.aiosman.riderpro.test.TestDatabase
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@@ -57,7 +58,13 @@ data class Moment(
|
||||
commentCount = commentCount.toInt(),
|
||||
shareCount = 0,
|
||||
favoriteCount = favoriteCount.toInt(),
|
||||
images = images.map { ApiClient.BASE_SERVER + it.url + "?token=${AppStore.token}" },
|
||||
images = images.map {
|
||||
MomentImageEntity(
|
||||
url = ApiClient.BASE_SERVER + it.url + "?token=${AppStore.token}",
|
||||
thumbnail = ApiClient.BASE_SERVER + it.thumbnail + "?token=${AppStore.token}",
|
||||
id = it.id
|
||||
)
|
||||
},
|
||||
authorId = user.id.toInt(),
|
||||
liked = isLiked,
|
||||
isFavorite = isFavorite
|
||||
@@ -82,12 +89,14 @@ data class User(
|
||||
@SerializedName("avatar")
|
||||
val avatar: String
|
||||
)
|
||||
|
||||
data class UploadImage(
|
||||
val file: File,
|
||||
val filename: String,
|
||||
val url: String,
|
||||
val ext: String
|
||||
)
|
||||
|
||||
interface MomentService {
|
||||
suspend fun getMomentById(id: Int): MomentEntity
|
||||
suspend fun likeMoment(id: Int)
|
||||
@@ -95,7 +104,8 @@ interface MomentService {
|
||||
suspend fun getMoments(
|
||||
pageNumber: Int,
|
||||
author: Int? = null,
|
||||
timelineId: Int? = null
|
||||
timelineId: Int? = null,
|
||||
contentSearch: String? = null
|
||||
): ListContainer<MomentEntity>
|
||||
|
||||
suspend fun createMoment(
|
||||
@@ -104,6 +114,7 @@ interface MomentService {
|
||||
images: List<UploadImage>,
|
||||
relPostId: Int? = null
|
||||
): MomentEntity
|
||||
|
||||
suspend fun favoriteMoment(id: Int)
|
||||
suspend fun unfavoriteMoment(id: Int)
|
||||
}
|
||||
@@ -112,7 +123,8 @@ interface MomentService {
|
||||
class MomentPagingSource(
|
||||
private val remoteDataSource: MomentRemoteDataSource,
|
||||
private val author: Int? = null,
|
||||
private val timelineId: Int? = null
|
||||
private val timelineId: Int? = null,
|
||||
private val contentSearch: String? = null
|
||||
) : PagingSource<Int, MomentEntity>() {
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MomentEntity> {
|
||||
return try {
|
||||
@@ -120,7 +132,8 @@ class MomentPagingSource(
|
||||
val moments = remoteDataSource.getMoments(
|
||||
pageNumber = currentPage,
|
||||
author = author,
|
||||
timelineId = timelineId
|
||||
timelineId = timelineId,
|
||||
contentSearch = contentSearch
|
||||
)
|
||||
|
||||
LoadResult.Page(
|
||||
@@ -145,9 +158,10 @@ class MomentRemoteDataSource(
|
||||
suspend fun getMoments(
|
||||
pageNumber: Int,
|
||||
author: Int?,
|
||||
timelineId: Int?
|
||||
timelineId: Int?,
|
||||
contentSearch: String?
|
||||
): ListContainer<MomentEntity> {
|
||||
return momentService.getMoments(pageNumber, author, timelineId)
|
||||
return momentService.getMoments(pageNumber, author, timelineId, contentSearch)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,9 +172,10 @@ class TestMomentServiceImpl() : MomentService {
|
||||
override suspend fun getMoments(
|
||||
pageNumber: Int,
|
||||
author: Int?,
|
||||
timelineId: Int?
|
||||
timelineId: Int?,
|
||||
contentSearch: String?
|
||||
): ListContainer<MomentEntity> {
|
||||
return testMomentBackend.fetchMomentItems(pageNumber, author, timelineId)
|
||||
return testMomentBackend.fetchMomentItems(pageNumber, author, timelineId, contentSearch)
|
||||
}
|
||||
|
||||
override suspend fun getMomentById(id: Int): MomentEntity {
|
||||
@@ -202,13 +217,15 @@ class TestMomentBackend(
|
||||
suspend fun fetchMomentItems(
|
||||
pageNumber: Int,
|
||||
author: Int? = null,
|
||||
timelineId: Int?
|
||||
timelineId: Int?,
|
||||
contentSearch: String?
|
||||
): ListContainer<MomentEntity> {
|
||||
val resp = ApiClient.api.getPosts(
|
||||
pageSize = DataBatchSize,
|
||||
page = pageNumber,
|
||||
timelineId = timelineId,
|
||||
authorId = author
|
||||
authorId = author,
|
||||
contentSearch = contentSearch
|
||||
)
|
||||
val body = resp.body() ?: throw ServiceException("Failed to get moments")
|
||||
return ListContainer(
|
||||
@@ -258,6 +275,7 @@ class TestMomentBackend(
|
||||
suspend fun favoriteMoment(id: Int) {
|
||||
ApiClient.api.favoritePost(id)
|
||||
}
|
||||
|
||||
suspend fun unfavoriteMoment(id: Int) {
|
||||
ApiClient.api.unfavoritePost(id)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,53 @@
|
||||
package com.aiosman.riderpro.data
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.aiosman.riderpro.data.api.ApiClient
|
||||
import com.aiosman.riderpro.model.MomentEntity
|
||||
import com.aiosman.riderpro.test.TestDatabase
|
||||
import java.io.IOException
|
||||
|
||||
data class UserAuth(
|
||||
val id: Int,
|
||||
val token: String? = null
|
||||
)
|
||||
|
||||
class AccountPagingSource(
|
||||
private val userService: UserService,
|
||||
private val nickname: String? = null
|
||||
) : PagingSource<Int, AccountProfileEntity>() {
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AccountProfileEntity> {
|
||||
return try {
|
||||
val currentPage = params.key ?: 1
|
||||
val users = userService.getUsers(
|
||||
page = currentPage,
|
||||
nickname = nickname
|
||||
)
|
||||
LoadResult.Page(
|
||||
data = users.list,
|
||||
prevKey = if (currentPage == 1) null else currentPage - 1,
|
||||
nextKey = if (users.list.isEmpty()) null else users.page + 1
|
||||
)
|
||||
} catch (exception: IOException) {
|
||||
return LoadResult.Error(exception)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Int, AccountProfileEntity>): Int? {
|
||||
return state.anchorPosition
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface UserService {
|
||||
suspend fun getUserProfile(id: String): AccountProfileEntity
|
||||
suspend fun followUser(id: String)
|
||||
suspend fun unFollowUser(id: String)
|
||||
suspend fun getUsers(
|
||||
pageSize: Int = 20,
|
||||
page: Int = 1,
|
||||
nickname: String? = null
|
||||
): ListContainer<AccountProfileEntity>
|
||||
|
||||
}
|
||||
|
||||
@@ -31,4 +67,19 @@ class TestUserServiceImpl : UserService {
|
||||
val resp = ApiClient.api.unfollowUser(id.toInt())
|
||||
return
|
||||
}
|
||||
|
||||
override suspend fun getUsers(
|
||||
pageSize: Int,
|
||||
page: Int,
|
||||
nickname: String?
|
||||
): ListContainer<AccountProfileEntity> {
|
||||
val resp = ApiClient.api.getUsers(page, pageSize, nickname)
|
||||
val body = resp.body() ?: throw ServiceException("Failed to get account")
|
||||
return ListContainer<AccountProfileEntity>(
|
||||
list = body.list.map { it.toAccountProfileEntity() },
|
||||
page = body.page,
|
||||
total = body.total,
|
||||
pageSize = body.pageSize
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,7 @@ interface RiderProAPI {
|
||||
@Query("pageSize") pageSize: Int = 20,
|
||||
@Query("timelineId") timelineId: Int? = null,
|
||||
@Query("authorId") authorId: Int? = null,
|
||||
@Query("contentSearch") contentSearch: String? = null,
|
||||
): Response<ListContainer<Moment>>
|
||||
|
||||
@Multipart
|
||||
@@ -163,4 +164,11 @@ interface RiderProAPI {
|
||||
@Path("id") id: Int
|
||||
): Response<Unit>
|
||||
|
||||
@GET("users")
|
||||
suspend fun getUsers(
|
||||
@Query("page") page: Int = 1,
|
||||
@Query("pageSize") pageSize: Int = 20,
|
||||
@Query("nickname") search: String? = null,
|
||||
): Response<ListContainer<AccountProfile>>
|
||||
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
package com.aiosman.riderpro.model
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
|
||||
data class MomentImageEntity(
|
||||
val id: Long,
|
||||
val url: String,
|
||||
val thumbnail: String
|
||||
)
|
||||
data class MomentEntity(
|
||||
val id: Int,
|
||||
val avatar: String,
|
||||
@@ -15,7 +19,7 @@ data class MomentEntity(
|
||||
val commentCount: Int,
|
||||
val shareCount: Int,
|
||||
val favoriteCount: Int,
|
||||
val images: List<String> = emptyList(),
|
||||
val images: List<MomentImageEntity> = emptyList(),
|
||||
val authorId: Int = 0,
|
||||
var liked: Boolean = false,
|
||||
var relPostId: Int? = null,
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.aiosman.riderpro.R
|
||||
import com.aiosman.riderpro.data.AccountProfileEntity
|
||||
import com.aiosman.riderpro.data.CommentEntity
|
||||
import com.aiosman.riderpro.model.MomentEntity
|
||||
import com.aiosman.riderpro.model.MomentImageEntity
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import io.github.serpro69.kfaker.faker
|
||||
@@ -124,7 +125,13 @@ object TestDatabase {
|
||||
commentCount = commentCount + 1,
|
||||
shareCount = faker.random.nextInt(0, 100),
|
||||
favoriteCount = faker.random.nextInt(0, 100),
|
||||
images = imageList.shuffled().take(3),
|
||||
images = imageList.shuffled().take(3).map {
|
||||
MomentImageEntity(
|
||||
id = faker.random.nextLong(),
|
||||
url = it,
|
||||
thumbnail = it
|
||||
)
|
||||
},
|
||||
authorId = person.id
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ fun RelPostCard(
|
||||
image?.let {
|
||||
CustomAsyncImage(
|
||||
context,
|
||||
image,
|
||||
image.thumbnail,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(100.dp),
|
||||
contentScale = ContentScale.Crop
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.aiosman.riderpro.ui.imageviewer
|
||||
|
||||
import com.aiosman.riderpro.model.MomentImageEntity
|
||||
|
||||
object ImageViewerViewModel {
|
||||
var imageList = mutableListOf<String>()
|
||||
var imageList = mutableListOf<MomentImageEntity>()
|
||||
var initialIndex = 0
|
||||
fun asNew(images: List<String>, index: Int = 0) {
|
||||
fun asNew(images: List<MomentImageEntity>, index: Int = 0) {
|
||||
imageList.clear()
|
||||
imageList.addAll(images)
|
||||
initialIndex = index
|
||||
|
||||
@@ -54,7 +54,7 @@ fun ImageViewer() {
|
||||
with(sharedTransitionScope) {
|
||||
CustomAsyncImage(
|
||||
context,
|
||||
images[page],
|
||||
images[page].url,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.sharedElement(
|
||||
rememberSharedContentState(key = images[page]),
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.ui.unit.dp
|
||||
import com.aiosman.riderpro.ui.index.tabs.add.AddPage
|
||||
import com.aiosman.riderpro.ui.index.tabs.moment.MomentsList
|
||||
import com.aiosman.riderpro.ui.index.tabs.profile.ProfilePage
|
||||
import com.aiosman.riderpro.ui.index.tabs.search.SearchScreen
|
||||
import com.aiosman.riderpro.ui.index.tabs.shorts.ShortVideo
|
||||
import com.aiosman.riderpro.ui.index.tabs.street.StreetPage
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
@@ -40,9 +41,10 @@ fun IndexScreen() {
|
||||
}
|
||||
val item = listOf(
|
||||
NavigationItem.Home,
|
||||
NavigationItem.Street,
|
||||
NavigationItem.Search,
|
||||
// NavigationItem.Street,
|
||||
NavigationItem.Add,
|
||||
NavigationItem.Message,
|
||||
// NavigationItem.Message,
|
||||
NavigationItem.Profile
|
||||
)
|
||||
val systemUiController = rememberSystemUiController()
|
||||
@@ -99,17 +101,22 @@ fun IndexScreen() {
|
||||
) {
|
||||
Home()
|
||||
}
|
||||
1 -> Box(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
SearchScreen()
|
||||
}
|
||||
|
||||
1 -> Street()
|
||||
// 1 -> Street()
|
||||
2 -> Box(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) { Add() }
|
||||
|
||||
3 -> Box(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) { Video() }
|
||||
// 3 -> Box(
|
||||
// modifier = Modifier.padding(innerPadding)
|
||||
// ) { Video() }
|
||||
|
||||
4 -> Box(
|
||||
3 -> Box(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) { Profile() }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.aiosman.riderpro.ui.index
|
||||
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
@@ -34,4 +37,9 @@ sealed class NavigationItem(
|
||||
icon = { ImageVector.vectorResource(R.drawable.rider_pro_profile) },
|
||||
selectedIcon = { ImageVector.vectorResource(R.drawable.rider_pro_profile_filed) }
|
||||
)
|
||||
|
||||
data object Search : NavigationItem("Search",
|
||||
icon = { Icons.Default.Search },
|
||||
selectedIcon = { Icons.Default.Search }
|
||||
)
|
||||
}
|
||||
@@ -34,7 +34,7 @@ fun AddPage(){
|
||||
NewPostViewModel.asNewPost()
|
||||
navController.navigate("NewPost")
|
||||
}
|
||||
AddBtn(icon = R.drawable.rider_pro_location_create, text = "Location Create")
|
||||
// AddBtn(icon = R.drawable.rider_pro_location_create, text = "Location Create")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,9 +131,10 @@ fun MomentsList() {
|
||||
@Composable
|
||||
fun MomentCard(
|
||||
momentEntity: MomentEntity,
|
||||
onLikeClick: () -> Unit,
|
||||
onLikeClick: () -> Unit = {},
|
||||
onFavoriteClick: () -> Unit = {},
|
||||
onAddComment: () -> Unit = {}
|
||||
onAddComment: () -> Unit = {},
|
||||
hideAction: Boolean = false
|
||||
) {
|
||||
val navController = LocalNavController.current
|
||||
Column(
|
||||
@@ -153,18 +154,21 @@ fun MomentCard(
|
||||
.fillMaxHeight()
|
||||
.weight(1f)
|
||||
// ModificationListHeader()
|
||||
MomentBottomOperateRowGroup(
|
||||
momentOperateBtnBoxModifier,
|
||||
momentEntity = momentEntity,
|
||||
onLikeClick = onLikeClick,
|
||||
onAddComment = onAddComment,
|
||||
onShareClick = {
|
||||
NewPostViewModel.asNewPost()
|
||||
NewPostViewModel.relPostId = momentEntity.id
|
||||
navController.navigate(NavigationRoute.NewPost.route)
|
||||
},
|
||||
onFavoriteClick = onFavoriteClick
|
||||
)
|
||||
if (!hideAction){
|
||||
MomentBottomOperateRowGroup(
|
||||
momentOperateBtnBoxModifier,
|
||||
momentEntity = momentEntity,
|
||||
onLikeClick = onLikeClick,
|
||||
onAddComment = onAddComment,
|
||||
onShareClick = {
|
||||
NewPostViewModel.asNewPost()
|
||||
NewPostViewModel.relPostId = momentEntity.id
|
||||
navController.navigate(NavigationRoute.NewPost.route)
|
||||
},
|
||||
onFavoriteClick = onFavoriteClick
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +346,7 @@ fun MomentContentGroup(
|
||||
with(sharedTransitionScope) {
|
||||
CustomAsyncImage(
|
||||
context,
|
||||
it,
|
||||
it.thumbnail,
|
||||
modifier = Modifier
|
||||
.sharedElement(
|
||||
rememberSharedContentState(key = it),
|
||||
|
||||
@@ -396,26 +396,26 @@ fun CommunicationOperatorGroup(
|
||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(width = 142.dp, height = 40.dp)
|
||||
.clickable {
|
||||
navController.navigate("ProfileTimeline")
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
painter = painterResource(id = R.drawable.rider_pro_profile_follow),
|
||||
contentDescription = ""
|
||||
)
|
||||
Text(
|
||||
text = "GALLERY",
|
||||
fontSize = 16.sp,
|
||||
color = Color.White,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
||||
)
|
||||
}
|
||||
// Box(
|
||||
// modifier = Modifier
|
||||
// .size(width = 142.dp, height = 40.dp)
|
||||
// .clickable {
|
||||
// navController.navigate("ProfileTimeline")
|
||||
// },
|
||||
// contentAlignment = Alignment.Center
|
||||
// ) {
|
||||
// Image(
|
||||
// modifier = Modifier.fillMaxSize(),
|
||||
// painter = painterResource(id = R.drawable.rider_pro_profile_follow),
|
||||
// contentDescription = ""
|
||||
// )
|
||||
// Text(
|
||||
// text = "GALLERY",
|
||||
// fontSize = 16.sp,
|
||||
// color = Color.White,
|
||||
// style = TextStyle(fontWeight = FontWeight.Bold)
|
||||
// )
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -504,7 +504,7 @@ fun MomentPostUnit(momentEntity: MomentEntity) {
|
||||
TimeGroup(momentEntity.time)
|
||||
ProfileMomentCard(
|
||||
momentEntity.momentTextContent,
|
||||
momentEntity.images[0],
|
||||
momentEntity.images[0].thumbnail,
|
||||
momentEntity.likeCount.toString(),
|
||||
momentEntity.commentCount.toString(),
|
||||
momentId = momentEntity.id
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
package com.aiosman.riderpro.ui.index.tabs.search
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
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.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.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Tab
|
||||
import androidx.compose.material.TabRow
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.aiosman.riderpro.LocalNavController
|
||||
import com.aiosman.riderpro.data.AccountProfileEntity
|
||||
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.riderpro.ui.index.tabs.moment.MomentCard
|
||||
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Preview
|
||||
@Composable
|
||||
fun SearchScreen() {
|
||||
val model = SearchViewModel
|
||||
val categories = listOf("Moment", "User")
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val pagerState = rememberPagerState(pageCount = { categories.size })
|
||||
val selectedTabIndex = remember { derivedStateOf { pagerState.currentPage } }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val systemUiController = rememberSystemUiController()
|
||||
LaunchedEffect(Unit) {
|
||||
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = true)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(Color.White)
|
||||
.fillMaxSize()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 24.dp)
|
||||
) {
|
||||
SearchInput(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = model.searchText,
|
||||
onTextChange = {
|
||||
model.searchText = it
|
||||
},
|
||||
onSearch = {
|
||||
model.search()
|
||||
// hide ime
|
||||
keyboardController?.hide() // Hide the keyboard
|
||||
}
|
||||
)
|
||||
if (model.showResult) {
|
||||
Spacer(modifier = Modifier.padding(8.dp))
|
||||
|
||||
TabRow(
|
||||
selectedTabIndex = selectedTabIndex.value,
|
||||
backgroundColor = Color.White,
|
||||
) {
|
||||
categories.forEachIndexed { index, category ->
|
||||
Tab(
|
||||
selected = selectedTabIndex.value == index,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
pagerState.animateScrollToPage(index)
|
||||
}
|
||||
},
|
||||
text = { Text(category) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SearchPager(
|
||||
pagerState = pagerState
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SearchInput(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String = "",
|
||||
onTextChange: (String) -> Unit = {},
|
||||
onSearch: () -> Unit = {}
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(shape = RoundedCornerShape(8.dp))
|
||||
|
||||
.background(Color(0xFFEEEEEE))
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Search,
|
||||
contentDescription = null
|
||||
)
|
||||
Box {
|
||||
if (text.isEmpty()) {
|
||||
Text(
|
||||
text = "Search",
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
color = Color(0xFF9E9E9E),
|
||||
fontSize = 18.sp
|
||||
)
|
||||
}
|
||||
BasicTextField(
|
||||
value = text,
|
||||
onValueChange = {
|
||||
onTextChange(it)
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
textStyle = TextStyle(
|
||||
fontSize = 18.sp
|
||||
),
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
imeAction = ImeAction.Search
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onSearch = {
|
||||
onSearch()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun SearchPager(
|
||||
pagerState: PagerState,
|
||||
) {
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
||||
) { page ->
|
||||
when (page) {
|
||||
0 -> MomentResultTab()
|
||||
1 -> UserResultTab()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MomentResultTab() {
|
||||
val model = SearchViewModel
|
||||
var dataFlow = model.momentsFlow
|
||||
var moments = dataFlow.collectAsLazyPagingItems()
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
items(moments.itemCount) { idx ->
|
||||
val momentItem = moments[idx] ?: return@items
|
||||
Spacer(modifier = Modifier.padding(8.dp))
|
||||
MomentCard(momentEntity = momentItem, hideAction = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserResultTab() {
|
||||
val model = SearchViewModel
|
||||
val users = model.usersFlow.collectAsLazyPagingItems()
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
items(users.itemCount) { idx ->
|
||||
val userItem = users[idx] ?: return@items
|
||||
UserItem(userItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserItem(accountProfile: AccountProfileEntity) {
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(16.dp).noRippleClickable {
|
||||
navController.navigate("AccountProfile/${accountProfile.id}")
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
CustomAsyncImage(
|
||||
context,
|
||||
imageUrl = accountProfile.avatar,
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.clip(CircleShape),
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(16.dp))
|
||||
Text(text = accountProfile.nickName, fontSize = 18.sp, fontWeight = FontWeight.Bold)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.aiosman.riderpro.ui.index.tabs.search
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.aiosman.riderpro.data.AccountPagingSource
|
||||
import com.aiosman.riderpro.data.AccountProfileEntity
|
||||
import com.aiosman.riderpro.data.MomentPagingSource
|
||||
import com.aiosman.riderpro.data.MomentRemoteDataSource
|
||||
import com.aiosman.riderpro.data.MomentService
|
||||
import com.aiosman.riderpro.data.TestMomentServiceImpl
|
||||
import com.aiosman.riderpro.data.TestUserServiceImpl
|
||||
import com.aiosman.riderpro.model.MomentEntity
|
||||
import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel
|
||||
import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel.accountService
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object SearchViewModel : ViewModel() {
|
||||
var searchText by mutableStateOf("")
|
||||
private val momentService: MomentService = TestMomentServiceImpl()
|
||||
private val _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
|
||||
val momentsFlow = _momentsFlow.asStateFlow()
|
||||
|
||||
private val userService = TestUserServiceImpl()
|
||||
private val _usersFlow = MutableStateFlow<PagingData<AccountProfileEntity>>(PagingData.empty())
|
||||
val usersFlow = _usersFlow.asStateFlow()
|
||||
var showResult by mutableStateOf(false)
|
||||
fun search() {
|
||||
viewModelScope.launch {
|
||||
Pager(
|
||||
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
|
||||
pagingSourceFactory = {
|
||||
MomentPagingSource(
|
||||
MomentRemoteDataSource(momentService),
|
||||
contentSearch = searchText
|
||||
)
|
||||
}
|
||||
).flow.cachedIn(viewModelScope).collectLatest {
|
||||
_momentsFlow.value = it
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
Pager(
|
||||
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
|
||||
pagingSourceFactory = {
|
||||
AccountPagingSource(
|
||||
userService,
|
||||
nickname = searchText
|
||||
)
|
||||
}
|
||||
).flow.cachedIn(viewModelScope).collectLatest {
|
||||
_usersFlow.value = it
|
||||
}
|
||||
}
|
||||
showResult = true
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,7 @@ import com.aiosman.riderpro.data.TestMomentServiceImpl
|
||||
import com.aiosman.riderpro.data.TestUserServiceImpl
|
||||
import com.aiosman.riderpro.data.UserService
|
||||
import com.aiosman.riderpro.model.MomentEntity
|
||||
import com.aiosman.riderpro.model.MomentImageEntity
|
||||
import com.aiosman.riderpro.ui.NavigationRoute
|
||||
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
|
||||
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
|
||||
@@ -426,7 +427,7 @@ fun Header(
|
||||
@Composable
|
||||
fun PostImageView(
|
||||
postId: String,
|
||||
images: List<String>,
|
||||
images: List<MomentImageEntity>,
|
||||
) {
|
||||
val pagerState = rememberPagerState(pageCount = { images.size })
|
||||
val navController = LocalNavController.current
|
||||
@@ -445,7 +446,7 @@ fun PostImageView(
|
||||
with(sharedTransitionScope) {
|
||||
CustomAsyncImage(
|
||||
context,
|
||||
image,
|
||||
image.thumbnail,
|
||||
contentDescription = "Image",
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
|
||||
@@ -18,6 +18,7 @@ object Utils {
|
||||
.okHttpClient(okHttpClient)
|
||||
.diskCachePolicy(CachePolicy.ENABLED)
|
||||
.memoryCachePolicy(CachePolicy.ENABLED)
|
||||
|
||||
.components {
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user