diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..0c0c338 --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index ae388c2..753085c 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,6 +4,18 @@ diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cae9627..ed1235e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,23 +1,20 @@ +@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed plugins { - id("com.android.application") - id("org.jetbrains.kotlin.android") + alias(libs.plugins.pohahang.android.application) + alias(libs.plugins.pohahang.android.application.compose) + alias(libs.plugins.pohahang.android.feature) + alias(libs.plugins.pohahang.android.hilt) } android { namespace = "com.koreatech.kwanhee_jo_compose_study" - compileSdk = 34 defaultConfig { applicationId = "com.koreatech.kwanhee_jo_compose_study" - minSdk = 26 targetSdk = 34 versionCode = 1 versionName = "1.0" - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables { - useSupportLibrary = true - } } buildTypes { @@ -29,57 +26,17 @@ android { ) } } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" - } - buildFeatures { - compose = true - } - composeOptions { - kotlinCompilerExtensionVersion = "1.4.3" - } - packaging { - resources { - excludes += "/META-INF/{AL2.0,LGPL2.1}" - } - } } dependencies { - - implementation("androidx.core:core-ktx:1.9.0") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") - implementation("androidx.activity:activity-compose:1.8.2") - implementation(platform("androidx.compose:compose-bom:2023.03.00")) - implementation("androidx.compose.ui:ui") - implementation("androidx.compose.ui:ui-graphics") - implementation("androidx.compose.ui:ui-tooling-preview") - implementation("androidx.compose.material3:material3") - testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.5") - androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") - - androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00")) - androidTestImplementation("androidx.compose.ui:ui-test-junit4") - debugImplementation("androidx.compose.ui:ui-tooling") - debugImplementation("androidx.compose.ui:ui-test-manifest") - - // orbit - implementation("org.orbit-mvi:orbit-core:6.1.1") - implementation("org.orbit-mvi:orbit-viewmodel:6.1.1") - implementation("org.orbit-mvi:orbit-compose:6.1.1") - testImplementation("org.orbit-mvi:orbit-test:6.1.1") - - // viewmodel - implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1") - - // Navigation Compose - implementation ("androidx.navigation:navigation-compose:2.4.2") - - // coil - implementation("io.coil-kt:coil-compose:2.6.0") -} \ No newline at end of file + implementation(project(mapOf("path" to ":core:network"))) + implementation(project(mapOf("path" to ":core:data"))) + + implementation(libs.androidx.lifecycle.runtime.compose) + implementation(libs.hilt.navigation.compose) + implementation(libs.androidx.navigation.compose) + testImplementation(libs.junit4) + testImplementation(libs.orbit.test) + androidTestImplementation(libs.androidx.test.ext) + androidTestImplementation(libs.androidx.test.espresso) +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f574e81..881f0c7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ -// NavHost( -// navController = navController, -// startDestination = Screen.Login.route, -// modifier = Modifier.padding(paddingValues) -// ) { -// composable(route = Screen.Login.route) { entry -> -// val loginViewModel: LoginViewModel = viewModel() -// val loginState by loginViewModel.collectAsState() -// -// entry.savedStateHandle.apply { -// loginViewModel.updateLoginSavedStateHandle( -// id = get(ID) ?: "", -// password = get(PASSWORD) ?: "", -// nickname = get(NICKNAME) ?: "" -// ) -// } -// -// loginViewModel.collectSideEffect { -// when (it) { -// is LoginSideEffect.MoveToHomePage -> { -// navController.navigate(route = Screen.BottomNavItem.Home.route) { -// popUpTo(navController.graph.id) { -// inclusive = true -// } -// } -// navController.currentBackStackEntry?.savedStateHandle?.apply { -// set(ID, loginViewModel.id) -// set(PASSWORD, loginViewModel.password) -// set(NICKNAME, loginViewModel.nickname) -// } -// } -// -// is LoginSideEffect.MoveToSignUpPage -> { -// navController.navigate(route = Screen.Signup.route) -// } -// } -// } -// -// LoginPage( -// onClickLoginButton = { id, password -> -// loginViewModel.login(id, password) -// } -// ) { -// loginViewModel.signUp() -// } -// } -// composable(route = Screen.Signup.route) { -// val signUpViewModel: SignupViewModel = viewModel() -// val signUpState by signUpViewModel.collectAsState() -// -// signUpViewModel.collectSideEffect { -// when (it) { -// is SignupSideEffect.MoveToLoginPage -> { -// navController.previousBackStackEntry?.savedStateHandle?.apply { -// set(ID, it.id) -// set(PASSWORD, it.password) -// set(NICKNAME, it.nickname) -// } -// navController.popBackStack() -// } -// } -// } -// -// SignupPage { id, password, nickname -> -// signUpViewModel.signup(id, password, nickname) -// } -// } -// composable( -// route = Screen.BottomNavItem.Home.route -// ) { -// val homeViewModel: HomeViewModel = composableActivityViewModel() -// val homeState by homeViewModel.collectAsState() -// -// it.savedStateHandle.apply { -// if (get(ID)?.isNotEmpty() == true) { -// homeViewModel.updateHomeData( -// id = get(ID) ?: "", -// password = get(PASSWORD) ?: "", -// nickname = get(NICKNAME) ?: "" -// ) -// } -// } -// -// homeViewModel.collectSideEffect { -// when (it) { } -// } -// -// HomePage( -// context = context, -// id = homeViewModel.id, -// password = homeViewModel.password, -// nickname = homeViewModel.nickname, -// modifier = Modifier -// .padding(16.dp) -// .fillMaxSize() -// ) -// } -// composable(Screen.BottomNavItem.Search.route) { -// val homeViewModel: HomeViewModel = composableActivityViewModel() -// SearchPage() -// } -// composable(Screen.BottomNavItem.Setting.route) { -// SettingPage() -// } -// } -// -// } } } } diff --git a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/PohahangApplication.kt b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/PohahangApplication.kt new file mode 100644 index 0000000..8464937 --- /dev/null +++ b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/PohahangApplication.kt @@ -0,0 +1,11 @@ +package com.koreatech.kwanhee_jo_compose_study + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class PohahangApplication: Application() { + override fun onCreate() { + super.onCreate() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/composable/AccountComposable.kt b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/composable/AccountComposable.kt index b5a391d..c198d4a 100644 --- a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/composable/AccountComposable.kt +++ b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/composable/AccountComposable.kt @@ -1,16 +1,16 @@ package com.koreatech.kwanhee_jo_compose_study.composable -import android.util.Log import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable import com.koreatech.kwanhee_jo_compose_study.Constants -import com.koreatech.kwanhee_jo_compose_study.Screen +import com.koreatech.kwanhee_jo_compose_study.data.LoginData +import com.koreatech.kwanhee_jo_compose_study.ui.Screen import com.koreatech.kwanhee_jo_compose_study.utils.clearStackNavigate -import com.koreatech.kwanhee_jo_compose_study.utils.setArgumentCurrentStack import com.koreatech.kwanhee_jo_compose_study.utils.showToast import com.koreatech.kwanhee_jo_compose_study.view.login.LoginPage import com.koreatech.kwanhee_jo_compose_study.view.login.LoginSideEffect @@ -27,18 +27,15 @@ fun NavGraphBuilder.accountComposable(navController: NavHostController) { ) { val focusManager = LocalFocusManager.current val context = LocalContext.current - val loginViewModel: LoginViewModel = viewModel() + val loginViewModel: LoginViewModel = hiltViewModel() // val loginState by loginViewModel.collectAsState() loginViewModel.collectSideEffect { when (it) { is LoginSideEffect.MoveToHomePage -> { navController.clearStackNavigate(Screen.BottomNavItem.Home.route) - navController.setArgumentCurrentStack( - mapOf(Constants.ID to it.id), - mapOf(Constants.PASSWORD to it.password), - mapOf(Constants.NICKNAME to it.nickname), - ) + val loginData = LoginData(it.id, it.password, it.nickname) + navController.currentBackStackEntry?.savedStateHandle?.set("loginData", loginData) } is LoginSideEffect.MoveToSignUpPage -> navController.navigate(route = Screen.Signup.route) @@ -68,7 +65,6 @@ fun NavGraphBuilder.accountComposable(navController: NavHostController) { val context = LocalContext.current val focusManager = LocalFocusManager.current val signupViewModel: SignupViewModel = viewModel() - // val signupState by signupViewModel.collectAsState() signupViewModel.collectSideEffect { when (it) { diff --git a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/composable/HomeComposable.kt b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/composable/HomeComposable.kt index 48f7668..a7689c0 100644 --- a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/composable/HomeComposable.kt +++ b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/composable/HomeComposable.kt @@ -1,15 +1,20 @@ package com.koreatech.kwanhee_jo_compose_study.composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalContext +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable -import com.koreatech.kwanhee_jo_compose_study.Constants -import com.koreatech.kwanhee_jo_compose_study.Screen +import com.koreatech.kwanhee_jo_compose_study.data.LoginData +import com.koreatech.kwanhee_jo_compose_study.ui.Screen import com.koreatech.kwanhee_jo_compose_study.view.composableActivityViewModel import com.koreatech.kwanhee_jo_compose_study.view.home.HomePage import com.koreatech.kwanhee_jo_compose_study.view.home.HomeViewModel import com.koreatech.kwanhee_jo_compose_study.view.navi.serach.SearchPage +import com.koreatech.kwanhee_jo_compose_study.view.navi.serach.SearchViewModel import com.koreatech.kwanhee_jo_compose_study.view.navi.setting.SettingPage fun NavGraphBuilder.homeComposable(navController: NavHostController) { @@ -18,37 +23,26 @@ fun NavGraphBuilder.homeComposable(navController: NavHostController) { ) { val context = LocalContext.current val homeViewModel: HomeViewModel = composableActivityViewModel() - // val homeState by homeViewModel.collectAsState() - it.savedStateHandle.apply { - val id = get(Constants.ID) - val password = get(Constants.PASSWORD) - val nickname = get(Constants.NICKNAME) - if (id != null || password != null || nickname != null) { - homeViewModel.updateUserData( - id ?: "", password ?: "", nickname ?: "" - ) - HomePage( - context = context, - id = id ?: "", - password = password ?: "", - nickname = nickname ?: "" - ) - } else { - HomePage( - context = context, - id = homeViewModel.id, - password = homeViewModel.password, - nickname = homeViewModel.nickname - ) - } + val state by homeViewModel.loginData.collectAsState() + val loginData = it.savedStateHandle.get("loginData") + if (loginData != null) { + homeViewModel.updateUserData(loginData) } + HomePage( + context = context, + id = state.id, + password = state.password, + nickname = state.nickname + ) } composable(route = Screen.BottomNavItem.Search.route) { - SearchPage() - } - composable(route = Screen.BottomNavItem.Setting.route) { - SettingPage() + val context = LocalContext.current + val searchViewModel: SearchViewModel = hiltViewModel() + val cats by searchViewModel.cats.collectAsStateWithLifecycle() + + SearchPage(context, cats) } + composable(route = Screen.BottomNavItem.Setting.route) { SettingPage() } } diff --git a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/data/LoginData.kt b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/data/LoginData.kt new file mode 100644 index 0000000..3c50301 --- /dev/null +++ b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/data/LoginData.kt @@ -0,0 +1,12 @@ +package com.koreatech.kwanhee_jo_compose_study.data + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class LoginData( + val id: String = "", + val password: String ="", + val nickname: String ="" +): Parcelable + diff --git a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/Screen.kt b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/ui/Screen.kt similarity index 92% rename from app/src/main/java/com/koreatech/kwanhee_jo_compose_study/Screen.kt rename to app/src/main/java/com/koreatech/kwanhee_jo_compose_study/ui/Screen.kt index 6f06199..65044d8 100644 --- a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/Screen.kt +++ b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/ui/Screen.kt @@ -1,8 +1,9 @@ -package com.koreatech.kwanhee_jo_compose_study +package com.koreatech.kwanhee_jo_compose_study.ui import androidx.navigation.NamedNavArgument import androidx.navigation.NavType import androidx.navigation.navArgument +import com.koreatech.kwanhee_jo_compose_study.R sealed class Screen( val route: String, diff --git a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/SoptApp.kt b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/ui/SoptApp.kt similarity index 85% rename from app/src/main/java/com/koreatech/kwanhee_jo_compose_study/SoptApp.kt rename to app/src/main/java/com/koreatech/kwanhee_jo_compose_study/ui/SoptApp.kt index ecfd27b..6017ef4 100644 --- a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/SoptApp.kt +++ b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/ui/SoptApp.kt @@ -1,4 +1,4 @@ -package com.koreatech.kwanhee_jo_compose_study +package com.koreatech.kwanhee_jo_compose_study.ui import androidx.compose.runtime.Composable import androidx.navigation.compose.rememberNavController diff --git a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/SoptNavHost.kt b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/SoptNavHost.kt index 9bf7e83..19e2f0a 100644 --- a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/SoptNavHost.kt +++ b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/SoptNavHost.kt @@ -13,7 +13,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.currentBackStackEntryAsState -import com.koreatech.kwanhee_jo_compose_study.Screen +import com.koreatech.kwanhee_jo_compose_study.ui.Screen import com.koreatech.kwanhee_jo_compose_study.composable.accountComposable import com.koreatech.kwanhee_jo_compose_study.composable.homeComposable import com.koreatech.kwanhee_jo_compose_study.view.navi.BottomNavigationBar diff --git a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/home/HomeState.kt b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/home/HomeState.kt index 998c2ca..c81e6af 100644 --- a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/home/HomeState.kt +++ b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/home/HomeState.kt @@ -1,7 +1,10 @@ package com.koreatech.kwanhee_jo_compose_study.view.home +import com.koreatech.data.model.response.CatResponseDto import com.koreatech.kwanhee_jo_compose_study.common.UiStatus + data class HomeState( - val status: UiStatus = UiStatus.Loading + val status: UiStatus = UiStatus.Loading, + val cats: List = emptyList() ) \ No newline at end of file diff --git a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/home/HomeViewModel.kt b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/home/HomeViewModel.kt index c2804d1..f7f17a6 100644 --- a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/home/HomeViewModel.kt +++ b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/home/HomeViewModel.kt @@ -3,27 +3,38 @@ package com.koreatech.kwanhee_jo_compose_study.view.home import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel -import com.koreatech.kwanhee_jo_compose_study.Constants.ID -import com.koreatech.kwanhee_jo_compose_study.Constants.NICKNAME -import com.koreatech.kwanhee_jo_compose_study.Constants.PASSWORD +import androidx.lifecycle.viewModelScope +import com.koreatech.core.network.repository.PohahangRepository +import com.koreatech.data.model.response.CatResponseDto +import com.koreatech.kwanhee_jo_compose_study.data.LoginData +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch import org.orbitmvi.orbit.Container import org.orbitmvi.orbit.ContainerHost import org.orbitmvi.orbit.syntax.simple.intent +import org.orbitmvi.orbit.syntax.simple.reduce import org.orbitmvi.orbit.viewmodel.container +import javax.inject.Inject -class HomeViewModel ( - savedStateHandle: SavedStateHandle -): ContainerHost, ViewModel() { - override val container: Container - = container(HomeState()) +@HiltViewModel +class HomeViewModel @Inject constructor( + private val pohahangRepository: PohahangRepository, +) : ContainerHost, ViewModel() { + override val container: Container = container(HomeState()) - var id : String = "" - var password: String = "" - var nickname: String = "" - fun updateUserData(id: String, password: String, nickname: String) { - this.id = id - this.password = password - this.nickname = nickname + + + val loginData = MutableStateFlow(LoginData()) + + fun updateUserData(loginData: LoginData) { + this.loginData.value = loginData } } \ No newline at end of file diff --git a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/login/LoginViewModel.kt b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/login/LoginViewModel.kt index 4c3b106..07bc3ad 100644 --- a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/login/LoginViewModel.kt +++ b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/login/LoginViewModel.kt @@ -1,31 +1,59 @@ package com.koreatech.kwanhee_jo_compose_study.view.login -import android.util.Log -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel -import com.koreatech.kwanhee_jo_compose_study.Constants.ID -import com.koreatech.kwanhee_jo_compose_study.Constants.NICKNAME -import com.koreatech.kwanhee_jo_compose_study.Constants.PASSWORD +import androidx.lifecycle.viewModelScope +import com.koreatech.core.network.repository.PohahangRepository +import com.koreatech.data.model.request.LoginRequestDto +import com.koreatech.data.model.response.CatResponseDto import com.koreatech.kwanhee_jo_compose_study.common.UiStatus +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch import org.orbitmvi.orbit.Container import org.orbitmvi.orbit.ContainerHost import org.orbitmvi.orbit.syntax.simple.intent import org.orbitmvi.orbit.syntax.simple.postSideEffect import org.orbitmvi.orbit.syntax.simple.reduce import org.orbitmvi.orbit.viewmodel.container +import javax.inject.Inject -class LoginViewModel() : ContainerHost, ViewModel() { +@HiltViewModel +class LoginViewModel @Inject constructor( +) : ContainerHost, ViewModel() { override val container: Container = container(LoginState()) - fun login(inputId: String, receiveId: String, inputPassword: String, receivePassword: String, receiveNickname: String) { + + + fun login( + inputId: String, + receiveId: String, + inputPassword: String, + receivePassword: String, + receiveNickname: String, + ) { intent { when { - (inputId.isEmpty() && inputPassword.isEmpty()) -> postSideEffect(LoginSideEffect.ShowToast("로그인 실패")) + (inputId.isEmpty() && inputPassword.isEmpty()) -> postSideEffect( + LoginSideEffect.ShowToast( + "로그인 실패" + ) + ) + inputId != receiveId -> postSideEffect(LoginSideEffect.ShowToast("아이디 실패")) inputPassword != receivePassword -> postSideEffect(LoginSideEffect.ShowToast("비밀번호 실패")) else -> { reduce { state.copy(status = UiStatus.Success) } - postSideEffect(LoginSideEffect.MoveToHomePage(inputId, inputPassword, receiveNickname)) + postSideEffect( + LoginSideEffect.MoveToHomePage( + inputId, + inputPassword, + receiveNickname + ) + ) } } } diff --git a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/navi/BottomNavigationBar.kt b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/navi/BottomNavigationBar.kt index 9b439ce..6bc9ae7 100644 --- a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/navi/BottomNavigationBar.kt +++ b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/navi/BottomNavigationBar.kt @@ -7,7 +7,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.res.painterResource import androidx.navigation.NavHostController -import com.koreatech.kwanhee_jo_compose_study.Screen +import com.koreatech.kwanhee_jo_compose_study.ui.Screen import com.koreatech.kwanhee_jo_compose_study.view.currentRoute @Composable diff --git a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/navi/serach/SearchPage.kt b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/navi/serach/SearchPage.kt index 5e1cb8f..7be7863 100644 --- a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/navi/serach/SearchPage.kt +++ b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/navi/serach/SearchPage.kt @@ -1,10 +1,71 @@ package com.koreatech.kwanhee_jo_compose_study.view.navi.serach +import android.content.Context +import androidx.compose.foundation.layout.Arrangement +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.height +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.koreatech.data.model.response.CatResponseDto @Composable -fun SearchPage(modifier: Modifier = Modifier) { - Text(text = "Search") +fun SearchPage( + context: Context, + cats: List, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier.fillMaxSize() + ) { + Text( + text = "고양이 보실?", + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(10.dp)) + LazyVerticalStaggeredGrid( + columns = StaggeredGridCells.Adaptive(100.dp), + verticalItemSpacing = 4.dp, + horizontalArrangement = Arrangement.spacedBy(4.dp), + content = { + items(cats) { + AsyncImage( + model = ImageRequest.Builder(context) + .data(it.url) + .crossfade(true) + .build(), + contentScale = ContentScale.Crop, + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + ) + } + }, + modifier = Modifier.fillMaxSize() + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/navi/serach/SearchViewModel.kt b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/navi/serach/SearchViewModel.kt new file mode 100644 index 0000000..fba02bd --- /dev/null +++ b/app/src/main/java/com/koreatech/kwanhee_jo_compose_study/view/navi/serach/SearchViewModel.kt @@ -0,0 +1,30 @@ +package com.koreatech.kwanhee_jo_compose_study.view.navi.serach + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.koreatech.core.network.repository.PohahangRepository +import com.koreatech.data.model.response.CatResponseDto +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SearchViewModel @Inject constructor( + private val pohahangRepository: PohahangRepository, +) : ViewModel() { + val _cats: MutableStateFlow> = MutableStateFlow(emptyList()) + val cats = _cats.asStateFlow() + + init { + viewModelScope.launch { + try { + _cats.emit(pohahangRepository.getCats()) + } catch (e: CancellationException) { + + } + } + } +} \ No newline at end of file diff --git a/build-logic/convention/.gitignore b/build-logic/convention/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/build-logic/convention/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts new file mode 100644 index 0000000..de53fc9 --- /dev/null +++ b/build-logic/convention/build.gradle.kts @@ -0,0 +1,39 @@ +@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed +plugins { + `kotlin-dsl` +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +dependencies { + compileOnly(libs.android.gradlePlugin) + compileOnly(libs.kotlin.gradlePlugin) +} + +gradlePlugin { + plugins { + register("AndroidApplicationConventionPlugin") { + id = "pohahang.android.application" + implementationClass = "AndroidApplicationConventionPlugin" + } + register("AndroidApplicationComposeConventionPlugin") { + id = "pohahang.android.application.compose" + implementationClass = "AndroidApplicationComposeConventionPlugin" + } + register("AndroidFeatureConventionPlugin") { + id = "pohahang.android.plugin.feature" + implementationClass = "AndroidFeatureConventionPlugin" + } + register("AndroidHiltConventionPlugin") { + id = "pohahang.android.plugin.hilt" + implementationClass = "AndroidHiltConventionPlugin" + } + register("AndroidLibraryConventionPlugin") { + id = "pohahang.android.library" + implementationClass = "AndroidLibraryConventionPlugin" + } + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/java/AndroidApplicationComposeConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidApplicationComposeConventionPlugin.kt new file mode 100644 index 0000000..af42186 --- /dev/null +++ b/build-logic/convention/src/main/java/AndroidApplicationComposeConventionPlugin.kt @@ -0,0 +1,19 @@ +import com.android.build.gradle.internal.dsl.BaseAppModuleExtension +import com.koreatech.convention.configureAndroidCompose +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.getByType + +internal class AndroidApplicationComposeConventionPlugin: Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.android.application") + apply("org.jetbrains.kotlin.android") + apply("kotlin-parcelize") + } + val extension = extensions.getByType() + configureAndroidCompose(extension) + } + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/java/AndroidApplicationConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidApplicationConventionPlugin.kt new file mode 100644 index 0000000..7e68526 --- /dev/null +++ b/build-logic/convention/src/main/java/AndroidApplicationConventionPlugin.kt @@ -0,0 +1,19 @@ +import com.android.build.api.dsl.ApplicationExtension +import com.koreatech.convention.configureAndroidProject +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure + +internal class AndroidApplicationConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.android.application") + apply("org.jetbrains.kotlin.android") + } + extensions.configure { + configureAndroidProject(this) + } + } + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/java/AndroidFeatureConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidFeatureConventionPlugin.kt new file mode 100644 index 0000000..0bc5943 --- /dev/null +++ b/build-logic/convention/src/main/java/AndroidFeatureConventionPlugin.kt @@ -0,0 +1,21 @@ +import com.koreatech.convention.implementation +import com.koreatech.convention.libs +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies + +internal class AndroidFeatureConventionPlugin: Plugin { + override fun apply(target: Project) { + with(target) { + dependencies { + implementation(libs.findLibrary("androidx.core.ktx").get()) + implementation(libs.findLibrary("androidx.lifecycle.runtime.ktx").get()) + implementation(libs.findLibrary("androidx.activity.compose").get()) + implementation(libs.findLibrary("androidx.lifecycle.viewmodel.compose").get()) + implementation(libs.findLibrary("androidx.navigation.compose").get()) + implementation(libs.findLibrary("coil.compose").get()) + implementation(libs.findBundle("orbit").get()) + } + } + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/java/AndroidHiltConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidHiltConventionPlugin.kt new file mode 100644 index 0000000..8a3d9a5 --- /dev/null +++ b/build-logic/convention/src/main/java/AndroidHiltConventionPlugin.kt @@ -0,0 +1,21 @@ +import com.koreatech.convention.implementation +import com.koreatech.convention.kapt +import com.koreatech.convention.libs +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies + +internal class AndroidHiltConventionPlugin: Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("dagger.hilt.android.plugin") + apply("kotlin-kapt") + } + dependencies { + implementation(libs.findLibrary("hilt.android").get()) + kapt(libs.findLibrary("hilt.compiler").get()) + } + } + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/java/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidLibraryConventionPlugin.kt new file mode 100644 index 0000000..7ffe3ee --- /dev/null +++ b/build-logic/convention/src/main/java/AndroidLibraryConventionPlugin.kt @@ -0,0 +1,20 @@ +import com.android.build.api.dsl.LibraryExtension +import com.koreatech.convention.configureAndroidProject +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure + +class AndroidLibraryConventionPlugin: Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.android.library") + apply("org.jetbrains.kotlin.android") + } + extensions.configure { + configureAndroidProject(this) + defaultConfig.targetSdk = 34 + } + } + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/java/com/koreatech/convention/AndroidCompose.kt b/build-logic/convention/src/main/java/com/koreatech/convention/AndroidCompose.kt new file mode 100644 index 0000000..130379b --- /dev/null +++ b/build-logic/convention/src/main/java/com/koreatech/convention/AndroidCompose.kt @@ -0,0 +1,36 @@ +package com.koreatech.convention + +import com.android.build.api.dsl.CommonExtension +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.getByType + +internal fun Project.configureAndroidCompose( + commonExtension: CommonExtension<*, *, *, *, *>, +) { + val libs = extensions.getByType().named("libs") + + commonExtension.apply { + composeOptions { + kotlinCompilerExtensionVersion = + libs.findVersion("composeCompilerVersion").get().requiredVersion + } + buildFeatures { + compose = true + } + + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + } + dependencies { + api(platform(libs.findLibrary("androidx.compose.bom").get())) + implementation(libs.findBundle("compose").get()) + debugImplementation(libs.findLibrary("androidx.compose.ui.test.manifest").get()) + androidTestImplementation(libs.findLibrary("androidx.compose.ui.test").get()) + } +} + diff --git a/build-logic/convention/src/main/java/com/koreatech/convention/DependencyHandlerExtensions.kt b/build-logic/convention/src/main/java/com/koreatech/convention/DependencyHandlerExtensions.kt new file mode 100644 index 0000000..5cbbbc6 --- /dev/null +++ b/build-logic/convention/src/main/java/com/koreatech/convention/DependencyHandlerExtensions.kt @@ -0,0 +1,26 @@ +package com.koreatech.convention + +import org.gradle.api.artifacts.dsl.DependencyHandler + +fun DependencyHandler.kapt(dependency: Any) { + add("kapt", dependency) +} +fun DependencyHandler.implementation(dependency: Any) { + add("implementation", dependency) +} + +fun DependencyHandler.debugImplementation(dependency: Any) { + add("debugImplementation", dependency) +} + +fun DependencyHandler.api(dependency: Any) { + add("api", dependency) +} + +fun DependencyHandler.androidTestImplementation(dependency: Any) { + add("androidTestImplementation", dependency) +} + +fun DependencyHandler.testImplementation(dependency: Any) { + add("testImplementation", dependency) +} \ No newline at end of file diff --git a/build-logic/convention/src/main/java/com/koreatech/convention/KotlinAndroid.kt b/build-logic/convention/src/main/java/com/koreatech/convention/KotlinAndroid.kt new file mode 100644 index 0000000..b604d9f --- /dev/null +++ b/build-logic/convention/src/main/java/com/koreatech/convention/KotlinAndroid.kt @@ -0,0 +1,59 @@ +package com.koreatech.convention + +import com.android.build.api.dsl.CommonExtension +import org.gradle.api.JavaVersion +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.provideDelegate +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +internal fun Project.configureAndroidProject( + commonExtension: CommonExtension<*, *, *, *, *>, +) { + commonExtension.apply { + + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary = true + } + buildFeatures { + buildConfig = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + } + configureKotlinJvm() +} + +internal fun Project.configureKotlinJvm() { + extensions.configure { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + configureKotlin() +} + +private fun Project.configureKotlin() { + tasks.withType().configureEach { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + // Treat all Kotlin warnings as errors (disabled by default) + // Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties + val warningsAsErrors: String? by project + allWarningsAsErrors = warningsAsErrors.toBoolean() + freeCompilerArgs = freeCompilerArgs + listOf( + // Enable experimental coroutines APIs, including Flow + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + ) + } + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/java/com/koreatech/convention/ProjectExtensions.kt b/build-logic/convention/src/main/java/com/koreatech/convention/ProjectExtensions.kt new file mode 100644 index 0000000..3d359b7 --- /dev/null +++ b/build-logic/convention/src/main/java/com/koreatech/convention/ProjectExtensions.kt @@ -0,0 +1,9 @@ +package com.koreatech.convention + +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalog +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.getByType + +val Project.libs + get(): VersionCatalog = extensions.getByType().named("libs") \ No newline at end of file diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 0000000..6225785 --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,14 @@ +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} + +rootProject.name = "build-logic" +include(":convention") \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 5873551..ddd5032 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,15 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + mavenCentral() + } +} plugins { - id("com.android.application") version "8.1.4" apply false - id("org.jetbrains.kotlin.android") version "1.8.10" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.anroid) apply false + alias(libs.plugins.hilt) apply false + alias(libs.plugins.org.jetbrains.kotlin.jvm) apply false + alias(libs.plugins.com.android.library) apply false + alias(libs.plugins.org.jetbrains.kotlin.serialization) apply false } \ No newline at end of file diff --git a/core/data/.gitignore b/core/data/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/data/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts new file mode 100644 index 0000000..5321258 --- /dev/null +++ b/core/data/build.gradle.kts @@ -0,0 +1,26 @@ +@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed +plugins { + alias(libs.plugins.pohahang.android.library) + alias(libs.plugins.pohahang.android.hilt) +} + +android { + namespace = "com.koreatech.core.data" + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } +} + +dependencies { + implementation(libs.kotlinx.serialization.json) + implementation(libs.retrofit.core) + implementation(libs.retrofit.kotlin.serialization) + implementation(libs.okhttp.logging) +} \ No newline at end of file diff --git a/core/data/consumer-rules.pro b/core/data/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/core/data/proguard-rules.pro b/core/data/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/core/data/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/data/src/androidTest/java/com/koreatech/data/ExampleInstrumentedTest.kt b/core/data/src/androidTest/java/com/koreatech/data/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..5b60238 --- /dev/null +++ b/core/data/src/androidTest/java/com/koreatech/data/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.koreatech.data + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.koreatech.data.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/core/data/src/main/AndroidManifest.xml b/core/data/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/core/data/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/core/data/src/main/java/com/koreatech/data/model/request/LoginRequestDto.kt b/core/data/src/main/java/com/koreatech/data/model/request/LoginRequestDto.kt new file mode 100644 index 0000000..57e7e1e --- /dev/null +++ b/core/data/src/main/java/com/koreatech/data/model/request/LoginRequestDto.kt @@ -0,0 +1,9 @@ +package com.koreatech.data.model.request + +import kotlinx.serialization.Serializable + +@Serializable +data class LoginRequestDto( + val username: String, + val password: String, +) diff --git a/core/data/src/main/java/com/koreatech/data/model/request/SignupRequestDto.kt b/core/data/src/main/java/com/koreatech/data/model/request/SignupRequestDto.kt new file mode 100644 index 0000000..6778320 --- /dev/null +++ b/core/data/src/main/java/com/koreatech/data/model/request/SignupRequestDto.kt @@ -0,0 +1,11 @@ +package com.koreatech.data.model.request + +import kotlinx.serialization.Serializable + + +@Serializable +data class SignupRequestDto( + val username: String, + val password: String, + val nickname: String, +) diff --git a/core/data/src/main/java/com/koreatech/data/model/response/CatResponseDto.kt b/core/data/src/main/java/com/koreatech/data/model/response/CatResponseDto.kt new file mode 100644 index 0000000..ffe812e --- /dev/null +++ b/core/data/src/main/java/com/koreatech/data/model/response/CatResponseDto.kt @@ -0,0 +1,15 @@ +package com.koreatech.data.model.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +data class CatResponseDto( + @SerialName("id") + val id: String, + @SerialName("url") + val url: String, + @SerialName("width") + val width: Int, + @SerialName("height") + val height: Int, +) diff --git a/core/data/src/test/java/com/koreatech/data/ExampleUnitTest.kt b/core/data/src/test/java/com/koreatech/data/ExampleUnitTest.kt new file mode 100644 index 0000000..4016eda --- /dev/null +++ b/core/data/src/test/java/com/koreatech/data/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.koreatech.data + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/core/network/.gitignore b/core/network/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/network/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts new file mode 100644 index 0000000..587cd63 --- /dev/null +++ b/core/network/build.gradle.kts @@ -0,0 +1,40 @@ +@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed +plugins { + alias(libs.plugins.pohahang.android.library) + alias(libs.plugins.pohahang.android.hilt) + id("org.jetbrains.kotlin.plugin.serialization") +} + +android { + namespace = "com.koreatech.core.network" + + + defaultConfig { + buildConfigField("String", "base_url", getPropertyKey("base_url")) + } + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } +} + +dependencies { + implementation(libs.kotlinx.serialization.json) + implementation(libs.retrofit.core) + implementation(libs.retrofit.kotlin.serialization) + implementation(libs.okhttp.logging) + implementation(libs.gson) + implementation(project(mapOf("path" to ":core:data"))) +} + +fun getPropertyKey(propertyKey: String): String { + val nullableProperty: String? = + com.android.build.gradle.internal.cxx.configure.gradleLocalProperties(rootDir) + .getProperty(propertyKey) + return nullableProperty ?: "null" +} \ No newline at end of file diff --git a/core/network/consumer-rules.pro b/core/network/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/core/network/proguard-rules.pro b/core/network/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/core/network/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/network/src/androidTest/java/com/koreatech/core/network/ExampleInstrumentedTest.kt b/core/network/src/androidTest/java/com/koreatech/core/network/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..c87f316 --- /dev/null +++ b/core/network/src/androidTest/java/com/koreatech/core/network/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.koreatech.core.network + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.koreatech.core.network.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/core/network/src/main/AndroidManifest.xml b/core/network/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/core/network/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/core/network/src/main/java/com/koreatech/core/network/NetworkModule.kt b/core/network/src/main/java/com/koreatech/core/network/NetworkModule.kt new file mode 100644 index 0000000..04940f5 --- /dev/null +++ b/core/network/src/main/java/com/koreatech/core/network/NetworkModule.kt @@ -0,0 +1,32 @@ +package com.koreatech.core.network + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object NetworkModule { + @Provides + @Singleton + fun providesNetworkJson(): Json = Json { + ignoreUnknownKeys = true + } + + @Provides + @Singleton + fun okHttpCallFactory(): Call.Factory = OkHttpClient.Builder() + .addInterceptor( + HttpLoggingInterceptor().apply { + if (BuildConfig.DEBUG) { + setLevel(HttpLoggingInterceptor.Level.BODY) + } + } + ).build() +} \ No newline at end of file diff --git a/core/network/src/main/java/com/koreatech/core/network/NetworkResponse.kt b/core/network/src/main/java/com/koreatech/core/network/NetworkResponse.kt new file mode 100644 index 0000000..97d139e --- /dev/null +++ b/core/network/src/main/java/com/koreatech/core/network/NetworkResponse.kt @@ -0,0 +1,8 @@ +package com.koreatech.core.network + +import kotlinx.serialization.Serializable + +@Serializable +data class NetworkResponse( + val data: T +) diff --git a/core/network/src/main/java/com/koreatech/core/network/PohahangApi.kt b/core/network/src/main/java/com/koreatech/core/network/PohahangApi.kt new file mode 100644 index 0000000..5bba680 --- /dev/null +++ b/core/network/src/main/java/com/koreatech/core/network/PohahangApi.kt @@ -0,0 +1,26 @@ +package com.koreatech.core.network + +import com.koreatech.data.model.request.LoginRequestDto +import com.koreatech.data.model.request.SignupRequestDto +import com.koreatech.data.model.response.CatResponseDto +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Query + +interface PohahangApi { + @POST("/api/v1/members") + suspend fun signup( + @Body signupRequestDto: SignupRequestDto + ): NetworkResponse + + @POST("/api/v1/members/sign-in") + suspend fun login( + @Body loginRequestDto: LoginRequestDto + ): NetworkResponse + + @GET("/v1/images/search") + suspend fun getCats( + @Query("limit") limit: Int + ): List +} \ No newline at end of file diff --git a/core/network/src/main/java/com/koreatech/core/network/RetrofitNetwork.kt b/core/network/src/main/java/com/koreatech/core/network/RetrofitNetwork.kt new file mode 100644 index 0000000..9337f50 --- /dev/null +++ b/core/network/src/main/java/com/koreatech/core/network/RetrofitNetwork.kt @@ -0,0 +1,40 @@ +package com.koreatech.core.network + +import com.koreatech.core.network.repository.PohahangRepository +import com.koreatech.data.model.request.LoginRequestDto +import com.koreatech.data.model.request.SignupRequestDto +import com.koreatech.data.model.response.CatResponseDto +import kotlinx.serialization.json.Json +import okhttp3.Call +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +internal class RetrofitNetwork @Inject constructor( + networkJson: Json, + okHttpCallFactory: dagger.Lazy, +) : PohahangRepository { + private val retrofitApi = Retrofit.Builder() + .baseUrl("https://api.thecatapi.com/") + .callFactory { okHttpCallFactory.get().newCall(it) } +// .addConverterFactory( +// networkJson.asConverterFactory("application/json".toMediaType()) +// ) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(PohahangApi::class.java) + + override suspend fun signup(signupRequestDto: SignupRequestDto) { + retrofitApi.signup(signupRequestDto) + } + + override suspend fun login(loginRequestDto: LoginRequestDto) { + retrofitApi.login(loginRequestDto) + } + + override suspend fun getCats(): List = + retrofitApi.getCats(100) + +} \ No newline at end of file diff --git a/core/network/src/main/java/com/koreatech/core/network/di/RepositoryModule.kt b/core/network/src/main/java/com/koreatech/core/network/di/RepositoryModule.kt new file mode 100644 index 0000000..b6b53fc --- /dev/null +++ b/core/network/src/main/java/com/koreatech/core/network/di/RepositoryModule.kt @@ -0,0 +1,27 @@ +package com.koreatech.core.network.di + +import android.util.Log +import com.koreatech.core.network.RetrofitNetwork +import com.koreatech.core.network.repository.PohahangRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import okhttp3.Call +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object RepositoryModule { + @Provides + @Singleton + fun providesAuthRepository( + networkJson: Json, + okHttpCallFactory: dagger.Lazy, + ): PohahangRepository { + return RetrofitNetwork( + networkJson, okHttpCallFactory + ) + } +} diff --git a/core/network/src/main/java/com/koreatech/core/network/repository/PohahangRepository.kt b/core/network/src/main/java/com/koreatech/core/network/repository/PohahangRepository.kt new file mode 100644 index 0000000..644dc68 --- /dev/null +++ b/core/network/src/main/java/com/koreatech/core/network/repository/PohahangRepository.kt @@ -0,0 +1,12 @@ +package com.koreatech.core.network.repository + +import com.koreatech.data.model.request.LoginRequestDto +import com.koreatech.data.model.request.SignupRequestDto +import com.koreatech.data.model.response.CatResponseDto +import kotlinx.coroutines.flow.Flow + +interface PohahangRepository { + suspend fun signup(signupRequestDto: SignupRequestDto) + suspend fun login(loginRequestDto: LoginRequestDto) + suspend fun getCats(): List +} \ No newline at end of file diff --git a/core/network/src/test/java/com/koreatech/core/network/ExampleUnitTest.kt b/core/network/src/test/java/com/koreatech/core/network/ExampleUnitTest.kt new file mode 100644 index 0000000..7010f52 --- /dev/null +++ b/core/network/src/test/java/com/koreatech/core/network/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.koreatech.core.network + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..ddbf1dc --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,104 @@ +[versions] +androidGradlePlugin = "8.1.4" +androidxCore = "1.9.0" +androidxLifecycle = "2.7.0" +androidxActivity = "1.8.2" +androidxNavigation = "2.5.3" +androidxComposeBom = "2023.03.00" + +androidxTestExt = "1.1.5" +androidxEspresso = "3.5.1" + +kotlinxSerializationJson = "1.6.3" +kotlinxCoroutinesCoreVersion = "1.6.3" + +kotlinVersion = "1.8.10" +composeCompilerVersion = "1.4.3" + +orbitVersion = "6.1.1" +coilCompose = "2.6.0" + +hiltVersion = "2.44" +hiltNavigationCompose = "1.0.0" +okhttp = "4.12.0" +retrofit = "2.9.0" +retrofitKotlinxSerializationJson = "1.0.0" +gson = "2.9.0" + +junit4 = "4.13.2" +org-jetbrains-kotlin-jvm = "1.8.10" +org-jetbrains-kotlin-jvm1810 = "1.8.10" +org-jetbrains-kotlin-serialization = "1.8.0" +espresso-core = "3.5.1" +appcompat = "1.6.1" +material = "1.11.0" + +javaxInjectVersion = "1" + +[libraries] +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "androidxLifecycle" } +androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidxLifecycle"} +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" } +androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" } +androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxNavigation" } +androidx-test-ext = { group = "androidx.test.ext", name = "junit", version.ref = "androidxTestExt" } +androidx-test-espresso = { group = "androidx.test.espresso", name = "espresso-cor", version.ref = "androidxEspresso" } + +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" } +androidx-compose-ui = { module = "androidx.compose.ui:ui" } +androidx-compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" } +androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } +androidx-compose-material3 = { module = "androidx.compose.material3:material3" } +androidx-compose-ui-test = { module = "androidx.compose.ui:ui-test-junit4" } +androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" } + +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCoreVersion" } + +junit4 = { group = "junit", name = "junit", version.ref = "junit4" } + +orbit-core = { group = "org.orbit-mvi", name = "orbit-core", version.ref = "orbitVersion" } +orbit-viewmodel = { group = "org.orbit-mvi", name = "orbit-viewmodel", version.ref = "orbitVersion" } +orbit-compose = { group = "org.orbit-mvi", name = "orbit-compose", version.ref = "orbitVersion" } +orbit-test = { group = "org.orbit-mvi", name = "orbit-test", version.ref = "orbitVersion" } + +coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coilCompose" } + +hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hiltVersion" } +hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hiltVersion" } +hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" } + +okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } +retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } +retrofit-kotlin-serialization = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofitKotlinxSerializationJson" } +gson = {group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "gson"} + +javax-inject = { module = "javax.inject:javax.inject", version.ref = "javaxInjectVersion" } + +# Dependencies build-logic +android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } +kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlinVersion" } +espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } +appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } + + +[plugins] +android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } +kotlin-anroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlinVersion" } +hilt = { id = "com.google.dagger.hilt.android", version.ref = "hiltVersion" } +org-jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "org-jetbrains-kotlin-jvm1810" } +com-android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } +org-jetbrains-kotlin-serialization = { id ="org.jetbrains.kotlin.plugin.serialization", version.ref = "org-jetbrains-kotlin-serialization"} + +#Plugins defined +pohahang-android-application = { id = "pohahang.android.application", version = "unspecified" } +pohahang-android-application-compose = { id = "pohahang.android.application.compose", version = "unspecified" } +pohahang-android-feature = { id = "pohahang.android.plugin.feature", version = "unspecified" } +pohahang-android-hilt = { id = "pohahang.android.plugin.hilt", version = "unspecified" } +pohahang-android-library = { id = "pohahang.android.library", version = "unspecified" } + +[bundles] +orbit = ["orbit-core", "orbit-viewmodel", "orbit-compose"] +compose = ["androidx-compose-bom", "androidx-compose-ui", "androidx-compose-ui-graphics", "androidx-compose-ui-tooling-preview", "androidx-compose-material3"] \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 57b89b7..da28159 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,5 @@ pluginManagement { + includeBuild("build-logic") repositories { google() mavenCentral() @@ -15,3 +16,6 @@ dependencyResolutionManagement { rootProject.name = "kwanhee-jo-compose-study" include(":app") +include(":core") +include(":core:network") +include(":core:data")