From e05b60f664504dc3898a726c31ad60902619f0ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=B0=AC?= Date: Sun, 21 Apr 2024 13:01:30 +0900 Subject: [PATCH 1/3] =?UTF-8?q?Feat:[snsproject]=20#11=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EB=8B=89=EB=84=A4=EC=9E=84=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/data/di/UserModule.kt | 5 ++ .../example/data/model/UpdateMyInfoParam.kt | 15 ++++ .../com/example/data/retrofit/UserService.kt | 14 ++-- .../main/setting/GetMyUserUseCaseImpl.kt | 2 +- .../main/setting/UpdateMyNameUseCaseImpl.kt | 23 ++++++ .../main/setting/UpdateMyNameUseCase.kt | 5 ++ .../main/setting/SettingScreen.kt | 37 ++++++--- .../main/setting/SettingViewModel.kt | 81 ++++++++++--------- .../main/setting/UsernameDialog.kt | 76 +++++++++++++++++ 9 files changed, 203 insertions(+), 55 deletions(-) create mode 100644 data/src/main/java/com/example/data/model/UpdateMyInfoParam.kt create mode 100644 data/src/main/java/com/example/data/usecase/main/setting/UpdateMyNameUseCaseImpl.kt create mode 100644 domain/src/main/java/com/example/domain/usecase/main/setting/UpdateMyNameUseCase.kt create mode 100644 presentation/src/main/java/com/example/presentation/main/setting/UsernameDialog.kt diff --git a/data/src/main/java/com/example/data/di/UserModule.kt b/data/src/main/java/com/example/data/di/UserModule.kt index b912336..36f1a48 100644 --- a/data/src/main/java/com/example/data/di/UserModule.kt +++ b/data/src/main/java/com/example/data/di/UserModule.kt @@ -6,12 +6,14 @@ import com.example.data.usecase.LoginUseCaseImpl import com.example.data.usecase.SetTokenUseCaseImpl import com.example.data.usecase.SignUpUseCaseImpl import com.example.data.usecase.main.setting.GetMyUserUseCaseImpl +import com.example.data.usecase.main.setting.UpdateMyNameUseCaseImpl import com.example.domain.usecase.login.ClearTokenUseCase import com.example.domain.usecase.login.GetTokenUseCase import com.example.domain.usecase.login.LoginUseCase import com.example.domain.usecase.login.SetTokenUseCase import com.example.domain.usecase.login.SignUpUseCase import com.example.domain.usecase.main.setting.GetMyUserUseCase +import com.example.domain.usecase.main.setting.UpdateMyNameUseCase import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -38,4 +40,7 @@ abstract class UserModule { @Binds abstract fun bindGetMyUserUseCase(getMyUserUseCaseImpl: GetMyUserUseCaseImpl) : GetMyUserUseCase + + @Binds + abstract fun bindGetMyNameInfoUserUseCase(getMyNameUseCaseImpl: UpdateMyNameUseCaseImpl) : UpdateMyNameUseCase } diff --git a/data/src/main/java/com/example/data/model/UpdateMyInfoParam.kt b/data/src/main/java/com/example/data/model/UpdateMyInfoParam.kt new file mode 100644 index 0000000..efee499 --- /dev/null +++ b/data/src/main/java/com/example/data/model/UpdateMyInfoParam.kt @@ -0,0 +1,15 @@ +package com.example.data.model + +import com.google.gson.Gson +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody + +data class UpdateMyInfoParam( + val userName: String, + val extraUserInfo: String, + val profileFilePath: String +){ + fun toRequestBody() : RequestBody { + return Gson().toJson(this).toRequestBody() + } +} diff --git a/data/src/main/java/com/example/data/retrofit/UserService.kt b/data/src/main/java/com/example/data/retrofit/UserService.kt index 20f86e8..58f02f0 100644 --- a/data/src/main/java/com/example/data/retrofit/UserService.kt +++ b/data/src/main/java/com/example/data/retrofit/UserService.kt @@ -5,23 +5,25 @@ import com.example.data.model.UserDto import okhttp3.RequestBody import retrofit2.http.Body import retrofit2.http.GET -import retrofit2.http.Headers +import retrofit2.http.PATCH import retrofit2.http.POST interface UserService { @POST("users/login") - @Headers("Content-Type:application/json; charset=UTF-8") suspend fun login( @Body requestBody: RequestBody - ) : CommonResponse + ): CommonResponse @POST("users/sign-up") - @Headers("Content-Type:application/json; charset=UTF-8") suspend fun signUp( @Body requestBody: RequestBody ): CommonResponse @GET("users/my-page") - @Headers("Content-Type:application/json; charset=UTF-8") - suspend fun myPage(): CommonResponse + suspend fun getMyPage(): CommonResponse + + @PATCH("users/my-page") + suspend fun patchMyPage( + @Body request: RequestBody + ): CommonResponse } diff --git a/data/src/main/java/com/example/data/usecase/main/setting/GetMyUserUseCaseImpl.kt b/data/src/main/java/com/example/data/usecase/main/setting/GetMyUserUseCaseImpl.kt index f8ded63..54bf55e 100644 --- a/data/src/main/java/com/example/data/usecase/main/setting/GetMyUserUseCaseImpl.kt +++ b/data/src/main/java/com/example/data/usecase/main/setting/GetMyUserUseCaseImpl.kt @@ -10,7 +10,7 @@ class GetMyUserUseCaseImpl @Inject constructor( private val userService: UserService ) : GetMyUserUseCase { override suspend fun invoke(): Result = runCatching { - val userDto = userService.myPage().data + val userDto = userService.getMyPage().data Log.e("GetMyUserUseCaseImpl", "userDto: $userDto") userDto.toUser() } diff --git a/data/src/main/java/com/example/data/usecase/main/setting/UpdateMyNameUseCaseImpl.kt b/data/src/main/java/com/example/data/usecase/main/setting/UpdateMyNameUseCaseImpl.kt new file mode 100644 index 0000000..7e3ad09 --- /dev/null +++ b/data/src/main/java/com/example/data/usecase/main/setting/UpdateMyNameUseCaseImpl.kt @@ -0,0 +1,23 @@ +package com.example.data.usecase.main.setting + +import com.example.data.model.UpdateMyInfoParam +import com.example.data.retrofit.UserService +import com.example.domain.usecase.main.setting.GetMyUserUseCase +import com.example.domain.usecase.main.setting.UpdateMyNameUseCase +import javax.inject.Inject + +class UpdateMyNameUseCaseImpl @Inject constructor( + private val service: UserService, + private val getMyUserUseCase: GetMyUserUseCase +) : UpdateMyNameUseCase { + override suspend fun invoke(userName: String): Result = runCatching { + val user = getMyUserUseCase().getOrThrow() + val requestBody = UpdateMyInfoParam( + userName = userName, + extraUserInfo = user.profileImageUrl.orEmpty(), + profileFilePath = "", + + ).toRequestBody() + service.patchMyPage(requestBody) + } +} diff --git a/domain/src/main/java/com/example/domain/usecase/main/setting/UpdateMyNameUseCase.kt b/domain/src/main/java/com/example/domain/usecase/main/setting/UpdateMyNameUseCase.kt new file mode 100644 index 0000000..189844f --- /dev/null +++ b/domain/src/main/java/com/example/domain/usecase/main/setting/UpdateMyNameUseCase.kt @@ -0,0 +1,5 @@ +package com.example.domain.usecase.main.setting + +interface UpdateMyNameUseCase { + suspend operator fun invoke(userName : String) : Result +} diff --git a/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt b/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt index 96d6b8c..02664fd 100644 --- a/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt +++ b/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt @@ -21,6 +21,10 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -39,6 +43,10 @@ import org.orbitmvi.orbit.compose.collectSideEffect fun SettingScreen(viewModel: SettingViewModel = hiltViewModel()) { val state = viewModel.collectAsState().value val context = LocalContext.current + + var usernameDialogVisible by remember { + mutableStateOf(false) + } viewModel.collectSideEffect { sideEffect: SettingSideEffect -> when (sideEffect) { is SettingSideEffect.Toast -> { @@ -58,10 +66,17 @@ fun SettingScreen(viewModel: SettingViewModel = hiltViewModel()) { SettingScreen( username = state.username, profileImageUrl = state.profileImageUrl, - onNameChangeClick = { }, + onNameChangeClick = { usernameDialogVisible = true }, onLogoutClick = viewModel::onLogoutClick, onImageChangeClick = { }, ) + + UsernameDialog( + visible = usernameDialogVisible, + initialUsername = state.username, + onDismissRequest = {usernameDialogVisible = false}, + onUserNameChange = viewModel::onUsernameChange + ) } @Composable @@ -89,16 +104,16 @@ fun SettingScreen( ) { Box( modifier = - Modifier - .size(30.dp) - .border(width = 1.dp, color = Color.Gray, shape = CircleShape) - .background(color = Color.White, shape = CircleShape), + Modifier + .size(30.dp) + .border(width = 1.dp, color = Color.Gray, shape = CircleShape) + .background(color = Color.White, shape = CircleShape), ) { Icon( modifier = - Modifier - .align(Alignment.Center) - .size(20.dp), + Modifier + .align(Alignment.Center) + .size(20.dp), imageVector = Icons.Default.Settings, contentDescription = null, tint = MaterialTheme.colorScheme.primary, @@ -108,9 +123,9 @@ fun SettingScreen( } Text( modifier = - Modifier - .padding(top = 8.dp) - .clickable { onNameChangeClick() }, + Modifier + .padding(top = 8.dp) + .clickable { onNameChangeClick() }, text = username, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, diff --git a/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt b/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt index 9e2b42d..4818345 100644 --- a/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt +++ b/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.Immutable import androidx.lifecycle.ViewModel import com.example.domain.usecase.login.ClearTokenUseCase import com.example.domain.usecase.main.setting.GetMyUserUseCase +import com.example.domain.usecase.main.setting.UpdateMyNameUseCase import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineExceptionHandler @@ -17,44 +18,50 @@ import org.orbitmvi.orbit.viewmodel.container @HiltViewModel class SettingViewModel - @Inject - constructor( - private val clearTokenUseCase: ClearTokenUseCase, - private val getMyUserUseCase: GetMyUserUseCase, - ) : ViewModel(), ContainerHost { - override val container: Container = - container( - initialState = SettingState(), - buildSettings = { - this.exceptionHandler = - CoroutineExceptionHandler { _, throwable -> - intent { postSideEffect(SettingSideEffect.Toast(throwable.message ?: "")) } - } - }, - ) - - init { - load() - } - - private fun load() = - intent { - val user = getMyUserUseCase().getOrThrow() - Log.e("SettingViewModel", "user : $user") - reduce { - state.copy( - profileImageUrl = user.profileImageUrl, - username = user.username, - ) - } - } - - fun onLogoutClick() = - intent { - clearTokenUseCase().getOrThrow() - postSideEffect(SettingSideEffect.NavigateToLoginActivity) +@Inject +constructor( + private val clearTokenUseCase: ClearTokenUseCase, + private val getMyUserUseCase: GetMyUserUseCase, + private val updateMyNameUseCase: UpdateMyNameUseCase +) : ViewModel(), ContainerHost { + override val container: Container = + container( + initialState = SettingState(), + buildSettings = { + this.exceptionHandler = + CoroutineExceptionHandler { _, throwable -> + intent { postSideEffect(SettingSideEffect.Toast(throwable.message ?: "")) } + } + }, + ) + + init { + load() + } + + private fun load() = + intent { + val user = getMyUserUseCase().getOrThrow() + Log.e("SettingViewModel", "user : $user") + reduce { + state.copy( + profileImageUrl = user.profileImageUrl, + username = user.username, + ) } + } + + fun onLogoutClick() = + intent { + clearTokenUseCase().getOrThrow() + postSideEffect(SettingSideEffect.NavigateToLoginActivity) + } + + fun onUsernameChange(username: String) = intent { + updateMyNameUseCase(username).getOrThrow() + load() } +} @Immutable data class SettingState( @@ -64,6 +71,6 @@ data class SettingState( sealed interface SettingSideEffect { class Toast(val message: String) : SettingSideEffect - + data object NavigateToLoginActivity : SettingSideEffect } diff --git a/presentation/src/main/java/com/example/presentation/main/setting/UsernameDialog.kt b/presentation/src/main/java/com/example/presentation/main/setting/UsernameDialog.kt new file mode 100644 index 0000000..64f03ba --- /dev/null +++ b/presentation/src/main/java/com/example/presentation/main/setting/UsernameDialog.kt @@ -0,0 +1,76 @@ +package com.example.presentation.main.setting + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.window.Dialog +import com.example.presentation.ui.theme.SnsProjectTheme + +@Composable +fun UsernameDialog( + visible: Boolean, + initialUsername: String, + onUserNameChange: (String) -> Unit, + onDismissRequest: () -> Unit, +) { + + if (visible) { + var username by remember { + mutableStateOf(initialUsername) + } + Dialog(onDismissRequest = onDismissRequest) { + Surface { + Column(modifier = Modifier.fillMaxWidth(0.8f)) { + TextField( + modifier = Modifier + .fillMaxWidth(), + value = username, + onValueChange = { username = it }, + textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), + + ) + + Row { + TextButton( + modifier = Modifier.weight(1f), + onClick = { + onUserNameChange(username) + onDismissRequest() + }) { + Text(text = "변경") + } + + TextButton( + modifier = Modifier.weight(1f), + onClick = onDismissRequest) { + Text(text = "취소") + } + } + } + } + } + } +} + +@Preview +@Composable +private fun UsernameDialogPreview() { + SnsProjectTheme { + UsernameDialog(visible = true, initialUsername = "chan", onUserNameChange = {}, onDismissRequest = {}) + } +} From 11fb7b140e40981e6adda6b49e8dd9fcbb226d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=B0=AC?= Date: Thu, 25 Apr 2024 20:12:40 +0900 Subject: [PATCH 2/3] =?UTF-8?q?Feat:[snsproject]=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/data/di/FileModule.kt | 28 ++++++++++ .../com/example/data/di/RetrofitModule.kt | 4 ++ .../java/com/example/data/di/UserModule.kt | 12 +++-- .../java/com/example/data/model/FileDto.kt | 14 +++++ .../com/example/data/retrofit/FileService.kt | 21 ++++++++ .../example/data/retrofit/UriRequestBody.kt | 36 +++++++++++++ .../data/usecase/file/GetImageUseCaseImpl.kt | 53 +++++++++++++++++++ .../usecase/file/GetInputStreamUseCaseImpl.kt | 17 ++++++ .../usecase/file/UploadImageUseCaseImpl.kt | 39 ++++++++++++++ ...UseCaseImpl.kt => SetMyUserUseCaseImpl.kt} | 17 +++--- .../setting/SetProfileImageUseCaseImpl.kt | 35 ++++++++++++ domain/build.gradle.kts | 2 + .../java/com/example/domain/model/Image.kt | 15 ++++++ .../domain/usecase/file/GetImageUseCase.kt | 7 +++ .../usecase/file/GetInputStreamUseCase.kt | 7 +++ .../domain/usecase/file/UploadImageUseCase.kt | 9 ++++ .../usecase/main/setting/SetMyUserUseCase.kt | 5 ++ .../main/setting/SetProfileImageUseCase.kt | 5 ++ .../main/setting/UpdateMyNameUseCase.kt | 5 -- .../main/setting/SettingScreen.kt | 21 ++++++-- .../main/setting/SettingViewModel.kt | 16 ++++-- .../main/setting/UsernameDialog.kt | 16 ++++-- 22 files changed, 359 insertions(+), 25 deletions(-) create mode 100644 data/src/main/java/com/example/data/di/FileModule.kt create mode 100644 data/src/main/java/com/example/data/model/FileDto.kt create mode 100644 data/src/main/java/com/example/data/retrofit/FileService.kt create mode 100644 data/src/main/java/com/example/data/retrofit/UriRequestBody.kt create mode 100644 data/src/main/java/com/example/data/usecase/file/GetImageUseCaseImpl.kt create mode 100644 data/src/main/java/com/example/data/usecase/file/GetInputStreamUseCaseImpl.kt create mode 100644 data/src/main/java/com/example/data/usecase/file/UploadImageUseCaseImpl.kt rename data/src/main/java/com/example/data/usecase/main/setting/{UpdateMyNameUseCaseImpl.kt => SetMyUserUseCaseImpl.kt} (54%) create mode 100644 data/src/main/java/com/example/data/usecase/main/setting/SetProfileImageUseCaseImpl.kt create mode 100644 domain/src/main/java/com/example/domain/model/Image.kt create mode 100644 domain/src/main/java/com/example/domain/usecase/file/GetImageUseCase.kt create mode 100644 domain/src/main/java/com/example/domain/usecase/file/GetInputStreamUseCase.kt create mode 100644 domain/src/main/java/com/example/domain/usecase/file/UploadImageUseCase.kt create mode 100644 domain/src/main/java/com/example/domain/usecase/main/setting/SetMyUserUseCase.kt create mode 100644 domain/src/main/java/com/example/domain/usecase/main/setting/SetProfileImageUseCase.kt delete mode 100644 domain/src/main/java/com/example/domain/usecase/main/setting/UpdateMyNameUseCase.kt diff --git a/data/src/main/java/com/example/data/di/FileModule.kt b/data/src/main/java/com/example/data/di/FileModule.kt new file mode 100644 index 0000000..ea0aec3 --- /dev/null +++ b/data/src/main/java/com/example/data/di/FileModule.kt @@ -0,0 +1,28 @@ +package com.example.data.di + +import com.example.data.usecase.file.GetImageUseCaseImpl +import com.example.data.usecase.file.GetInputStreamUseCaseImpl +import com.example.data.usecase.file.UploadImageUseCaseImpl +import com.example.domain.usecase.file.GetImageUseCase +import com.example.domain.usecase.file.GetInputStreamUseCase +import com.example.domain.usecase.file.UploadImageUseCase +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + + +@Module +@InstallIn(SingletonComponent::class) +abstract class FileModule { + + @Binds + abstract fun bindGetInputStreamUseCase(getInputStreamUseCaseImpl: GetInputStreamUseCaseImpl) : GetInputStreamUseCase + + @Binds + abstract fun bindGetImageUseCase(getImageUseCaseImpl: GetImageUseCaseImpl) : GetImageUseCase + + + @Binds + abstract fun bindUploadImageUseCase(uploadImageUseCaseImpl: UploadImageUseCaseImpl) : UploadImageUseCase +} diff --git a/data/src/main/java/com/example/data/di/RetrofitModule.kt b/data/src/main/java/com/example/data/di/RetrofitModule.kt index 3c2d8d7..f65f87c 100644 --- a/data/src/main/java/com/example/data/di/RetrofitModule.kt +++ b/data/src/main/java/com/example/data/di/RetrofitModule.kt @@ -2,6 +2,7 @@ package com.example.data.di import com.example.data.BuildConfig import com.example.data.retrofit.AddInterceptor +import com.example.data.retrofit.FileService import com.example.data.retrofit.UserService import com.google.gson.GsonBuilder import dagger.Module @@ -41,4 +42,7 @@ class RetrofitModule { @Provides fun provideUserService(retrofit: Retrofit): UserService = retrofit.create(UserService::class.java) + @Provides + fun provideFileService(retrofit: Retrofit): FileService = retrofit.create(FileService::class.java) + } diff --git a/data/src/main/java/com/example/data/di/UserModule.kt b/data/src/main/java/com/example/data/di/UserModule.kt index 36f1a48..8dc2ad1 100644 --- a/data/src/main/java/com/example/data/di/UserModule.kt +++ b/data/src/main/java/com/example/data/di/UserModule.kt @@ -6,14 +6,16 @@ import com.example.data.usecase.LoginUseCaseImpl import com.example.data.usecase.SetTokenUseCaseImpl import com.example.data.usecase.SignUpUseCaseImpl import com.example.data.usecase.main.setting.GetMyUserUseCaseImpl -import com.example.data.usecase.main.setting.UpdateMyNameUseCaseImpl +import com.example.data.usecase.main.setting.SetMyUserUseCaseImpl +import com.example.data.usecase.main.setting.SetProfileImageUseCaseImpl import com.example.domain.usecase.login.ClearTokenUseCase import com.example.domain.usecase.login.GetTokenUseCase import com.example.domain.usecase.login.LoginUseCase import com.example.domain.usecase.login.SetTokenUseCase import com.example.domain.usecase.login.SignUpUseCase import com.example.domain.usecase.main.setting.GetMyUserUseCase -import com.example.domain.usecase.main.setting.UpdateMyNameUseCase +import com.example.domain.usecase.main.setting.SetMyUserUseCase +import com.example.domain.usecase.main.setting.SetProfileImageUseCase import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -42,5 +44,9 @@ abstract class UserModule { abstract fun bindGetMyUserUseCase(getMyUserUseCaseImpl: GetMyUserUseCaseImpl) : GetMyUserUseCase @Binds - abstract fun bindGetMyNameInfoUserUseCase(getMyNameUseCaseImpl: UpdateMyNameUseCaseImpl) : UpdateMyNameUseCase + abstract fun bindGetMyNameInfoUserUseCase(getMyNameUseCaseImpl: SetMyUserUseCaseImpl) : SetMyUserUseCase + + @Binds + abstract fun bindSetProfileImageUseCase(setProfileImageUseCaseImpl: SetProfileImageUseCaseImpl) : SetProfileImageUseCase + } diff --git a/data/src/main/java/com/example/data/model/FileDto.kt b/data/src/main/java/com/example/data/model/FileDto.kt new file mode 100644 index 0000000..97b3f68 --- /dev/null +++ b/data/src/main/java/com/example/data/model/FileDto.kt @@ -0,0 +1,14 @@ +package com.example.data.model + +import com.google.gson.annotations.SerializedName + +data class FileDto( + @SerializedName("id") + val id: Long, + @SerializedName("fileName") + val fileName: String, + @SerializedName("createdAt") + val createdAt: String, + @SerializedName("filePath") + val filePath:String +) diff --git a/data/src/main/java/com/example/data/retrofit/FileService.kt b/data/src/main/java/com/example/data/retrofit/FileService.kt new file mode 100644 index 0000000..f39c915 --- /dev/null +++ b/data/src/main/java/com/example/data/retrofit/FileService.kt @@ -0,0 +1,21 @@ +package com.example.data.retrofit + +import com.example.data.model.CommonResponse +import com.example.data.model.FileDto +import okhttp3.MultipartBody +import retrofit2.http.Headers +import retrofit2.http.Multipart +import retrofit2.http.POST +import retrofit2.http.Part + +interface FileService { + + @POST("files") + @Multipart + @Headers("ContentType: multipart/form-data;") + suspend fun uploadFile( + @Part fileName: MultipartBody.Part, + @Part file: MultipartBody.Part + ): CommonResponse + +} diff --git a/data/src/main/java/com/example/data/retrofit/UriRequestBody.kt b/data/src/main/java/com/example/data/retrofit/UriRequestBody.kt new file mode 100644 index 0000000..84fe4f6 --- /dev/null +++ b/data/src/main/java/com/example/data/retrofit/UriRequestBody.kt @@ -0,0 +1,36 @@ +package com.example.data.retrofit + +import android.net.Uri +import android.util.Log +import com.example.domain.usecase.file.GetInputStreamUseCase +import okhttp3.MediaType +import okhttp3.RequestBody +import okio.BufferedSink +import okio.FileNotFoundException +import okio.source + +class UriRequestBody( + private val contentUri: String, + private val getInputStreamUseCase: GetInputStreamUseCase, + private val contentType: MediaType? = null, + private val contentLength: Long +) : RequestBody() { + override fun contentType(): MediaType? { + return contentType + } + + override fun contentLength(): Long { + return contentLength + } + + override fun writeTo(sink: BufferedSink) { + try{ + getInputStreamUseCase(contentUri).getOrThrow() + .use { inputStream -> + sink.writeAll(inputStream.source()) + } + }catch (e: FileNotFoundException) { + Log.e("UriRequestBody", "${e.message}") + } + } +} diff --git a/data/src/main/java/com/example/data/usecase/file/GetImageUseCaseImpl.kt b/data/src/main/java/com/example/data/usecase/file/GetImageUseCaseImpl.kt new file mode 100644 index 0000000..ad6577f --- /dev/null +++ b/data/src/main/java/com/example/data/usecase/file/GetImageUseCaseImpl.kt @@ -0,0 +1,53 @@ +package com.example.data.usecase.file + +import android.content.Context +import android.net.Uri +import android.provider.MediaStore +import android.util.Log +import com.example.domain.model.Image +import com.example.domain.usecase.file.GetImageUseCase +import javax.inject.Inject + +class GetImageUseCaseImpl @Inject constructor( + private val context: Context +) : GetImageUseCase { + + override fun invoke(contentUri: String): Image? { + val uri = Uri.parse(contentUri) + val projection = arrayOf( + MediaStore.Images.Media._ID, + MediaStore.Images.Media.DISPLAY_NAME, + MediaStore.Images.Media.SIZE, + MediaStore.Images.Media.MIME_TYPE, + ) + val cursor = context.contentResolver.query( + uri, + projection, + null, + null, + null + ) + + return cursor?.use { + it.moveToNext() + val idIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID) + val nameIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME) + val sizeIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE) + val mimeIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE) + + val name = it.getString(nameIndex) + val size = it.getLong(sizeIndex) + val mimeType = it.getString(mimeIndex) + val image = Image( + uri = contentUri, + name = name, + size = size, + mimeType = mimeType + ) + + Log.e("GetImageUseCaseImpl curosr", "${image}") + + image + } + } +} diff --git a/data/src/main/java/com/example/data/usecase/file/GetInputStreamUseCaseImpl.kt b/data/src/main/java/com/example/data/usecase/file/GetInputStreamUseCaseImpl.kt new file mode 100644 index 0000000..5e90f66 --- /dev/null +++ b/data/src/main/java/com/example/data/usecase/file/GetInputStreamUseCaseImpl.kt @@ -0,0 +1,17 @@ +package com.example.data.usecase.file + +import android.content.Context +import android.net.Uri +import com.example.domain.usecase.file.GetInputStreamUseCase +import java.io.InputStream +import javax.inject.Inject + +class GetInputStreamUseCaseImpl @Inject constructor( + private val context: Context +) : GetInputStreamUseCase{ + + override fun invoke(contentUri: String): Result = runCatching { + val uri = Uri.parse(contentUri) + context.contentResolver.openInputStream(uri) ?: throw IllegalStateException("inputStream Error") + } +} diff --git a/data/src/main/java/com/example/data/usecase/file/UploadImageUseCaseImpl.kt b/data/src/main/java/com/example/data/usecase/file/UploadImageUseCaseImpl.kt new file mode 100644 index 0000000..cc812f9 --- /dev/null +++ b/data/src/main/java/com/example/data/usecase/file/UploadImageUseCaseImpl.kt @@ -0,0 +1,39 @@ +package com.example.data.usecase.file + +import com.example.data.retrofit.FileService +import com.example.data.retrofit.UriRequestBody +import com.example.domain.model.Image +import com.example.domain.usecase.file.GetInputStreamUseCase +import com.example.domain.usecase.file.UploadImageUseCase +import javax.inject.Inject +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.MultipartBody + +class UploadImageUseCaseImpl @Inject constructor( + private val fileService: FileService, + private val getInputStreamUseCase: GetInputStreamUseCase +): UploadImageUseCase { + override suspend fun invoke(image: Image): Result = runCatching { + val fileNamePart = MultipartBody.Part.createFormData( + "fileName", + image.name + ) + + val requestBody = UriRequestBody( + image.uri, + getInputStreamUseCase, + image.mimeType.toMediaType(), + image.size + ) + + val filePart = MultipartBody.Part.createFormData( + "file", + image.name, + requestBody + ) + fileService.uploadFile( + fileNamePart, + filePart + ).data.filePath + } +} diff --git a/data/src/main/java/com/example/data/usecase/main/setting/UpdateMyNameUseCaseImpl.kt b/data/src/main/java/com/example/data/usecase/main/setting/SetMyUserUseCaseImpl.kt similarity index 54% rename from data/src/main/java/com/example/data/usecase/main/setting/UpdateMyNameUseCaseImpl.kt rename to data/src/main/java/com/example/data/usecase/main/setting/SetMyUserUseCaseImpl.kt index 7e3ad09..ae650da 100644 --- a/data/src/main/java/com/example/data/usecase/main/setting/UpdateMyNameUseCaseImpl.kt +++ b/data/src/main/java/com/example/data/usecase/main/setting/SetMyUserUseCaseImpl.kt @@ -3,19 +3,22 @@ package com.example.data.usecase.main.setting import com.example.data.model.UpdateMyInfoParam import com.example.data.retrofit.UserService import com.example.domain.usecase.main.setting.GetMyUserUseCase -import com.example.domain.usecase.main.setting.UpdateMyNameUseCase +import com.example.domain.usecase.main.setting.SetMyUserUseCase import javax.inject.Inject -class UpdateMyNameUseCaseImpl @Inject constructor( +class SetMyUserUseCaseImpl @Inject constructor( private val service: UserService, private val getMyUserUseCase: GetMyUserUseCase -) : UpdateMyNameUseCase { - override suspend fun invoke(userName: String): Result = runCatching { +) : SetMyUserUseCase { + override suspend fun invoke( + userName: String?, + profileImageUrl: String? + ): Result = runCatching { val user = getMyUserUseCase().getOrThrow() val requestBody = UpdateMyInfoParam( - userName = userName, - extraUserInfo = user.profileImageUrl.orEmpty(), - profileFilePath = "", + userName = userName ?: user.username, + profileFilePath = profileImageUrl ?: user.profileImageUrl.orEmpty(), + extraUserInfo = "", ).toRequestBody() service.patchMyPage(requestBody) diff --git a/data/src/main/java/com/example/data/usecase/main/setting/SetProfileImageUseCaseImpl.kt b/data/src/main/java/com/example/data/usecase/main/setting/SetProfileImageUseCaseImpl.kt new file mode 100644 index 0000000..530b000 --- /dev/null +++ b/data/src/main/java/com/example/data/usecase/main/setting/SetProfileImageUseCaseImpl.kt @@ -0,0 +1,35 @@ +package com.example.data.usecase.main.setting + +import com.example.data.BuildConfig +import com.example.domain.model.Image +import com.example.domain.usecase.file.GetImageUseCase +import com.example.domain.usecase.file.UploadImageUseCase +import com.example.domain.usecase.main.setting.GetMyUserUseCase +import com.example.domain.usecase.main.setting.SetMyUserUseCase +import com.example.domain.usecase.main.setting.SetProfileImageUseCase +import javax.inject.Inject + +class SetProfileImageUseCaseImpl @Inject constructor( + private val uploadImageUseCase: UploadImageUseCase, + private val getImageUseCase: GetImageUseCase, + private val setMyUserUseCase: SetMyUserUseCase, + private val getMyUserUseCase: GetMyUserUseCase, +): SetProfileImageUseCase { + override suspend fun invoke(contentUri: String) : Result = runCatching{ + //0. 현재 user 정보 가져오기 + val user = getMyUserUseCase().getOrThrow() + + // 1. 이미지 가져오기 + val image = getImageUseCase(contentUri) ?: throw NullPointerException("이미지를 찾을 수 없습니다.") + + //2. 이미지 서버에 업로드하기 + val imagePath = uploadImageUseCase(image).getOrThrow() + + // 4. 내 정보 업데이트 + setMyUserUseCase( + userName = user.username, + profileImageUrl = "${BuildConfig.api_key}/${imagePath}" + ).getOrThrow() + + } +} diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 89d6536..e56ce3e 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -3,6 +3,7 @@ import java.util.Properties plugins { id("com.android.library") id("org.jetbrains.kotlin.android") + alias(libs.plugins.kotlin.serialization) } val properties = Properties() @@ -53,5 +54,6 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) + implementation(libs.converter.gson) } diff --git a/domain/src/main/java/com/example/domain/model/Image.kt b/domain/src/main/java/com/example/domain/model/Image.kt new file mode 100644 index 0000000..5e89248 --- /dev/null +++ b/domain/src/main/java/com/example/domain/model/Image.kt @@ -0,0 +1,15 @@ +package com.example.domain.model + +import com.google.gson.annotations.SerializedName + + +data class Image( + @SerializedName("uri") + val uri: String, + @SerializedName("name") + val name: String, + @SerializedName("size") + val size: Long, + @SerializedName("mimeType") + val mimeType: String, +) diff --git a/domain/src/main/java/com/example/domain/usecase/file/GetImageUseCase.kt b/domain/src/main/java/com/example/domain/usecase/file/GetImageUseCase.kt new file mode 100644 index 0000000..448a56e --- /dev/null +++ b/domain/src/main/java/com/example/domain/usecase/file/GetImageUseCase.kt @@ -0,0 +1,7 @@ +package com.example.domain.usecase.file + +import com.example.domain.model.Image + +interface GetImageUseCase { + operator fun invoke(contentUri: String) : Image? +} diff --git a/domain/src/main/java/com/example/domain/usecase/file/GetInputStreamUseCase.kt b/domain/src/main/java/com/example/domain/usecase/file/GetInputStreamUseCase.kt new file mode 100644 index 0000000..242c606 --- /dev/null +++ b/domain/src/main/java/com/example/domain/usecase/file/GetInputStreamUseCase.kt @@ -0,0 +1,7 @@ +package com.example.domain.usecase.file + +import java.io.InputStream + +interface GetInputStreamUseCase { + operator fun invoke(contentUri: String): Result +} diff --git a/domain/src/main/java/com/example/domain/usecase/file/UploadImageUseCase.kt b/domain/src/main/java/com/example/domain/usecase/file/UploadImageUseCase.kt new file mode 100644 index 0000000..82fe35c --- /dev/null +++ b/domain/src/main/java/com/example/domain/usecase/file/UploadImageUseCase.kt @@ -0,0 +1,9 @@ +package com.example.domain.usecase.file + +import com.example.domain.model.Image + +interface UploadImageUseCase { + suspend operator fun invoke( + image: Image + ): Result +} diff --git a/domain/src/main/java/com/example/domain/usecase/main/setting/SetMyUserUseCase.kt b/domain/src/main/java/com/example/domain/usecase/main/setting/SetMyUserUseCase.kt new file mode 100644 index 0000000..5b008f9 --- /dev/null +++ b/domain/src/main/java/com/example/domain/usecase/main/setting/SetMyUserUseCase.kt @@ -0,0 +1,5 @@ +package com.example.domain.usecase.main.setting + +interface SetMyUserUseCase { + suspend operator fun invoke(userName : String? = null, profileImageUrl: String? = null) : Result +} diff --git a/domain/src/main/java/com/example/domain/usecase/main/setting/SetProfileImageUseCase.kt b/domain/src/main/java/com/example/domain/usecase/main/setting/SetProfileImageUseCase.kt new file mode 100644 index 0000000..45cbd7e --- /dev/null +++ b/domain/src/main/java/com/example/domain/usecase/main/setting/SetProfileImageUseCase.kt @@ -0,0 +1,5 @@ +package com.example.domain.usecase.main.setting + +interface SetProfileImageUseCase { + suspend operator fun invoke(contentUri: String) : Result +} diff --git a/domain/src/main/java/com/example/domain/usecase/main/setting/UpdateMyNameUseCase.kt b/domain/src/main/java/com/example/domain/usecase/main/setting/UpdateMyNameUseCase.kt deleted file mode 100644 index 189844f..0000000 --- a/domain/src/main/java/com/example/domain/usecase/main/setting/UpdateMyNameUseCase.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.domain.usecase.main.setting - -interface UpdateMyNameUseCase { - suspend operator fun invoke(userName : String) : Result -} diff --git a/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt b/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt index 02664fd..4e91d78 100644 --- a/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt +++ b/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt @@ -3,6 +3,10 @@ package com.example.presentation.main.setting import android.content.Intent import android.util.Log import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContract +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -53,6 +57,7 @@ fun SettingScreen(viewModel: SettingViewModel = hiltViewModel()) { Log.e("SettingScreen", sideEffect.message) Toast.makeText(context, sideEffect.message, Toast.LENGTH_SHORT).show() } + is SettingSideEffect.NavigateToLoginActivity -> { context.startActivity( Intent(context, LoginActivity::class.java).apply { @@ -62,19 +67,27 @@ fun SettingScreen(viewModel: SettingViewModel = hiltViewModel()) { } } } - + + val visualMediaPickerLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.PickVisualMedia()) { + viewModel.onImageChange(it) + } + SettingScreen( username = state.username, profileImageUrl = state.profileImageUrl, onNameChangeClick = { usernameDialogVisible = true }, onLogoutClick = viewModel::onLogoutClick, - onImageChangeClick = { }, + onImageChangeClick = { + visualMediaPickerLauncher.launch( + PickVisualMediaRequest(mediaType = ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + }, ) UsernameDialog( visible = usernameDialogVisible, initialUsername = state.username, - onDismissRequest = {usernameDialogVisible = false}, + onDismissRequest = { usernameDialogVisible = false }, onUserNameChange = viewModel::onUsernameChange ) } @@ -97,7 +110,7 @@ fun SettingScreen( modifier = Modifier.size(150.dp), profileImageUrl = profileImageUrl, ) - + IconButton( modifier = Modifier.align(Alignment.BottomEnd), onClick = onImageChangeClick, diff --git a/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt b/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt index 4818345..a82e236 100644 --- a/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt +++ b/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt @@ -1,11 +1,13 @@ package com.example.presentation.main.setting +import android.net.Uri import android.util.Log import androidx.compose.runtime.Immutable import androidx.lifecycle.ViewModel import com.example.domain.usecase.login.ClearTokenUseCase import com.example.domain.usecase.main.setting.GetMyUserUseCase -import com.example.domain.usecase.main.setting.UpdateMyNameUseCase +import com.example.domain.usecase.main.setting.SetMyUserUseCase +import com.example.domain.usecase.main.setting.SetProfileImageUseCase import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineExceptionHandler @@ -22,7 +24,8 @@ class SettingViewModel constructor( private val clearTokenUseCase: ClearTokenUseCase, private val getMyUserUseCase: GetMyUserUseCase, - private val updateMyNameUseCase: UpdateMyNameUseCase + private val setMyUserUseCase: SetMyUserUseCase, + private val setProfileImageUseCase: SetProfileImageUseCase ) : ViewModel(), ContainerHost { override val container: Container = container( @@ -58,7 +61,14 @@ constructor( } fun onUsernameChange(username: String) = intent { - updateMyNameUseCase(username).getOrThrow() + setMyUserUseCase(username).getOrThrow() + load() + } + + fun onImageChange(uri : Uri?) = intent { + setProfileImageUseCase( + contentUri = uri.toString() + ).getOrThrow() load() } } diff --git a/presentation/src/main/java/com/example/presentation/main/setting/UsernameDialog.kt b/presentation/src/main/java/com/example/presentation/main/setting/UsernameDialog.kt index 64f03ba..45d9f8b 100644 --- a/presentation/src/main/java/com/example/presentation/main/setting/UsernameDialog.kt +++ b/presentation/src/main/java/com/example/presentation/main/setting/UsernameDialog.kt @@ -1,7 +1,10 @@ package com.example.presentation.main.setting +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.LocalTextStyle @@ -14,11 +17,14 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.compose.ui.window.SecureFlagPolicy import com.example.presentation.ui.theme.SnsProjectTheme @Composable @@ -33,7 +39,9 @@ fun UsernameDialog( var username by remember { mutableStateOf(initialUsername) } - Dialog(onDismissRequest = onDismissRequest) { + Dialog( + onDismissRequest = onDismissRequest, + ) { Surface { Column(modifier = Modifier.fillMaxWidth(0.8f)) { TextField( @@ -43,7 +51,7 @@ fun UsernameDialog( onValueChange = { username = it }, textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), - ) + ) Row { TextButton( @@ -62,6 +70,8 @@ fun UsernameDialog( } } } + + } } } From d0d0008c0b69d5edf1beb8a2f164e162e0471846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=B0=AC?= Date: Thu, 25 Apr 2024 20:15:14 +0900 Subject: [PATCH 3/3] Fix:[snsproject] ktlintFormat --- .../main/setting/SettingScreen.kt | 44 ++++---- .../main/setting/SettingViewModel.kt | 104 +++++++++--------- .../main/setting/UsernameDialog.kt | 29 ++--- 3 files changed, 85 insertions(+), 92 deletions(-) diff --git a/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt b/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt index 4e91d78..eda5a0d 100644 --- a/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt +++ b/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt @@ -5,7 +5,6 @@ import android.util.Log import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest -import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -47,7 +46,7 @@ import org.orbitmvi.orbit.compose.collectSideEffect fun SettingScreen(viewModel: SettingViewModel = hiltViewModel()) { val state = viewModel.collectAsState().value val context = LocalContext.current - + var usernameDialogVisible by remember { mutableStateOf(false) } @@ -57,7 +56,7 @@ fun SettingScreen(viewModel: SettingViewModel = hiltViewModel()) { Log.e("SettingScreen", sideEffect.message) Toast.makeText(context, sideEffect.message, Toast.LENGTH_SHORT).show() } - + is SettingSideEffect.NavigateToLoginActivity -> { context.startActivity( Intent(context, LoginActivity::class.java).apply { @@ -67,11 +66,12 @@ fun SettingScreen(viewModel: SettingViewModel = hiltViewModel()) { } } } - - val visualMediaPickerLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.PickVisualMedia()) { - viewModel.onImageChange(it) - } - + + val visualMediaPickerLauncher = + rememberLauncherForActivityResult(contract = ActivityResultContracts.PickVisualMedia()) { + viewModel.onImageChange(it) + } + SettingScreen( username = state.username, profileImageUrl = state.profileImageUrl, @@ -79,16 +79,16 @@ fun SettingScreen(viewModel: SettingViewModel = hiltViewModel()) { onLogoutClick = viewModel::onLogoutClick, onImageChangeClick = { visualMediaPickerLauncher.launch( - PickVisualMediaRequest(mediaType = ActivityResultContracts.PickVisualMedia.ImageOnly) + PickVisualMediaRequest(mediaType = ActivityResultContracts.PickVisualMedia.ImageOnly), ) }, ) - + UsernameDialog( visible = usernameDialogVisible, initialUsername = state.username, onDismissRequest = { usernameDialogVisible = false }, - onUserNameChange = viewModel::onUsernameChange + onUserNameChange = viewModel::onUsernameChange, ) } @@ -110,23 +110,23 @@ fun SettingScreen( modifier = Modifier.size(150.dp), profileImageUrl = profileImageUrl, ) - + IconButton( modifier = Modifier.align(Alignment.BottomEnd), onClick = onImageChangeClick, ) { Box( modifier = - Modifier - .size(30.dp) - .border(width = 1.dp, color = Color.Gray, shape = CircleShape) - .background(color = Color.White, shape = CircleShape), + Modifier + .size(30.dp) + .border(width = 1.dp, color = Color.Gray, shape = CircleShape) + .background(color = Color.White, shape = CircleShape), ) { Icon( modifier = - Modifier - .align(Alignment.Center) - .size(20.dp), + Modifier + .align(Alignment.Center) + .size(20.dp), imageVector = Icons.Default.Settings, contentDescription = null, tint = MaterialTheme.colorScheme.primary, @@ -136,9 +136,9 @@ fun SettingScreen( } Text( modifier = - Modifier - .padding(top = 8.dp) - .clickable { onNameChangeClick() }, + Modifier + .padding(top = 8.dp) + .clickable { onNameChangeClick() }, text = username, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, diff --git a/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt b/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt index a82e236..71f586e 100644 --- a/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt +++ b/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt @@ -20,58 +20,60 @@ import org.orbitmvi.orbit.viewmodel.container @HiltViewModel class SettingViewModel -@Inject -constructor( - private val clearTokenUseCase: ClearTokenUseCase, - private val getMyUserUseCase: GetMyUserUseCase, - private val setMyUserUseCase: SetMyUserUseCase, - private val setProfileImageUseCase: SetProfileImageUseCase -) : ViewModel(), ContainerHost { - override val container: Container = - container( - initialState = SettingState(), - buildSettings = { - this.exceptionHandler = - CoroutineExceptionHandler { _, throwable -> - intent { postSideEffect(SettingSideEffect.Toast(throwable.message ?: "")) } - } - }, - ) - - init { - load() - } - - private fun load() = - intent { - val user = getMyUserUseCase().getOrThrow() - Log.e("SettingViewModel", "user : $user") - reduce { - state.copy( - profileImageUrl = user.profileImageUrl, - username = user.username, - ) - } - } - - fun onLogoutClick() = - intent { - clearTokenUseCase().getOrThrow() - postSideEffect(SettingSideEffect.NavigateToLoginActivity) + @Inject + constructor( + private val clearTokenUseCase: ClearTokenUseCase, + private val getMyUserUseCase: GetMyUserUseCase, + private val setMyUserUseCase: SetMyUserUseCase, + private val setProfileImageUseCase: SetProfileImageUseCase, + ) : ViewModel(), ContainerHost { + override val container: Container = + container( + initialState = SettingState(), + buildSettings = { + this.exceptionHandler = + CoroutineExceptionHandler { _, throwable -> + intent { postSideEffect(SettingSideEffect.Toast(throwable.message ?: "")) } + } + }, + ) + + init { + load() } - - fun onUsernameChange(username: String) = intent { - setMyUserUseCase(username).getOrThrow() - load() - } - - fun onImageChange(uri : Uri?) = intent { - setProfileImageUseCase( - contentUri = uri.toString() - ).getOrThrow() - load() + + private fun load() = + intent { + val user = getMyUserUseCase().getOrThrow() + Log.e("SettingViewModel", "user : $user") + reduce { + state.copy( + profileImageUrl = user.profileImageUrl, + username = user.username, + ) + } + } + + fun onLogoutClick() = + intent { + clearTokenUseCase().getOrThrow() + postSideEffect(SettingSideEffect.NavigateToLoginActivity) + } + + fun onUsernameChange(username: String) = + intent { + setMyUserUseCase(username).getOrThrow() + load() + } + + fun onImageChange(uri: Uri?) = + intent { + setProfileImageUseCase( + contentUri = uri.toString(), + ).getOrThrow() + load() + } } -} @Immutable data class SettingState( @@ -81,6 +83,6 @@ data class SettingState( sealed interface SettingSideEffect { class Toast(val message: String) : SettingSideEffect - + data object NavigateToLoginActivity : SettingSideEffect } diff --git a/presentation/src/main/java/com/example/presentation/main/setting/UsernameDialog.kt b/presentation/src/main/java/com/example/presentation/main/setting/UsernameDialog.kt index 45d9f8b..13a52cf 100644 --- a/presentation/src/main/java/com/example/presentation/main/setting/UsernameDialog.kt +++ b/presentation/src/main/java/com/example/presentation/main/setting/UsernameDialog.kt @@ -1,11 +1,7 @@ package com.example.presentation.main.setting -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.Surface @@ -17,14 +13,10 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties -import androidx.compose.ui.window.SecureFlagPolicy import com.example.presentation.ui.theme.SnsProjectTheme @Composable @@ -34,7 +26,6 @@ fun UsernameDialog( onUserNameChange: (String) -> Unit, onDismissRequest: () -> Unit, ) { - if (visible) { var username by remember { mutableStateOf(initialUsername) @@ -45,33 +36,33 @@ fun UsernameDialog( Surface { Column(modifier = Modifier.fillMaxWidth(0.8f)) { TextField( - modifier = Modifier - .fillMaxWidth(), + modifier = + Modifier + .fillMaxWidth(), value = username, onValueChange = { username = it }, textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), - - ) - + ) + Row { TextButton( modifier = Modifier.weight(1f), onClick = { onUserNameChange(username) onDismissRequest() - }) { + }, + ) { Text(text = "변경") } - + TextButton( modifier = Modifier.weight(1f), - onClick = onDismissRequest) { + onClick = onDismissRequest, + ) { Text(text = "취소") } } } - - } } }