diff --git a/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt b/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt index 9e45875..14d26e0 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt @@ -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, diff --git a/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt b/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt index 4a50a55..e564af0 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt @@ -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 suspend fun createMoment( @@ -104,6 +114,7 @@ interface MomentService { images: List, 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() { override suspend fun load(params: LoadParams): LoadResult { 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 { - 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 { - 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 { 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) } diff --git a/app/src/main/java/com/aiosman/riderpro/data/UserService.kt b/app/src/main/java/com/aiosman/riderpro/data/UserService.kt index 77176a4..caeff8e 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/UserService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/UserService.kt @@ -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() { + override suspend fun load(params: LoadParams): LoadResult { + 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? { + 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 } @@ -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 { + val resp = ApiClient.api.getUsers(page, pageSize, nickname) + val body = resp.body() ?: throw ServiceException("Failed to get account") + return ListContainer( + list = body.list.map { it.toAccountProfileEntity() }, + page = body.page, + total = body.total, + pageSize = body.pageSize + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt b/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt index 51b4248..28c39cb 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt @@ -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> @Multipart @@ -163,4 +164,11 @@ interface RiderProAPI { @Path("id") id: Int ): Response + @GET("users") + suspend fun getUsers( + @Query("page") page: Int = 1, + @Query("pageSize") pageSize: Int = 20, + @Query("nickname") search: String? = null, + ): Response> + } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/model/MomentEntity.kt b/app/src/main/java/com/aiosman/riderpro/model/MomentEntity.kt index 010356e..55ec89f 100644 --- a/app/src/main/java/com/aiosman/riderpro/model/MomentEntity.kt +++ b/app/src/main/java/com/aiosman/riderpro/model/MomentEntity.kt @@ -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 = emptyList(), + val images: List = emptyList(), val authorId: Int = 0, var liked: Boolean = false, var relPostId: Int? = null, diff --git a/app/src/main/java/com/aiosman/riderpro/test/TestDatabase.kt b/app/src/main/java/com/aiosman/riderpro/test/TestDatabase.kt index 6275a38..94ca734 100644 --- a/app/src/main/java/com/aiosman/riderpro/test/TestDatabase.kt +++ b/app/src/main/java/com/aiosman/riderpro/test/TestDatabase.kt @@ -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 ) } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/composables/RelPostCard.kt b/app/src/main/java/com/aiosman/riderpro/ui/composables/RelPostCard.kt index 81054da..4c9172d 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/composables/RelPostCard.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/composables/RelPostCard.kt @@ -30,7 +30,7 @@ fun RelPostCard( image?.let { CustomAsyncImage( context, - image, + image.thumbnail, contentDescription = null, modifier = Modifier.size(100.dp), contentScale = ContentScale.Crop diff --git a/app/src/main/java/com/aiosman/riderpro/ui/imageviewer/ImageViewerViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/imageviewer/ImageViewerViewModel.kt index 772a8ca..5d34b01 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/imageviewer/ImageViewerViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/imageviewer/ImageViewerViewModel.kt @@ -1,9 +1,11 @@ package com.aiosman.riderpro.ui.imageviewer +import com.aiosman.riderpro.model.MomentImageEntity + object ImageViewerViewModel { - var imageList = mutableListOf() + var imageList = mutableListOf() var initialIndex = 0 - fun asNew(images: List, index: Int = 0) { + fun asNew(images: List, index: Int = 0) { imageList.clear() imageList.addAll(images) initialIndex = index diff --git a/app/src/main/java/com/aiosman/riderpro/ui/imageviewer/imageviewer.kt b/app/src/main/java/com/aiosman/riderpro/ui/imageviewer/imageviewer.kt index 030850a..0782b08 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/imageviewer/imageviewer.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/imageviewer/imageviewer.kt @@ -54,7 +54,7 @@ fun ImageViewer() { with(sharedTransitionScope) { CustomAsyncImage( context, - images[page], + images[page].url, contentDescription = null, modifier = Modifier.sharedElement( rememberSharedContentState(key = images[page]), diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt index 372b5fe..9ad293e 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt @@ -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() } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/NavigationItem.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/NavigationItem.kt index 030a343..b0a09df 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/NavigationItem.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/NavigationItem.kt @@ -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 } + ) } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/add/AddPage.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/add/AddPage.kt index aeb5d11..4e990ba 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/add/AddPage.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/add/AddPage.kt @@ -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") } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/Moment.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/Moment.kt index b485865..588adee 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/Moment.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/Moment.kt @@ -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), diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt index a8da066..074e7dc 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt @@ -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 diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/SearchScreen.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/SearchScreen.kt new file mode 100644 index 0000000..36300b4 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/SearchScreen.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/SearchViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/SearchViewModel.kt new file mode 100644 index 0000000..8013089 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/SearchViewModel.kt @@ -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.empty()) + val momentsFlow = _momentsFlow.asStateFlow() + + private val userService = TestUserServiceImpl() + private val _usersFlow = MutableStateFlow>(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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt b/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt index 0ef5dbf..642480f 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt @@ -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, + images: List, ) { 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 diff --git a/app/src/main/java/com/aiosman/riderpro/utils/Utils.kt b/app/src/main/java/com/aiosman/riderpro/utils/Utils.kt index 150c965..044cf89 100644 --- a/app/src/main/java/com/aiosman/riderpro/utils/Utils.kt +++ b/app/src/main/java/com/aiosman/riderpro/utils/Utils.kt @@ -18,6 +18,7 @@ object Utils { .okHttpClient(okHttpClient) .diskCachePolicy(CachePolicy.ENABLED) .memoryCachePolicy(CachePolicy.ENABLED) + .components { }