diff --git a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt index 8475b54..554801e 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt @@ -43,6 +43,7 @@ import com.aiosman.riderpro.ui.login.LoginPage import com.aiosman.riderpro.ui.login.SignupScreen import com.aiosman.riderpro.ui.login.UserAuthScreen import com.aiosman.riderpro.ui.index.tabs.message.NotificationsScreen +import com.aiosman.riderpro.ui.index.tabs.search.SearchScreen import com.aiosman.riderpro.ui.modification.EditModificationScreen import com.aiosman.riderpro.ui.post.NewPostImageGridScreen import com.aiosman.riderpro.ui.post.NewPostScreen @@ -75,6 +76,7 @@ sealed class NavigationRoute( data object ChangePasswordScreen : NavigationRoute("ChangePasswordScreen") data object FavouritesScreen : NavigationRoute("FavouritesScreen") data object NewPostImageGrid : NavigationRoute("NewPostImageGrid") + data object Search : NavigationRoute("Search") } @@ -219,6 +221,13 @@ fun NavigationController( composable(route = NavigationRoute.NewPostImageGrid.route) { NewPostImageGridScreen() } + composable(route = NavigationRoute.Search.route) { + CompositionLocalProvider( + LocalAnimatedContentScope provides this, + ) { + SearchScreen() + } + } } 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 5fcd86b..90568ff 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 @@ -38,6 +38,7 @@ import com.aiosman.riderpro.ui.index.tabs.add.AddPage import com.aiosman.riderpro.ui.index.tabs.message.NotificationsScreen 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.DiscoverScreen 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 @@ -124,7 +125,7 @@ fun IndexScreen() { ) { page -> when (page) { 0 -> Home() - 1 -> SearchScreen() + 1 -> DiscoverScreen() 2 -> Add() 3 -> Notifications() 4 -> Profile() diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/DiscoverScreen.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/DiscoverScreen.kt new file mode 100644 index 0000000..8571f65 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/DiscoverScreen.kt @@ -0,0 +1,187 @@ +package com.aiosman.riderpro.ui.index.tabs.search + +import android.graphics.drawable.VectorDrawable +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +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.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +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.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.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.res.painterResource +import androidx.compose.ui.text.TextStyle +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.R +import com.aiosman.riderpro.ui.NavigationRoute +import com.aiosman.riderpro.ui.composables.CustomAsyncImage +import com.aiosman.riderpro.ui.modifiers.noRippleClickable +import com.aiosman.riderpro.ui.post.PostViewModel +import com.google.accompanist.systemuicontroller.rememberSystemUiController + + +@OptIn(ExperimentalFoundationApi::class) +@Preview +@Composable +fun DiscoverScreen() { + val model = DiscoverViewModel + val navController = LocalNavController.current + val coroutineScope = rememberCoroutineScope() + val systemUiController = rememberSystemUiController() + val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() + val navigationBarPaddings = + WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp + LaunchedEffect(Unit) { + systemUiController.setStatusBarColor(Color.Transparent, darkIcons = true) + } + + Column( + modifier = Modifier + .background(Color.White) + .fillMaxSize() + .padding(bottom = navigationBarPaddings) + ) { + Spacer(modifier = Modifier.height(statusBarPaddingValues.calculateTopPadding())) + + SearchButton( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp, start = 24.dp, end = 24.dp), + ) { + navController.navigate("Search") + } + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp) + .weight(1f) + ) { + DiscoverView() + } + + + } +} + +@Composable +fun SearchButton( + modifier: Modifier = Modifier, + clickAction: () -> Unit = {} +) { + Box( + modifier = modifier + .clip(shape = RoundedCornerShape(8.dp)) + + .background(Color(0xFFEEEEEE)) + .padding(horizontal = 16.dp, vertical = 8.dp) + .noRippleClickable { + clickAction() + } + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + Icons.Default.Search, + contentDescription = null + ) + Box { + Text( + text = "Search", + modifier = Modifier.padding(start = 8.dp), + color = Color(0xFF9E9E9E), + fontSize = 18.sp + ) + } + } + } +} + +@Composable +fun DiscoverView() { + val model = DiscoverViewModel + var dataFlow = model.discoverMomentsFlow + var moments = dataFlow.collectAsLazyPagingItems() + val context = LocalContext.current + val navController = LocalNavController.current + LazyVerticalGrid( + columns = GridCells.Fixed(3), + modifier = Modifier.fillMaxSize() + ) { + items(moments.itemCount) { idx -> + val momentItem = moments[idx] ?: return@items + Box( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + .padding(2.dp) + + .noRippleClickable { + PostViewModel.preTransit(momentItem) + navController.navigate( + NavigationRoute.Post.route.replace( + "{id}", + momentItem.id.toString() + ) + ) + } + ) { + CustomAsyncImage( + imageUrl = momentItem.images[0].thumbnail, + contentDescription = "", + modifier = Modifier + .fillMaxSize(), + context = context + ) + if (momentItem.images.size > 1) { + Box( + modifier = Modifier + .padding(top = 8.dp, end = 8.dp) + .align(Alignment.TopEnd) + ) { + Image( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.rider_pro_images), + contentDescription = "", + ) + } + } + + + } + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/DiscoverViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/DiscoverViewModel.kt new file mode 100644 index 0000000..017ada9 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/DiscoverViewModel.kt @@ -0,0 +1,38 @@ +package com.aiosman.riderpro.ui.index.tabs.search + +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.MomentService +import com.aiosman.riderpro.entity.MomentEntity +import com.aiosman.riderpro.entity.MomentPagingSource +import com.aiosman.riderpro.entity.MomentRemoteDataSource +import com.aiosman.riderpro.entity.MomentServiceImpl +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +object DiscoverViewModel:ViewModel() { + private val momentService: MomentService = MomentServiceImpl() + private val _discoverMomentsFlow = + MutableStateFlow>(PagingData.empty()) + val discoverMomentsFlow = _discoverMomentsFlow.asStateFlow() + init { + viewModelScope.launch { + Pager( + config = PagingConfig(pageSize = 5, enablePlaceholders = false), + pagingSourceFactory = { + MomentPagingSource( + MomentRemoteDataSource(momentService), + ) + } + ).flow.cachedIn(viewModelScope).collectLatest { + _discoverMomentsFlow.value = it + } + } + } +} \ No newline at end of file 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 index fef5dbd..8968e2c 100644 --- 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 @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBars @@ -37,6 +38,8 @@ 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.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController @@ -68,19 +71,28 @@ fun SearchScreen() { val keyboardController = LocalSoftwareKeyboardController.current val systemUiController = rememberSystemUiController() val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() + val navigationBarPaddings = + WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + val focusRequester = remember { FocusRequester() } LaunchedEffect(Unit) { systemUiController.setStatusBarColor(Color.Transparent, darkIcons = true) } + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } + Column( modifier = Modifier .background(Color.White) .fillMaxSize() - + .padding(bottom = navigationBarPaddings) ) { Spacer(modifier = Modifier.height(statusBarPaddingValues.calculateTopPadding())) SearchInput( - modifier = Modifier.fillMaxWidth().padding(top = 16.dp, start = 24.dp, end = 24.dp), + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp, start = 24.dp, end = 24.dp), text = model.searchText, onTextChange = { model.searchText = it @@ -89,7 +101,8 @@ fun SearchScreen() { model.search() // hide ime keyboardController?.hide() // Hide the keyboard - } + }, + focusRequester = focusRequester ) if (model.showResult) { Spacer(modifier = Modifier.padding(8.dp)) @@ -115,8 +128,6 @@ fun SearchScreen() { pagerState = pagerState ) } - - } } @@ -125,7 +136,8 @@ fun SearchInput( modifier: Modifier = Modifier, text: String = "", onTextChange: (String) -> Unit = {}, - onSearch: () -> Unit = {} + onSearch: () -> Unit = {}, + focusRequester: FocusRequester = remember { FocusRequester() } ) { Box( modifier = modifier @@ -157,7 +169,8 @@ fun SearchInput( }, modifier = Modifier .padding(start = 8.dp) - .fillMaxWidth(), + .fillMaxWidth() + .focusRequester(focusRequester), singleLine = true, textStyle = TextStyle( fontSize = 18.sp @@ -238,9 +251,12 @@ 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}") - }, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + .noRippleClickable { + navController.navigate("AccountProfile/${accountProfile.id}") + }, verticalAlignment = Alignment.CenterVertically ) { CustomAsyncImage( diff --git a/app/src/main/res/drawable/rider_pro_images.xml b/app/src/main/res/drawable/rider_pro_images.xml new file mode 100644 index 0000000..d70a354 --- /dev/null +++ b/app/src/main/res/drawable/rider_pro_images.xml @@ -0,0 +1,5 @@ + + + + +