diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 4417424..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/server/.DS_Store b/server/.DS_Store deleted file mode 100644 index 374d576..0000000 Binary files a/server/.DS_Store and /dev/null differ diff --git a/teawon/project/toyproejct/app/build.gradle b/teawon/project/toyproejct/app/build.gradle index 1fb5b75..74b64ca 100644 --- a/teawon/project/toyproejct/app/build.gradle +++ b/teawon/project/toyproejct/app/build.gradle @@ -65,15 +65,7 @@ dependencies { implementation("io.ktor:ktor-client-serialization:1.4.0") implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1" -// implementation("io.ktor:ktor-client-core:1.6.7") -// implementation("io.ktor:ktor-client-cio:1.6.7") -// implementation("io.ktor:ktor-client-android:1.6.0") -// implementation "io.ktor:ktor-client-logging:1.6.0" -// implementation("io.ktor:ktor-client-serialization:1.4.0") -// implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1" //ktor - -// implementation "io.ktor:ktor-client-core:1.6.1" -// implementation "io.ktor:ktor-client-cio:1.6.1" + implementation "androidx.compose.runtime:runtime-livedata:$compose_ui_version" //ui testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/User.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/User.kt new file mode 100644 index 0000000..4b1498b --- /dev/null +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/User.kt @@ -0,0 +1,6 @@ +package com.example.toy_proejct.data + +data class User( + val username: String, + val phone: String +) \ No newline at end of file diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/api/getDetail/DetailDto.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/product/detail/DetailDto.kt similarity index 79% rename from teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/api/getDetail/DetailDto.kt rename to teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/product/detail/DetailDto.kt index a33cdfe..d04bc4f 100644 --- a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/api/getDetail/DetailDto.kt +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/product/detail/DetailDto.kt @@ -1,4 +1,4 @@ -package com.example.toy_proejct.api.getDetail +package com.example.toy_proejct.data.product.detail @kotlinx.serialization.Serializable data class DetailDto( diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/api/getDetail/MallDtoInfo.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/product/detail/MallDtoInfo.kt similarity index 80% rename from teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/api/getDetail/MallDtoInfo.kt rename to teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/product/detail/MallDtoInfo.kt index 3a80d4f..b18fc0d 100644 --- a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/api/getDetail/MallDtoInfo.kt +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/product/detail/MallDtoInfo.kt @@ -1,4 +1,4 @@ -package com.example.toy_proejct.api.getDetail +package com.example.toy_proejct.data.product.detail @kotlinx.serialization.Serializable data class MallDtoInfo( val delivery: Int, diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/api/getSearchList/GetSearchList.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/product/list/GetSearchList.kt similarity index 73% rename from teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/api/getSearchList/GetSearchList.kt rename to teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/product/list/GetSearchList.kt index 53f82e1..e923de8 100644 --- a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/api/getSearchList/GetSearchList.kt +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/product/list/GetSearchList.kt @@ -1,4 +1,4 @@ -package com.example.toy_proejct.api.getSearchList +package com.example.toy_proejct.data.product.list @kotlinx.serialization.Serializable data class GetSearchList( diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/api/getSearchList/ProductListDto.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/product/list/ProductListDto.kt similarity index 76% rename from teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/api/getSearchList/ProductListDto.kt rename to teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/product/list/ProductListDto.kt index 283138c..18c3f0c 100644 --- a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/api/getSearchList/ProductListDto.kt +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/data/product/list/ProductListDto.kt @@ -1,4 +1,4 @@ -package com.example.toy_proejct.api.getSearchList +package com.example.toy_proejct.data.product.list @kotlinx.serialization.Serializable data class ProductListDto( diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/di/DataModule.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/di/DataModule.kt new file mode 100644 index 0000000..c5d8570 --- /dev/null +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/di/DataModule.kt @@ -0,0 +1,40 @@ +package com.example.toy_proejct.di + +import com.example.toy_proejct.domain.product.ProductRepository +import com.example.toy_proejct.domain.product.ProductRepositoryImpl +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.features.json.* +import io.ktor.client.features.json.serializer.* + +object DataModule { + private val client: HttpClient = HttpClient(CIO) { + install(JsonFeature) { + serializer = KotlinxSerializer( + kotlinx.serialization.json.Json { + prettyPrint = true + isLenient = true + ignoreUnknownKeys = true + } + ) + } + } + + val productRepository: ProductRepository = ProductRepositoryImpl(client) + + /** [의존성 주입(DI)과 이유] + * 1. client는 모든 api에서 사용된다. 따라서 매 호출마다 각각 부르는 것보다 하나의 모듈로 관리해야 한다. 보일러플레이트 문제의 방지 + * + * 2. 인터페이스를 통한 도메인 구현 + * 23번째 줄을 보면 인터페이스로 선언한 ProductRepository을 통해 실제 필요한 객체를 불러오고있다. + * + * 3. 각 인터페이스를 상속받아 대해 실제 데이터을 가져오는 도메인을 각 DB , 특성에 따라 구현 + * + * 4. 각 ViewModel에서는 위의 도메인객체를 통해 값을 가져온다. + * + * -> 왜? 만약 DB를 바꾸게된다면 각각의 종속적인 DB에 대해 접근하는 메소드를 따로 다 만들어야한다. + * -> 하지만 23번째 줄에서 이미 구현한 도메인에 대해 사용하려는 도메인만 바꿔주면 전체 코드를 뜯어고치지 않고 바로 변경이 가능하다. + * + * + * **/ +} \ No newline at end of file diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/product/ProductRepository.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/product/ProductRepository.kt new file mode 100644 index 0000000..b3272ce --- /dev/null +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/product/ProductRepository.kt @@ -0,0 +1,9 @@ +package com.example.toy_proejct.domain.product + +import com.example.toy_proejct.data.product.detail.DetailDto +import com.example.toy_proejct.data.product.list.GetSearchList + +interface ProductRepository { + suspend fun fetchProductList(keyword: String): GetSearchList + suspend fun fetchProductDetail(url: String): DetailDto +} \ No newline at end of file diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/product/ProductRepositoryImpl.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/product/ProductRepositoryImpl.kt new file mode 100644 index 0000000..e9fdab1 --- /dev/null +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/product/ProductRepositoryImpl.kt @@ -0,0 +1,14 @@ +package com.example.toy_proejct.domain.product + +import com.example.toy_proejct.data.product.detail.DetailDto +import com.example.toy_proejct.data.product.list.GetSearchList +import io.ktor.client.* +import io.ktor.client.request.* + +class ProductRepositoryImpl(private val client: HttpClient): ProductRepository { + override suspend fun fetchProductList(keyword: String): GetSearchList = + client.get("http://3.39.75.19:8080/api/v1/crawler/search/products?word=$keyword") + + override suspend fun fetchProductDetail(url: String): DetailDto = + client.get("http://3.39.75.19:8080/api/v1/crawler/search/product?url=${url}") +} \ No newline at end of file diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/product/ProductRepositoryRetrofit.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/product/ProductRepositoryRetrofit.kt new file mode 100644 index 0000000..5c7f27c --- /dev/null +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/product/ProductRepositoryRetrofit.kt @@ -0,0 +1,15 @@ +package com.example.toy_proejct.domain.product + +import com.example.toy_proejct.data.product.detail.DetailDto +import com.example.toy_proejct.data.product.list.GetSearchList +import io.ktor.client.* + +class ProductRepositoryRetrofit(private val client: HttpClient): ProductRepository { + override suspend fun fetchProductList(keyword: String): GetSearchList { + TODO("Not yet implemented") + } + + override suspend fun fetchProductDetail(url: String): DetailDto { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/user/UserRepository.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/user/UserRepository.kt new file mode 100644 index 0000000..a24e15b --- /dev/null +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/user/UserRepository.kt @@ -0,0 +1,7 @@ +package com.example.toy_proejct.domain.user + +import com.example.toy_proejct.data.User + +interface UserRepository { + suspend fun getUserList(): List +} \ No newline at end of file diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/user/UserRepositoryImpl.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/user/UserRepositoryImpl.kt new file mode 100644 index 0000000..2d8ef74 --- /dev/null +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/domain/user/UserRepositoryImpl.kt @@ -0,0 +1,9 @@ +package com.example.toy_proejct.domain.user + +import com.example.toy_proejct.data.User + +class UserRepositoryImpl: UserRepository { + override suspend fun getUserList(): List { + return emptyList() + } +} \ No newline at end of file diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/detail/DetailScreen.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/detail/DetailScreen.kt index 70cdbd3..32185e0 100644 --- a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/detail/DetailScreen.kt +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/detail/DetailScreen.kt @@ -12,34 +12,23 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.CornerSize -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment -import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.core.content.ContextCompat.startActivity import coil.compose.AsyncImage -import com.example.toy_proejct.api.getDetail.DetailDto -import com.example.toy_proejct.api.getDetail.MallDtoInfo -import com.example.toy_proejct.api.getSearchList.ProductListDto -import com.example.toy_proejct.scenarios.detail.DetailActivity -import com.example.toy_proejct.scenarios.home.ItemRow +import com.example.toy_proejct.data.product.detail.DetailDto +import com.example.toy_proejct.data.product.detail.MallDtoInfo import com.example.toy_proejct.ui.component.CommonComponent +import com.example.toy_proejct.utils.UnitHelper import kotlinx.coroutines.launch +import java.util.* @Composable fun DetailScreen(viewModel: DetailViewModel, url: String, back:() -> Unit) { @@ -130,7 +119,7 @@ fun MallList(item: MallDtoInfo) { //각 상품에 대한 설명 style = MaterialTheme.typography.h6 ) Text( - text = " ${item.price}원", + text = UnitHelper.getStringFromMoneyInteger(item.price), style = MaterialTheme.typography.h6, color = Color.Blue diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/detail/DetailViewModel.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/detail/DetailViewModel.kt index 72b5516..7a3b6f4 100644 --- a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/detail/DetailViewModel.kt +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/detail/DetailViewModel.kt @@ -4,56 +4,34 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel -import com.example.toy_proejct.LogHelper -import com.example.toy_proejct.api.getDetail.DetailDto -import com.example.toy_proejct.api.getDetail.MallDtoInfo -import com.example.toy_proejct.api.getSearchList.GetSearchList -import com.example.toy_proejct.api.getSearchList.ProductListDto -import io.ktor.client.* -import io.ktor.client.engine.cio.* -import io.ktor.client.features.json.* -import io.ktor.client.features.json.serializer.* -import io.ktor.client.request.* +import com.example.toy_proejct.di.DataModule +import com.example.toy_proejct.domain.product.ProductRepository +import com.example.toy_proejct.data.product.detail.DetailDto import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import kotlinx.serialization.json.Json -class DetailViewModel : ViewModel() { +class DetailViewModel(private val productRepository: ProductRepository = DataModule.productRepository) : ViewModel() { + private val _detailInfo: MutableState = mutableStateOf( + DetailDto( + "", + listOf(), 0, "", "" + ) + ) + val detailInfo: MutableState = _detailInfo; //화면에 표현될 list - private val _detailInfo : MutableState = mutableStateOf(DetailDto("", - listOf(),0,"","")) - val detailInfo: MutableState =_detailInfo; //화면에 표현될 list + private val _isLoading: MutableState = mutableStateOf(value = false) + val isLoading: State = _isLoading - private val _isLoading: MutableState = mutableStateOf(value = false) - val isLoading : State = _isLoading - - private val client: HttpClient = HttpClient(CIO) { - install(JsonFeature) { - serializer = KotlinxSerializer( - kotlinx.serialization.json.Json { - prettyPrint = true - isLenient = true - ignoreUnknownKeys = true - } - ) - } - } - - - suspend fun getDetailInfo(url:String) { - _isLoading.value = true + suspend fun getDetailInfo(url: String) { withContext(Dispatchers.IO) { + _isLoading.value = true kotlin.runCatching { - client.get("http://3.39.75.19:8080/api/v1/crawler/search/product?url=${url}") + productRepository.fetchProductDetail(url) }.onSuccess { _detailInfo.value = it //성공시 데이터 갱신 _isLoading.value = false - LogHelper.print("success111: ${it}") - }.onFailure { - LogHelper.print("faaaailed: $it") } } } - } \ No newline at end of file diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/home/HomeScreen.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/home/HomeScreen.kt index 7df3892..5d08c52 100644 --- a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/home/HomeScreen.kt +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/home/HomeScreen.kt @@ -1,10 +1,14 @@ package com.example.toy_proejct.scenarios.home import android.content.Intent +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions @@ -12,32 +16,41 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.* +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import coil.compose.AsyncImage -import com.example.toy_proejct.api.getSearchList.ProductListDto +import com.example.toy_proejct.LogHelper +import com.example.toy_proejct.data.product.list.ProductListDto import com.example.toy_proejct.scenarios.detail.DetailActivity import com.example.toy_proejct.ui.component.CommonComponent +import com.example.toy_proejct.utils.UnitHelper import kotlinx.coroutines.launch - +import kotlinx.serialization.json.JsonNull.content @Composable fun HomeScreen(viewModel: HomeViewModel) { - Search(viewModel) { + + val scrollState = rememberLazyListState() + val scrollUpState = viewModel.scrollUp.observeAsState() + viewModel.updateScrollPosition(scrollState.firstVisibleItemIndex) + + Search(viewModel,scrollUpState) { Column(modifier = Modifier.fillMaxSize()) { - ItemContent(modifier = Modifier.weight(1f), itemList = viewModel.itemList.value) + ItemContent(modifier = Modifier.weight(1f), itemList = viewModel.itemList.value, scrollState=scrollState) CommonComponent.ButtomNavbar() } } @@ -45,11 +58,13 @@ fun HomeScreen(viewModel: HomeViewModel) { @Composable -fun ItemContent(modifier: Modifier = Modifier, itemList: List){ - Column(modifier = modifier - .padding(12.dp)) { - LazyColumn{ - items(items = itemList){ +fun ItemContent(modifier: Modifier = Modifier, itemList: List, scrollState:LazyListState) { + Column( + modifier = modifier + .padding(12.dp) + ) { + LazyColumn(state = scrollState) { + items(items = itemList) { ItemRow(item = it) } } @@ -64,7 +79,7 @@ fun ItemRow(item: ProductListDto) { //각 상품에 대한 설명 modifier = Modifier .padding(4.dp) .fillMaxWidth() - .height(120.dp) + .height(320.dp) .clickable { context.startActivity( Intent(context, DetailActivity::class.java).apply { @@ -76,55 +91,62 @@ fun ItemRow(item: ProductListDto) { //각 상품에 대한 설명 shape = RoundedCornerShape(corner = CornerSize(14.dp)), elevation = 5.dp ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.Bottom ) { - Surface( + Box( modifier = Modifier - .padding(12.dp) - .size(100.dp), - shape = RectangleShape, - elevation = 4.dp + .fillMaxWidth() + .height(230.dp) ) { - AsyncImage( - model = item.imageUrl, // - //따로 설정하기 - contentDescription = "temp" + modifier = Modifier + .fillMaxWidth(), + model = item.imageUrl, + contentDescription = "image" ) - } - Column(modifier = Modifier.padding(4.dp).fillMaxWidth()) { + Column( + modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { Text( text = item.title, - style = MaterialTheme.typography.h6 + style = MaterialTheme.typography.h5, + overflow = TextOverflow.Ellipsis, + maxLines = 1 ) Text( - text = "${item.minimumPrice} 원", - style = MaterialTheme.typography.caption + text = UnitHelper.getStringFromMoneyInteger(item.minimumPrice), + style = MaterialTheme.typography.h6 ) } + } - } -} + } +} @Composable -private fun Search(viewModel: HomeViewModel, content: @Composable () -> Unit) { +private fun Search(viewModel: HomeViewModel, scrollUpState: +State, content: @Composable () -> Unit ) { val searchWidgetState by viewModel.searchWidgetState //활성화 여부 val searchTextState by viewModel.searchTextState // 검색 변수 val isLoading by viewModel.isLoading //로딩 함수 + val coroutineScope = rememberCoroutineScope() //코루틴 생성 + - val coroutineScope = rememberCoroutineScope() //코루틴 생성 Scaffold( @@ -140,25 +162,26 @@ private fun Search(viewModel: HomeViewModel, content: @Composable () -> Unit) { }, onSearchClicked = { - coroutineScope.launch{viewModel.searchApi(it)} //코루틴에서 Ktor-api호출 + coroutineScope.launch { viewModel.searchApi(it) } //코루틴에서 Ktor-api호출 }, onSearchTriggered = { viewModel.updateSearchWidgetState(newState = true) //Search영역이 클릭되면 Search영역 활성화 viewModel.updateSearchTextState("") - } + }, + scrollUpState = scrollUpState ) } ) { - if(isLoading){ + if (isLoading) { CommonComponent.LoadingSpinner() - } - else{ + } else { content() } } } + @Composable fun SearchBar( searchWidgetState: Boolean, @@ -166,13 +189,21 @@ fun SearchBar( onTextChange: (String) -> Unit, onCloseClicked: () -> Unit, onSearchClicked: (String) -> Unit, - onSearchTriggered: () -> Unit -) { + onSearchTriggered: () -> Unit, + scrollUpState : State + + ) { + + val position by animateFloatAsState(if (scrollUpState.value == true) -150f else 0f) + val height = if(scrollUpState.value == true) 0 else 50 + when (searchWidgetState) { - false -> { + false -> { DefaultAppBar( onSearchClicked = onSearchTriggered, //영역이 비활성화라면 초기에 보여줄 컴포넌트로 보여주기 - text = searchTextState + text = searchTextState, + position = position, + height = height ) } true -> { @@ -180,59 +211,77 @@ fun SearchBar( text = searchTextState, onTextChange = onTextChange, onCloseClicked = onCloseClicked, - onSearchClicked = onSearchClicked + onSearchClicked = onSearchClicked, + position = position, + height = height ) } } } @Composable -fun DefaultAppBar(onSearchClicked: () -> Unit , text: String) { - TopAppBar( - title = { - if(text.isNotEmpty()){ - Text( - modifier = Modifier.fillMaxWidth(), - text = text, - textAlign = TextAlign.Center - ) - } - else{ - Text( - modifier = Modifier.fillMaxWidth(), - text="상품 검색", - textAlign = TextAlign.Center - ) - } - }, - actions = { - IconButton( - onClick = { onSearchClicked() } //버튼 클릭시 Search영역 활성화 - ) { - Icon( - imageVector = Icons.Filled.Search, - contentDescription = "Search Icon", - tint = Color.White - ) +fun DefaultAppBar(onSearchClicked: () -> Unit, text: String, position:Float , height: Int) { + + Surface( + modifier = Modifier + .fillMaxWidth() + .height(height.dp) + .graphicsLayer { translationY = (position) }, + elevation = AppBarDefaults.TopAppBarElevation, + color = MaterialTheme.colors.primary + ) { + TopAppBar( + title = { + if (text.isNotEmpty()) { + Text( + modifier = Modifier.fillMaxWidth(), + text = text, + textAlign = TextAlign.Center + ) + } else { + Text( + modifier = Modifier.fillMaxWidth(), + text = "상품 검색", + textAlign = TextAlign.Center + ) + } + }, + actions = { + IconButton( + onClick = { onSearchClicked() } //버튼 클릭시 Search영역 활성화 + ) { + Icon( + imageVector = Icons.Filled.Search, + contentDescription = "Search Icon", + tint = Color.White + ) + } + }, + navigationIcon = { + Spacer(modifier = Modifier.size(20.dp)) } - }, - navigationIcon = { - Spacer(modifier = Modifier.size(20.dp)) - } - ) + ) + } } + @Composable fun SearchAppBar( text: String, onTextChange: (String) -> Unit, onCloseClicked: () -> Unit, onSearchClicked: (String) -> Unit, + position:Float , + height: Int ) { + + + Surface( modifier = Modifier .fillMaxWidth() - .height(56.dp), + .height(height.dp) + .graphicsLayer { translationY = (position) }, elevation = AppBarDefaults.TopAppBarElevation, color = MaterialTheme.colors.primary ) { @@ -270,7 +319,7 @@ fun SearchAppBar( trailingIcon = { IconButton( onClick = { - onCloseClicked() //취소 버튼 누르면 closeClick() + onCloseClicked() //취소 버튼 누르면 closeClick() } ) { Icon( @@ -297,3 +346,7 @@ fun SearchAppBar( } + + + + diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/home/HomeViewModel.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/home/HomeViewModel.kt index 1486158..4e6a036 100644 --- a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/home/HomeViewModel.kt +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/home/HomeViewModel.kt @@ -1,35 +1,22 @@ package com.example.toy_proejct.scenarios.home -import android.content.ContentValues.TAG -import android.util.Log import androidx.compose.runtime.MutableState import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.example.toy_proejct.LogHelper -import com.example.toy_proejct.api.getSearchList.GetSearchList -import com.example.toy_proejct.api.getSearchList.ProductListDto -import com.example.toy_proejct.scenarios.home.data.Item -import io.ktor.client.* -import io.ktor.client.engine.cio.* -import io.ktor.client.features.json.* -import io.ktor.client.features.json.serializer.* -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* +import com.example.toy_proejct.di.DataModule +import com.example.toy_proejct.domain.product.ProductRepository +import com.example.toy_proejct.data.product.list.ProductListDto import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -class HomeViewModel : ViewModel() { - - - - - +class HomeViewModel(private val productRepository: ProductRepository = DataModule.productRepository) : ViewModel() { private val _isLoading: MutableState = mutableStateOf(value = false) val isLoading : State = _isLoading - private val _searchWidgetState: MutableState = //검색창 활성화 여부 mutableStateOf(value = false) val searchWidgetState: State = _searchWidgetState @@ -49,21 +36,17 @@ class HomeViewModel : ViewModel() { _searchTextState.value = newValue } - - - - - - private val client: HttpClient = HttpClient(CIO) { - install(JsonFeature) { - serializer = KotlinxSerializer( - kotlinx.serialization.json.Json { - prettyPrint = true - isLenient = true - ignoreUnknownKeys = true - } - ) - } + private var lastScrollIndex = 0 + private val _scrollUp = MutableLiveData(false) + val scrollUp: LiveData + get() = _scrollUp + + fun updateScrollPosition(newScrollIndex: Int) { + if (newScrollIndex == lastScrollIndex) return + LogHelper.print("newScrollIndex: $newScrollIndex") + //LogHelper.print("newScrollIndex: $scrollUp.observeAsState()") + _scrollUp.value = newScrollIndex > lastScrollIndex + lastScrollIndex = newScrollIndex } @@ -71,18 +54,16 @@ class HomeViewModel : ViewModel() { _isLoading.value = true withContext(Dispatchers.IO) { kotlin.runCatching { - client.get("http://3.39.75.19:8080/api/v1/crawler/search/products?word=$keyword") + productRepository.fetchProductList(keyword) }.onSuccess { _isLoading.value = false - _itemList.value = it.productListDtoList //성공시 데이터 갱신 - LogHelper.print("succses: ${it.productListDtoList.size}") - }.onFailure { - LogHelper.print("Failure: $it") + _itemList.value = it.productListDtoList //성공시 데이터 갱신 + LogHelper.print("succses: ${it.productListDtoList.size}") + }.onFailure { + LogHelper.print("Failure: $it") } } } - - } diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/home/data/item.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/home/data/item.kt deleted file mode 100644 index 465d385..0000000 --- a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/scenarios/home/data/item.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.example.toy_proejct.scenarios.home.data - -data class Item( - val url : String, - val title : String, - val price : Int, - val image_url : String -) - -fun getTempItems(): List { - return listOf( - Item(image_url ="data:image/png;base64,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" - ,title = "APPLE 2021 맥북프로16 MK193KH/A", - price = 2886040, - url = "https://prod.danawa.com/info/?pcode=15462674&keyword=%EB%A7%A5%EB%B6%81&cate=112758" - - ), - Item(image_url = "http://img.danawa.com/prod_img/500000/674/462/img/15462674_1.jpg?shrink=130:130&_v=20220215194029", - title = "APPLE 2021 맥북프로16 MK193KH/A", - price = 2886040, - url = "https://prod.danawa.com/info/?pcode=15462674&keyword=%EB%A7%A5%EB%B6%81&cate=112758" - - ), - Item(image_url = "http://img.danawa.com/prod_img/500000/674/462/img/15462674_1.jpg?shrink=130:130&_v=20220215194029", - title = "APPLE 2021 맥북프로16 MK193KH/A", - price = 2886040, - url = "https://prod.danawa.com/info/?pcode=15462674&keyword=%EB%A7%A5%EB%B6%81&cate=112758" - - ), - Item(image_url = "http://img.danawa.com/prod_img/500000/674/462/img/15462674_1.jpg?shrink=130:130&_v=20220215194029", - title = "APPLE 2021 맥북프로16 MK193KH/A", - price = 2886040, - url = "https://prod.danawa.com/info/?pcode=15462674&keyword=%EB%A7%A5%EB%B6%81&cate=112758" - - ), - Item(image_url = "http://img.danawa.com/prod_img/500000/674/462/img/15462674_1.jpg?shrink=130:130&_v=20220215194029", - title = "APPLE 2021 맥북프로16 MK193KH/A", - price = 2886040, - url = "https://prod.danawa.com/info/?pcode=15462674&keyword=%EB%A7%A5%EB%B6%81&cate=112758" - - ) - ) -} - diff --git a/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/utils/UnitHelper.kt b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/utils/UnitHelper.kt new file mode 100644 index 0000000..ab0ee5b --- /dev/null +++ b/teawon/project/toyproejct/app/src/main/java/com/example/toy_proejct/utils/UnitHelper.kt @@ -0,0 +1,11 @@ +package com.example.toy_proejct.utils + +import java.text.NumberFormat +import java.util.* + +object UnitHelper { + fun getStringFromMoneyInteger(money: Int): String { + val format = NumberFormat.getCurrencyInstance(Locale.KOREA) + return format.format(money).substring(1) + "원" + } +}