Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
95acefa
⚡️ :: add JoinActivity
bhindor Feb 11, 2026
c9e4b8e
⚡️ :: add file
bhindor Feb 11, 2026
6b62ec1
⚡️ :: navigation plugin
bhindor Feb 11, 2026
22eec22
⚡️ :: account view
bhindor Feb 11, 2026
da3bf66
⚡️ :: accountFragment
bhindor Feb 11, 2026
85fa754
⚡️ :: nav_graph_join
bhindor Feb 11, 2026
1b03aff
⚡️ :: add NameFragment
bhindor Feb 11, 2026
0ed19bb
⚡️ :: navigation version update
bhindor Feb 11, 2026
f16209c
⚡️ :: activity_join view
bhindor Feb 11, 2026
664558c
⚡️ :: join move code
bhindor Feb 11, 2026
1d71c50
⚡️ :: JoinViewModel
bhindor Feb 11, 2026
40ce947
⚡️ :: Login to Join
bhindor Feb 11, 2026
eb848d8
⚡️ :: connect JoinFragment to Activity
bhindor Feb 11, 2026
92b34c2
⚡️ :: modify next btn logic
bhindor Feb 11, 2026
6dc4273
⚡️ :: Modify View
bhindor Feb 11, 2026
05cfbd8
⚡️ :: modify back btn logic
bhindor Feb 11, 2026
5abdede
⚡️ :: account view check logic modfiy
bhindor Feb 11, 2026
9d3f718
⚡️ :: editbox maxline setting
bhindor Feb 11, 2026
ce4792f
⚡️ :: connect name to join
bhindor Feb 12, 2026
3363d53
⚡️ :: add PhoneFragment
bhindor Feb 21, 2026
6657411
⚡️ :: bg_edit_underline xml
bhindor Feb 21, 2026
b08b1de
⚡️ :: nav에 phone 추가
bhindor Feb 21, 2026
d1daec0
⚡️ :: only 전화번호 입력
bhindor Feb 21, 2026
724ea3a
⚡️ :: editbox 한줄 처리
bhindor Feb 21, 2026
254f2cc
⚡️ :: 인증 뷰 상태 추가
bhindor Feb 21, 2026
5b262e8
⚡️ :: 전화번호 인증 에러뷰 수정
bhindor Feb 21, 2026
4b26b43
⚡️ :: 주소 fragment 추가
bhindor Feb 22, 2026
e2072d0
⚡️ :: address view
bhindor Feb 22, 2026
3bbebff
⚡️ :: 주소 viewModel & fragment code
bhindor Feb 22, 2026
d313b4a
⚡️ :: nav에 phone to address 추가
bhindor Feb 22, 2026
57fac68
⚡️ :: add 약관 fragment
bhindor Feb 22, 2026
31793e9
⚡️ :: 약관 뷰 작업
bhindor Feb 23, 2026
df074f0
⚡️ :: fragment & viewModel 작업
bhindor Feb 23, 2026
5d20559
⚡️ :: nav에 address to terms 추가
bhindor Feb 23, 2026
e913b6d
⚡️ :: 주소 뷰 수정
bhindor Feb 23, 2026
ef941fd
⚡️ :: 화면 이동 로직 수정
bhindor Feb 23, 2026
7f3c9ee
⚡️ :: finishFragment
bhindor Feb 23, 2026
1f0b648
⚡️ :: 화면 이동 로직 수정
bhindor Feb 24, 2026
166d0d7
⚡️ :: api 연결
bhindor Feb 24, 2026
5c11f25
🎨 :: ktlint
bhindor Feb 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,8 @@ dependencies {
// UI 확장
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.fragment.ktx)

// 네비게이션
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
}
6 changes: 4 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

<application
android:name=".MumuApplication"
android:usesCleartextTraffic="true"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -15,7 +14,11 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Mumu"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".presentation.join.JoinActivity"
android:exported="false" />
<activity
android:name=".presentation.login.LoginActivity"
android:exported="true">
Expand All @@ -30,5 +33,4 @@
android:exported="false" />
</application>


</manifest>
14 changes: 14 additions & 0 deletions app/src/main/java/kr/ac/anu/mumu/data/datasource/JoinService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package kr.ac.anu.mumu.data.datasource

import kr.ac.anu.mumu.data.model.JoinRequest
import kr.ac.anu.mumu.data.model.JoinResponse
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.POST

interface JoinService {
@POST("/api/users/register")
suspend fun registerUser(
@Body request: JoinRequest
): Response<JoinResponse>
}
30 changes: 30 additions & 0 deletions app/src/main/java/kr/ac/anu/mumu/data/model/JoinDto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package kr.ac.anu.mumu.data.model

import com.google.gson.annotations.SerializedName

data class JoinRequest(
val id: String,
val password: String,
val name: String,
val phone: String,
val address: String,
@SerializedName("detail_address") val detailAddress: String,
@SerializedName("postal_code") val postalCode: String,
@SerializedName("terms_agreed") val termsAgreed: Boolean,
@SerializedName("privacy_agreed") val privacyAgreed: Boolean,
@SerializedName("marketing_agreed") val marketingAgreed: Boolean
)

data class JoinResponse(
val success: Boolean,
val message: String,
val token: String?,
val user: UserDto?
) {
data class UserDto(
val userId: Int,
val id: String,
val name: String,
val phone: String
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package kr.ac.anu.mumu.data.repository

import kr.ac.anu.mumu.data.datasource.JoinService
import kr.ac.anu.mumu.data.model.JoinRequest
import kr.ac.anu.mumu.domain.repository.JoinRepository
import org.json.JSONObject
import javax.inject.Inject

class JoinRepositoryImpl @Inject constructor(
private val joinService: JoinService
) : JoinRepository {
override suspend fun register(request: JoinRequest): Result<Unit> {
return try {
val response = joinService.registerUser(request)
if (response.isSuccessful) {
Result.success(Unit)
} else {
val errorString = response.errorBody()?.string()
val errorMessage = try {
JSONObject(errorString ?: "").getString("error")
} catch (e: Exception) {
"회원가입에 실패했습니다."
}
Result.failure(Exception(errorMessage))
}
} catch (e: Exception) {
Result.failure(e)
}
}
}
7 changes: 7 additions & 0 deletions app/src/main/java/kr/ac/anu/mumu/di/NetworkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kr.ac.anu.mumu.data.datasource.AuthService
import kr.ac.anu.mumu.data.datasource.JoinService
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton
Expand All @@ -26,4 +27,10 @@ object NetworkModule {
fun provideAuthService(retrofit: Retrofit): AuthService {
return retrofit.create(AuthService::class.java)
}

@Provides
@Singleton
fun provideJoinService(retrofit: Retrofit): JoinService {
return retrofit.create(JoinService::class.java)
}
}
8 changes: 8 additions & 0 deletions app/src/main/java/kr/ac/anu/mumu/di/RepositoryModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kr.ac.anu.mumu.data.repository.JoinRepositoryImpl
import kr.ac.anu.mumu.data.repository.LoginRepositoryImpl
import kr.ac.anu.mumu.domain.repository.JoinRepository
import kr.ac.anu.mumu.domain.repository.LoginRepository
import javax.inject.Singleton

Expand All @@ -16,4 +18,10 @@ abstract class RepositoryModule {
abstract fun bindLoginRepository(
loginRepositoryImpl: LoginRepositoryImpl
): LoginRepository

@Binds
@Singleton
abstract fun bindJoinRepository(
joinRepositoryImpl: JoinRepositoryImpl
): JoinRepository
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kr.ac.anu.mumu.domain.repository

import kr.ac.anu.mumu.data.model.JoinRequest

interface JoinRepository {
suspend fun register(request: JoinRequest): Result<Unit>
}
13 changes: 13 additions & 0 deletions app/src/main/java/kr/ac/anu/mumu/domain/usecase/JoinUseCase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kr.ac.anu.mumu.domain.usecase

import kr.ac.anu.mumu.data.model.JoinRequest
import kr.ac.anu.mumu.domain.repository.JoinRepository
import javax.inject.Inject

class JoinUseCase @Inject constructor(
private val repository: JoinRepository
) {
suspend operator fun invoke(request: JoinRequest): Result<Unit> {
return repository.register(request)
}
}
107 changes: 107 additions & 0 deletions app/src/main/java/kr/ac/anu/mumu/presentation/join/AccountFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package kr.ac.anu.mumu.presentation.join

import android.os.Bundle
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.transition.TransitionManager
import kr.ac.anu.mumu.R
import kr.ac.anu.mumu.databinding.FragmentAccountBinding

class AccountFragment : Fragment() {

private var _binding: FragmentAccountBinding? = null
private val binding get() = _binding!!

// Activity와 공유하는 ViewModel
private val viewModel: JoinViewModel by activityViewModels()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAccountBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

// 텍스트 색상 변경
viewSet()

// 입력값 리스너
binding.etId.addTextChangedListener {
viewModel.inputId.value = it.toString()
viewModel.checkButtonEnabled()
viewModel.isIdErrorVisible.value = false
}
binding.etPw.addTextChangedListener {
viewModel.inputPw.value = it.toString()
viewModel.checkButtonEnabled()
viewModel.isPwErrorVisible.value = false
}
binding.etPwCheck.addTextChangedListener {
viewModel.inputPwCheck.value = it.toString()
viewModel.checkButtonEnabled()
viewModel.isPwCheckErrorVisible.value = false
}

// 단계별 UI 오픈
viewModel.accountStep.observe(viewLifecycleOwner) { step ->
TransitionManager.beginDelayedTransition(binding.root as ViewGroup)
binding.groupStepPw.visibility = if (step >= 1) View.VISIBLE else View.GONE

binding.groupStepPwCheck.visibility = if (step >= 2) View.VISIBLE else View.GONE

viewModel.checkButtonEnabled()
}

// 에러 메시지 UI 반영
viewModel.isIdErrorVisible.observe(viewLifecycleOwner) { isVisible ->
binding.tvIdError.visibility = if (isVisible) View.VISIBLE else View.GONE
}
viewModel.isPwErrorVisible.observe(viewLifecycleOwner) { isVisible ->
binding.tvPwError.visibility = if (isVisible) View.VISIBLE else View.GONE
}
viewModel.isPwCheckErrorVisible.observe(viewLifecycleOwner) { isVisible ->
binding.tvPwCheckError.visibility = if (isVisible) View.VISIBLE else View.GONE
}
}

private fun viewSet() {
val originText = binding.tvTitle.text.toString()
val spannable = SpannableStringBuilder(originText)

val targetWord = "Mumu"
val start = originText.indexOf(targetWord)
val end = start + targetWord.length

if (start != -1) {
context?.let { ctx ->
val color = ContextCompat.getColor(ctx, R.color.mumumint_300)

spannable.setSpan(
ForegroundColorSpan(color),
start,
end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
binding.tvTitle.text = spannable
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package kr.ac.anu.mumu.presentation.join

import android.os.Bundle
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import kr.ac.anu.mumu.R
import kr.ac.anu.mumu.databinding.FragmentAddressBinding

class AddressFragment : Fragment() {
private var _binding: FragmentAddressBinding? = null
private val binding get() = _binding!!
private val viewModel: JoinViewModel by activityViewModels()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentAddressBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

viewSet()

viewModel.checkAddressStep()

viewModel.inputPostalCode.observe(viewLifecycleOwner) { postal ->
binding.tvPostalCode.text = postal
}

viewModel.inputAddress.observe(viewLifecycleOwner) { address ->
binding.tvAddress.text = address
}

binding.btnSearch.setOnClickListener {
// TODO 카카오 API
viewModel.inputPostalCode.value = "36729"
viewModel.inputAddress.value = "경상북도 안동시 경동로 1375"
}

binding.etAddress.addTextChangedListener { text ->
viewModel.inputDetailAddress.value = text.toString()

viewModel.checkAddressStep()
}
}

private fun viewSet() {
val originText = binding.tvTitle.text.toString()
val spannable = SpannableStringBuilder(originText)

val targetWord = "Mumu"
val start = originText.indexOf(targetWord)
val end = start + targetWord.length

if (start != -1) {
val color = ContextCompat.getColor(requireContext(), R.color.mumumint_300)

spannable.setSpan(
ForegroundColorSpan(color),
start,
end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}

binding.tvTitle.text = spannable
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Loading