Skip to content
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

[Feature/#92] 프로필 수정 구현 #95

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,13 @@
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
android:theme="@style/LicenseTheme" />

<activity android:name=".presentation.my.terms.TermsActivity"
<activity
android:name=".presentation.my.terms.TermsActivity"
android:exported="false"
android:screenOrientation="portrait" />

<activity
android:name=".presentation.my.update.ProfileUpdateActivity"
android:exported="false"
android:screenOrientation="portrait" />

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.teamfillin.fillin.data.response

data class ResponseUpdateUser(
val updateUser: UpdateUserDto
) {
data class UpdateUserDto(
val id: Int,
val social: String,
val nickname: String,
val imageUrl: String,
val refreshToken: String,
val isDeleted: Boolean,
val updatedAt: String,
val email: String,
val createdAt: String,
val idKey: String,
val camera: List<String>
)
}

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import com.teamfillin.fillin.core.converter.JsonConverter
import com.teamfillin.fillin.data.response.BaseResponse
import com.teamfillin.fillin.data.response.ResponseAuth
import com.teamfillin.fillin.data.response.ResponseRefresh
import com.teamfillin.fillin.data.response.ResponseUpdateUser
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.Call
import retrofit2.http.*

Expand All @@ -22,4 +25,11 @@ interface AuthService {
@Header("accesstoken") accessToken: String,
@Header("refreshtoken") refreshToken: String
): Call<BaseResponse<ResponseRefresh>>

@Multipart
@PUT("user/edit")
suspend fun putUser(
@Part media: MultipartBody.Part?,
@PartMap body: HashMap<String, RequestBody>
): BaseResponse<ResponseUpdateUser>
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.teamfillin.fillin.databinding.ActivityMyPageBinding
import com.teamfillin.fillin.presentation.dialog.PhotoDialogFragment
import com.teamfillin.fillin.presentation.map.SpaceDecoration
import com.teamfillin.fillin.presentation.my.terms.TermsActivity
import com.teamfillin.fillin.presentation.my.update.ProfileUpdateActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
Expand Down Expand Up @@ -69,6 +70,12 @@ class MyPageActivity : BindingActivity<ActivityMyPageBinding>(R.layout.activity_
finish()
}

binding.ivEdit.setOnSingleClickListener {
Intent(this, ProfileUpdateActivity::class.java).apply {
startActivity(this)
}
}

binding.ivUp.setOnSingleClickListener {
binding.rvMyPage.isVisible = !binding.rvMyPage.isVisible
imageRotation(binding.ivUp, binding.rvMyPage.isVisible)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.teamfillin.fillin.presentation.my.update

import android.content.Intent
import android.graphics.Color
import android.graphics.ImageDecoder
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glide 뗄까...

import com.teamfillin.fillin.R
import com.teamfillin.fillin.core.base.BindingActivity
import com.teamfillin.fillin.core.context.colorOf
import com.teamfillin.fillin.core.context.toast
import com.teamfillin.fillin.core.view.UiState
import com.teamfillin.fillin.databinding.ActivityProfileUpdateBinding
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import java.io.IOException

@AndroidEntryPoint
class ProfileUpdateActivity :
BindingActivity<ActivityProfileUpdateBinding>(R.layout.activity_profile_update) {
private val viewModel by viewModels<ProfileUpdateViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding.viewModel = viewModel
initView()
initEvent()
observe()
}

private fun initView() {
binding.etNickname.doAfterTextChanged {
editTextNameBlankCheck()
}

binding.etCamera.doAfterTextChanged {
editTextCameraBlankCheck()
}
}

private fun initEvent() {
binding.ivNicknameClear.setOnClickListener {
viewModel.nickname.value = ""
}

binding.ivCameraClear.setOnClickListener {
viewModel.cameraName.value = ""
}
Comment on lines +53 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 함수로 빼보자


binding.ivProfile.setOnClickListener {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
Comment on lines +62 to +63
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 사용합시다

filterActivityLauncher.launch(intent)
}

binding.btnAddPhoto.setOnClickListener {
toast("asdf")
viewModel.putUser()
}
}

private fun observe() {
viewModel.isClickable.flowWithLifecycle(lifecycle)
.onEach {
with(binding.btnAddPhoto) {
isClickable = it
setBackgroundColor(
if (it) colorOf(R.color.fillin_red) else Color.parseColor("#474645")
)
setTextColor(
if (it) colorOf(R.color.fillin_black) else Color.parseColor("#6F6F6F")
)
Comment on lines +78 to +83
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Color.parseColor는 최대한 지양해보자!

}
}.launchIn(lifecycleScope)

viewModel.updateUser.flowWithLifecycle(lifecycle)
.onEach {
when (it) {
is UiState.Success -> {
toast("프로필 수정이 완료되었습니다.")
finish()
}
is UiState.Failure -> {
toast(it.msg)
}
else -> {}
}
}
}

private fun editTextNameBlankCheck() {
binding.ivNicknameClear.isVisible = !binding.etNickname.text.isNullOrEmpty()
}

private fun editTextCameraBlankCheck() {
binding.ivCameraClear.isVisible = !binding.etCamera.text.isNullOrEmpty()
}

private val filterActivityLauncher: ActivityResultLauncher<Intent> =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK && it.data != null) {
val imageUri = it.data?.data
try {
imageUri?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
viewModel.setImageBitmap(ImageDecoder.decodeBitmap(ImageDecoder.createSource(contentResolver, imageUri)))
} else {
viewModel.setImageBitmap(MediaStore.Images.Media.getBitmap(contentResolver, imageUri))
}
}
Comment on lines +115 to +121
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ContentUriRequestBody 사용하는 쪽으로 가보는건 어떨깝쇼

} catch (e: IOException) {
e.printStackTrace()
}

Glide.with(this).load(imageUri).circleCrop().into(binding.ivProfile)
} else if (it.resultCode == RESULT_CANCELED) {
toast("사진 선택 취소")
} else {
Log.d("ActivityResult", "something wrong")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.teamfillin.fillin.presentation.my.update

import android.graphics.Bitmap
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.teamfillin.fillin.core.view.UiState
import com.teamfillin.fillin.data.response.ResponseUpdateUser
import com.teamfillin.fillin.data.service.AuthService
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okio.BufferedSink
import javax.inject.Inject

@HiltViewModel
class ProfileUpdateViewModel @Inject constructor(
private val service: AuthService
): ViewModel() {
val nickname = MutableStateFlow("")
val isClickable: Flow<Boolean> = nickname.map { it.isNotBlank() }

val cameraName = MutableStateFlow("")

private val _updateUser = MutableStateFlow<UiState<ResponseUpdateUser>>(UiState.Loading)
val updateUser: StateFlow<UiState<ResponseUpdateUser>> = _updateUser

private var imageBitmap: Bitmap? = null

fun setImageBitmap(bitmap: Bitmap) {
imageBitmap = bitmap
}

fun putUser() {
val textHashMap = hashMapOf<String, RequestBody>()
textHashMap["nickname"] = nickname.value.toRequestBody("text/plain".toMediaTypeOrNull())
textHashMap["camera"] = cameraName.value.toRequestBody("text/plain".toMediaTypeOrNull())
val bitmapRequestBody = imageBitmap?.let { BitmapRequestBody(it) }
val bitmapMultipartBody = bitmapRequestBody?.let { MultipartBody.Part.createFormData("imageUrl", "imageUrl", it) }

viewModelScope.launch {
runCatching {
service.putUser(bitmapMultipartBody, textHashMap)
}.onSuccess {
_updateUser.value = UiState.Success(it.data)
}.onFailure {
_updateUser.value = UiState.Failure("서버 통신 오류 : $it")
}
}
}

companion object {
class BitmapRequestBody(private val bitmap: Bitmap) : RequestBody() {
override fun contentType(): MediaType? {
return "image/png".toMediaTypeOrNull()
}

override fun writeTo(sink: BufferedSink) {
bitmap.compress(Bitmap.CompressFormat.PNG, 99, sink.outputStream()) //99프로 압축
}
}
}
}
16 changes: 16 additions & 0 deletions app/src/main/res/drawable/ic_clear.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:strokeWidth="1"
android:pathData="M4,2.002L14,12.002"
android:fillColor="#00000000"
android:strokeColor="#FDFAF9"/>
<path
android:strokeWidth="1"
android:pathData="M4,12L14,2"
android:fillColor="#00000000"
android:strokeColor="#FDFAF9"/>
</vector>
Loading