-
Notifications
You must be signed in to change notification settings - Fork 1
Feature/온보딩 프로필신고및차단 #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
The head ref may contain hidden characters: "feature/\uC628\uBCF4\uB529-\uD504\uB85C\uD544\uC2E0\uACE0\uBC0F\uCC28\uB2E8"
Changes from 9 commits
07799ab
32fa6ae
b89c89c
3ab683b
7019f20
3c8a7b1
1a6f511
f08afb6
fb07c17
e364f12
a61d912
533b736
d4a2755
0cd1ac6
adfaa89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package umc.onairmate.data.api | ||
|
|
||
| import retrofit2.http.Body | ||
| import retrofit2.http.GET | ||
| import retrofit2.http.Header | ||
| import retrofit2.http.PUT | ||
| import retrofit2.http.Path | ||
| import umc.onairmate.data.model.response.NicknameResponse | ||
| import umc.onairmate.data.model.response.RawDefaultResponse | ||
|
|
||
| interface NicknameService { | ||
| @GET("auth/check-nickname/{nickname}") | ||
| suspend fun checkNickname( | ||
| @Path("nickname") nickname: String | ||
| ): NicknameResponse | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package umc.onairmate.data.model.entity | ||
|
|
||
| import com.google.gson.annotations.SerializedName | ||
|
|
||
| data class NicknameData( | ||
| @SerializedName("available") | ||
| val available: Boolean, | ||
|
|
||
| @SerializedName("message") | ||
| val message: String | ||
| ) |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,12 @@ | ||||||||||||||||||||||||||||||
| package umc.onairmate.data.model.response | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain패키지-디렉터리 대소문자 일치 검증 요청 해당 파일 경로는 다음 스크립트로 확인 가능합니다: 🏁 Script executed: #!/bin/bash
echo "[NicknameResponse.kt package 확인]"
fd -a 'NicknameResponse.kt' | while read -r f; do
echo "=== $f"
rg -n '^package ' "$f" -n || true
doneLength of output: 483 디렉터리 이름 소문자 일치 필요: JVM 패키지-디렉터리 매핑에서 대소문자 불일치는 빌드 또는 런타임 오류를 유발할 수 있습니다.
다른 패키지 경로에도 동일한 대소문자 불일치가 없는지 추가 확인 바랍니다. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import com.google.gson.annotations.SerializedName | ||||||||||||||||||||||||||||||
| import umc.onairmate.data.model.entity.NicknameData | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| class NicknameResponse ( | ||||||||||||||||||||||||||||||
| @SerializedName("success") | ||||||||||||||||||||||||||||||
| val success: Boolean, | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| @SerializedName("data") | ||||||||||||||||||||||||||||||
| val data: NicknameData | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion DTO는 data class 사용 권장 동등성/복사/디버깅에 유리하도록 data class로 선언을 권장합니다. 다음 diff를 적용해 주세요: -class NicknameResponse (
+data class NicknameResponse (
@SerializedName("success")
val success: Boolean,
@SerializedName("data")
val data: NicknameData
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package umc.onairmate.data.repository | ||
|
|
||
| interface NicknameRepository { | ||
| suspend fun isNicknameDuplicated(nickname: String): Boolean | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain패키지-디렉터리 경로 불일치 가능성(OnAirMate vs onairmate, repository/repository) 파일 경로가 다음 스크립트로 대소문자 혼용 및 중복 디렉터리 여부를 점검해 주세요: 🏁 Script executed: #!/bin/bash
# 대소문자 혼용 디렉터리 탐지
echo "Directories containing 'OnAirMate' (capitalized):"
fd OnAirMate -t d
echo
echo "Directories containing 'onairmate' (lowercase):"
fd onairmate -t d
echo
echo "Check for duplicated 'repository/repository' paths:"
fd -t f 'NicknameRepository.kt' -x dirname {} | sort -uLength of output: 691 패키지 선언 및 디렉터리 구조 정합성 확보 필요 현재
위 조치로 대소문자 혼용 및 중복 디렉터리 문제를 해소하고, 빌드/IDE 환경 전반의 안정성을 확보할 수 있습니다. 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package umc.onairmate.data.repository.repositoryImpl | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain패키지-디렉터리 대소문자 불일치 가능성 점검 파일 경로에는 'OnAirMate'(대문자) 디렉터리가 보이고, 패키지는 'umc.onairmate'(소문자)입니다. Linux와 같이 대소문자 구분 파일시스템 환경에서 빌드 이슈가 발생할 수 있으니, 디렉터리/패키지 네이밍 일관성을 확인해 주세요. 점검 스크립트: 🏁 Script executed: #!/bin/bash
# 패키지 선언 케이스 통계
echo "[package umc.onairmate] count:"
rg -n '^package umc\.onairmate' | wc -l
echo "[package umc.OnAirMate] count:"
rg -n '^package umc\.OnAirMate' | wc -l
# 디렉터리 케이스 확인
echo "[Directories named onairmate]"
fd -t d -a "onairmate" app/src/main/java/umc || true
echo "[Directories named OnAirMate]"
fd -t d -a "OnAirMate" app/src/main/java/umc || true
# import/참조에서 케이스 혼용 여부
rg -n 'umc\.OnAirMate' -S || trueLength of output: 650 패키지/디렉터리 네이밍 불일치 해결 Linux 환경에서 빌드 오류를 방지하려면
수정 후 import 및 패키지 경로가 모두 🤖 Prompt for AI Agents |
||
|
|
||
| import android.util.Log | ||
| import umc.onairmate.data.api.NicknameService | ||
| import umc.onairmate.data.repository.NicknameRepository | ||
| import javax.inject.Inject | ||
|
|
||
| class NicknameRepositoryImpl @Inject constructor( | ||
| private val api: NicknameService | ||
| ): NicknameRepository { | ||
|
|
||
| override suspend fun isNicknameDuplicated(nickname: String): Boolean { | ||
| return try { | ||
| val response = api.checkNickname(nickname) | ||
| // available == true 면 사용 가능한 닉네임, 중복 아님 → 따라서 중복 여부는 반대(!) | ||
| !response.data.available | ||
| } catch (e: Exception) { | ||
| Log.e("NicknameRepository", "닉네임 중복 검사 실패", e) | ||
| // 실패 시 기본값 false 또는 true 선택 가능 (보통 실패는 중복 아님 false로 처리) | ||
| false | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 예외 삼키기와 성공/응답 검증 누락으로 잘못된 판정/크래시 가능성이 있습니다
최소 수정안: 서버 성공 플래그 체크 + 오류 타입별 로깅/처리 분기 추가(기존 Boolean 시그니처 유지). 아래 패치 제안 적용을 고려해 주세요: - return try {
- val response = api.checkNickname(nickname)
- // available == true 면 사용 가능한 닉네임, 중복 아님 → 따라서 중복 여부는 반대(!)
- !response.data.available
- } catch (e: Exception) {
- Log.e("NicknameRepository", "닉네임 중복 검사 실패", e)
- // 실패 시 기본값 false 또는 true 선택 가능 (보통 실패는 중복 아님 false로 처리)
- false
- }
+ return try {
+ val response = api.checkNickname(nickname)
+ if (!response.success) {
+ Log.w(TAG, "닉네임 중복 검사 API가 success=false를 반환했습니다. message=${response.data?.message}")
+ return false
+ }
+ // available == true 면 사용 가능한 닉네임, 중복 아님 → 따라서 중복 여부는 반대(!)
+ !response.data.available
+ } catch (e: IOException) {
+ Log.e(TAG, "네트워크 오류로 닉네임 중복 검사 실패", e)
+ false
+ } catch (e: HttpException) {
+ Log.e(TAG, "HTTP 오류로 닉네임 중복 검사 실패", e)
+ false
+ } catch (e: Exception) {
+ Log.e(TAG, "알 수 없는 오류로 닉네임 중복 검사 실패", e)
+ false
+ }추가로 필요한 외부 변경(선택 적용):
import retrofit2.HttpException
import java.io.IOException
companion object {
private const val TAG = "NicknameRepository"
}장기적으로는 Boolean 반환 대신 Result-스타일 도메인 결과(예: Available/Duplicated/Error(메시지 포함))로 표현하여 UI에서 오류를 명확히 처리하도록 전환하는 것을 권장합니다. |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,119 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package umc.onairmate.ui.pop_up | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.os.Bundle | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.text.Editable | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.text.TextWatcher | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.view.LayoutInflater | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.view.View | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.view.ViewGroup | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.widget.Toast | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.lifecycle.lifecycleScope | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.google.android.material.bottomsheet.BottomSheetDialogFragment | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import dagger.hilt.android.AndroidEntryPoint | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.Dispatchers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.launch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.withContext | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import umc.onairmate.R | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import umc.onairmate.data.repository.NicknameRepository | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import umc.onairmate.databinding.PopupChangeNicknameBinding | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import javax.inject.Inject | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain대소문자 혼용 디렉터리 구조 점검(OnAirMate vs onairmate) 이 파일(디렉터리 경로의 OnAirMate 대문자)과 패키지명(umc.onairmate 소문자)의 불일치가 프로젝트 전반에 존재할 경우, 케이스-센시티브 환경에서 문제를 유발할 수 있습니다. 다음 스크립트로 모듈 전체의 디렉터리 대소문자 혼용을 점검해 주세요: 🏁 Script executed: #!/bin/bash
echo "Check Kotlin/Java source directories with 'OnAirMate' vs 'onairmate':"
fd -t d 'OnAirMate'
fd -t d 'onairmate'
echo
echo "List files under both to inspect duplicates:"
fd -t f '.kt' OnAirMate
fd -t f '.kt' onairmateLength of output: 721 대소문자 디렉터리 경로 일관성 확인 및 수정 필요
리팩토링 후에는 패키지 선언( 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @AndroidEntryPoint | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class ChangeNicknamePopup : BottomSheetDialogFragment() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private var _binding: PopupChangeNicknameBinding? = null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val binding get() = _binding!! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 외부에서 중복 체크를 처리하기 위한 콜백 람다 추가 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var onCheckNickname: ((String, (Boolean) -> Unit) -> Unit)? = null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion onCheckNickname == null일 때 동작 정의 및 버튼 상태 처리 필요 콜백이 설정되지 않은 경우 클릭 시 아무 일도 발생하지 않습니다. 초기/텍스트 변경 시 - var onCheckNickname: ((String, (Boolean) -> Unit) -> Unit)? = null
+ var onCheckNickname: ((String, (Boolean) -> Unit) -> Unit)? = null추가 변경은 아래 텍스트 변경/클릭 로직에서 제안합니다.
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // @Inject | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // lateinit var repository: NicknameRepository | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| override fun onCreateView( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inflater: LayoutInflater, container: ViewGroup?, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| savedInstanceState: Bundle? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): View { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _binding = PopupChangeNicknameBinding.inflate(inflater, container, false) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return binding.root | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| super.onViewCreated(view, savedInstanceState) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| binding.editNickname.addTextChangedListener(object : TextWatcher { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| override fun afterTextChanged(s: Editable?) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val input = s.toString() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val isValid = input.length in 3..10 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| binding.checkNickname.setBackgroundResource( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isValid) R.drawable.bg_btn_main | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else R.drawable.bg_btn_disabled | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| binding.checkNickname.isEnabled = isValid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 버튼 활성화 조건에 콜백 존재 여부 포함 중복 체크 콜백이 없으면 버튼을 활성화하지 않는 것이 안전합니다. - binding.checkNickname.setBackgroundResource(
- if (isValid) R.drawable.bg_btn_main
- else R.drawable.bg_btn_disabled
- )
- binding.checkNickname.isEnabled = isValid
+ val canCheck = isValid && onCheckNickname != null
+ binding.checkNickname.setBackgroundResource(
+ if (canCheck) R.drawable.bg_btn_main else R.drawable.bg_btn_disabled
+ )
+ binding.checkNickname.isEnabled = canCheck📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| binding.checkNickname.setOnClickListener { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val nickname = binding.editNickname.text.toString() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (nickname.length !in 3..10) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Toast.makeText(requireContext(), "닉네임은 3자 이상 10자 이하여야 합니다.", Toast.LENGTH_SHORT).show() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return@setOnClickListener | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 외부 콜백 호출해서 중복 검사 요청 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCheckNickname?.invoke(nickname) { isDuplicated -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isDuplicated) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| binding.editNickname.text?.clear() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| binding.checkNickname.isEnabled = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 비동기 콜백에서의 UI 업데이트: 생명주기/스레드 안전성 보장 현재 콜백 람다 내에서 아래와 같이 진행 중 상태 처리, 메인 스레드 전환, 안전한 바인딩 접근을 적용하세요. binding.checkNickname.setOnClickListener {
- val nickname = binding.editNickname.text.toString()
+ val nickname = binding.editNickname.text.toString().trim()
if (nickname.length !in 3..10) {
- Toast.makeText(requireContext(), "닉네임은 3자 이상 10자 이하여야 합니다.", Toast.LENGTH_SHORT).show()
+ Toast.makeText(requireContext(), "닉네임은 3자 이상 10자 이하여야 합니다.", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
// 외부 콜백 호출해서 중복 검사 요청
- onCheckNickname?.invoke(nickname) { isDuplicated ->
- if (isDuplicated) {
- Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show()
- binding.editNickname.text?.clear()
- binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
- binding.checkNickname.isEnabled = false
- } else {
- Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show()
- }
- }
+ // 진행 중 상태 표시: 버튼 비활성화
+ binding.checkNickname.isEnabled = false
+ onCheckNickname?.invoke(nickname) { isDuplicated ->
+ viewLifecycleOwner.lifecycleScope.launch {
+ val b = _binding ?: return@launch
+ if (isDuplicated) {
+ Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show()
+ b.editNickname.text?.clear()
+ b.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
+ b.checkNickname.isEnabled = false
+ } else {
+ Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show()
+ // 성공 시 버튼을 재활성화(다시 검사 허용)하거나 완료 버튼 활성화 등 후속 상태 전환
+ b.checkNickname.setBackgroundResource(R.drawable.bg_btn_main)
+ b.checkNickname.isEnabled = true
+ }
+ }
+ }추가로 "완료" 흐름을 위해 중복 확인 성공 시에만 완료 버튼 활성화 등 상태 머신을 명확히 하면 UX가 좋아집니다. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewLifecycleOwner.lifecycleScope.launch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val isDuplicated = withContext(Dispatchers.IO) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repository.isNicknameDuplicated(nickname) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isDuplicated) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| binding.editNickname.text.clear() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| binding.checkNickname.isEnabled = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // viewLifecycleOwner.lifecycleScope.launch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // val isDuplicated = withContext(Dispatchers.IO) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // repository.isNicknameDuplicated(nickname) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // if (isDuplicated) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // binding.editNickname.text.clear() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // binding.checkNickname.isEnabled = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| override fun onDestroyView() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| super.onDestroyView() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _binding = null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package umc.onairmate.ui.pop_up | ||
|
|
||
| import androidx.lifecycle.ViewModel | ||
| import androidx.lifecycle.viewModelScope | ||
| import dagger.hilt.android.lifecycle.HiltViewModel | ||
| import kotlinx.coroutines.launch | ||
| import javax.inject.Inject | ||
| import umc.onairmate.data.repository.NicknameRepository | ||
|
|
||
| @HiltViewModel | ||
| class ChangeNicknameViewModel @Inject constructor( | ||
| private val nicknameRepository: NicknameRepository | ||
| ) : ViewModel() { | ||
|
|
||
| // 닉네임 중복 확인 결과를 콜백으로 전달하거나 LiveData로 관리 가능 | ||
| fun checkNickname( | ||
| nickname: String, | ||
| onResult: (Boolean) -> Unit | ||
| ) { | ||
| viewModelScope.launch { | ||
| try { | ||
| val isDuplicated = nicknameRepository.isNicknameDuplicated(nickname) | ||
| onResult(isDuplicated) | ||
| } catch (e: Exception) { | ||
| // 에러 처리 필요하면 여기서 | ||
| onResult(false) // 중복 아님으로 처리하거나 별도 처리 | ||
| } | ||
| } | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 에러 처리 시 중복 아님(false)으로 반환 — 잘못된 UX/로직 가능성 네트워크/서버 에러가 발생해도 다음과 같이 삼진 결과로 분리해 주세요: 성공(isDuplicated), 실패(Error). 콜백 시그니처 변경이 가능하다면 권장합니다. - fun checkNickname(
- nickname: String,
- onResult: (Boolean) -> Unit
- ) {
+ fun checkNickname(
+ nickname: String,
+ onResult: (NicknameCheckResult) -> Unit
+ ) {
viewModelScope.launch {
try {
- val isDuplicated = nicknameRepository.isNicknameDuplicated(nickname)
- onResult(isDuplicated)
+ val isDuplicated = nicknameRepository.isNicknameDuplicated(nickname)
+ onResult(NicknameCheckResult.Success(isDuplicated))
} catch (e: Exception) {
- // 에러 처리 필요하면 여기서
- onResult(false) // 중복 아님으로 처리하거나 별도 처리
+ // 로깅 및 에러 전달
+ // Timber.e(e, "checkNickname failed") // Timber 사용 시
+ onResult(NicknameCheckResult.Error(e))
}
}
}다음 보조 타입을 추가해 주세요: sealed class NicknameCheckResult {
data class Success(val isDuplicated: Boolean) : NicknameCheckResult()
data class Error(val throwable: Throwable) : NicknameCheckResult()
}콜백 시그니처 변경이 어렵다면, 최소한 에러 시 안전 불허 정책으로 바꿔 주세요: - onResult(false)
+ onResult(true) // 실패 시 중복으로 간주해 통과를 막음detekt(예외 삼킴)도 함께 해소됩니다. 🧰 Tools🪛 detekt (1.23.8)[warning] 24-24: The caught exception is swallowed. The original exception could be lost. (detekt.exceptions.SwallowedException) 🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package umc.onairmate.ui.profile | ||
|
|
||
| import android.view.LayoutInflater | ||
| import android.view.ViewGroup | ||
| import androidx.recyclerview.widget.DiffUtil | ||
| import androidx.recyclerview.widget.ListAdapter | ||
| import androidx.recyclerview.widget.RecyclerView | ||
| import umc.onairmate.databinding.ItemBlockedUserBinding | ||
|
|
||
| data class BlockedUser(val nickname: String, val reason: String, val date: String) | ||
|
|
||
| class BlockListAdapter( | ||
| private val onUnblockClick: (BlockedUser) -> Unit | ||
| ) : ListAdapter<BlockedUser, BlockListAdapter.BlockViewHolder>(diffCallback) { | ||
|
|
||
| override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BlockViewHolder { | ||
| val binding = ItemBlockedUserBinding.inflate(LayoutInflater.from(parent.context), parent, false) | ||
| return BlockViewHolder(binding) | ||
| } | ||
|
|
||
| override fun onBindViewHolder(holder: BlockViewHolder, position: Int) { | ||
| holder.bind(getItem(position)) | ||
| } | ||
|
|
||
| inner class BlockViewHolder(private val binding: ItemBlockedUserBinding) : | ||
| RecyclerView.ViewHolder(binding.root) { | ||
|
|
||
| fun bind(user: BlockedUser) { | ||
| binding.tvNickname.text = "[${user.nickname}]" | ||
| binding.tvReason.text = user.reason | ||
| binding.tvDate.text = user.date | ||
|
|
||
| binding.btnUnblock.setOnClickListener { | ||
| onUnblockClick(user) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| companion object { | ||
| val diffCallback = object : DiffUtil.ItemCallback<BlockedUser>() { | ||
| override fun areItemsTheSame(oldItem: BlockedUser, newItem: BlockedUser) = oldItem.nickname == newItem.nickname | ||
| override fun areContentsTheSame(oldItem: BlockedUser, newItem: BlockedUser) = oldItem == newItem | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,66 @@ | ||||||||||||||||||||
| package umc.onairmate.ui.profile | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import android.os.Bundle | ||||||||||||||||||||
| import android.util.Log | ||||||||||||||||||||
| import android.view.LayoutInflater | ||||||||||||||||||||
| import android.view.View | ||||||||||||||||||||
| import android.view.ViewGroup | ||||||||||||||||||||
| import androidx.fragment.app.Fragment | ||||||||||||||||||||
| import androidx.recyclerview.widget.LinearLayoutManager | ||||||||||||||||||||
| import umc.onairmate.R | ||||||||||||||||||||
| import umc.onairmate.databinding.FragmentBlockListBinding | ||||||||||||||||||||
|
|
||||||||||||||||||||
| class BlockListFragment : Fragment() { | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private var _binding: FragmentBlockListBinding? = null | ||||||||||||||||||||
| private val binding get() = _binding!! | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private lateinit var blockListAdapter: BlockListAdapter | ||||||||||||||||||||
|
|
||||||||||||||||||||
| override fun onCreateView( | ||||||||||||||||||||
| inflater: LayoutInflater, container: ViewGroup?, | ||||||||||||||||||||
| savedInstanceState: Bundle? | ||||||||||||||||||||
| ): View { | ||||||||||||||||||||
| _binding = FragmentBlockListBinding.inflate(inflater, container, false) | ||||||||||||||||||||
| return binding.root | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||||||||||||||||||
| super.onViewCreated(view, savedInstanceState) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Log.d("BlockListFragment", "view created, binding.ivBack=${binding.ivBack}") | ||||||||||||||||||||
| setupRecyclerView() | ||||||||||||||||||||
| setupListeners() | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private fun setupRecyclerView() { | ||||||||||||||||||||
| blockListAdapter = BlockListAdapter { blockedUser -> | ||||||||||||||||||||
| // 차단 해제 클릭 시 처리 | ||||||||||||||||||||
| } | ||||||||||||||||||||
| binding.rvBlockList.apply { | ||||||||||||||||||||
| layoutManager = LinearLayoutManager(requireContext()) | ||||||||||||||||||||
| adapter = blockListAdapter | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // 테스트 데이터 | ||||||||||||||||||||
| blockListAdapter.submitList( | ||||||||||||||||||||
| listOf( | ||||||||||||||||||||
| BlockedUser("차단한 사용자 닉네임", "차단 사유", "0000년 00월 00일"), | ||||||||||||||||||||
| BlockedUser("차단한 사용자 닉네임", "차단 사유", "0000년 00월 00일"), | ||||||||||||||||||||
| BlockedUser("차단한 사용자 닉네임", "차단 사유", "0000년 00월 00일") | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private fun setupListeners() { | ||||||||||||||||||||
| binding.ivBack.setOnClickListener { | ||||||||||||||||||||
| requireActivity().onBackPressed() | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
Comment on lines
+59
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 백버튼 처리: Navigation API 사용 권장 Activity.onBackPressed()는 deprecated입니다. NavController를 통해 navigateUp/popBackStack을 호출하는 방식이 안전합니다. - binding.ivBack.setOnClickListener {
- requireActivity().onBackPressed()
- }
+ binding.ivBack.setOnClickListener {
+ findNavController().navigateUp()
+ }추가로 파일 상단에 다음 import가 필요합니다: 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
|
|
||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| override fun onDestroyView() { | ||||||||||||||||||||
| super.onDestroyView() | ||||||||||||||||||||
| _binding = null | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
Comment on lines
+65
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion onDestroyView에서 RecyclerView 어댑터 해제 Fragment 뷰 생명주기와 어댑터/뷰홀더 참조가 얽혀 메모리 누수가 발생할 수 있습니다. 뷰 파괴 시 어댑터를 null로 해제하세요. override fun onDestroyView() {
super.onDestroyView()
- _binding = null
+ binding.rvBlockList.adapter = null
+ _binding = null
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
| } | ||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
패키지 경로 대소문자 불일치 가능성 점검
파일 시스템 경로(app/src/main/java/umc/OnAirMate/…)와 선언 패키지(package umc.onairmate…)의 대소문자 불일치가 보입니다. CI(리눅스 등 대소문자 구분) 환경에서 중복 패키지/클래스 인식 문제를 유발할 수 있어 디렉터리명을 패키지 규약(all-lowercase)으로 통일하는 것을 권장합니다.
다음 스크립트로 대소문자 혼재 여부와 해당 파일들의 package 선언을 확인해 주세요.
🏁 Script executed:
Length of output: 779
추가로
OnAirMate디렉터리 내 Kotlin 파일들의 패키지 선언을 확인합니다:🏁 Script executed:
Length of output: 15518
패키지 디렉터리명 소문자 통일 필요
app/src/main/java/umc경로에 대문자 혼용 디렉터리(OnAirMate)가 존재하여 선언된 패키지umc.onairmate와 불일치합니다. Linux CI 환경에서 클래스 인식 문제를 방지하려면 디렉터리명을 모두 소문자로 변경하세요.app/src/main/java/umc/OnAirMate/...app/src/main/java/umc/onairmate/...🤖 Prompt for AI Agents