Skip to content

Commit a15c2cc

Browse files
authored
Merge pull request #46 from wafflestudio/ui-fix
UI fix
2 parents 628d691 + dc42519 commit a15c2cc

28 files changed

+570
-35
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ dependencies {
5252
implementation(libs.androidx.activity)
5353
implementation(libs.androidx.constraintlayout)
5454
implementation(libs.androidx.espresso.core)
55+
implementation(libs.androidx.ui.text.android)
5556
testImplementation(libs.junit)
5657
androidTestImplementation(libs.androidx.junit)
5758
androidTestImplementation(libs.androidx.espresso.core)
@@ -61,6 +62,7 @@ dependencies {
6162
implementation(libs.retrofit.gson)
6263
implementation(libs.okhttp)
6364
implementation(libs.okhttp.logging.interceptor)
65+
implementation(libs.okhttp.urlconnection)
6466
implementation(libs.hilt)
6567
implementation(libs.flexbox)
6668
kapt(libs.hilt.compiler)

app/src/main/java/com/example/memowithtags/common/network/api/AuthApi.kt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,17 @@ interface AuthApi {
2222
@POST("api/v1/auth/register")
2323
suspend fun signup(@Body signupRequest: SignupRequest): Response<SignupResponse>
2424

25-
@POST("api/v1/auth/send-email")
26-
suspend fun sendEmail(@Body sendEmailRequest: SendEmailRequest): Response<Unit>
27-
28-
@POST("api/v1/auth/verify-email")
29-
suspend fun verifyEmail(@Body verifyEmailRequest: VerifyEmailRequest): Response<Unit>
25+
@POST("api/v1/mail")
26+
suspend fun sendEmail(
27+
@Query("type") type: String,
28+
@Body request: SendEmailRequest
29+
): Response<Unit>
30+
31+
@POST("api/v1/mail/verify")
32+
suspend fun verifyEmail(
33+
@Query("type") type: String,
34+
@Body verifyEmailRequest: VerifyEmailRequest
35+
): Response<Unit>
3036

3137
@POST("api/v1/auth/login")
3238
suspend fun login(@Body loginRequest: LoginRequest): Response<LoginResponse>

app/src/main/java/com/example/memowithtags/common/network/di/NetworkModule.kt

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,21 @@ import dagger.Provides
1515
import dagger.hilt.InstallIn
1616
import dagger.hilt.android.qualifiers.ApplicationContext
1717
import dagger.hilt.components.SingletonComponent
18+
import okhttp3.JavaNetCookieJar
1819
import okhttp3.OkHttpClient
1920
import okhttp3.logging.HttpLoggingInterceptor
2021
import retrofit2.Retrofit
2122
import retrofit2.converter.gson.GsonConverterFactory
23+
import java.net.CookieManager
24+
import java.net.CookiePolicy
2225
import javax.inject.Named
2326
import javax.inject.Singleton
2427

2528
@Module
2629
@InstallIn(SingletonComponent::class)
2730
object NetworkModule {
2831

29-
private const val BASE_URL = "https://memowithtags.kro.kr"
32+
private const val BASE_URL = "https://memowithtags-api.wafflestudio.com"
3033

3134
@Provides
3235
@Singleton
@@ -50,6 +53,12 @@ object NetworkModule {
5053
return SharedPrefsTokenProvider(sharedPreferences)
5154
}
5255

56+
@Provides @Singleton
57+
fun provideCookieManager(): java.net.CookieManager =
58+
java.net.CookieManager().apply {
59+
setCookiePolicy(java.net.CookiePolicy.ACCEPT_ALL)
60+
}
61+
5362
@Provides
5463
fun provideTokenAuthenticator(
5564
tokenProvider: TokenProvider,
@@ -62,8 +71,12 @@ object NetworkModule {
6271
@Provides
6372
@Singleton
6473
@Named("NoAuth")
65-
fun provideOkHttpClient(loggingInterceptor: HttpLoggingInterceptor): OkHttpClient {
74+
fun provideOkHttpClient(
75+
loggingInterceptor: HttpLoggingInterceptor,
76+
cookieManager: CookieManager
77+
): OkHttpClient {
6678
return OkHttpClient.Builder()
79+
.cookieJar(JavaNetCookieJar(cookieManager))
6780
.addInterceptor(loggingInterceptor)
6881
.build()
6982
}
@@ -74,9 +87,11 @@ object NetworkModule {
7487
fun provideOkHttpClientWithAuth(
7588
loggingInterceptor: HttpLoggingInterceptor,
7689
authInterceptor: AuthInterceptor,
77-
tokenAuthenticator: TokenAuthenticator
90+
tokenAuthenticator: TokenAuthenticator,
91+
cookieManager: CookieManager
7892
): OkHttpClient {
7993
return OkHttpClient.Builder()
94+
.cookieJar(JavaNetCookieJar(cookieManager))
8095
.authenticator(tokenAuthenticator)
8196
.addInterceptor(loggingInterceptor)
8297
.addInterceptor(authInterceptor)

app/src/main/java/com/example/memowithtags/common/network/interceptor/AuthInterceptor.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@ class AuthInterceptor(
88
private val tokenProvider: TokenProvider
99
) : Interceptor {
1010
override fun intercept(chain: Interceptor.Chain): Response {
11-
val requestBuilder = chain.request().newBuilder()
12-
tokenProvider.getAccessToken().let { token ->
13-
requestBuilder.addHeader("Authorization", "Bearer $token")
11+
val original = chain.request()
12+
val token = tokenProvider.getAccessToken()
13+
val req = if (!token.isNullOrBlank()) {
14+
original.newBuilder()
15+
.header("Authorization", "Bearer $token")
16+
.build()
17+
} else {
18+
original
1419
}
15-
return chain.proceed(requestBuilder.build())
20+
return chain.proceed(req)
1621
}
1722
}

app/src/main/java/com/example/memowithtags/mainMemo/fragments/MainMemoFragment.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,22 @@ class MainMemoFragment : Fragment() {
162162
}
163163
}
164164

165+
memoViewModel.editingMemo.observe(viewLifecycleOwner) { editing ->
166+
val isEditing = (editing != null)
167+
168+
// 새 메모 버튼/아이콘 숨기기
169+
binding.newMemoButton.visibility = if (isEditing) View.GONE else View.VISIBLE
170+
binding.newMemoIcon.visibility = if (isEditing) View.GONE else View.VISIBLE
171+
binding.cancelNewMemoButton.visibility = if (isEditing) View.GONE else View.VISIBLE
172+
173+
// 확인/취소 버튼 보이기
174+
binding.btnEditConfirm.visibility = if (isEditing) View.VISIBLE else View.GONE
175+
binding.btnEditCancel.visibility = if (isEditing) View.VISIBLE else View.GONE
176+
177+
// 편집 중엔 아이콘 바 항상 보이게 (키보드 상태 상관없이)
178+
if (isEditing) binding.newMemoIconBar.visibility = View.VISIBLE
179+
}
180+
165181
if (initialFlag) {
166182
val usedCache = memoViewModel.useInitialMemoCacheIfAvailable()
167183

@@ -178,10 +194,14 @@ class MainMemoFragment : Fragment() {
178194
val keypadHeight = screenHeight - r.bottom
179195

180196
val isKeyboardVisible = keypadHeight > screenHeight * 0.15
197+
val isEditing = memoViewModel.editingMemo.value != null
181198

182199
binding.tagInputLayout.visibility = if (isKeyboardVisible) View.VISIBLE else View.GONE
183200
binding.newMemoIconBar.visibility = if (isKeyboardVisible) View.VISIBLE else View.GONE
184201
binding.newMemoIcon.visibility = if (isKeyboardVisible) View.GONE else View.VISIBLE
202+
203+
binding.newMemoIconBar.visibility = if (isEditing || isKeyboardVisible) View.VISIBLE else View.GONE
204+
binding.newMemoIcon.visibility = if (!isEditing && !isKeyboardVisible) View.VISIBLE else View.GONE
185205
}
186206

187207
var selectedColor: String? = null
@@ -234,6 +254,21 @@ class MainMemoFragment : Fragment() {
234254
binding.newMemoButton.setOnClickListener(postOrUpdateMemoClickListener)
235255
binding.newMemoIcon.setOnClickListener(postOrUpdateMemoClickListener)
236256

257+
// 메모 수정 및 수정 취소 버튼
258+
binding.btnEditConfirm.setOnClickListener(postOrUpdateMemoClickListener)
259+
binding.btnEditCancel.setOnClickListener {
260+
memoViewModel.clearEditing()
261+
binding.newMemoText.text.clear()
262+
tagViewModel.clearSelectedTags()
263+
}
264+
265+
binding.cancelNewMemoButton.setOnClickListener {
266+
binding.newMemoText.text.clear()
267+
tagViewModel.clearSelectedTags()
268+
binding.newMemoText.clearFocus()
269+
binding.tagInputEditText.text.clear()
270+
}
271+
237272
// 편집 화면으로 이동 버튼
238273
binding.zoomButton.setOnClickListener {
239274
val editingMemo = memoViewModel.editingMemo.value

app/src/main/java/com/example/memowithtags/mainMemo/fragments/SearchFragment.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ class SearchFragment : Fragment() {
6868
queryTagAdapter.submitList(selectedQueryTags.toList().map { tagViewModel.getTag(it) })
6969
binding.querytagRecyclerView.visibility = View.VISIBLE
7070
memoViewModel.addSelectedTagId(tagId)
71+
72+
binding.searchText.text?.clear()
73+
memoViewModel.updateQuery("")
74+
tagViewModel.updateQuery("")
7175
}
7276
}
7377

app/src/main/java/com/example/memowithtags/settings/fragments/ChangeNicknameFragment.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.os.Bundle
44
import android.view.LayoutInflater
55
import android.view.View
66
import android.view.ViewGroup
7+
import android.widget.Toast
78
import androidx.core.widget.addTextChangedListener
89
import androidx.fragment.app.Fragment
910
import androidx.fragment.app.viewModels
@@ -36,6 +37,8 @@ class ChangeNicknameFragment : Fragment() {
3637
findNavController().popBackStack()
3738
}
3839

40+
binding.nicknameInput.filters = arrayOf(android.text.InputFilter.LengthFilter(16))
41+
3942
binding.nicknameInput.addTextChangedListener {
4043
viewModel.onTextChanged(it.toString())
4144
binding.limitText.text = "${it.toString().length}/16"
@@ -55,6 +58,7 @@ class ChangeNicknameFragment : Fragment() {
5558

5659
viewModel.changeNicknameResult.observe(viewLifecycleOwner) { result ->
5760
result.onSuccess {
61+
Toast.makeText(requireContext(), "아이디가 변경되었습니다.", Toast.LENGTH_SHORT).show()
5862
findNavController().popBackStack()
5963
}.onFailure {
6064
}

app/src/main/java/com/example/memowithtags/settings/fragments/ChangePWLoginedFragment.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package com.example.memowithtags.settings.fragments
22

33
import android.os.Bundle
4+
import android.text.Editable
5+
import android.text.TextWatcher
46
import android.view.LayoutInflater
57
import android.view.View
68
import android.view.ViewGroup
79
import android.widget.Toast
10+
import androidx.core.content.ContextCompat
811
import androidx.core.widget.addTextChangedListener
912
import androidx.fragment.app.Fragment
1013
import androidx.fragment.app.viewModels
1114
import androidx.navigation.fragment.findNavController
15+
import com.example.memowithtags.R
1216
import com.example.memowithtags.databinding.FragmentChangePwLoginedBinding
1317
import com.example.memowithtags.settings.viewModel.ChangePWLoginedViewModel
1418
import dagger.hilt.android.AndroidEntryPoint
@@ -44,6 +48,23 @@ class ChangePWLoginedFragment : Fragment() {
4448
binding.newPasswordInput.addTextChangedListener {
4549
viewModel.onNewPWChanged(it.toString())
4650
}
51+
binding.newPasswordInput.addTextChangedListener(object : TextWatcher {
52+
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
53+
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
54+
override fun afterTextChanged(s: Editable?) {
55+
val password = s.toString()
56+
if (password.length in 8..16) {
57+
binding.passwordLengthCheck.setTextColor(ContextCompat.getColor(binding.passwordLengthCheck.context, R.color.text_primary))
58+
} else {
59+
binding.passwordLengthCheck.setTextColor(ContextCompat.getColor(binding.passwordLengthCheck.context, R.color.text_secondary))
60+
}
61+
if (containsRequiredCharacters(password)) {
62+
binding.passwordCharacterCheck.setTextColor(ContextCompat.getColor(binding.passwordCharacterCheck.context, R.color.text_primary))
63+
} else {
64+
binding.passwordCharacterCheck.setTextColor(ContextCompat.getColor(binding.passwordCharacterCheck.context, R.color.text_secondary))
65+
}
66+
}
67+
})
4768

4869
binding.confirmBtn.setOnClickListener {
4970
viewModel.changePW()
@@ -59,10 +80,20 @@ class ChangePWLoginedFragment : Fragment() {
5980

6081
viewModel.changePWResult.observe(viewLifecycleOwner) { result ->
6182
result.onSuccess {
83+
Toast.makeText(requireContext(), "비밀번호가 변경되었습니다.", Toast.LENGTH_SHORT).show()
6284
findNavController().popBackStack()
6385
}.onFailure {
6486
Toast.makeText(requireContext(), "오류: ${it.message}", Toast.LENGTH_SHORT).show()
6587
}
6688
}
6789
}
90+
91+
private fun containsRequiredCharacters(input: String): Boolean {
92+
// 숫자 1개 이상: (?=.*[0-9])
93+
// 대문자 1개 이상: (?=.*[A-Z])
94+
// 소문자 1개 이상: (?=.*[a-z])
95+
// 특수문자 1개 이상: (?=.*[!@#\$%^&*(),.?":{}|<>])
96+
val pattern = Regex("^(?=.*[0-9])(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#\$%^&*(),.?\":{}|<>]).+$")
97+
return pattern.containsMatchIn(input)
98+
}
6899
}

app/src/main/java/com/example/memowithtags/signup/fragments/ChangePWFragment.kt

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package com.example.memowithtags.signup.fragments
22

33
import android.content.Intent
44
import android.os.Bundle
5+
import android.text.Editable
6+
import android.text.TextWatcher
57
import android.view.LayoutInflater
68
import android.view.View
79
import android.view.ViewGroup
810
import android.widget.Toast
11+
import androidx.activity.OnBackPressedCallback
12+
import androidx.core.content.ContextCompat
913
import androidx.fragment.app.Fragment
1014
import androidx.fragment.app.activityViewModels
1115
import androidx.lifecycle.lifecycleScope
@@ -51,13 +55,33 @@ class ChangePWFragment : Fragment() {
5155
goToLogin()
5256
}
5357

58+
binding.passwordEditText.addTextChangedListener(object : TextWatcher {
59+
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
60+
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
61+
override fun afterTextChanged(s: Editable?) {
62+
val password = s.toString()
63+
if (password.length in 8..16) {
64+
binding.passwordLengthCheck.setTextColor(ContextCompat.getColor(binding.passwordLengthCheck.context, R.color.text_primary))
65+
} else {
66+
binding.passwordLengthCheck.setTextColor(ContextCompat.getColor(binding.passwordLengthCheck.context, R.color.text_secondary))
67+
}
68+
if (containsRequiredCharacters(password)) {
69+
binding.passwordCharacterCheck.setTextColor(ContextCompat.getColor(binding.passwordCharacterCheck.context, R.color.text_primary))
70+
} else {
71+
binding.passwordCharacterCheck.setTextColor(ContextCompat.getColor(binding.passwordCharacterCheck.context, R.color.text_secondary))
72+
}
73+
}
74+
})
75+
5476
viewLifecycleOwner.lifecycleScope.launch {
5577
signupViewModel.changePwEvent.collect { result ->
5678
when (result) {
5779
is ChangePwResult.Success -> {
5880
Toast.makeText(requireContext(), "비밀번호가 변경되었습니다.", Toast.LENGTH_SHORT).show()
5981

60-
findNavController().navigate(R.id.action_chpw_to_step4)
82+
val newPw = binding.passwordEditText.text.toString()
83+
val bundle = Bundle().apply { putString("newPassword", newPw) }
84+
findNavController().navigate(R.id.action_chpw_to_step4, bundle)
6185
}
6286
is ChangePwResult.Error -> {
6387
when (result.code) {
@@ -76,6 +100,15 @@ class ChangePWFragment : Fragment() {
76100
}
77101
}
78102
}
103+
104+
requireActivity().onBackPressedDispatcher.addCallback(
105+
viewLifecycleOwner,
106+
object : OnBackPressedCallback(true) {
107+
override fun handleOnBackPressed() {
108+
// 뒤로가기 무시
109+
}
110+
}
111+
)
79112
}
80113

81114
private fun goToLogin() {
@@ -84,6 +117,15 @@ class ChangePWFragment : Fragment() {
84117
startActivity(intent)
85118
}
86119

120+
private fun containsRequiredCharacters(input: String): Boolean {
121+
// 숫자 1개 이상: (?=.*[0-9])
122+
// 대문자 1개 이상: (?=.*[A-Z])
123+
// 소문자 1개 이상: (?=.*[a-z])
124+
// 특수문자 1개 이상: (?=.*[!@#\$%^&*(),.?":{}|<>])
125+
val pattern = Regex("^(?=.*[0-9])(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#\$%^&*(),.?\":{}|<>]).+$")
126+
return pattern.containsMatchIn(input)
127+
}
128+
87129
override fun onDestroyView() {
88130
super.onDestroyView()
89131
_binding = null

app/src/main/java/com/example/memowithtags/signup/fragments/SignupStep1Fragment.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ class SignupStep1Fragment : Fragment() {
8181
// 이메일 발송
8282
binding.nextButton.text = "인증 코드 전송 중..."
8383

84-
signupViewModel.sendEmail(email)
84+
val mode = requireActivity().intent.getStringExtra("mode") ?: "signUp"
85+
signupViewModel.sendEmail(email, mode)
8586
}
8687

8788
binding.emailEditText.addTextChangedListener(object : TextWatcher {

0 commit comments

Comments
 (0)