diff --git a/app/src/main/java/team/retum/jobisandroidv2/JobisNavigator.kt b/app/src/main/java/team/retum/jobisandroidv2/JobisNavigator.kt
index 96ad93e94..ec355b63b 100644
--- a/app/src/main/java/team/retum/jobisandroidv2/JobisNavigator.kt
+++ b/app/src/main/java/team/retum/jobisandroidv2/JobisNavigator.kt
@@ -16,6 +16,7 @@ import team.retum.jobis.application.navigation.navigateToApplication
import team.retum.jobis.change.password.navigation.navigateToComparePassword
import team.retum.jobis.change.password.navigation.navigateToResetPassword
import team.retum.jobis.interests.navigation.navigateToInterests
+import team.retum.jobis.interests.navigation.navigateToInterestsComplete
import team.retum.jobis.notice.navigation.navigateToNoticeDetails
import team.retum.jobis.notice.navigation.navigateToNotices
import team.retum.jobis.recruitment.navigation.navigateToRecruitmentDetails
@@ -104,6 +105,10 @@ internal class JobisNavigator(
navController.navigateToInterests()
}
+ fun navigateToInterestsComplete(studentName: String) {
+ navController.navigateToInterestsComplete(studentName = studentName)
+ }
+
fun navigateToComparePassword() {
navController.navigateToComparePassword()
}
diff --git a/app/src/main/java/team/retum/jobisandroidv2/navigation/MainNavigation.kt b/app/src/main/java/team/retum/jobisandroidv2/navigation/MainNavigation.kt
index 0fb18cce5..8fb820644 100644
--- a/app/src/main/java/team/retum/jobisandroidv2/navigation/MainNavigation.kt
+++ b/app/src/main/java/team/retum/jobisandroidv2/navigation/MainNavigation.kt
@@ -10,6 +10,7 @@ import team.retum.employment.navigation.employment
import team.retum.employment.navigation.employmentDetail
import team.retum.jobis.application.navigation.application
import team.retum.jobis.interests.navigation.interests
+import team.retum.jobis.interests.navigation.interestsComplete
import team.retum.jobis.notice.navigation.noticeDetails
import team.retum.jobis.notice.navigation.notices
import team.retum.jobis.recruitment.navigation.recruitmentDetails
@@ -75,7 +76,13 @@ internal fun NavGraphBuilder.mainNavigation(
onBackPressed = navigator::popBackStackIfNotHome,
)
reportBug(onBackPressed = navigator::popBackStackIfNotHome)
- interests(onBackPressed = navigator::popBackStackIfNotHome)
+ interests(
+ onBackPressed = navigator::popBackStackIfNotHome,
+ navigateToInterestsComplete = navigator::navigateToInterestsComplete,
+ )
+ interestsComplete(
+ onBackPressed = navigator::popBackStackIfNotHome,
+ )
noticeDetails(onBackPressed = navigator::popBackStackIfNotHome)
companies(
onBackPressed = navigator::popBackStackIfNotHome,
diff --git a/core/common/src/main/java/team/retum/common/utils/ResourceKeys.kt b/core/common/src/main/java/team/retum/common/utils/ResourceKeys.kt
index cd94f4d58..e7c43a93b 100644
--- a/core/common/src/main/java/team/retum/common/utils/ResourceKeys.kt
+++ b/core/common/src/main/java/team/retum/common/utils/ResourceKeys.kt
@@ -22,4 +22,5 @@ object ResourceKeys {
const val EMAIL = "@dsm.hs.kr"
const val DATABASE_NAME = "jobis-database"
const val CLASS_ID = "classId"
+ const val STUDENT_NAME = "studentName"
}
diff --git a/core/data/src/main/java/team/retum/data/di/RepositoryModule.kt b/core/data/src/main/java/team/retum/data/di/RepositoryModule.kt
index 13ed8b589..d459c1a89 100644
--- a/core/data/src/main/java/team/retum/data/di/RepositoryModule.kt
+++ b/core/data/src/main/java/team/retum/data/di/RepositoryModule.kt
@@ -22,6 +22,8 @@ import team.retum.data.repository.company.CompanyRepository
import team.retum.data.repository.company.CompanyRepositoryImpl
import team.retum.data.repository.file.FileRepository
import team.retum.data.repository.file.FileRepositoryImpl
+import team.retum.data.repository.interests.InterestsRepository
+import team.retum.data.repository.interests.InterestsRepositoryImpl
import team.retum.data.repository.intern.WinterInterRepository
import team.retum.data.repository.intern.WinterInternRepositoryImpl
import team.retum.data.repository.notice.NoticeRepository
@@ -104,4 +106,8 @@ abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindServerStatusCheckRepository(serverStatusCheckRepositoryImpl: ServerStatusCheckRepositoryImpl): ServerStatusCheckRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindInterestsRepository(interestsRepositoryImpl: InterestsRepositoryImpl): InterestsRepository
}
diff --git a/core/data/src/main/java/team/retum/data/repository/interests/InterestsRepository.kt b/core/data/src/main/java/team/retum/data/repository/interests/InterestsRepository.kt
new file mode 100644
index 000000000..c89f1f995
--- /dev/null
+++ b/core/data/src/main/java/team/retum/data/repository/interests/InterestsRepository.kt
@@ -0,0 +1,11 @@
+package team.retum.data.repository.interests
+
+import team.retum.network.model.request.interests.InterestsToggleRequest
+import team.retum.network.model.response.interests.FetchInterestsRecruitmentsResponse
+import team.retum.network.model.response.interests.FetchInterestsResponse
+
+interface InterestsRepository {
+ suspend fun setInterestsToggle(codes: InterestsToggleRequest)
+ suspend fun fetchInterests(): FetchInterestsResponse
+ suspend fun fetchInterestsSearchRecruitments(): FetchInterestsRecruitmentsResponse
+}
diff --git a/core/data/src/main/java/team/retum/data/repository/interests/InterestsRepositoryImpl.kt b/core/data/src/main/java/team/retum/data/repository/interests/InterestsRepositoryImpl.kt
new file mode 100644
index 000000000..5ac1bc559
--- /dev/null
+++ b/core/data/src/main/java/team/retum/data/repository/interests/InterestsRepositoryImpl.kt
@@ -0,0 +1,21 @@
+package team.retum.data.repository.interests
+
+import team.retum.network.datasource.interests.InterestsDataSource
+import team.retum.network.model.request.interests.InterestsToggleRequest
+import team.retum.network.model.response.interests.FetchInterestsRecruitmentsResponse
+import team.retum.network.model.response.interests.FetchInterestsResponse
+import javax.inject.Inject
+
+class InterestsRepositoryImpl @Inject constructor(
+ private val interestsDataSource: InterestsDataSource,
+) : InterestsRepository {
+ override suspend fun setInterestsToggle(codes: InterestsToggleRequest) {
+ interestsDataSource.setInterestsToggle(codes = codes)
+ }
+
+ override suspend fun fetchInterests(): FetchInterestsResponse =
+ interestsDataSource.fetchInterests()
+
+ override suspend fun fetchInterestsSearchRecruitments(): FetchInterestsRecruitmentsResponse =
+ interestsDataSource.fetchInterestsSearchRecruitments()
+}
diff --git a/core/design-system/src/main/res/drawable/success.xml b/core/design-system/src/main/res/drawable/success.xml
new file mode 100644
index 000000000..5ede9dddd
--- /dev/null
+++ b/core/design-system/src/main/res/drawable/success.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/domain/src/main/java/team/retum/usecase/entity/interests/InterestsEntity.kt b/core/domain/src/main/java/team/retum/usecase/entity/interests/InterestsEntity.kt
new file mode 100644
index 000000000..5d9c9bd18
--- /dev/null
+++ b/core/domain/src/main/java/team/retum/usecase/entity/interests/InterestsEntity.kt
@@ -0,0 +1,29 @@
+package team.retum.usecase.entity.interests
+
+import androidx.compose.runtime.Immutable
+import team.retum.network.model.response.interests.FetchInterestsResponse
+
+@Immutable
+data class InterestsEntity(
+ val studentName: String,
+ val interests: List,
+) {
+ data class InterestMajorEntity(
+ val id: Int,
+ val studentId: Int,
+ val code: Int,
+ val keyword: String,
+ )
+}
+
+internal fun FetchInterestsResponse.toEntity() = InterestsEntity(
+ studentName = this.studentName,
+ interests = this.interests.map { it.toEntity() },
+)
+
+private fun FetchInterestsResponse.InterestMajor.toEntity() = InterestsEntity.InterestMajorEntity(
+ id = this.id,
+ studentId = this.studentId,
+ code = this.code,
+ keyword = this.keyword,
+)
diff --git a/core/domain/src/main/java/team/retum/usecase/entity/interests/InterestsRecruitmentEntity.kt b/core/domain/src/main/java/team/retum/usecase/entity/interests/InterestsRecruitmentEntity.kt
new file mode 100644
index 000000000..8e33a8890
--- /dev/null
+++ b/core/domain/src/main/java/team/retum/usecase/entity/interests/InterestsRecruitmentEntity.kt
@@ -0,0 +1,34 @@
+package team.retum.usecase.entity.interests
+
+import androidx.compose.runtime.Immutable
+import team.retum.network.model.response.interests.FetchInterestsRecruitmentsResponse
+
+@Immutable
+data class InterestsRecruitmentsEntity(
+ val recruitments: List,
+) {
+ data class InterestsRecruitmentEntity(
+ val id: Int,
+ val companyId: String,
+ val companyProfileUrl: String,
+ val trainPay: Int,
+ val militarySupport: Boolean,
+ val hiringJobs: String,
+ val bookmarked: Boolean,
+ )
+}
+
+internal fun FetchInterestsRecruitmentsResponse.toEntity() = InterestsRecruitmentsEntity(
+ recruitments = this.recruitments.map { it.toEntity() },
+)
+
+private fun FetchInterestsRecruitmentsResponse.InterestRecruitments.toEntity() =
+ InterestsRecruitmentsEntity.InterestsRecruitmentEntity(
+ id = this.id,
+ companyId = this.companyId,
+ companyProfileUrl = this.companyProfileUrl,
+ trainPay = this.trainPay,
+ militarySupport = this.militarySupport,
+ hiringJobs = this.hiringJobs,
+ bookmarked = this.bookmarked,
+ )
diff --git a/core/domain/src/main/java/team/retum/usecase/usecase/interests/FetchInterestsSearchRecruitmentsUseCase.kt b/core/domain/src/main/java/team/retum/usecase/usecase/interests/FetchInterestsSearchRecruitmentsUseCase.kt
new file mode 100644
index 000000000..74b7ded64
--- /dev/null
+++ b/core/domain/src/main/java/team/retum/usecase/usecase/interests/FetchInterestsSearchRecruitmentsUseCase.kt
@@ -0,0 +1,13 @@
+package team.retum.usecase.usecase.interests
+
+import team.retum.data.repository.interests.InterestsRepository
+import team.retum.usecase.entity.interests.toEntity
+import javax.inject.Inject
+
+class FetchInterestsSearchRecruitmentsUseCase @Inject constructor(
+ private val interestsRepository: InterestsRepository,
+) {
+ suspend operator fun invoke() = runCatching {
+ interestsRepository.fetchInterestsSearchRecruitments().toEntity()
+ }
+}
diff --git a/core/domain/src/main/java/team/retum/usecase/usecase/interests/FetchInterestsUseCase.kt b/core/domain/src/main/java/team/retum/usecase/usecase/interests/FetchInterestsUseCase.kt
new file mode 100644
index 000000000..65a6be1aa
--- /dev/null
+++ b/core/domain/src/main/java/team/retum/usecase/usecase/interests/FetchInterestsUseCase.kt
@@ -0,0 +1,13 @@
+package team.retum.usecase.usecase.interests
+
+import team.retum.data.repository.interests.InterestsRepository
+import team.retum.usecase.entity.interests.toEntity
+import javax.inject.Inject
+
+class FetchInterestsUseCase @Inject constructor(
+ private val interestsRepository: InterestsRepository,
+) {
+ suspend operator fun invoke() = runCatching {
+ interestsRepository.fetchInterests().toEntity()
+ }
+}
diff --git a/core/domain/src/main/java/team/retum/usecase/usecase/interests/SetInterestsToggleUseCase.kt b/core/domain/src/main/java/team/retum/usecase/usecase/interests/SetInterestsToggleUseCase.kt
new file mode 100644
index 000000000..caa4d9c24
--- /dev/null
+++ b/core/domain/src/main/java/team/retum/usecase/usecase/interests/SetInterestsToggleUseCase.kt
@@ -0,0 +1,13 @@
+package team.retum.usecase.usecase.interests
+
+import team.retum.data.repository.interests.InterestsRepository
+import team.retum.network.model.request.interests.InterestsToggleRequest
+import javax.inject.Inject
+
+class SetInterestsToggleUseCase @Inject constructor(
+ private val interestsRepository: InterestsRepository,
+) {
+ suspend operator fun invoke(codes: InterestsToggleRequest) = runCatching {
+ interestsRepository.setInterestsToggle(codes = codes)
+ }
+}
diff --git a/core/network/src/main/java/team/retum/network/api/InterestsApi.kt b/core/network/src/main/java/team/retum/network/api/InterestsApi.kt
new file mode 100644
index 000000000..f93b2792e
--- /dev/null
+++ b/core/network/src/main/java/team/retum/network/api/InterestsApi.kt
@@ -0,0 +1,22 @@
+package team.retum.network.api
+
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.PATCH
+import team.retum.network.di.RequestUrls
+import team.retum.network.model.request.interests.InterestsToggleRequest
+import team.retum.network.model.response.interests.FetchInterestsRecruitmentsResponse
+import team.retum.network.model.response.interests.FetchInterestsResponse
+
+interface InterestsApi {
+ @PATCH(RequestUrls.Interests.interests)
+ suspend fun setInterestsToggle(
+ @Body codes: InterestsToggleRequest,
+ )
+
+ @GET(RequestUrls.Interests.interests)
+ suspend fun fetchInterests(): FetchInterestsResponse
+
+ @GET(RequestUrls.Interests.interestsRecruitments)
+ suspend fun fetchInterestsSearchRecruitments(): FetchInterestsRecruitmentsResponse
+}
diff --git a/core/network/src/main/java/team/retum/network/datasource/interests/InterestsDataSource.kt b/core/network/src/main/java/team/retum/network/datasource/interests/InterestsDataSource.kt
new file mode 100644
index 000000000..11a76450f
--- /dev/null
+++ b/core/network/src/main/java/team/retum/network/datasource/interests/InterestsDataSource.kt
@@ -0,0 +1,11 @@
+package team.retum.network.datasource.interests
+
+import team.retum.network.model.request.interests.InterestsToggleRequest
+import team.retum.network.model.response.interests.FetchInterestsRecruitmentsResponse
+import team.retum.network.model.response.interests.FetchInterestsResponse
+
+interface InterestsDataSource {
+ suspend fun setInterestsToggle(codes: InterestsToggleRequest)
+ suspend fun fetchInterests(): FetchInterestsResponse
+ suspend fun fetchInterestsSearchRecruitments(): FetchInterestsRecruitmentsResponse
+}
diff --git a/core/network/src/main/java/team/retum/network/datasource/interests/InterestsDataSourceImpl.kt b/core/network/src/main/java/team/retum/network/datasource/interests/InterestsDataSourceImpl.kt
new file mode 100644
index 000000000..4aaddf22e
--- /dev/null
+++ b/core/network/src/main/java/team/retum/network/datasource/interests/InterestsDataSourceImpl.kt
@@ -0,0 +1,30 @@
+package team.retum.network.datasource.interests
+
+import team.retum.network.api.InterestsApi
+import team.retum.network.model.request.interests.InterestsToggleRequest
+import team.retum.network.model.response.interests.FetchInterestsRecruitmentsResponse
+import team.retum.network.model.response.interests.FetchInterestsResponse
+import team.retum.network.util.RequestHandler
+import javax.inject.Inject
+
+class InterestsDataSourceImpl @Inject constructor(
+ private val interestsApi: InterestsApi,
+) : InterestsDataSource {
+ override suspend fun setInterestsToggle(codes: InterestsToggleRequest) {
+ RequestHandler().request {
+ interestsApi.setInterestsToggle(codes = codes)
+ }
+ }
+
+ override suspend fun fetchInterests(): FetchInterestsResponse {
+ return RequestHandler().request {
+ interestsApi.fetchInterests()
+ }
+ }
+
+ override suspend fun fetchInterestsSearchRecruitments(): FetchInterestsRecruitmentsResponse {
+ return RequestHandler().request {
+ interestsApi.fetchInterestsSearchRecruitments()
+ }
+ }
+}
diff --git a/core/network/src/main/java/team/retum/network/di/NetworkModule.kt b/core/network/src/main/java/team/retum/network/di/NetworkModule.kt
index a5695edb8..89e779250 100644
--- a/core/network/src/main/java/team/retum/network/di/NetworkModule.kt
+++ b/core/network/src/main/java/team/retum/network/di/NetworkModule.kt
@@ -22,6 +22,7 @@ import team.retum.network.api.BugApi
import team.retum.network.api.CodeApi
import team.retum.network.api.CompanyApi
import team.retum.network.api.FileApi
+import team.retum.network.api.InterestsApi
import team.retum.network.api.NoticeApi
import team.retum.network.api.NotificationApi
import team.retum.network.api.RecruitmentApi
@@ -188,4 +189,10 @@ object NetworkModule {
fun provideServerStatusCheckApi(retrofit: Retrofit): ServerStatusCheckApi {
return retrofit.create(ServerStatusCheckApi::class.java)
}
+
+ @Provides
+ @Singleton
+ fun provideInterestsApi(retrofit: Retrofit): InterestsApi {
+ return retrofit.create(InterestsApi::class.java)
+ }
}
diff --git a/core/network/src/main/java/team/retum/network/di/RemoteDataSourceModule.kt b/core/network/src/main/java/team/retum/network/di/RemoteDataSourceModule.kt
index 64350f1bb..87b045e8b 100644
--- a/core/network/src/main/java/team/retum/network/di/RemoteDataSourceModule.kt
+++ b/core/network/src/main/java/team/retum/network/di/RemoteDataSourceModule.kt
@@ -22,6 +22,8 @@ import team.retum.network.datasource.company.CompanyDataSource
import team.retum.network.datasource.company.CompanyDataSourceImpl
import team.retum.network.datasource.file.RemoteFileDataSource
import team.retum.network.datasource.file.RemoteFileDataSourceImpl
+import team.retum.network.datasource.interests.InterestsDataSource
+import team.retum.network.datasource.interests.InterestsDataSourceImpl
import team.retum.network.datasource.notice.NoticeDataSource
import team.retum.network.datasource.notice.NoticeDataSourceImpl
import team.retum.network.datasource.notification.NotificationDataSource
@@ -104,4 +106,8 @@ abstract class RemoteDataSourceModule {
@Binds
@Singleton
abstract fun bindServerStatusCheckDataSource(serverStatusCheckDataSourceImpl: RemoteServerStatusCheckDataSourceImpl): RemoteServerStatusCheckDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindInterestsDataSource(interestsDataSourceImpl: InterestsDataSourceImpl): InterestsDataSource
}
diff --git a/core/network/src/main/java/team/retum/network/di/RequestUrls.kt b/core/network/src/main/java/team/retum/network/di/RequestUrls.kt
index a78255c70..6e5074a59 100644
--- a/core/network/src/main/java/team/retum/network/di/RequestUrls.kt
+++ b/core/network/src/main/java/team/retum/network/di/RequestUrls.kt
@@ -133,4 +133,11 @@ internal object RequestUrls {
const val checkServerStatus = path
}
+
+ data object Interests {
+ private const val path = "/interests"
+
+ const val interests = path
+ const val interestsRecruitments = "$path/recruitment"
+ }
}
diff --git a/core/network/src/main/java/team/retum/network/model/request/interests/InterestsToggleRequest.kt b/core/network/src/main/java/team/retum/network/model/request/interests/InterestsToggleRequest.kt
new file mode 100644
index 000000000..cc581b380
--- /dev/null
+++ b/core/network/src/main/java/team/retum/network/model/request/interests/InterestsToggleRequest.kt
@@ -0,0 +1,9 @@
+package team.retum.network.model.request.interests
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class InterestsToggleRequest(
+ @Json(name = "code_ids") val codeIds: List,
+)
diff --git a/core/network/src/main/java/team/retum/network/model/response/interests/FetchInterestsRecruitmentsResponse.kt b/core/network/src/main/java/team/retum/network/model/response/interests/FetchInterestsRecruitmentsResponse.kt
new file mode 100644
index 000000000..107a791ad
--- /dev/null
+++ b/core/network/src/main/java/team/retum/network/model/response/interests/FetchInterestsRecruitmentsResponse.kt
@@ -0,0 +1,19 @@
+package team.retum.network.model.response.interests
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class FetchInterestsRecruitmentsResponse(
+ @Json(name = "recruitments") val recruitments: List,
+) {
+ data class InterestRecruitments(
+ @Json(name = "id") val id: Int,
+ @Json(name = "company_id") val companyId: String,
+ @Json(name = "company_profile_url") val companyProfileUrl: String,
+ @Json(name = "train_pay") val trainPay: Int,
+ @Json(name = "military_support") val militarySupport: Boolean,
+ @Json(name = "hiring_jobs") val hiringJobs: String,
+ @Json(name = "bookmarked") val bookmarked: Boolean,
+ )
+}
diff --git a/core/network/src/main/java/team/retum/network/model/response/interests/FetchInterestsResponse.kt b/core/network/src/main/java/team/retum/network/model/response/interests/FetchInterestsResponse.kt
new file mode 100644
index 000000000..79504b418
--- /dev/null
+++ b/core/network/src/main/java/team/retum/network/model/response/interests/FetchInterestsResponse.kt
@@ -0,0 +1,17 @@
+package team.retum.network.model.response.interests
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class FetchInterestsResponse(
+ @Json(name = "student_name") val studentName: String,
+ @Json(name = "interests") val interests: List,
+) {
+ data class InterestMajor(
+ @Json(name = "id") val id: Int,
+ @Json(name = "student_id") val studentId: Int,
+ @Json(name = "code") val code: Int,
+ @Json(name = "keyword") val keyword: String,
+ )
+}
diff --git a/feature/interests/build.gradle.kts b/feature/interests/build.gradle.kts
index 70bac8f32..744c48ba4 100644
--- a/feature/interests/build.gradle.kts
+++ b/feature/interests/build.gradle.kts
@@ -21,6 +21,7 @@ android {
dependencies {
implementation(project(":core:common"))
implementation(project(":core:domain"))
+ implementation(project(":core:network"))
implementation(libs.kotlinx.collections.immutable)
}
diff --git a/feature/interests/src/main/java/team/retum/jobis/interests/navigation/InterestsCompleteNavigation.kt b/feature/interests/src/main/java/team/retum/jobis/interests/navigation/InterestsCompleteNavigation.kt
new file mode 100644
index 000000000..71fd85dd9
--- /dev/null
+++ b/feature/interests/src/main/java/team/retum/jobis/interests/navigation/InterestsCompleteNavigation.kt
@@ -0,0 +1,32 @@
+package team.retum.jobis.interests.navigation
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavType
+import androidx.navigation.compose.composable
+import androidx.navigation.navArgument
+import team.retum.common.utils.ResourceKeys
+import team.retum.jobis.interests.ui.InterestsComplete
+
+const val NAVIGATION_INTERESTS_COMPLETE = "interestsComplete"
+fun NavGraphBuilder.interestsComplete(
+ onBackPressed: () -> Unit,
+) {
+ composable(
+ route = "$NAVIGATION_INTERESTS_COMPLETE/{${ResourceKeys.STUDENT_NAME}}",
+ arguments = listOf(
+ navArgument(ResourceKeys.STUDENT_NAME) { type = NavType.StringType },
+ ),
+ ) {
+ val studentName = it.arguments?.getString(ResourceKeys.STUDENT_NAME) ?: ""
+
+ InterestsComplete(
+ onBackPressed = onBackPressed,
+ studentName = studentName,
+ )
+ }
+}
+
+fun NavController.navigateToInterestsComplete(studentName: String) {
+ navigate("$NAVIGATION_INTERESTS_COMPLETE/$studentName")
+}
diff --git a/feature/interests/src/main/java/team/retum/jobis/interests/navigation/InterestsNavigation.kt b/feature/interests/src/main/java/team/retum/jobis/interests/navigation/InterestsNavigation.kt
index b17abb0ee..9d7cfbccd 100644
--- a/feature/interests/src/main/java/team/retum/jobis/interests/navigation/InterestsNavigation.kt
+++ b/feature/interests/src/main/java/team/retum/jobis/interests/navigation/InterestsNavigation.kt
@@ -6,9 +6,15 @@ import androidx.navigation.compose.composable
import team.retum.jobis.interests.ui.Interests
const val NAVIGATION_INTERESTS = "interests"
-fun NavGraphBuilder.interests(onBackPressed: () -> Unit) {
+fun NavGraphBuilder.interests(
+ onBackPressed: () -> Unit,
+ navigateToInterestsComplete: (String) -> Unit,
+) {
composable(NAVIGATION_INTERESTS) {
- Interests(onBackPressed = onBackPressed)
+ Interests(
+ onBackPressed = onBackPressed,
+ navigateToInterestsComplete = navigateToInterestsComplete,
+ )
}
}
diff --git a/feature/interests/src/main/java/team/retum/jobis/interests/ui/InterestsCompleteScreen.kt b/feature/interests/src/main/java/team/retum/jobis/interests/ui/InterestsCompleteScreen.kt
new file mode 100644
index 000000000..5da745fbc
--- /dev/null
+++ b/feature/interests/src/main/java/team/retum/jobis/interests/ui/InterestsCompleteScreen.kt
@@ -0,0 +1,79 @@
+package team.retum.jobis.interests.ui
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import team.retum.jobis.interests.R
+import team.retum.jobisdesignsystemv2.appbar.JobisSmallTopAppBar
+import team.retum.jobisdesignsystemv2.foundation.JobisTheme
+import team.retum.jobisdesignsystemv2.foundation.JobisTypography
+import team.retum.jobisdesignsystemv2.text.JobisText
+
+@Composable
+internal fun InterestsComplete(
+ onBackPressed: () -> Unit,
+ studentName: String,
+) {
+ InterestsCompleteScreen(
+ studentName = studentName,
+ onBackPressed = onBackPressed,
+ )
+}
+
+@Composable
+private fun InterestsCompleteScreen(
+ studentName: String,
+ onBackPressed: () -> Unit,
+) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ JobisSmallTopAppBar(
+ title = stringResource(R.string.set_interests),
+ onBackPressed = onBackPressed,
+ )
+ Column(
+ modifier = Modifier.fillMaxHeight(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ ) {
+ Image(
+ painter = painterResource(team.retum.design_system.R.drawable.success),
+ contentDescription = "major_check_success",
+ )
+ JobisText(
+ modifier = Modifier.padding(
+ top = 20.dp,
+ start = 24.dp,
+ end = 24.dp,
+ ),
+ text = stringResource(R.string.interests_check_title, studentName),
+ textAlign = TextAlign.Center,
+ style = JobisTypography.PageTitle,
+ color = JobisTheme.colors.onBackground,
+ )
+ JobisText(
+ modifier = Modifier.padding(
+ top = 8.dp,
+ bottom = 20.dp,
+ start = 24.dp,
+ end = 24.dp,
+ ),
+ text = stringResource(R.string.interests_alarm),
+ textAlign = TextAlign.Center,
+ style = JobisTypography.SubBody,
+ )
+ }
+ }
+}
diff --git a/feature/interests/src/main/java/team/retum/jobis/interests/ui/InterestsScreen.kt b/feature/interests/src/main/java/team/retum/jobis/interests/ui/InterestsScreen.kt
index 5ab2ed111..12d00d563 100644
--- a/feature/interests/src/main/java/team/retum/jobis/interests/ui/InterestsScreen.kt
+++ b/feature/interests/src/main/java/team/retum/jobis/interests/ui/InterestsScreen.kt
@@ -1,48 +1,92 @@
package team.retum.jobis.interests.ui
+import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshots.SnapshotStateList
-import androidx.compose.runtime.toMutableStateList
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import kotlinx.collections.immutable.toPersistentList
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import team.retum.jobis.interests.R
+import team.retum.jobis.interests.viewmodel.InterestsSideEffect
+import team.retum.jobis.interests.viewmodel.InterestsState
+import team.retum.jobis.interests.viewmodel.InterestsViewmodel
import team.retum.jobisdesignsystemv2.appbar.JobisSmallTopAppBar
+import team.retum.jobisdesignsystemv2.button.ButtonColor
+import team.retum.jobisdesignsystemv2.button.JobisButton
+import team.retum.jobisdesignsystemv2.foundation.JobisIcon
import team.retum.jobisdesignsystemv2.foundation.JobisTheme
-import team.retum.jobisdesignsystemv2.skills.Skills
-import team.retum.jobisdesignsystemv2.tab.TabBar
-import team.retum.jobisdesignsystemv2.textfield.JobisTextField
+import team.retum.jobisdesignsystemv2.foundation.JobisTypography
+import team.retum.jobisdesignsystemv2.text.JobisText
+import team.retum.jobisdesignsystemv2.toast.JobisToast
+import team.retum.jobisdesignsystemv2.utils.clickable
+import team.retum.usecase.entity.CodesEntity
@Composable
-internal fun Interests(onBackPressed: () -> Unit) {
- InterestsScreen(onBackPressed = onBackPressed)
+internal fun Interests(
+ onBackPressed: () -> Unit,
+ navigateToInterestsComplete: (String) -> Unit,
+ interestsViewmodel: InterestsViewmodel = hiltViewModel(),
+) {
+ val state by interestsViewmodel.state.collectAsStateWithLifecycle()
+ val context = LocalContext.current
+
+ LaunchedEffect(Unit) {
+ interestsViewmodel.sideEffect.collect {
+ when (it) {
+ is InterestsSideEffect.PatchMajorFail -> {
+ JobisToast.create(
+ context = context,
+ message = context.getString(R.string.cannot_submit_interests_major),
+ drawable = JobisIcon.Error,
+ )
+ }
+
+ is InterestsSideEffect.MoveToInterestsComplete -> {
+ navigateToInterestsComplete(it.studentName)
+ }
+ }
+ }
+ }
+
+ InterestsScreen(
+ onBackPressed = onBackPressed,
+ state = state,
+ setSelectedMajor = interestsViewmodel::setMajor,
+ patchInterestsMajor = interestsViewmodel::patchInterestsMajor,
+ setButtonState = interestsViewmodel::setButtonState,
+ )
}
@Composable
-private fun InterestsScreen(onBackPressed: () -> Unit) {
- // TODO 뷰모델로 옮기기
- var content by remember { mutableStateOf("") }
- var selectedCategoryIndex by remember { mutableIntStateOf(0) }
- val checkedSkills = remember { mutableStateListOf() }
- val categories = remember {
- mutableStateListOf(
- "Android",
- "Back-end",
- "Front-end",
- )
+private fun InterestsScreen(
+ onBackPressed: () -> Unit,
+ state: InterestsState,
+ setSelectedMajor: (Long) -> Unit,
+ patchInterestsMajor: () -> Unit,
+ setButtonState: (Boolean) -> Unit,
+) {
+ val buttonText = if (state.selectedMajorCount > 0) {
+ setButtonState(true)
+ stringResource(R.string.select_interests_button_count, state.selectedMajorCount)
+ } else {
+ setButtonState(false)
+ stringResource(R.string.select_interests_button)
}
Column(
@@ -54,54 +98,130 @@ private fun InterestsScreen(onBackPressed: () -> Unit) {
title = stringResource(id = R.string.set_interests),
onBackPressed = onBackPressed,
)
+ InterestsTitle(studentName = state.studentName)
InterestsInput(
- content = { content },
- onContentChange = { content = it },
- categories = categories,
- selectedCategoryIndex = selectedCategoryIndex,
- onSelectCategory = { selectedCategoryIndex = it },
- checkedSkills = checkedSkills,
+ categories = state.majorList,
+ selectedMajorCodes = state.selectedMajorCodes,
+ onSelectCategory = setSelectedMajor,
+ onUnselectCategory = { setSelectedMajor(it) },
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ JobisButton(
+ text = buttonText,
+ color = ButtonColor.Primary,
+ onClick = { patchInterestsMajor() },
+ enabled = state.buttonEnable,
)
- Spacer(modifier = Modifier.height(16.dp))
}
}
+@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun InterestsInput(
- content: () -> String,
- onContentChange: (String) -> Unit,
- selectedCategoryIndex: Int,
- categories: SnapshotStateList,
- onSelectCategory: (Int) -> Unit,
- checkedSkills: SnapshotStateList,
+ categories: List,
+ selectedMajorCodes: List,
+ onSelectCategory: (Long) -> Unit,
+ onUnselectCategory: (Long) -> Unit,
+) {
+ FlowRow(
+ modifier = Modifier.padding(
+ horizontal = 24.dp,
+ vertical = 36.dp,
+ ),
+ maxItemsInEachRow = 5,
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ categories.forEach {
+ MajorContent(
+ major = it.keyword,
+ majorId = it.code,
+ selected = selectedMajorCodes.contains(it.code),
+ onClick = { major ->
+ when (selectedMajorCodes.contains(it.code)) {
+ true -> { onUnselectCategory(major) }
+ false -> { onSelectCategory(major) }
+ }
+ },
+ )
+ }
+ }
+}
+
+@Composable
+private fun InterestsTitle(
+ studentName: String,
) {
- Column(modifier = Modifier.padding(vertical = 16.dp)) {
- JobisTextField(
- hint = stringResource(id = R.string.hint_keyword),
- value = content,
- onValueChange = onContentChange,
+ Column(
+ modifier = Modifier.padding(top = 34.dp),
+ ) {
+ JobisText(
+ modifier = Modifier.padding(
+ top = 20.dp,
+ start = 24.dp,
+ end = 24.dp,
+ ),
+ text = stringResource(R.string.interests_select_title, studentName),
+ style = JobisTypography.PageTitle,
+ color = JobisTheme.colors.onBackground,
)
- TabBar(
- selectedTabIndex = selectedCategoryIndex,
- tabs = categories.toPersistentList(),
- onSelectTab = onSelectCategory,
+ JobisText(
+ modifier = Modifier.padding(
+ top = 8.dp,
+ bottom = 20.dp,
+ start = 24.dp,
+ end = 24.dp,
+ ),
+ text = stringResource(R.string.interests_alarm),
+ style = JobisTypography.SubBody,
)
- // TODO 더미 데이터 제거
- Skills(
- skills = listOf(
- "Kotlin",
- "Java",
- ).toMutableStateList(),
- checkedSkills = checkedSkills.toPersistentList(),
- onCheckedChange = { index, checked, _ ->
- // TODO 뷰모델로 함수 옮기기
- checkedSkills.run {
- when (checked) {
- true -> add(index)
- false -> remove(index)
- }
- }
- },
+ }
+}
+
+@Composable
+private fun MajorContent(
+ modifier: Modifier = Modifier,
+ major: String,
+ majorId: Long,
+ selected: Boolean,
+ onClick: (Long) -> Unit,
+) {
+ val background by animateColorAsState(
+ targetValue = if (selected) {
+ JobisTheme.colors.onPrimary
+ } else {
+ JobisTheme.colors.inverseSurface
+ },
+ label = "",
+ )
+ val textColor by animateColorAsState(
+ targetValue = if (selected) {
+ JobisTheme.colors.background
+ } else {
+ JobisTheme.colors.onPrimaryContainer
+ },
+ label = "",
+ )
+
+ Box(
+ modifier = modifier
+ .clickable(
+ enabled = true,
+ onClick = { onClick(majorId) },
+ onPressed = {},
+ )
+ .clip(RoundedCornerShape(30.dp))
+ .background(background),
+ contentAlignment = Alignment.Center,
+ ) {
+ JobisText(
+ modifier = modifier.padding(
+ horizontal = 16.dp,
+ vertical = 6.dp,
+ ),
+ text = major,
+ style = JobisTypography.Body,
+ color = textColor,
)
}
}
diff --git a/feature/interests/src/main/java/team/retum/jobis/interests/viewmodel/InterestsViewmodel.kt b/feature/interests/src/main/java/team/retum/jobis/interests/viewmodel/InterestsViewmodel.kt
new file mode 100644
index 000000000..0f707d4ac
--- /dev/null
+++ b/feature/interests/src/main/java/team/retum/jobis/interests/viewmodel/InterestsViewmodel.kt
@@ -0,0 +1,139 @@
+package team.retum.jobis.interests.viewmodel
+
+import androidx.compose.runtime.Immutable
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import team.retum.common.base.BaseViewModel
+import team.retum.common.enums.CodeType
+import team.retum.network.model.request.interests.InterestsToggleRequest
+import team.retum.usecase.entity.CodesEntity
+import team.retum.usecase.entity.interests.InterestsEntity
+import team.retum.usecase.entity.interests.InterestsRecruitmentsEntity
+import team.retum.usecase.usecase.code.FetchCodeUseCase
+import team.retum.usecase.usecase.interests.FetchInterestsSearchRecruitmentsUseCase
+import team.retum.usecase.usecase.interests.FetchInterestsUseCase
+import team.retum.usecase.usecase.interests.SetInterestsToggleUseCase
+import javax.inject.Inject
+
+@HiltViewModel
+internal class InterestsViewmodel @Inject constructor(
+ private val fetchCodeUseCase: FetchCodeUseCase,
+ private val fetchInterestsUseCase: FetchInterestsUseCase,
+ private val fetchInterestsSearchRecruitmentsUseCase: FetchInterestsSearchRecruitmentsUseCase,
+ private val setInterestsToggleUseCase: SetInterestsToggleUseCase,
+) : BaseViewModel(InterestsState.getInitialState()) {
+
+ init {
+ fetchInterests()
+ fetchInterestsSearchRecruitments()
+ fetchCodes()
+ }
+
+ private fun fetchCodes() {
+ viewModelScope.launch(Dispatchers.IO) {
+ fetchCodeUseCase(
+ keyword = null,
+ type = CodeType.JOB,
+ parentCode = null,
+ ).onSuccess {
+ setState {
+ state.value.copy(
+ majorList = it.codes,
+ )
+ }
+ }
+ }
+ }
+
+ private fun fetchInterests() {
+ viewModelScope.launch(Dispatchers.IO) {
+ fetchInterestsUseCase().onSuccess {
+ setState {
+ state.value.copy(
+ studentName = it.studentName,
+ interestsMajorList = it.interests,
+ )
+ }
+ }
+ }
+ }
+
+ private fun fetchInterestsSearchRecruitments() {
+ viewModelScope.launch(Dispatchers.IO) {
+ fetchInterestsSearchRecruitmentsUseCase().onSuccess {
+ setState {
+ state.value.copy(
+ interestsRecruitments = it,
+ )
+ }
+ }
+ }
+ }
+
+ internal fun patchInterestsMajor() {
+ viewModelScope.launch(Dispatchers.IO) {
+ setInterestsToggleUseCase(
+ codes = InterestsToggleRequest(codeIds = state.value.selectedMajorCodes.toMutableList()),
+ ).onSuccess {
+ postSideEffect(sideEffect = InterestsSideEffect.MoveToInterestsComplete(state.value.studentName))
+ }.onFailure {
+ postSideEffect(sideEffect = InterestsSideEffect.PatchMajorFail)
+ }
+ }
+ }
+
+ internal fun setMajor(majorId: Long) {
+ viewModelScope.launch {
+ val currentSelected = state.value.selectedMajorCodes.toMutableList()
+ if (currentSelected.contains(majorId)) {
+ currentSelected.remove(majorId)
+ } else {
+ currentSelected.add(majorId)
+ }
+ setState {
+ state.value.copy(
+ selectedMajorCodes = currentSelected,
+ selectedMajorCount = currentSelected.size,
+ )
+ }
+ }
+ }
+
+ internal fun setButtonState(buttonState: Boolean) {
+ setState {
+ state.value.copy(
+ buttonEnable = buttonState,
+ )
+ }
+ }
+}
+
+@Immutable
+internal data class InterestsState(
+ val studentName: String,
+ val majorList: List,
+ val interestsMajorList: List,
+ val interestsRecruitments: InterestsRecruitmentsEntity?,
+ val selectedMajorCodes: List,
+ val selectedMajorCount: Int,
+ val buttonEnable: Boolean,
+) {
+ companion object {
+ fun getInitialState() = InterestsState(
+ studentName = "",
+ majorList = emptyList(),
+ interestsMajorList = emptyList(),
+ interestsRecruitments = null,
+ selectedMajorCodes = emptyList(),
+ selectedMajorCount = 0,
+ buttonEnable = false,
+ )
+ }
+}
+
+internal sealed class InterestsSideEffect {
+ data class MoveToInterestsComplete(val studentName: String) : InterestsSideEffect()
+ data object PatchMajorFail : InterestsSideEffect()
+}
diff --git a/feature/interests/src/main/res/values/strings.xml b/feature/interests/src/main/res/values/strings.xml
index 60967a638..1ee4e9d6d 100644
--- a/feature/interests/src/main/res/values/strings.xml
+++ b/feature/interests/src/main/res/values/strings.xml
@@ -1,5 +1,11 @@
- 관심사 설정
- 검색어를 입력해주세요
+ 관심 분야 설정
+ %s님의\n관심사를 선택해주세요
+ %s님의\n관심사를 확인했어요!
+ 관심 분야를 선택해주세요!
+ %s개 선택
+ 관심사에 맞는 모집 의뢰서가 업로드되면 \n알림을 드립니다!
+
+ 관심 분야를 등록하지 못했어요
diff --git a/feature/landing/src/main/java/team/retum/landing/LandingViewModel.kt b/feature/landing/src/main/java/team/retum/landing/LandingViewModel.kt
new file mode 100644
index 000000000..a7b68e0e6
--- /dev/null
+++ b/feature/landing/src/main/java/team/retum/landing/LandingViewModel.kt
@@ -0,0 +1,3 @@
+package team.retum.landing
+
+class LandingViewModel
diff --git a/feature/mypage/src/main/java/team/retum/jobis/navigation/MyPageNavigation.kt b/feature/mypage/src/main/java/team/retum/jobis/navigation/MyPageNavigation.kt
index 44eb27503..1012b8de8 100644
--- a/feature/mypage/src/main/java/team/retum/jobis/navigation/MyPageNavigation.kt
+++ b/feature/mypage/src/main/java/team/retum/jobis/navigation/MyPageNavigation.kt
@@ -30,5 +30,11 @@ fun NavGraphBuilder.myPage(
}
fun NavController.navigateToMyPage() {
- navigate(NAVIGATION_MY_PAGE)
+ navigate(NAVIGATION_MY_PAGE) {
+ popUpTo(0) {
+ saveState = true
+ }
+ launchSingleTop = true
+ restoreState = true
+ }
}
diff --git a/feature/mypage/src/main/java/team/retum/jobis/ui/MyPageScreen.kt b/feature/mypage/src/main/java/team/retum/jobis/ui/MyPageScreen.kt
index 244d4b235..1451dfc61 100644
--- a/feature/mypage/src/main/java/team/retum/jobis/ui/MyPageScreen.kt
+++ b/feature/mypage/src/main/java/team/retum/jobis/ui/MyPageScreen.kt
@@ -235,7 +235,7 @@ private fun MyPageScreen(
imageResource = painterResource(id = JobisIcon.Code),
description = "interest field icon",
contentTitle = stringResource(id = R.string.interest_field),
- onClick = showUpdateLaterToast,
+ onClick = onSelectInterestClick,
iconColor = JobisTheme.colors.onPrimary,
),
ListItemInfo(