Skip to content

Commit 611f5b5

Browse files
authored
feat - ai 요약 기능 추가 (#70)
* feat: 사용자 성별 저장 및 선택 가능 여부 추가 * feat: ai 요약 api 연동 * feat: 선호, 비선호 직업 추가 * feat: 내 ai요약정보 api 연동 * fix: 매칭 조회 실패 토스트 제거
1 parent 3b8ef89 commit 611f5b5

File tree

10 files changed

+304
-25
lines changed

10 files changed

+304
-25
lines changed
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.apptive.japkor.data.api
22

3+
import com.apptive.japkor.data.model.AiSummaryResponse
34
import com.apptive.japkor.data.model.MatchingResponse
45
import retrofit2.Call
56
import retrofit2.http.GET
@@ -8,12 +9,12 @@ import retrofit2.http.Path
89
import com.apptive.japkor.data.model.MyStatusResponse
910

1011
interface MatchingService {
12+
@GET("members/ai-summary")
13+
fun getMyAiSummary(): Call<AiSummaryResponse>
14+
1115
@GET("members/matchings/female")
1216
fun getFemaleMatchings(): Call<List<MatchingResponse>>
1317

1418
@POST("members/matchings/{matchingId}/select")
15-
fun selectMatching(@Path("matchingId") matchingId: Long): Call<Void>
16-
17-
@GET("members/status")
18-
suspend fun getMyStatus(): MyStatusResponse
19+
fun femaleSelectMatching(@Path("matchingId") matchingId: Long): Call<Void>
1920
}

app/src/main/java/com/apptive/japkor/data/local/DataStoreManager.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class DataStoreManager(private val context: Context) {
1616
val KEY_NAME = stringPreferencesKey("name")
1717
val KEY_TOKEN = stringPreferencesKey("token")
1818
val KEY_STATUS = stringPreferencesKey("status")
19+
val KEY_GENDER = stringPreferencesKey("gender")
1920
val KEY_REMEMBERED_EMAIL = stringPreferencesKey("remembered_email")
2021
val KEY_LANGUAGE = stringPreferencesKey("app_language")
2122
val KEY_FCM_TOKEN = stringPreferencesKey("fcm_token")
@@ -39,7 +40,8 @@ class DataStoreManager(private val context: Context) {
3940
"memberId" to (it[KEY_MEMBER_ID] ?: -1),
4041
"name" to (it[KEY_NAME] ?: ""),
4142
"token" to (it[KEY_TOKEN] ?: ""),
42-
"status" to (it[KEY_STATUS] ?: "")
43+
"status" to (it[KEY_STATUS] ?: ""),
44+
"gender" to (it[KEY_GENDER] ?: "")
4345
)
4446
}
4547

@@ -57,6 +59,14 @@ class DataStoreManager(private val context: Context) {
5759
}
5860
}
5961

62+
suspend fun saveUserGender(gender: String) {
63+
context.dataStore.edit { prefs ->
64+
prefs[KEY_GENDER] = gender
65+
}
66+
}
67+
68+
fun getUserGender() = context.dataStore.data.map { it[KEY_GENDER] ?: "" }
69+
6070
suspend fun saveFcmToken(token: String) {
6171
context.dataStore.edit { prefs ->
6272
prefs[KEY_FCM_TOKEN] = token
@@ -93,6 +103,7 @@ class DataStoreManager(private val context: Context) {
93103
prefs.remove(KEY_NAME)
94104
prefs.remove(KEY_TOKEN)
95105
prefs.remove(KEY_STATUS)
106+
prefs.remove(KEY_GENDER)
96107
}
97108
}
98109

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.apptive.japkor.data.model
2+
3+
import com.google.gson.annotations.SerializedName
4+
5+
data class AiSummaryResponse(
6+
@SerializedName(value = "memberId", alternate = ["member_id"])
7+
val memberId: Long,
8+
@SerializedName(value = "name")
9+
val name: String,
10+
@SerializedName(value = "aiSummaryKo", alternate = ["ai_summary_ko", "aiSummary", "ai_summary"])
11+
val aiSummaryKo: String?,
12+
@SerializedName(value = "aiSummaryJa", alternate = ["ai_summary_ja"])
13+
val aiSummaryJa: String?
14+
)

app/src/main/java/com/apptive/japkor/ui/localization/Localization.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ object AppLocalizer {
203203
"신청이 완료되었습니다" to "申請が完了しました",
204204
"심사는 약 일주일 정도 걸립니다.\n심사가 완료되면 매칭이 시작됩니다!" to
205205
"審査は約1週間かかります。\n審査が完了するとマッチングが始まります!",
206+
"내 AI 요약본" to "私のAI要約",
207+
"AI 요약본을 불러오는 중입니다." to "AI要約を読み込み中です。",
208+
"AI 요약본을 불러오지 못했습니다." to "AI要約を読み込めませんでした。",
206209
"확인했어요" to "確認しました",
207210
"선호 직업은 최대 3개까지 선택 가능합니다." to "希望職業は最大3つまで選択できます。",
208211
"비선호 직업은 최대 3개까지 선택 가능합니다." to "避けたい職業は最大3つまで選択できます。",

app/src/main/java/com/apptive/japkor/ui/main/MainRouteScreen.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ private object HomeRoute {
6767
const val Setting = "home_setting"
6868
}
6969

70+
private const val FEMALE_GENDER = "JAPANESE_FEMALE"
71+
7072
@Composable
7173
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
7274
fun MainRouteScreen(
@@ -109,6 +111,8 @@ fun MainRouteScreen(
109111
val context = LocalContext.current
110112
val dataStoreManager = remember { DataStoreManager(context) }
111113
val coroutineScope = rememberCoroutineScope()
114+
val gender by dataStoreManager.getUserGender().collectAsState(initial = "")
115+
val canSelectMatching = gender == FEMALE_GENDER
112116

113117
BackHandler(enabled = isMainScreen && selectedMatching != null) {
114118
viewModel.hideDetails()
@@ -231,6 +235,7 @@ fun MainRouteScreen(
231235
HomeScreen(
232236
uiState = uiState,
233237
pagerState = pagerState,
238+
canSelectMatching = canSelectMatching,
234239
onShowDetails = { viewModel.showDetails(it) },
235240
onNoMatch = { viewModel.noMatchSelected() },
236241
onConfirm = { viewModel.selectMatching(it) },

app/src/main/java/com/apptive/japkor/ui/main/home/HomeScreen.kt

Lines changed: 100 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import kotlin.math.absoluteValue
4444
fun HomeScreen(
4545
uiState: HomeUiState,
4646
pagerState: PagerState,
47+
canSelectMatching: Boolean,
4748
onShowDetails: (MatchingResponse) -> Unit,
4849
onNoMatch: () -> Unit,
4950
onConfirm: (Long) -> Unit,
@@ -59,11 +60,22 @@ fun HomeScreen(
5960
selectedMatching != null -> {
6061
MatchingDetailContent(
6162
matching = selectedMatching,
62-
onConfirm = { onConfirm(selectedMatching.matchingId) }
63+
canSelectMatching = canSelectMatching,
64+
onConfirm = {
65+
if (canSelectMatching) {
66+
onConfirm(selectedMatching.matchingId)
67+
}
68+
}
6369
)
6470
}
6571
uiState.isWaiting || matchings.isEmpty() -> {
66-
WaitingContent(modifier = Modifier.fillMaxSize())
72+
WaitingContent(
73+
modifier = Modifier.fillMaxSize(),
74+
aiSummaryKo = uiState.aiSummaryKo,
75+
aiSummaryJa = uiState.aiSummaryJa,
76+
isAiSummaryLoading = uiState.isAiSummaryLoading,
77+
aiSummaryError = uiState.aiSummaryError
78+
)
6779
}
6880
else -> {
6981
MatchingCarouselContent(
@@ -78,19 +90,97 @@ fun HomeScreen(
7890
}
7991

8092
@Composable
81-
private fun WaitingContent(modifier: Modifier = Modifier) {
82-
Box(
83-
modifier = modifier,
84-
contentAlignment = Alignment.Center
93+
private fun WaitingContent(
94+
modifier: Modifier = Modifier,
95+
aiSummaryKo: String?,
96+
aiSummaryJa: String?,
97+
isAiSummaryLoading: Boolean,
98+
aiSummaryError: String?
99+
) {
100+
val summaryKo = aiSummaryKo?.takeIf { it.isNotBlank() }
101+
val summaryJa = aiSummaryJa?.takeIf { it.isNotBlank() }
102+
Column(
103+
modifier = modifier
104+
.padding(horizontal = 24.dp),
105+
horizontalAlignment = Alignment.CenterHorizontally,
106+
verticalArrangement = Arrangement.Center
85107
) {
86108
CustomText(
87109
text = "매칭 진행 중입니다..",
88110
type = CustomTextType.body,
89111
color = CustomColor.gray400
90112
)
113+
Spacer(modifier = Modifier.height(16.dp))
114+
if (summaryKo != null || summaryJa != null) {
115+
Card(
116+
shape = RoundedCornerShape(16.dp),
117+
colors = CardDefaults.cardColors(containerColor = CustomColor.gray100),
118+
modifier = Modifier.fillMaxWidth()
119+
) {
120+
Column(
121+
modifier = Modifier
122+
.fillMaxWidth()
123+
.padding(16.dp),
124+
horizontalAlignment = Alignment.CenterHorizontally
125+
) {
126+
CustomText(
127+
text = "내 AI 요약본",
128+
type = CustomTextType.label,
129+
color = CustomColor.gray400
130+
)
131+
Spacer(modifier = Modifier.height(8.dp))
132+
if (summaryKo != null) {
133+
CustomText(
134+
text = "한국어",
135+
type = CustomTextType.label,
136+
color = CustomColor.gray400
137+
)
138+
Spacer(modifier = Modifier.height(4.dp))
139+
CustomText(
140+
text = summaryKo,
141+
type = CustomTextType.body,
142+
color = CustomColor.black,
143+
textAlign = TextAlign.Center
144+
)
145+
}
146+
if (summaryKo != null && summaryJa != null) {
147+
Spacer(modifier = Modifier.height(12.dp))
148+
}
149+
if (summaryJa != null) {
150+
CustomText(
151+
text = "일본어",
152+
type = CustomTextType.label,
153+
color = CustomColor.gray400
154+
)
155+
Spacer(modifier = Modifier.height(4.dp))
156+
CustomText(
157+
text = summaryJa,
158+
type = CustomTextType.body,
159+
color = CustomColor.black,
160+
textAlign = TextAlign.Center
161+
)
162+
}
163+
}
164+
}
165+
} else if (isAiSummaryLoading) {
166+
CustomText(
167+
text = "AI 요약본을 불러오는 중입니다.",
168+
type = CustomTextType.body,
169+
color = CustomColor.gray400,
170+
textAlign = TextAlign.Center
171+
)
172+
} else if (!aiSummaryError.isNullOrBlank()) {
173+
CustomText(
174+
text = aiSummaryError,
175+
type = CustomTextType.body,
176+
color = CustomColor.gray400,
177+
textAlign = TextAlign.Center
178+
)
179+
}
91180
}
92181
}
93182

183+
94184
@Composable
95185
@OptIn(ExperimentalFoundationApi::class)
96186
private fun MatchingCarouselContent(
@@ -254,6 +344,7 @@ private fun PagerIndicator(total: Int, current: Int) {
254344
@Composable
255345
private fun MatchingDetailContent(
256346
matching: MatchingResponse,
347+
canSelectMatching: Boolean,
257348
onConfirm: () -> Unit
258349
) {
259350
Column(
@@ -275,11 +366,13 @@ private fun MatchingDetailContent(
275366
}
276367
Button(
277368
onClick = onConfirm,
369+
enabled = canSelectMatching,
278370
modifier = Modifier
279371
.fillMaxWidth()
280372
.height(50.dp),
281373
colors = ButtonDefaults.buttonColors(
282-
containerColor = CustomColor.primary600
374+
containerColor = CustomColor.primary600,
375+
disabledContainerColor = CustomColor.primary300
283376
),
284377
shape = RoundedCornerShape(16.dp)
285378
) {

0 commit comments

Comments
 (0)