KLingo Project Documentation 1.0.0
Unreal Engine 5.6 C++ Project Documentation
로딩중...
검색중...
일치하는것 없음
UPopup_InterviewHello.cpp
이 파일의 문서화 페이지로 가기
1// Copyright (c) 2025 Doppleddiggong. All rights reserved.
2// Unauthorized copying, modification, or distribution of this file,
3// via any medium is strictly prohibited. Proprietary and confidential.
4
6
7#include "APlayerControl.h"
8#include "UPopupManager.h"
10#include "ULingoGameHelper.h"
11#include "UDialogManager.h"
12#include "UBroadcastManager.h"
13#include "GameLogging.h"
14#include "UImageButton.h"
15#include "UTextureButton.h"
16#include "Components/TextBlock.h"
17#include "Components/EditableText.h"
18#include "Components/ProgressBar.h"
19#include "Components/CheckBox.h"
20#include "UConfigLibrary.h"
21
23{
24 Super::NativeConstruct();
25
26 // 버튼 이벤트 바인딩
28 {
29 Button_PrevArrow->OnButtonClickedEvent.RemoveDynamic(this, &UPopup_InterviewHello::OnClickPrevArrow);
30 Button_PrevArrow->OnButtonClickedEvent.AddDynamic(this, &UPopup_InterviewHello::OnClickPrevArrow);
31 }
32
34 {
35 Button_NextArrow->OnButtonClickedEvent.RemoveDynamic(this, &UPopup_InterviewHello::OnClickNextArrow);
36 Button_NextArrow->OnButtonClickedEvent.AddDynamic(this, &UPopup_InterviewHello::OnClickNextArrow);
37 }
38
39 if (Btn_Next)
40 {
41 Btn_Next->OnButtonClickedEvent.RemoveDynamic(this, &UPopup_InterviewHello::OnClickNext);
42 Btn_Next->OnButtonClickedEvent.AddDynamic(this, &UPopup_InterviewHello::OnClickNext);
43 }
44
45 if (Btn_Submit)
46 {
47 Btn_Submit->OnButtonClickedEvent.RemoveDynamic(this, &UPopup_InterviewHello::OnClickSubmit);
48 Btn_Submit->OnButtonClickedEvent.AddDynamic(this, &UPopup_InterviewHello::OnClickSubmit);
49 }
50
51 if (Btn_Close)
52 {
53 Btn_Close->OnButtonClickedEvent.RemoveDynamic(this, &UPopup_InterviewHello::OnClickClose);
54 Btn_Close->OnButtonClickedEvent.AddDynamic(this, &UPopup_InterviewHello::OnClickClose);
55 }
56
58 {
59 Button_CheckToday->OnCheckStateChanged.RemoveDynamic(this, &UPopup_InterviewHello::OnCheckToday);
60 Button_CheckToday->OnCheckStateChanged.AddDynamic(this, &UPopup_InterviewHello::OnCheckToday);
61 }
62
63 // 답변 입력란 텍스트 변경 이벤트 바인딩
64 if (Edit_Answer)
65 {
66 Edit_Answer->OnTextChanged.RemoveDynamic(this, &UPopup_InterviewHello::OnAnswerTextChanged);
67 Edit_Answer->OnTextChanged.AddDynamic(this, &UPopup_InterviewHello::OnAnswerTextChanged);
68 }
69}
70
72{
73 // 질문 데이터 저장
74 SavedQuestions = InterviewData.Questions;
75
76 // 답변 임시 저장 배열 초기화
77 TempAnswers.SetNum(SavedQuestions.Num());
78 for (int32 i = 0; i < TempAnswers.Num(); ++i)
79 {
80 TempAnswers[i] = TEXT("");
81 }
82
83 // 첫 번째 질문으로 초기화
85
86 // UI 업데이트
87 RefreshUI();
91}
92
94{
95 // 유효성 체크
96 if (!SavedQuestions.IsValidIndex(CurQuestionIndex))
97 {
98 PRINTLOG(TEXT("[UPopup_InterviewHello] RefreshUI - Invalid CurrentQuestionIndex: %d"), CurQuestionIndex);
99 return;
100 }
101
103
104 // 질문 텍스트 업데이트
105 if (TXt_Question)
106 {
107 TXt_Question->SetText(FText::FromString(CurrentQuestion.Eng));
108 }
109
110 // 현재 질문의 답변 불러오기
112}
113
115{
116 // Prev 버튼: 첫 번째 질문이 아닐 때만 표시
117 Button_PrevArrow->SetVisibility( CurQuestionIndex > 0 ? ESlateVisibility::Visible : ESlateVisibility::Hidden );
118
119 // Next 버튼: 마지막 질문이 아닐 때만 표시
120 Button_NextArrow->SetVisibility( CurQuestionIndex < SavedQuestions.Num() - 1 ? ESlateVisibility::Visible : ESlateVisibility::Hidden );
121}
122
124{
125 // 모든 답변이 채워졌는지 확인
126 bool bAllAnswered = true;
127 for (const FString& Answer : TempAnswers)
128 {
129 if (Answer.TrimStartAndEnd().IsEmpty())
130 {
131 bAllAnswered = false;
132 break;
133 }
134 }
135
136 // 마지막 질문인지 확인
137 bool bIsLastQuestion = (CurQuestionIndex == SavedQuestions.Num() - 1);
138
139 // 버튼 표시/숨김 및 활성화 상태 전환
140 if (bIsLastQuestion)
141 {
142 // 마지막 질문이면 무조건 Submit 표시
143 if (Btn_Submit)
144 {
145 Btn_Submit->SetVisibility(ESlateVisibility::Visible);
146 // 모든 답변이 완료되었을 때만 활성화
147 Btn_Submit->SetIsEnabled(bAllAnswered);
148 }
149
150 if (Btn_Next)
151 {
152 Btn_Next->SetVisibility(ESlateVisibility::Hidden);
153 }
154 }
155 else
156 {
157 // 마지막 질문이 아니면 Next 표시
158 if (Btn_Next)
159 {
160 Btn_Next->SetVisibility(ESlateVisibility::Visible);
161 }
162 if (Btn_Submit)
163 {
164 Btn_Submit->SetVisibility(ESlateVisibility::Hidden);
165 }
166 }
167}
168
170{
171 if (ProgressBar_Question && SavedQuestions.Num() > 0)
172 {
173 float Progress = static_cast<float>(CurQuestionIndex + 1) / static_cast<float>(SavedQuestions.Num());
174 ProgressBar_Question->SetPercent(Progress);
175 }
176}
177
179{
180 if (!Edit_Answer || !TempAnswers.IsValidIndex(CurQuestionIndex))
181 return;
182
183 TempAnswers[CurQuestionIndex] = Edit_Answer->GetText().ToString();
184}
185
187{
188 if (!Edit_Answer || !TempAnswers.IsValidIndex(CurQuestionIndex))
189 return;
190
191 Edit_Answer->SetText(FText::FromString(TempAnswers[CurQuestionIndex]));
192}
193
195{
196 // 현재 답변 저장
198
199 // 인덱스 감소
200 if (CurQuestionIndex > 0)
201 {
203
204 // UI 업데이트
205 RefreshUI();
209 }
210}
211
213{
214 // 현재 답변 저장
216
217 // 인덱스 증가
218 if (CurQuestionIndex < SavedQuestions.Num() - 1)
219 {
221
222 // UI 업데이트
223 RefreshUI();
227 }
228}
229
234
236{
237 // 마지막 답변 저장
239
240 // 모든 답변 검증
241 for (int32 i = 0; i < TempAnswers.Num(); ++i)
242 {
243 if (TempAnswers[i].TrimStartAndEnd().IsEmpty())
244 {
245 // 빈 답변이 있으면 Toast 메시지 표시
246 if (UDialogManager* DialogMgr = UDialogManager::Get(GetWorld()))
247 {
248 FString Message = FString::Printf( TEXT("Question %d is not answered. Please fill in all answers."), i + 1 );
249 DialogMgr->ShowToast(Message);
250 }
251 return;
252 }
253 }
254
255 // 답변 데이터 생성
256 TArray<FInterviewAnswerData> AnswerDataList;
257 const int32 UserId = ULingoGameHelper::GetUserId(GetWorld());
258
259 for (int32 i = 0; i < SavedQuestions.Num(); ++i)
260 {
261 FInterviewAnswerData AnswerData;
262 AnswerData.interview_id = SavedQuestions[i].Id;
263 AnswerData.answer = TempAnswers[i];
264 AnswerData.user_id = UserId;
265 AnswerDataList.Add(AnswerData);
266 }
267
268 // 네트워크 전송
269 if (UKLingoNetworkSystem* NetworkSystem = UKLingoNetworkSystem::Get(GetWorld()))
270 {
272 Request.answer = AnswerDataList;
273
274 NetworkSystem->RequestInterviewAnswer(
275 Request,
276 FResponseInterviewAnswerDelegate::CreateUObject(
278 )
279 );
280 }
281}
282
284{
285 // PopupManager를 통해 팝업 닫기
286 if (UPopupManager* PopupMgr = UPopupManager::Get(GetWorld()))
287 {
288 PopupMgr->HideCurrentPopup();
289
290 if (APlayerController* PC = GetWorld()->GetFirstPlayerController())
291 {
292 if (APlayerControl* PlayerControl = Cast<APlayerControl>(PC))
293 {
294 if (!PlayerControl->ShouldSkipTutorial())
295 {
296 // 튜토리얼 여부 화면 띄우기
297 PopupMgr->ShowPopup(EPopupType::AskTutorial);
298 }
299 }
300 }
301 }
302}
303
305{
306 // 체크박스 상태만 저장 (실제 Config 저장은 Submit 성공 시)
307 bCheckTodayDoNotShow = bIsChecked;
308}
309
311{
312 // 현재 질문의 답변을 실시간으로 TempAnswers에 저장
313 if (TempAnswers.IsValidIndex(CurQuestionIndex))
314 {
315 TempAnswers[CurQuestionIndex] = Text.ToString();
316 }
317
318 // Submit 버튼 상태 업데이트 (마지막 질문에서만 영향)
320}
321
323{
324 if (bWasSuccessful)
325 {
326 PRINTLOG(TEXT("[UPopup_InterviewHello] Interview Answer SUCCESS"));
327
328 // "Today do not show" 체크되어 있으면 오늘 날짜 저장
330 {
331 const int32 UserId = ULingoGameHelper::GetUserId(GetWorld());
332
333 // 현재 날짜를 "YYYY-MM-DD" 형식으로 저장
334 const FDateTime Now = FDateTime::Now();
335 const FString TodayDate = FString::Printf(TEXT("%04d-%02d-%02d"), Now.GetYear(), Now.GetMonth(), Now.GetDay());
336
337 UConfigLibrary::SetUserString(UserId, TEXT("InterviewSkipDate"), TodayDate);
338
339 if (auto DM = UDialogManager::Get(GetWorld()))
340 {
341 DM->ShowToast(TEXT("Do not show again setting complete"));
342 }
343
344 PRINTLOG(TEXT("[UPopup_InterviewHello] 'Today do not show' saved for User %d, Date: %s"), UserId, *TodayDate);
345 }
346
347 // 성공 시 튜터 메시지 표시
348 if (auto BM = UBroadcastManager::Get(GetWorld()))
349 {
350 BM->SendTutorMessage( FText::FromString(TEXT("Interview answers submitted successfully!")) );
351 }
352
353 // 팝업 닫기
354 if (UPopupManager* PopupMgr = UPopupManager::Get(GetWorld()))
355 {
356 PopupMgr->HideCurrentPopup();
357
358 // 튜토리얼 완료 여부 확인 후 조건부로 AskTutorial 표시
359 if (APlayerController* PC = GetWorld()->GetFirstPlayerController())
360 {
361 if (APlayerControl* PlayerControl = Cast<APlayerControl>(PC))
362 {
363 if (!PlayerControl->ShouldSkipTutorial())
364 {
365 // 튜토리얼을 아직 안 했으면 AskTutorial 팝업 표시
366 PopupMgr->ShowPopup(EPopupType::AskTutorial);
367 }
368 }
369 }
370 }
371 }
372 else
373 {
374 PRINTLOG(TEXT("[UPopup_InterviewHello] Interview Answer FAILED"));
375 }
376}
377
378bool UPopup_InterviewHello::ShouldSkipInterviewToday(const UObject* WorldContextObject)
379{
380 const int32 UserId = ULingoGameHelper::GetUserId(WorldContextObject);
381
382 // 저장된 날짜 읽기
383 const FString SavedDate = UConfigLibrary::GetUserString(UserId, TEXT("InterviewSkipDate"), TEXT(""));
384 if (SavedDate.IsEmpty())
385 {
386 // 저장된 날짜 없음 → 보여줌
387 return false;
388 }
389
390 // 오늘 날짜 생성
391 const FDateTime Now = FDateTime::Now();
392 const FString TodayDate = FString::Printf(TEXT("%04d-%02d-%02d"), Now.GetYear(), Now.GetMonth(), Now.GetDay());
393
394 // 날짜 비교: 저장된 날짜 == 오늘 날짜 → Skip
395 if (SavedDate == TodayDate)
396 {
397 PRINTLOG(TEXT("[UPopup_InterviewHello] Skipping Interview today for User %d (Saved Date: %s)"), UserId, *SavedDate);
398 return true; // 오늘은 건너뜀
399 }
400
401 // 날짜가 다르면 → 보여줌
402 return false;
403}
APlayerControl 선언에 대한 Doxygen 주석을 제공합니다.
YiSan 전반에서 사용하는 공용 인터페이스를 선언합니다.
#define PRINTLOG(fmt,...)
Definition GameLogging.h:30
GConfig 래퍼 설정 관리 시스템
UDialogManager 클래스를 선언합니다.
KLingo API 요청을 담당하는 서브시스템을 선언합니다.
static FString GetUserString(int32 UserId, const FString &Key, const FString &DefaultValue=TEXT(""))
유저별 문자열 설정 읽기
static void SetUserString(int32 UserId, const FString &Key, const FString &Value, bool bAutoSave=true)
유저별 문자열 설정 저장
토스트 메시지와 같은 간단한 다이얼로그 위젯의 표시를 관리하는 LocalPlayer 서브시스템입니다.
KLingo 서버와의 HTTP 요청을 중재하는 게임 인스턴스 서브시스템입니다.
static int32 GetUserId(const UObject *WorldContextObject)
팝업 관리자
bool bCheckTodayDoNotShow
"Today do not show" 체크박스 상태 (Submit 시 사용)
TObjectPtr< class UEditableText > Edit_Answer
플레이어 답변 입력란
TObjectPtr< class UProgressBar > ProgressBar_Question
질문 진행률 표시
TObjectPtr< class UTextBlock > TXt_Question
현재 질문 텍스트 (영어)
int32 CurQuestionIndex
현재 표시 중인 질문 인덱스 (0-based)
void RefreshUI()
현재 질문과 답변을 UI에 반영
void OnClickPrevArrow()
이전 질문으로 이동 (좌측 화살표)
static bool ShouldSkipInterviewToday(const UObject *WorldContextObject)
오늘 Interview 팝업을 건너뛸지 확인
void OnCheckToday(bool bIsChecked)
"Today do not show" 체크박스 변경
void LoadCurrentAnswer()
TempAnswers에서 현재 질문의 답변을 불러와 Edit_Answer에 표시
void InitPopup(const FResponseInterviewHello &InterviewData)
팝업 초기화
TObjectPtr< class UImageButton > Btn_Submit
Submit 버튼 (중앙) - Next와 전환
void RefreshProgressBar()
Progress Bar 업데이트
void OnAnswerTextChanged(const FText &Text)
답변 입력란 텍스트 변경 시 호출 (실시간 Submit 버튼 상태 업데이트)
void RefreshSubmitButtonState()
Submit 버튼 활성화 상태 업데이트
void OnClickSubmit()
Submit 버튼 클릭
void SaveCurrentAnswer()
현재 질문의 답변을 TempAnswers에 저장
TObjectPtr< class UTextureButton > Button_NextArrow
다음 질문 버튼 (▶)
TArray< FString > TempAnswers
사용자가 입력한 답변 임시 저장 배열 (Questions.Num()과 크기 동일)
TObjectPtr< class UTextureButton > Button_PrevArrow
이전 질문 버튼 (◀)
virtual void NativeConstruct() override
TObjectPtr< class UTextureButton > Btn_Close
팝업 닫기 버튼 (우측 상단)
TObjectPtr< class UCheckBox > Button_CheckToday
"Today do not show" 체크박스
void OnClickNext()
Next 버튼 클릭 (중앙 버튼)
TObjectPtr< class UImageButton > Btn_Next
Next 버튼 (중앙, 초록색) - Submit과 전환
void OnClickClose()
닫기 버튼 클릭
void OnClickNextArrow()
다음 질문으로 이동 (우측 화살표)
void OnResponseInterviewAnswer(FResponseInterviewAnswer &ResponseData, bool bWasSuccessful)
Interview Answer API 응답 처리
TArray< FInterviewQuestionData > SavedQuestions
저장된 인터뷰 질문 데이터
void RefreshArrowButton()
Prev/Next 화살표 버튼 표시/숨김 처리
TArray< FInterviewAnswerData > answer
TArray< FInterviewQuestionData > Questions