diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index db51f9120..cc12ba8ef 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -23,6 +23,9 @@
android:theme="@style/Theme.Naaga"
android:usesCleartextTraffic="true"
tools:targetApi="33">
+
+ android:exported="false"
+ android:screenOrientation="portrait" />
+ android:exported="false"
+ android:screenOrientation="portrait" />
+ android:exported="false"
+ android:screenOrientation="portrait" />
+ android:exported="false"
+ android:screenOrientation="portrait" />
+ android:exported="false"
+ android:screenOrientation="portrait" />
+ android:exported="false"
+ android:screenOrientation="portrait" />
+ android:exported="false"
+ android:screenOrientation="portrait" />
+ android:exported="false"
+ android:screenOrientation="portrait" />
+ android:exported="false"
+ android:screenOrientation="portrait" />
+ android:exported="false"
+ android:screenOrientation="portrait" />
diff --git a/android/app/src/main/java/com/now/naaga/data/remote/dto/NicknameDto.kt b/android/app/src/main/java/com/now/naaga/data/remote/dto/NicknameDto.kt
new file mode 100644
index 000000000..cbd176ee3
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/data/remote/dto/NicknameDto.kt
@@ -0,0 +1,10 @@
+package com.now.naaga.data.remote.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class NicknameDto(
+ @SerialName("nickname")
+ val nickname: String,
+)
diff --git a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/ProfileService.kt b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/ProfileService.kt
new file mode 100644
index 000000000..e9b82a30d
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/ProfileService.kt
@@ -0,0 +1,18 @@
+package com.now.naaga.data.remote.retrofit.service
+
+import com.now.naaga.data.remote.dto.NicknameDto
+import com.now.naaga.data.remote.dto.PlayerDto
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.PATCH
+
+interface ProfileService {
+ @GET("/profiles/my")
+ suspend fun getProfile(): Response
+
+ @PATCH("/profiles/my")
+ suspend fun modifyNickname(
+ @Body nicknameDto: NicknameDto,
+ ): Response
+}
diff --git a/android/app/src/main/java/com/now/naaga/data/repository/DefaultProfileRepository.kt b/android/app/src/main/java/com/now/naaga/data/repository/DefaultProfileRepository.kt
new file mode 100644
index 000000000..a2fe8165f
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/data/repository/DefaultProfileRepository.kt
@@ -0,0 +1,23 @@
+package com.now.naaga.data.repository
+
+import com.now.domain.model.Player
+import com.now.domain.repository.ProfileRepository
+import com.now.naaga.data.mapper.toDomain
+import com.now.naaga.data.remote.dto.NicknameDto
+import com.now.naaga.data.remote.retrofit.service.ProfileService
+import com.now.naaga.util.extension.getValueOrThrow
+
+class DefaultProfileRepository(
+ private val profileService: ProfileService,
+) : ProfileRepository {
+ override suspend fun fetchProfile(): Player {
+ val response = profileService.getProfile().getValueOrThrow()
+ return response.toDomain()
+ }
+
+ override suspend fun modifyNickname(nickname: String): String {
+ val nicknameDto = NicknameDto(nickname)
+ val response = profileService.modifyNickname(nicknameDto).getValueOrThrow()
+ return response.nickname
+ }
+}
diff --git a/android/app/src/main/java/com/now/naaga/di/RepositoryModule.kt b/android/app/src/main/java/com/now/naaga/di/RepositoryModule.kt
index a05423df3..14f66f119 100644
--- a/android/app/src/main/java/com/now/naaga/di/RepositoryModule.kt
+++ b/android/app/src/main/java/com/now/naaga/di/RepositoryModule.kt
@@ -4,6 +4,7 @@ import com.now.domain.repository.AdventureRepository
import com.now.domain.repository.AuthRepository
import com.now.domain.repository.LetterRepository
import com.now.domain.repository.PlaceRepository
+import com.now.domain.repository.ProfileRepository
import com.now.domain.repository.RankRepository
import com.now.domain.repository.StatisticsRepository
import com.now.naaga.data.local.AuthDataSource
@@ -11,12 +12,14 @@ import com.now.naaga.data.remote.retrofit.service.AdventureService
import com.now.naaga.data.remote.retrofit.service.AuthService
import com.now.naaga.data.remote.retrofit.service.LetterService
import com.now.naaga.data.remote.retrofit.service.PlaceService
+import com.now.naaga.data.remote.retrofit.service.ProfileService
import com.now.naaga.data.remote.retrofit.service.RankService
import com.now.naaga.data.remote.retrofit.service.StatisticsService
import com.now.naaga.data.repository.DefaultAdventureRepository
import com.now.naaga.data.repository.DefaultAuthRepository
import com.now.naaga.data.repository.DefaultLetterRepository
import com.now.naaga.data.repository.DefaultPlaceRepository
+import com.now.naaga.data.repository.DefaultProfileRepository
import com.now.naaga.data.repository.DefaultRankRepository
import com.now.naaga.data.repository.DefaultStatisticsRepository
import dagger.Module
@@ -55,4 +58,9 @@ class RepositoryModule {
@Provides
fun provideLetterRepository(letterService: LetterService): LetterRepository =
DefaultLetterRepository(letterService)
+
+ @Singleton
+ @Provides
+ fun provideProfileRepository(profileService: ProfileService): ProfileRepository =
+ DefaultProfileRepository(profileService)
}
diff --git a/android/app/src/main/java/com/now/naaga/di/ServiceModule.kt b/android/app/src/main/java/com/now/naaga/di/ServiceModule.kt
index 340e12d02..0920fdb49 100644
--- a/android/app/src/main/java/com/now/naaga/di/ServiceModule.kt
+++ b/android/app/src/main/java/com/now/naaga/di/ServiceModule.kt
@@ -7,6 +7,7 @@ import com.now.naaga.data.remote.retrofit.service.AdventureService
import com.now.naaga.data.remote.retrofit.service.AuthService
import com.now.naaga.data.remote.retrofit.service.LetterService
import com.now.naaga.data.remote.retrofit.service.PlaceService
+import com.now.naaga.data.remote.retrofit.service.ProfileService
import com.now.naaga.data.remote.retrofit.service.RankService
import com.now.naaga.data.remote.retrofit.service.StatisticsService
import dagger.Module
@@ -61,4 +62,8 @@ class ServiceModule {
@Singleton
@Provides
fun provideLetterService(retrofit: Retrofit): LetterService = retrofit.create(LetterService::class.java)
+
+ @Singleton
+ @Provides
+ fun provideProfileService(retrofit: Retrofit): ProfileService = retrofit.create(ProfileService::class.java)
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageActivity.kt b/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageActivity.kt
index 516b77f63..1c50e185b 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageActivity.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageActivity.kt
@@ -3,6 +3,7 @@ package com.now.naaga.presentation.mypage
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.now.domain.model.Statistics
@@ -14,6 +15,7 @@ import com.now.naaga.data.throwable.DataThrowable
import com.now.naaga.databinding.ActivityMyPageBinding
import com.now.naaga.presentation.adventurehistory.AdventureHistoryActivity
import com.now.naaga.presentation.mypage.statistics.MyPageStatisticsAdapter
+import com.now.naaga.presentation.profile.ProfileActivity
import com.now.naaga.presentation.uimodel.model.StatisticsUiModel
import com.now.naaga.util.extension.showToast
import dagger.hilt.android.AndroidEntryPoint
@@ -23,6 +25,14 @@ class MyPageActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
private lateinit var binding: ActivityMyPageBinding
private val viewModel: MyPageViewModel by viewModels()
+ private val myPageActivityLauncher =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ if (it.resultCode == RESULT_OK) {
+ val nickname = it.data?.getStringExtra(NICKNAME_KEY) ?: ""
+ binding.tvMypageNickname.text = nickname
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMyPageBinding.inflate(layoutInflater)
@@ -48,10 +58,14 @@ class MyPageActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
val intent = AdventureHistoryActivity.getIntent(this)
startActivity(intent)
}
+ binding.ivMypageProfileModify.setOnClickListener {
+ val intent = ProfileActivity.getIntent(this)
+ myPageActivityLauncher.launch(intent)
+ }
}
private fun fetchData() {
- viewModel.fetchRank()
+ viewModel.fetchProfile()
viewModel.fetchStatistics()
viewModel.fetchPlaces()
}
@@ -82,8 +96,16 @@ class MyPageActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
}
companion object {
+ private const val NICKNAME_KEY = "nickname"
+
fun getIntent(context: Context): Intent {
return Intent(context, MyPageActivity::class.java)
}
+
+ fun getIntent(context: Context, nickname: String): Intent {
+ return Intent(context, MyPageActivity::class.java).apply {
+ putExtra(NICKNAME_KEY, nickname)
+ }
+ }
}
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageViewModel.kt b/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageViewModel.kt
index 5a70439ea..e2f4122b4 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageViewModel.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageViewModel.kt
@@ -5,12 +5,12 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.now.domain.model.Place
-import com.now.domain.model.Rank
+import com.now.domain.model.Player
import com.now.domain.model.Statistics
import com.now.domain.model.type.OrderType
import com.now.domain.model.type.SortType
import com.now.domain.repository.PlaceRepository
-import com.now.domain.repository.RankRepository
+import com.now.domain.repository.ProfileRepository
import com.now.domain.repository.StatisticsRepository
import com.now.naaga.data.throwable.DataThrowable
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -20,12 +20,12 @@ import javax.inject.Inject
@HiltViewModel
class MyPageViewModel @Inject constructor(
- private val rankRepository: RankRepository,
+ private val profileRepository: ProfileRepository,
private val statisticsRepository: StatisticsRepository,
private val placeRepository: PlaceRepository,
) : ViewModel() {
- private val _rank = MutableLiveData()
- val rank: LiveData = _rank
+ private val _profile = MutableLiveData()
+ val profile: LiveData = _profile
private val _statistics = MutableLiveData()
val statistics: LiveData = _statistics
@@ -36,12 +36,12 @@ class MyPageViewModel @Inject constructor(
private val _throwable = MutableLiveData()
val throwable: LiveData = _throwable
- fun fetchRank() {
+ fun fetchProfile() {
viewModelScope.launch {
runCatching {
- rankRepository.getMyRank()
- }.onSuccess { rank ->
- _rank.value = rank
+ profileRepository.fetchProfile()
+ }.onSuccess { profile ->
+ _profile.value = profile
}.onFailure {
setThrowable(it)
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/profile/ProfileActivity.kt b/android/app/src/main/java/com/now/naaga/presentation/profile/ProfileActivity.kt
new file mode 100644
index 000000000..f7b780bd0
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/presentation/profile/ProfileActivity.kt
@@ -0,0 +1,60 @@
+package com.now.naaga.presentation.profile
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.now.naaga.R
+import com.now.naaga.databinding.ActivityProfileBinding
+import com.now.naaga.presentation.mypage.MyPageActivity
+import com.now.naaga.util.extension.showToast
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class ProfileActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityProfileBinding
+ private val viewModel: ProfileViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityProfileBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ binding.lifecycleOwner = this
+ binding.viewModel = viewModel
+ subscribe()
+ setClickListeners()
+ }
+
+ private fun subscribe() {
+ viewModel.modifyStatus.observe(this) { status ->
+ if (status) {
+ showToast(getString(R.string.profile_modify_success_message))
+ setResult(RESULT_OK, MyPageActivity.getIntent(this, viewModel.nickname.value.toString()))
+ finish()
+ }
+ }
+ viewModel.throwable.observe(this) {
+ showToast(getString(R.string.profile_modify_fail_message))
+ }
+ }
+
+ private fun setClickListeners() {
+ binding.ivProfileBack.setOnClickListener {
+ finish()
+ }
+ binding.btnProfileNicknameModify.setOnClickListener {
+ if (viewModel.isFormValid()) {
+ viewModel.modifyNickname()
+ } else {
+ showToast(getString(R.string.profile_no_content_message))
+ }
+ }
+ }
+
+ companion object {
+ fun getIntent(context: Context): Intent {
+ return Intent(context, ProfileActivity::class.java)
+ }
+ }
+}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/profile/ProfileViewModel.kt b/android/app/src/main/java/com/now/naaga/presentation/profile/ProfileViewModel.kt
new file mode 100644
index 000000000..3951c6877
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/presentation/profile/ProfileViewModel.kt
@@ -0,0 +1,55 @@
+package com.now.naaga.presentation.profile
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.now.domain.repository.ProfileRepository
+import com.now.naaga.data.throwable.DataThrowable
+import com.now.naaga.data.throwable.DataThrowable.NetworkThrowable
+import com.now.naaga.data.throwable.DataThrowable.PlayerThrowable
+import com.now.naaga.data.throwable.DataThrowable.UniversalThrowable
+import com.now.naaga.util.singleliveevent.MutableSingleLiveData
+import com.now.naaga.util.singleliveevent.SingleLiveData
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import java.io.IOException
+import javax.inject.Inject
+
+@HiltViewModel
+class ProfileViewModel @Inject constructor(
+ private val profileRepository: ProfileRepository,
+) : ViewModel() {
+
+ val nickname = MutableLiveData()
+
+ private val _modifyStatus = MutableSingleLiveData()
+ val modifyStatus: SingleLiveData = _modifyStatus
+
+ private val _throwable = MutableLiveData()
+ val throwable: LiveData = _throwable
+
+ fun modifyNickname() {
+ viewModelScope.launch {
+ runCatching {
+ profileRepository.modifyNickname(nickname.value.toString())
+ }.onSuccess {
+ _modifyStatus.setValue(true)
+ }.onFailure {
+ setThrowable(it)
+ }
+ }
+ }
+
+ fun isFormValid(): Boolean {
+ return nickname.value != null
+ }
+
+ private fun setThrowable(throwable: Throwable) {
+ when (throwable) {
+ is IOException -> _throwable.value = NetworkThrowable()
+ is UniversalThrowable -> _throwable.value = throwable
+ is PlayerThrowable -> _throwable.value = throwable
+ }
+ }
+}
diff --git a/android/app/src/main/res/drawable/ic_profile_modify.xml b/android/app/src/main/res/drawable/ic_profile_modify.xml
new file mode 100644
index 000000000..faddfce42
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_profile_modify.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/rect_border.xml b/android/app/src/main/res/drawable/rect_border.xml
new file mode 100644
index 000000000..b90be845e
--- /dev/null
+++ b/android/app/src/main/res/drawable/rect_border.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/activity_my_page.xml b/android/app/src/main/res/layout/activity_my_page.xml
index 57ab97459..35663c5be 100644
--- a/android/app/src/main/res/layout/activity_my_page.xml
+++ b/android/app/src/main/res/layout/activity_my_page.xml
@@ -36,14 +36,12 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/space_default_large"
- android:layout_marginBottom="@dimen/space_default_medium"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
- android:text="@{viewModel.rank.player.nickname}"
+ android:text="@{viewModel.profile.nickname}"
android:textColor="@color/white"
android:textSize="52sp"
- app:layout_constraintBottom_toTopOf="@id/rv_mypage_statistics"
app:layout_constraintEnd_toEndOf="@id/g_mypage"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_mypage_back"
@@ -53,25 +51,25 @@
android:id="@+id/btn_mypage_adventure_history"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginEnd="20dp"
+ android:layout_marginTop="@dimen/space_default_medium"
+ android:layout_marginHorizontal="24dp"
android:paddingVertical="16dp"
android:text="@string/mypage_adventure_record"
- android:textSize="16sp"
+ android:textSize="24sp"
app:radius="8dp"
app:buttonColor="yellow"
- app:layout_constraintBottom_toBottomOf="@id/tv_mypage_nickname"
+ app:layout_constraintTop_toBottomOf="@id/rv_mypage_statistics"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="@id/g_mypage"
- app:layout_constraintTop_toTopOf="@id/tv_mypage_nickname" />
+ app:layout_constraintBottom_toTopOf="@id/customGrid_mypage_places"
+ app:layout_constraintStart_toStartOf="parent" />
+ app:layout_constraintTop_toBottomOf="@id/btn_mypage_adventure_history" />
+
+
diff --git a/android/app/src/main/res/layout/activity_profile.xml b/android/app/src/main/res/layout/activity_profile.xml
new file mode 100644
index 000000000..eaf8c79f3
--- /dev/null
+++ b/android/app/src/main/res/layout/activity_profile.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/activity_upload.xml b/android/app/src/main/res/layout/activity_upload.xml
index 7058e7f08..337f7e5d8 100644
--- a/android/app/src/main/res/layout/activity_upload.xml
+++ b/android/app/src/main/res/layout/activity_upload.xml
@@ -106,14 +106,15 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/space_default_large"
android:autofillHints="no"
+ android:backgroundTint="@color/light_gray"
android:ellipsize="end"
android:hint="@string/upload_photo_title_hint"
- android:textColorHint="@color/white"
android:inputType="text"
android:maxLines="1"
android:text="@={viewModel.name}"
android:textAlignment="textEnd"
android:textColor="@color/white"
+ android:textColorHint="@color/main_gray_opacity_medium"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="@id/v_upload_divide_line_1"
app:layout_constraintStart_toStartOf="@id/v_upload_divide_line_1"
diff --git a/android/app/src/main/res/layout/rv_mypage_item_adventure.xml b/android/app/src/main/res/layout/rv_mypage_item_adventure.xml
index aebfee68b..acfc614e6 100644
--- a/android/app/src/main/res/layout/rv_mypage_item_adventure.xml
+++ b/android/app/src/main/res/layout/rv_mypage_item_adventure.xml
@@ -71,7 +71,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/space_default_medium"
- android:layout_marginEnd="@dimen/space_default_large"
+ android:layout_marginEnd="32dp"
android:text="@string/mypage_item_adventure_count_suffix"
android:textColor="@color/white"
android:textSize="20sp"
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index de53b92e0..5615c1307 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -152,6 +152,14 @@
내용을 1자 이상 입력해주세요!
전송하기
+
+ 변경 할 닉네임을 입력하세요!
+ 한글 2~10 글자 사이\n영어 2~20 글자 사이\n공백 허용, 특수문자 사용 불가
+ 닉네임 변경
+ 변경 되었어요!
+ 변경을 실패 했어요! 다시 시도해주세요
+ 변경 할 닉네임을 2글자 이상 작성해주세요!
+
문제가 발생했어요. 다시 시도해주세요!
diff --git a/android/domain/src/main/java/com/now/domain/repository/ProfileRepository.kt b/android/domain/src/main/java/com/now/domain/repository/ProfileRepository.kt
new file mode 100644
index 000000000..d179c5df6
--- /dev/null
+++ b/android/domain/src/main/java/com/now/domain/repository/ProfileRepository.kt
@@ -0,0 +1,9 @@
+package com.now.domain.repository
+
+import com.now.domain.model.Player
+
+interface ProfileRepository {
+ suspend fun fetchProfile(): Player
+
+ suspend fun modifyNickname(nickname: String): String
+}