diff --git a/app/build.gradle b/app/build.gradle index 8e93865bb..fe1671de3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,6 +30,7 @@ android { buildConfigField "String", "NAVER_OAUTH_CLIENT_SECRET", properties['naver_oauth_client_secret'] buildConfigField "String", "NAVER_OAUTH_CLIENT_NAME", properties['naver_oauth_client_name'] buildConfigField "String", "GOOGLE_CLIENT_ID", properties['google_client_id'] + buildConfigField "String", "AWS_S3_BASE_URL", properties['aws_s3_base_url'] manifestPlaceholders["kakaoNativeAppKeyManifest"] = properties["kakao_native_app_key_manifest"] as String } diff --git a/app/src/main/java/com/project/meongcare/aws/model/data/remote/AWSS3Api.kt b/app/src/main/java/com/project/meongcare/aws/model/data/remote/AWSS3Api.kt new file mode 100644 index 000000000..637d63919 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/aws/model/data/remote/AWSS3Api.kt @@ -0,0 +1,25 @@ +package com.project.meongcare.aws.model.data.remote + +import com.project.meongcare.aws.model.entities.AWSS3Response +import okhttp3.RequestBody +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.PUT +import retrofit2.http.Query +import retrofit2.http.Url + +interface AWSS3Api { + @GET("aws/s3") + suspend fun getPreSignedUrl( + @Header("AccessToken") accessToken: String, + @Query("fileName") fileName: String, + ): Response + + @PUT + suspend fun uploadImageToS3( + @Url preSignedUrl: String, + @Body image: RequestBody, + ): Response +} diff --git a/app/src/main/java/com/project/meongcare/aws/model/data/remote/AWSS3RetrofitClient.kt b/app/src/main/java/com/project/meongcare/aws/model/data/remote/AWSS3RetrofitClient.kt new file mode 100644 index 000000000..aca6bc66f --- /dev/null +++ b/app/src/main/java/com/project/meongcare/aws/model/data/remote/AWSS3RetrofitClient.kt @@ -0,0 +1,43 @@ +package com.project.meongcare.aws.model.data.remote + +import com.project.meongcare.BuildConfig +import okhttp3.ResponseBody +import retrofit2.Converter +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.lang.reflect.Type +import javax.inject.Inject + +class AWSS3RetrofitClient + @Inject + constructor() { + val awsS3Api: AWSS3Api by lazy { + Retrofit.Builder() + .baseUrl(BuildConfig.SEMOBAN_DOMAIN) + .addConverterFactory(nullOnEmptyConverterFactory) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(AWSS3Api::class.java) + } + + private val nullOnEmptyConverterFactory = + object : Converter.Factory() { + fun converterFactory() = this + + override fun responseBodyConverter( + type: Type, + annotations: Array, + retrofit: Retrofit, + ) = object : Converter { + val nextResponseBodyConverter = + retrofit.nextResponseBodyConverter( + converterFactory(), + type, + annotations, + ) + + override fun convert(value: ResponseBody) = + if (value.contentLength() != 0L) nextResponseBodyConverter.convert(value) else null + } + } + } diff --git a/app/src/main/java/com/project/meongcare/aws/model/data/repository/AWSS3Module.kt b/app/src/main/java/com/project/meongcare/aws/model/data/repository/AWSS3Module.kt new file mode 100644 index 000000000..e28bba004 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/aws/model/data/repository/AWSS3Module.kt @@ -0,0 +1,15 @@ +package com.project.meongcare.aws.model.data.repository + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +object AWSS3Module { + @Provides + fun provideAWSS3Repository(awsS3RepositoryImpl: AWSS3RepositoryImpl): AWSS3Repository { + return awsS3RepositoryImpl + } +} diff --git a/app/src/main/java/com/project/meongcare/aws/model/data/repository/AWSS3Repository.kt b/app/src/main/java/com/project/meongcare/aws/model/data/repository/AWSS3Repository.kt new file mode 100644 index 000000000..3f94d1da1 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/aws/model/data/repository/AWSS3Repository.kt @@ -0,0 +1,16 @@ +package com.project.meongcare.aws.model.data.repository + +import com.project.meongcare.aws.model.entities.AWSS3Response +import okhttp3.RequestBody + +interface AWSS3Repository { + suspend fun getPreSignedUrl( + accessToken: String, + fileName: String, + ): AWSS3Response? + + suspend fun uploadImageToS3( + preSignedUrl: String, + image: RequestBody, + ): Int? +} diff --git a/app/src/main/java/com/project/meongcare/aws/model/data/repository/AWSS3RepositoryImpl.kt b/app/src/main/java/com/project/meongcare/aws/model/data/repository/AWSS3RepositoryImpl.kt new file mode 100644 index 000000000..bc3b803e6 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/aws/model/data/repository/AWSS3RepositoryImpl.kt @@ -0,0 +1,53 @@ +package com.project.meongcare.aws.model.data.repository + +import android.util.Log +import com.project.meongcare.aws.model.data.remote.AWSS3RetrofitClient +import com.project.meongcare.aws.model.entities.AWSS3Response +import okhttp3.RequestBody +import org.json.JSONObject +import java.lang.Exception +import javax.inject.Inject + +class AWSS3RepositoryImpl + @Inject + constructor(private val awsS3RetrofitClient: AWSS3RetrofitClient) : AWSS3Repository { + override suspend fun getPreSignedUrl( + accessToken: String, + fileName: String, + ): AWSS3Response? { + return try { + val response = awsS3RetrofitClient.awsS3Api.getPreSignedUrl(accessToken, fileName) + if (response.isSuccessful) { + Log.d("AWSS3Repo-getPreUrl", "통신 성공 ${response.code()}") + response.body() + } else { + val stringToJson = JSONObject(response.errorBody()?.string()!!) + Log.e("AWSS3Repo-getPreUrl", "통신 실패 ${response.code()}\n$stringToJson") + null + } + } catch (e: Exception) { + e.printStackTrace() + null + } + } + + override suspend fun uploadImageToS3( + preSignedUrl: String, + image: RequestBody, + ): Int? { + return try { + val response = awsS3RetrofitClient.awsS3Api.uploadImageToS3(preSignedUrl, image) + if (response.isSuccessful) { + Log.d("AWSS3Repo-upload", "통신 성공 ${response.code()}") + response.code() + } else { + val stringToJson = JSONObject(response.errorBody()?.string()!!) + Log.e("AWSS3Repo-upload", "통신 실패 ${response.code()}\n$stringToJson") + response.code() + } + } catch (e: Exception) { + e.printStackTrace() + null + } + } + } diff --git a/app/src/main/java/com/project/meongcare/aws/model/entities/AWSS3Response.kt b/app/src/main/java/com/project/meongcare/aws/model/entities/AWSS3Response.kt new file mode 100644 index 000000000..cfa204c5d --- /dev/null +++ b/app/src/main/java/com/project/meongcare/aws/model/entities/AWSS3Response.kt @@ -0,0 +1,5 @@ +package com.project.meongcare.aws.model.entities + +data class AWSS3Response( + val preSignedUrl: String, +) diff --git a/app/src/main/java/com/project/meongcare/aws/util/AWSS3Constants.kt b/app/src/main/java/com/project/meongcare/aws/util/AWSS3Constants.kt new file mode 100644 index 000000000..ab49ff04d --- /dev/null +++ b/app/src/main/java/com/project/meongcare/aws/util/AWSS3Constants.kt @@ -0,0 +1,12 @@ +package com.project.meongcare.aws.util + +// Parent Folder +const val PARENT_FOLDER_PATH = "meongcare/" + +// Sub Folder +const val DOG_FOLDER_PATH = "dog/" +const val EXCRETA_FOLDER_PATH = "excreta/" +const val FEED_FOLDER_PATH = "feed/" +const val MEDICAL_RECORD_FOLDER_PATH = "medical-record/" +const val MEMBER_FOLDER_PATH = "member/" +const val SUPPLEMENTS_FOLDER_PATH = "supplements/" diff --git a/app/src/main/java/com/project/meongcare/aws/util/AWSS3ImageUtils.kt b/app/src/main/java/com/project/meongcare/aws/util/AWSS3ImageUtils.kt new file mode 100644 index 000000000..a94229a31 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/aws/util/AWSS3ImageUtils.kt @@ -0,0 +1,30 @@ +package com.project.meongcare.aws.util + +import android.content.Context +import android.net.Uri +import java.io.File +import java.io.FileOutputStream +import java.util.UUID + +object AWSS3ImageUtils { + private fun createUUID(): String { + return UUID.randomUUID().toString() + } + + fun convertUriToFile( + context: Context, + uri: Uri, + ): File { + val contentResolver = context.contentResolver + val file = File(context.cacheDir, createUUID()) + file.deleteOnExit() + + contentResolver.openInputStream(uri).use { inputStream -> + FileOutputStream(file).use { outputStream -> + inputStream?.copyTo(outputStream) + } + } + + return file + } +} diff --git a/app/src/main/java/com/project/meongcare/aws/viewmodel/AWSS3ViewModel.kt b/app/src/main/java/com/project/meongcare/aws/viewmodel/AWSS3ViewModel.kt new file mode 100644 index 000000000..fb96db0a4 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/aws/viewmodel/AWSS3ViewModel.kt @@ -0,0 +1,43 @@ +package com.project.meongcare.aws.viewmodel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.project.meongcare.aws.model.data.repository.AWSS3Repository +import com.project.meongcare.aws.model.entities.AWSS3Response +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import okhttp3.RequestBody +import javax.inject.Inject + +@HiltViewModel +class AWSS3ViewModel + @Inject + constructor(private val awsS3Repository: AWSS3Repository) : ViewModel() { + private val _preSignedUrl = MutableLiveData() + val preSignedUrl: LiveData + get() = _preSignedUrl + + private val _uploadImageResponse = MutableLiveData() + val uploadImageResponse: LiveData + get() = _uploadImageResponse + + fun getPreSignedUrl( + accessToken: String, + fileName: String, + ) { + viewModelScope.launch { + _preSignedUrl.value = awsS3Repository.getPreSignedUrl(accessToken, fileName) + } + } + + fun uploadImageToS3( + preSignedUrl: String, + image: RequestBody, + ) { + viewModelScope.launch { + _uploadImageResponse.value = awsS3Repository.uploadImageToS3(preSignedUrl, image) + } + } + } diff --git a/app/src/main/java/com/project/meongcare/excreta/model/data/remote/ExcretaRemoteDataSource.kt b/app/src/main/java/com/project/meongcare/excreta/model/data/remote/ExcretaRemoteDataSource.kt index 4727e77a0..34bc8754b 100644 --- a/app/src/main/java/com/project/meongcare/excreta/model/data/remote/ExcretaRemoteDataSource.kt +++ b/app/src/main/java/com/project/meongcare/excreta/model/data/remote/ExcretaRemoteDataSource.kt @@ -2,9 +2,10 @@ package com.project.meongcare.excreta.model.data.remote import android.util.Log import com.project.meongcare.excreta.model.entities.ExcretaDetailGetResponse +import com.project.meongcare.excreta.model.entities.ExcretaPatchRequest +import com.project.meongcare.excreta.model.entities.ExcretaPostRequest import com.project.meongcare.excreta.model.entities.ExcretaRecordGetRequest import com.project.meongcare.excreta.model.entities.ExcretaRecordGetResponse -import com.project.meongcare.excreta.model.entities.ExcretaUploadRequest import com.project.meongcare.excreta.utils.SUCCESS import org.json.JSONObject import javax.inject.Inject @@ -16,14 +17,13 @@ class ExcretaRemoteDataSource suspend fun postExcreta( accessToken: String, - excretaPostRequest: ExcretaUploadRequest, + excretaPostRequest: ExcretaPostRequest, ): Int? { try { val postResponse = excretaApiService.postExcreta( accessToken, - excretaPostRequest.dto, - excretaPostRequest.file, + excretaPostRequest, ) if (postResponse.code() != SUCCESS) { @@ -120,14 +120,13 @@ class ExcretaRemoteDataSource suspend fun patchExcreta( accessToken: String, - excretaUploadRequest: ExcretaUploadRequest, + excretaPatchRequest: ExcretaPatchRequest, ): Int? { try { val patchResponse = excretaApiService.patchExcreta( accessToken, - excretaUploadRequest.dto, - excretaUploadRequest.file, + excretaPatchRequest, ) if (patchResponse.code() != SUCCESS) { diff --git a/app/src/main/java/com/project/meongcare/excreta/model/data/remote/ExcretaService.kt b/app/src/main/java/com/project/meongcare/excreta/model/data/remote/ExcretaService.kt index dc243e343..fd042df19 100644 --- a/app/src/main/java/com/project/meongcare/excreta/model/data/remote/ExcretaService.kt +++ b/app/src/main/java/com/project/meongcare/excreta/model/data/remote/ExcretaService.kt @@ -1,27 +1,24 @@ package com.project.meongcare.excreta.model.data.remote import com.project.meongcare.excreta.model.entities.ExcretaDetailGetResponse +import com.project.meongcare.excreta.model.entities.ExcretaPatchRequest +import com.project.meongcare.excreta.model.entities.ExcretaPostRequest import com.project.meongcare.excreta.model.entities.ExcretaRecordGetResponse -import okhttp3.MultipartBody -import okhttp3.RequestBody import retrofit2.Response +import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.Header -import retrofit2.http.Multipart import retrofit2.http.PATCH import retrofit2.http.POST -import retrofit2.http.Part import retrofit2.http.Path import retrofit2.http.Query interface ExcretaService { - @Multipart @POST("excreta") suspend fun postExcreta( @Header("AccessToken") accessToken: String, - @Part("dto") dto: RequestBody, - @Part file: MultipartBody.Part, + @Body excretaPostRequest: ExcretaPostRequest, ): Response @GET("excreta/{dogId}") @@ -43,11 +40,9 @@ interface ExcretaService { @Query("excretaIds") excretaIds: IntArray, ): Response - @Multipart @PATCH("excreta") suspend fun patchExcreta( @Header("AccessToken") accessToken: String, - @Part("dto") dto: RequestBody, - @Part file: MultipartBody.Part, + @Body excretaPatchRequest: ExcretaPatchRequest, ): Response } diff --git a/app/src/main/java/com/project/meongcare/excreta/model/data/repository/ExcretaRepository.kt b/app/src/main/java/com/project/meongcare/excreta/model/data/repository/ExcretaRepository.kt index 15a17b1eb..1bd461081 100644 --- a/app/src/main/java/com/project/meongcare/excreta/model/data/repository/ExcretaRepository.kt +++ b/app/src/main/java/com/project/meongcare/excreta/model/data/repository/ExcretaRepository.kt @@ -1,14 +1,15 @@ package com.project.meongcare.excreta.model.data.repository import com.project.meongcare.excreta.model.entities.ExcretaDetailGetResponse +import com.project.meongcare.excreta.model.entities.ExcretaPatchRequest +import com.project.meongcare.excreta.model.entities.ExcretaPostRequest import com.project.meongcare.excreta.model.entities.ExcretaRecordGetRequest import com.project.meongcare.excreta.model.entities.ExcretaRecordGetResponse -import com.project.meongcare.excreta.model.entities.ExcretaUploadRequest interface ExcretaRepository { suspend fun postExcreta( accessToken: String, - excretaUploadRequest: ExcretaUploadRequest, + excretaPostRequest: ExcretaPostRequest, ): Int? suspend fun getExcretaRecord( @@ -28,6 +29,6 @@ interface ExcretaRepository { suspend fun patchExcreta( accessToken: String, - excretaUploadRequest: ExcretaUploadRequest, + excretaPatchRequest: ExcretaPatchRequest, ): Int? } diff --git a/app/src/main/java/com/project/meongcare/excreta/model/data/repository/ExcretaRepositoryImpl.kt b/app/src/main/java/com/project/meongcare/excreta/model/data/repository/ExcretaRepositoryImpl.kt index c1dca8eef..c85c0d723 100644 --- a/app/src/main/java/com/project/meongcare/excreta/model/data/repository/ExcretaRepositoryImpl.kt +++ b/app/src/main/java/com/project/meongcare/excreta/model/data/repository/ExcretaRepositoryImpl.kt @@ -1,8 +1,9 @@ package com.project.meongcare.excreta.model.data.repository import com.project.meongcare.excreta.model.data.remote.ExcretaRemoteDataSource +import com.project.meongcare.excreta.model.entities.ExcretaPatchRequest +import com.project.meongcare.excreta.model.entities.ExcretaPostRequest import com.project.meongcare.excreta.model.entities.ExcretaRecordGetRequest -import com.project.meongcare.excreta.model.entities.ExcretaUploadRequest import javax.inject.Inject class ExcretaRepositoryImpl @@ -12,8 +13,8 @@ class ExcretaRepositoryImpl ) : ExcretaRepository { override suspend fun postExcreta( accessToken: String, - excretaUploadRequest: ExcretaUploadRequest, - ) = excretaRemoteDataSource.postExcreta(accessToken, excretaUploadRequest) + excretaPostRequest: ExcretaPostRequest, + ): Int? = excretaRemoteDataSource.postExcreta(accessToken, excretaPostRequest) override suspend fun getExcretaRecord( accessToken: String, @@ -32,6 +33,6 @@ class ExcretaRepositoryImpl override suspend fun patchExcreta( accessToken: String, - excretaUploadRequest: ExcretaUploadRequest, - ) = excretaRemoteDataSource.patchExcreta(accessToken, excretaUploadRequest) + excretaPatchRequest: ExcretaPatchRequest, + ): Int? = excretaRemoteDataSource.patchExcreta(accessToken, excretaPatchRequest) } diff --git a/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaDetailGetResponse.kt b/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaDetailGetResponse.kt index 8f8ccd20c..179f76f61 100644 --- a/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaDetailGetResponse.kt +++ b/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaDetailGetResponse.kt @@ -5,7 +5,7 @@ import kotlinx.parcelize.Parcelize @Parcelize data class ExcretaDetailGetResponse( - val excretaImageURL: String, + val excretaImageURL: String?, val dateTime: String, val excretaType: String, ) : Parcelable diff --git a/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaInfoPatch.kt b/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaPatchRequest.kt similarity index 70% rename from app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaInfoPatch.kt rename to app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaPatchRequest.kt index a62a81ea4..099a92bcd 100644 --- a/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaInfoPatch.kt +++ b/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaPatchRequest.kt @@ -1,7 +1,8 @@ package com.project.meongcare.excreta.model.entities -data class ExcretaInfoPatch( +data class ExcretaPatchRequest( val excretaId: Long, val excretaString: String, val dateTime: String, + val imageURL: String?, ) diff --git a/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaInfo.kt b/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaPostRequest.kt similarity index 68% rename from app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaInfo.kt rename to app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaPostRequest.kt index b1c67e082..28f4c7554 100644 --- a/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaInfo.kt +++ b/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaPostRequest.kt @@ -1,7 +1,8 @@ package com.project.meongcare.excreta.model.entities -data class ExcretaInfo( +data class ExcretaPostRequest( val dogId: Long, val excreta: String, val dateTime: String, + val imageURL: String?, ) diff --git a/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaUploadRequest.kt b/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaUploadRequest.kt deleted file mode 100644 index cb3b0dda9..000000000 --- a/app/src/main/java/com/project/meongcare/excreta/model/entities/ExcretaUploadRequest.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.project.meongcare.excreta.model.entities - -import okhttp3.MultipartBody -import okhttp3.RequestBody - -data class ExcretaUploadRequest( - val dto: RequestBody, - val file: MultipartBody.Part, -) diff --git a/app/src/main/java/com/project/meongcare/excreta/utils/ExcretaInfoUtils.kt b/app/src/main/java/com/project/meongcare/excreta/utils/ExcretaInfoUtils.kt index 5bc28bfd7..51f646af5 100644 --- a/app/src/main/java/com/project/meongcare/excreta/utils/ExcretaInfoUtils.kt +++ b/app/src/main/java/com/project/meongcare/excreta/utils/ExcretaInfoUtils.kt @@ -1,53 +1,10 @@ package com.project.meongcare.excreta.utils -import android.content.Context -import android.net.Uri import android.view.View -import com.google.gson.Gson import com.project.meongcare.R -import com.project.meongcare.excreta.model.entities.ExcretaInfo -import com.project.meongcare.excreta.model.entities.ExcretaInfoPatch import com.project.meongcare.snackbar.view.CustomSnackBar -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.MultipartBody -import okhttp3.RequestBody -import okhttp3.RequestBody.Companion.asRequestBody -import okhttp3.RequestBody.Companion.toRequestBody -import java.io.File object ExcretaInfoUtils { - fun convertExcretaPostDto(excretaInfo: ExcretaInfo): RequestBody { - val json = Gson().toJson(excretaInfo) - return json.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) - } - - fun convertExcretaPatchDto(excretaInfoPatch: ExcretaInfoPatch): RequestBody { - val json = Gson().toJson(excretaInfoPatch) - return json.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) - } - - fun convertExcretaFile( - context: Context, - uri: Uri, - ): MultipartBody.Part { - if (uri.toString().isEmpty()) { - val emptyFile = "".toRequestBody("multipart/form-data".toMediaTypeOrNull()) - return MultipartBody.Part.createFormData("file", "", emptyFile) - } - - // Uri로부터 InputStream을 얻고, 임시 파일로 복사 - val inputStream = context.contentResolver.openInputStream(uri) - val file = File(context.cacheDir, "tempFile") - inputStream.use { input -> - file.outputStream().use { output -> - input?.copyTo(output) - } - } - val requestFile = file.asRequestBody("multipart/form-data".toMediaTypeOrNull()) - - return MultipartBody.Part.createFormData("file", file.name, requestFile) - } - fun showSuccessSnackBar( view: View, message: String, diff --git a/app/src/main/java/com/project/meongcare/excreta/view/ExcretaAddFragment.kt b/app/src/main/java/com/project/meongcare/excreta/view/ExcretaAddFragment.kt index 1f8b03abe..600b13942 100644 --- a/app/src/main/java/com/project/meongcare/excreta/view/ExcretaAddFragment.kt +++ b/app/src/main/java/com/project/meongcare/excreta/view/ExcretaAddFragment.kt @@ -9,8 +9,13 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.bumptech.glide.Glide +import com.project.meongcare.BuildConfig import com.project.meongcare.CalendarBottomSheetFragment import com.project.meongcare.R +import com.project.meongcare.aws.util.AWSS3ImageUtils.convertUriToFile +import com.project.meongcare.aws.util.EXCRETA_FOLDER_PATH +import com.project.meongcare.aws.util.PARENT_FOLDER_PATH +import com.project.meongcare.aws.viewmodel.AWSS3ViewModel import com.project.meongcare.databinding.FragmentExcretaAddEditBinding import com.project.meongcare.excreta.model.data.local.PhotoListener import com.project.meongcare.excreta.model.entities.Excreta @@ -28,6 +33,10 @@ import com.project.meongcare.feed.viewmodel.DogViewModel import com.project.meongcare.feed.viewmodel.UserViewModel import com.project.meongcare.onboarding.model.data.local.DateSubmitListener import dagger.hilt.android.AndroidEntryPoint +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import java.io.File @AndroidEntryPoint class ExcretaAddFragment : Fragment(), DateSubmitListener, PhotoListener { @@ -37,12 +46,16 @@ class ExcretaAddFragment : Fragment(), DateSubmitListener, PhotoListener { private val excretaAddViewModel: ExcretaAddViewModel by viewModels() private val userViewModel: UserViewModel by viewModels() private val dogViewModel: DogViewModel by viewModels() + private val awsS3ViewModel: AWSS3ViewModel by viewModels() private val calendarModalBottomSheet = CalendarBottomSheetFragment() private var excretaDate = "" private var accessToken = "" private var dogId = 0L + private lateinit var imageFile: File + private lateinit var filePath: String + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -154,45 +167,76 @@ class ExcretaAddFragment : Fragment(), DateSubmitListener, PhotoListener { } if (isValid) { - val excretaType = - if (checkboxExcretaaddUrine.isChecked) { - Excreta.URINE.toString() - } else { - Excreta.FECES.toString() - } - - val excretaTime = convertTimeFormat(timepikerExcretaaddTime) - val excretaDateTime = "${excretaDate}T$excretaTime" - - val currentImageUri = excretaAddViewModel.excretaImage.value - - excretaAddViewModel.postExcreta( - dogId, - accessToken, - excretaType, - excretaDateTime, - requireContext(), - currentImageUri ?: Uri.EMPTY, - ) - excretaAddViewModel.excretaPosted.observe(viewLifecycleOwner) { response -> - if (response == SUCCESS) { - showSuccessSnackBar( - requireView(), - EXCRETA_POST_SUCCESS, - ) - findNavController().popBackStack() - } else { - showFailureSnackBar( - requireView(), - EXCRETA_POST_FAILURE, - ) - } + val uri = excretaAddViewModel.excretaImage.value + if (uri == null) { + postExcreta(null) + } else { + imageFile = convertUriToFile(requireContext(), uri) + filePath = "$PARENT_FOLDER_PATH$EXCRETA_FOLDER_PATH${imageFile.name}" + getPreSignedUrl() } } } } } + private fun getPreSignedUrl() { + awsS3ViewModel.getPreSignedUrl(accessToken, filePath) + awsS3ViewModel.preSignedUrl.observe(viewLifecycleOwner) { response -> + if (response != null) { + val requestBody = imageFile.asRequestBody("image/*".toMediaTypeOrNull()) + uploadImage(response.preSignedUrl, requestBody) + } + } + } + + private fun uploadImage( + preSignedUrl: String, + requestBody: RequestBody, + ) { + awsS3ViewModel.uploadImageToS3(preSignedUrl, requestBody) + awsS3ViewModel.uploadImageResponse.observe(viewLifecycleOwner) { response -> + if (response == 200) { + val imageURL = BuildConfig.AWS_S3_BASE_URL + filePath + postExcreta(imageURL) + } + } + } + + private fun postExcreta(imageURL: String?) { + val excretaType = + if (binding.checkboxExcretaaddUrine.isChecked) { + Excreta.URINE.toString() + } else { + Excreta.FECES.toString() + } + + val excretaTime = convertTimeFormat(binding.timepikerExcretaaddTime) + val excretaDateTime = "${excretaDate}T$excretaTime" + + excretaAddViewModel.postExcreta( + accessToken, + dogId, + excretaType, + excretaDateTime, + imageURL, + ) + excretaAddViewModel.excretaPosted.observe(viewLifecycleOwner) { response -> + if (response == SUCCESS) { + showSuccessSnackBar( + requireView(), + EXCRETA_POST_SUCCESS, + ) + findNavController().popBackStack() + } else { + showFailureSnackBar( + requireView(), + EXCRETA_POST_FAILURE, + ) + } + } + } + override fun onDestroyView() { super.onDestroyView() _binding = null diff --git a/app/src/main/java/com/project/meongcare/excreta/view/ExcretaEditFragment.kt b/app/src/main/java/com/project/meongcare/excreta/view/ExcretaEditFragment.kt index 94cb281b5..e6578c192 100644 --- a/app/src/main/java/com/project/meongcare/excreta/view/ExcretaEditFragment.kt +++ b/app/src/main/java/com/project/meongcare/excreta/view/ExcretaEditFragment.kt @@ -10,8 +10,13 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.bumptech.glide.Glide +import com.project.meongcare.BuildConfig import com.project.meongcare.CalendarBottomSheetFragment import com.project.meongcare.R +import com.project.meongcare.aws.util.AWSS3ImageUtils.convertUriToFile +import com.project.meongcare.aws.util.EXCRETA_FOLDER_PATH +import com.project.meongcare.aws.util.PARENT_FOLDER_PATH +import com.project.meongcare.aws.viewmodel.AWSS3ViewModel import com.project.meongcare.databinding.FragmentExcretaAddEditBinding import com.project.meongcare.excreta.model.data.local.PhotoListener import com.project.meongcare.excreta.model.entities.Excreta @@ -34,17 +39,24 @@ import com.project.meongcare.excreta.viewmodel.ExcretaPatchViewModel import com.project.meongcare.feed.viewmodel.UserViewModel import com.project.meongcare.onboarding.model.data.local.DateSubmitListener import dagger.hilt.android.AndroidEntryPoint +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import java.io.File @AndroidEntryPoint class ExcretaEditFragment : Fragment(), DateSubmitListener, PhotoListener { private var _binding: FragmentExcretaAddEditBinding? = null val binding get() = _binding!! + private val awsS3ViewModel: AWSS3ViewModel by viewModels() private val excretaPatchViewModel: ExcretaPatchViewModel by viewModels() private val userViewModel: UserViewModel by viewModels() private val calendarModalBottomSheet = CalendarBottomSheetFragment() private lateinit var excretaInfo: ExcretaDetailGetResponse + private lateinit var imageFile: File + private lateinit var filePath: String private var excretaDate = "" private var accessToken = "" @@ -97,7 +109,7 @@ class ExcretaEditFragment : Fragment(), DateSubmitListener, PhotoListener { private fun initExcretaImage() { val excretaImageURL = excretaInfo.excretaImageURL binding.apply { - if (excretaImageURL.isNotEmpty()) { + if (!excretaImageURL.isNullOrEmpty()) { Glide.with(this@ExcretaEditFragment) .load(excretaImageURL) .into(imageviewExcretaaddPicture) @@ -187,45 +199,82 @@ class ExcretaEditFragment : Fragment(), DateSubmitListener, PhotoListener { } if (isValid) { - val excretaType = - if (checkboxExcretaaddUrine.isChecked) { - Excreta.URINE.toString() + val uri = excretaPatchViewModel.excretaImage.value + if (uri == null) { // 새로 등록된 이미지가 없을 때 + if (excretaInfo.excretaImageURL == null) { // 기존 이미지 null + patchExcreta(null) } else { - Excreta.FECES.toString() - } - - val excretaTime = - ExcretaDateTimeUtils.convertTimeFormat(timepikerExcretaaddTime) - val excretaDateTime = "${excretaDate}T$excretaTime" - - val currentImageUri = excretaPatchViewModel.excretaImage.value - excretaPatchViewModel.patchExcreta( - accessToken, - getExcretaId(), - excretaType, - excretaDateTime, - requireContext(), - currentImageUri ?: Uri.EMPTY, - ) - excretaPatchViewModel.excretaPatched.observe(viewLifecycleOwner) { response -> - if (response == SUCCESS) { - showSuccessSnackBar( - requireView(), - EXCRETA_PATCH_SUCCESS, - ) - findNavController().popBackStack() - } else { - showFailureSnackBar( - requireView(), - EXCRETA_PATCH_FAILURE, - ) + patchExcreta(excretaInfo.excretaImageURL) } + } else { + getPreSignedUrl(uri) } } } } } + private fun getPreSignedUrl(uri: Uri) { + imageFile = convertUriToFile(requireContext(), uri) + filePath = "$PARENT_FOLDER_PATH$EXCRETA_FOLDER_PATH${imageFile.name}" + awsS3ViewModel.getPreSignedUrl(accessToken, filePath) + awsS3ViewModel.preSignedUrl.observe(viewLifecycleOwner) { response -> + if (response != null) { + val requestBody = imageFile.asRequestBody("image/*".toMediaTypeOrNull()) + uploadImage(response.preSignedUrl, requestBody) + } + } + } + + private fun uploadImage( + preSignedUrl: String, + requestBody: RequestBody, + ) { + awsS3ViewModel.uploadImageToS3(preSignedUrl, requestBody) + awsS3ViewModel.uploadImageResponse.observe(viewLifecycleOwner) { response -> + if (response == 200) { + val imageURL = BuildConfig.AWS_S3_BASE_URL + filePath + patchExcreta(imageURL) + } + } + } + + private fun patchExcreta(imageURL: String?) { + val excretaType = + if (binding.checkboxExcretaaddUrine.isChecked) { + Excreta.URINE.toString() + } else { + Excreta.FECES.toString() + } + + val excretaTime = + ExcretaDateTimeUtils.convertTimeFormat(binding.timepikerExcretaaddTime) + val excretaDateTime = "${excretaDate}T$excretaTime" + + excretaPatchViewModel.patchExcreta( + accessToken, + getExcretaId(), + excretaType, + excretaDateTime, + imageURL, + ) + + excretaPatchViewModel.excretaPatched.observe(viewLifecycleOwner) { response -> + if (response == SUCCESS) { + showSuccessSnackBar( + requireView(), + EXCRETA_PATCH_SUCCESS, + ) + findNavController().popBackStack() + } else { + showFailureSnackBar( + requireView(), + EXCRETA_PATCH_FAILURE, + ) + } + } + } + private fun getExcretaId() = arguments?.getLong("excretaId")!! private fun getExcretaInfo() = diff --git a/app/src/main/java/com/project/meongcare/excreta/view/ExcretaInfoFragment.kt b/app/src/main/java/com/project/meongcare/excreta/view/ExcretaInfoFragment.kt index 4d4bc11d0..4bde06ccd 100644 --- a/app/src/main/java/com/project/meongcare/excreta/view/ExcretaInfoFragment.kt +++ b/app/src/main/java/com/project/meongcare/excreta/view/ExcretaInfoFragment.kt @@ -35,7 +35,7 @@ class ExcretaInfoFragment : Fragment() { private val userViewModel: UserViewModel by viewModels() private lateinit var excretaInfo: ExcretaDetailGetResponse - private var excretaImageURL = "" + private var excretaImageURL: String? = "" private var excretaDateTime = "" private var excretaType = "" private var accessToken = "" @@ -125,9 +125,9 @@ class ExcretaInfoFragment : Fragment() { } } - private fun initExcretaImage(excretaImageURL: String) { + private fun initExcretaImage(excretaImageURL: String?) { binding.apply { - if (excretaImageURL.isNotEmpty()) { + if (!excretaImageURL.isNullOrEmpty()) { cardviewExcretaInfoVisibilityOff.visibility = View.VISIBLE imageviewExcretainfoFecesIllustration.visibility = View.INVISIBLE Glide.with(this@ExcretaInfoFragment) @@ -139,6 +139,9 @@ class ExcretaInfoFragment : Fragment() { imageviewExcretainfoPicture.setOnClickListener { cardviewExcretaInfoVisibilityOff.visibility = View.VISIBLE } + } else { + cardviewExcretaInfoVisibilityOff.visibility = View.GONE + imageviewExcretainfoFecesIllustration.visibility = View.VISIBLE } } } diff --git a/app/src/main/java/com/project/meongcare/excreta/viewmodel/ExcretaAddViewModel.kt b/app/src/main/java/com/project/meongcare/excreta/viewmodel/ExcretaAddViewModel.kt index eb1188dfa..afb7667e0 100644 --- a/app/src/main/java/com/project/meongcare/excreta/viewmodel/ExcretaAddViewModel.kt +++ b/app/src/main/java/com/project/meongcare/excreta/viewmodel/ExcretaAddViewModel.kt @@ -1,15 +1,11 @@ package com.project.meongcare.excreta.viewmodel -import android.content.Context import android.net.Uri import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.project.meongcare.excreta.model.data.repository.ExcretaRepositoryImpl -import com.project.meongcare.excreta.model.entities.ExcretaInfo -import com.project.meongcare.excreta.model.entities.ExcretaUploadRequest -import com.project.meongcare.excreta.utils.ExcretaInfoUtils.convertExcretaFile -import com.project.meongcare.excreta.utils.ExcretaInfoUtils.convertExcretaPostDto +import com.project.meongcare.excreta.model.entities.ExcretaPostRequest import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject @@ -41,27 +37,19 @@ class ExcretaAddViewModel } fun postExcreta( - dogId: Long, accessToken: String, + dogId: Long, excretaType: String, dateTime: String, - context: Context, - uri: Uri, + imageURL: String?, ) { viewModelScope.launch { - val excretaInfo = - ExcretaInfo( + val excretaPostRequest = + ExcretaPostRequest( dogId, excretaType, dateTime, - ) - val dto = convertExcretaPostDto(excretaInfo) - val file = convertExcretaFile(context, uri) - - val excretaPostRequest = - ExcretaUploadRequest( - dto, - file, + imageURL, ) _excretaPosted.value = excretaRepositoryImpl.postExcreta(accessToken, excretaPostRequest) diff --git a/app/src/main/java/com/project/meongcare/excreta/viewmodel/ExcretaPatchViewModel.kt b/app/src/main/java/com/project/meongcare/excreta/viewmodel/ExcretaPatchViewModel.kt index f8976671a..b05a92d4a 100644 --- a/app/src/main/java/com/project/meongcare/excreta/viewmodel/ExcretaPatchViewModel.kt +++ b/app/src/main/java/com/project/meongcare/excreta/viewmodel/ExcretaPatchViewModel.kt @@ -1,15 +1,11 @@ package com.project.meongcare.excreta.viewmodel -import android.content.Context import android.net.Uri import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.project.meongcare.excreta.model.data.repository.ExcretaRepositoryImpl -import com.project.meongcare.excreta.model.entities.ExcretaInfoPatch -import com.project.meongcare.excreta.model.entities.ExcretaUploadRequest -import com.project.meongcare.excreta.utils.ExcretaInfoUtils.convertExcretaFile -import com.project.meongcare.excreta.utils.ExcretaInfoUtils.convertExcretaPatchDto +import com.project.meongcare.excreta.model.entities.ExcretaPatchRequest import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject @@ -45,25 +41,16 @@ class ExcretaPatchViewModel excretaId: Long, excretaType: String, excretaDateTime: String, - context: Context, - uri: Uri, + imageURL: String?, ) { viewModelScope.launch { - val excretaInfoPatch = - ExcretaInfoPatch( + val excretaPatchRequest = + ExcretaPatchRequest( excretaId, excretaType, excretaDateTime, + imageURL, ) - val dto = convertExcretaPatchDto(excretaInfoPatch) - val file = convertExcretaFile(context, uri) - - val excretaPatchRequest = - ExcretaUploadRequest( - dto, - file, - ) - _excretaPatched.value = excretaRepositoryImpl.patchExcreta(accessToken, excretaPatchRequest) } } diff --git a/app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedRemoteDataSource.kt b/app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedRemoteDataSource.kt index f8e7891d8..d0f6394d8 100644 --- a/app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedRemoteDataSource.kt +++ b/app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedRemoteDataSource.kt @@ -6,8 +6,9 @@ import com.project.meongcare.feed.model.entities.FeedDetailGetResponse import com.project.meongcare.feed.model.entities.FeedGetResponse import com.project.meongcare.feed.model.entities.FeedPartRecords import com.project.meongcare.feed.model.entities.FeedPatchRequest +import com.project.meongcare.feed.model.entities.FeedPostRequest +import com.project.meongcare.feed.model.entities.FeedPutRequest import com.project.meongcare.feed.model.entities.FeedRecords -import com.project.meongcare.feed.model.entities.FeedUploadRequest import com.project.meongcare.feed.model.entities.Feeds import org.json.JSONObject import javax.inject.Inject @@ -19,14 +20,13 @@ class FeedRemoteDataSource suspend fun postFeed( accessToken: String, - feedUploadRequest: FeedUploadRequest, + feedPostRequest: FeedPostRequest, ): Int? { try { val postFeedResponse = feedApiService.postFeed( accessToken, - feedUploadRequest.dto, - feedUploadRequest.file, + feedPostRequest, ) if (postFeedResponse.code() != SUCCESS) { @@ -231,14 +231,13 @@ class FeedRemoteDataSource suspend fun putFeed( accessToken: String, - feedUploadRequest: FeedUploadRequest, + feedPutRequest: FeedPutRequest, ): Int? { try { val putFeedResponse = feedApiService.putFeed( accessToken, - feedUploadRequest.dto, - feedUploadRequest.file, + feedPutRequest, ) if (putFeedResponse.code() != SUCCESS) { diff --git a/app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedService.kt b/app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedService.kt index a75b56d81..7d9bf3df1 100644 --- a/app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedService.kt +++ b/app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedService.kt @@ -4,30 +4,26 @@ import com.project.meongcare.feed.model.entities.FeedDetailGetResponse import com.project.meongcare.feed.model.entities.FeedGetResponse import com.project.meongcare.feed.model.entities.FeedPartRecords import com.project.meongcare.feed.model.entities.FeedPatchRequest +import com.project.meongcare.feed.model.entities.FeedPostRequest +import com.project.meongcare.feed.model.entities.FeedPutRequest import com.project.meongcare.feed.model.entities.FeedRecords import com.project.meongcare.feed.model.entities.Feeds -import okhttp3.MultipartBody -import okhttp3.RequestBody import retrofit2.Response import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.Header -import retrofit2.http.Multipart import retrofit2.http.PATCH import retrofit2.http.POST import retrofit2.http.PUT -import retrofit2.http.Part import retrofit2.http.Path import retrofit2.http.Query interface FeedService { - @Multipart @POST("feed") suspend fun postFeed( @Header("AccessToken") accessToken: String, - @Part("dto") dto: RequestBody, - @Part file: MultipartBody.Part, + @Body feedPostRequest: FeedPostRequest, ): Response @GET("feed/{dogId}") @@ -75,12 +71,10 @@ interface FeedService { @Path("feedRecordId") feedRecordId: Long, ): Response - @Multipart @PUT("feed") suspend fun putFeed( @Header("AccessToken") accessToken: String, - @Part("dto") dto: RequestBody, - @Part file: MultipartBody.Part, + @Body feedPutRequest: FeedPutRequest, ): Response @DELETE("feed/{feedRecordId}") diff --git a/app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedRepository.kt b/app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedRepository.kt index 260e82af9..51f2be18a 100644 --- a/app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedRepository.kt +++ b/app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedRepository.kt @@ -4,8 +4,9 @@ import com.project.meongcare.feed.model.entities.FeedDetailGetResponse import com.project.meongcare.feed.model.entities.FeedGetResponse import com.project.meongcare.feed.model.entities.FeedPartRecords import com.project.meongcare.feed.model.entities.FeedPatchRequest +import com.project.meongcare.feed.model.entities.FeedPostRequest +import com.project.meongcare.feed.model.entities.FeedPutRequest import com.project.meongcare.feed.model.entities.FeedRecords -import com.project.meongcare.feed.model.entities.FeedUploadRequest import com.project.meongcare.feed.model.entities.Feeds interface FeedRepository { @@ -16,7 +17,7 @@ interface FeedRepository { suspend fun postFeed( accessToken: String, - feedUploadRequest: FeedUploadRequest, + feedPostRequest: FeedPostRequest, ): Int? suspend fun getFeedPart( @@ -54,7 +55,7 @@ interface FeedRepository { suspend fun putFeed( accessToken: String, - feedUploadRequest: FeedUploadRequest, + feedPutRequest: FeedPutRequest, ): Int? suspend fun deleteFeed( diff --git a/app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedRepositoryImpl.kt b/app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedRepositoryImpl.kt index 15dfdbba0..617f98a09 100644 --- a/app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedRepositoryImpl.kt +++ b/app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedRepositoryImpl.kt @@ -2,7 +2,8 @@ package com.project.meongcare.feed.model.data.repository import com.project.meongcare.feed.model.data.remote.FeedRemoteDataSource import com.project.meongcare.feed.model.entities.FeedPatchRequest -import com.project.meongcare.feed.model.entities.FeedUploadRequest +import com.project.meongcare.feed.model.entities.FeedPostRequest +import com.project.meongcare.feed.model.entities.FeedPutRequest import javax.inject.Inject class FeedRepositoryImpl @@ -17,8 +18,8 @@ class FeedRepositoryImpl override suspend fun postFeed( accessToken: String, - feedUploadRequest: FeedUploadRequest, - ) = feedRemoteDataSource.postFeed(accessToken, feedUploadRequest) + feedPostRequest: FeedPostRequest, + ) = feedRemoteDataSource.postFeed(accessToken, feedPostRequest) override suspend fun getFeedPart( accessToken: String, @@ -55,8 +56,8 @@ class FeedRepositoryImpl override suspend fun putFeed( accessToken: String, - feedUploadRequest: FeedUploadRequest, - ) = feedRemoteDataSource.putFeed(accessToken, feedUploadRequest) + feedPutRequest: FeedPutRequest, + ): Int? = feedRemoteDataSource.putFeed(accessToken, feedPutRequest) override suspend fun deleteFeed( accessToken: String, diff --git a/app/src/main/java/com/project/meongcare/feed/model/entities/Feed.kt b/app/src/main/java/com/project/meongcare/feed/model/entities/Feed.kt index 8b18ae0df..a2a3fa61a 100644 --- a/app/src/main/java/com/project/meongcare/feed/model/entities/Feed.kt +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/Feed.kt @@ -4,5 +4,5 @@ data class Feed( val feedId: Long, val brandName: String, val feedName: String, - val imageURL: String, + val imageURL: String?, ) diff --git a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedDetailGetResponse.kt b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedDetailGetResponse.kt index 4a1c773fc..ba468b6dc 100644 --- a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedDetailGetResponse.kt +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedDetailGetResponse.kt @@ -14,7 +14,7 @@ data class FeedDetailGetResponse( val etc: Double, val kcal: Double, val recommendIntake: Long, - val imageURL: String, + val imageURL: String?, val startDate: String, val endDate: String?, ) : Parcelable diff --git a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPartRecord.kt b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPartRecord.kt index 6992ea3e7..6098d8031 100644 --- a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPartRecord.kt +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPartRecord.kt @@ -5,6 +5,6 @@ data class FeedPartRecord( val feedName: String, val startDate: String, val endDate: String, - val feedImageURL: String, + val feedImageURL: String?, val feedRecordId: Long, ) diff --git a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedInfo.kt b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPostRequest.kt similarity index 86% rename from app/src/main/java/com/project/meongcare/feed/model/entities/FeedInfo.kt rename to app/src/main/java/com/project/meongcare/feed/model/entities/FeedPostRequest.kt index 4151cc5cd..5d82ff882 100644 --- a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedInfo.kt +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPostRequest.kt @@ -1,6 +1,6 @@ package com.project.meongcare.feed.model.entities -data class FeedInfo( +data class FeedPostRequest( val dogId: Long, val brand: String, val feedName: String, @@ -13,4 +13,5 @@ data class FeedInfo( val recommendIntake: Int, val startDate: String, val endDate: String? = null, + val imageURL: String?, ) diff --git a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPutInfo.kt b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPutRequest.kt similarity index 87% rename from app/src/main/java/com/project/meongcare/feed/model/entities/FeedPutInfo.kt rename to app/src/main/java/com/project/meongcare/feed/model/entities/FeedPutRequest.kt index 35297b862..c5ce1c923 100644 --- a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPutInfo.kt +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPutRequest.kt @@ -1,6 +1,6 @@ package com.project.meongcare.feed.model.entities -data class FeedPutInfo( +data class FeedPutRequest( val feedId: Long, val brand: String, val feedName: String, @@ -14,4 +14,5 @@ data class FeedPutInfo( val startDate: String, val endDate: String? = null, val feedRecordId: Long, + val imageURL: String?, ) diff --git a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedRecord.kt b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedRecord.kt index aa5a1db10..1f1527840 100644 --- a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedRecord.kt +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedRecord.kt @@ -6,6 +6,6 @@ data class FeedRecord( val startDate: String, val endDate: String, val feedRecordId: Long, - val imageURL: String, + val imageURL: String?, val feedId: Long, ) diff --git a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedUploadRequest.kt b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedUploadRequest.kt deleted file mode 100644 index 37baaca1e..000000000 --- a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedUploadRequest.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.project.meongcare.feed.model.entities - -import okhttp3.MultipartBody -import okhttp3.RequestBody - -data class FeedUploadRequest( - val dto: RequestBody, - val file: MultipartBody.Part, -) diff --git a/app/src/main/java/com/project/meongcare/feed/model/utils/FeedInfoUtils.kt b/app/src/main/java/com/project/meongcare/feed/model/utils/FeedInfoUtils.kt index 7a03300cb..bf56bc42f 100644 --- a/app/src/main/java/com/project/meongcare/feed/model/utils/FeedInfoUtils.kt +++ b/app/src/main/java/com/project/meongcare/feed/model/utils/FeedInfoUtils.kt @@ -1,86 +1,11 @@ package com.project.meongcare.feed.model.utils -import android.content.Context -import android.net.Uri -import android.util.Log import android.view.View import android.widget.TextView -import com.google.gson.Gson import com.project.meongcare.R -import com.project.meongcare.feed.model.entities.FeedInfo -import com.project.meongcare.feed.model.entities.FeedPutInfo import com.project.meongcare.snackbar.view.CustomSnackBar -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.MultipartBody -import okhttp3.RequestBody -import okhttp3.RequestBody.Companion.asRequestBody -import okhttp3.RequestBody.Companion.toRequestBody -import java.io.BufferedInputStream -import java.io.File -import java.net.URL object FeedInfoUtils { - fun convertFeedPostDto(feedInfo: FeedInfo): RequestBody { - val json = Gson().toJson(feedInfo) - return json.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) - } - - fun convertFeedPutDto(feedPutInfo: FeedPutInfo): RequestBody { - val json = Gson().toJson(feedPutInfo) - return json.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) - } - - fun convertFeedFile( - context: Context, - uri: Uri, - ): MultipartBody.Part { - if (uri.toString().isEmpty()) { - val emptyFile = "".toRequestBody("multipart/form-data".toMediaTypeOrNull()) - return MultipartBody.Part.createFormData("file", "", emptyFile) - } - - // Uri로부터 InputStream을 얻고, 임시 파일로 복사 - val inputStream = context.contentResolver.openInputStream(uri) - val file = File(context.cacheDir, "tempFile") - inputStream.use { input -> - file.outputStream().use { output -> - input?.copyTo(output) - } - } - val requestFile = file.asRequestBody("multipart/form-data".toMediaTypeOrNull()) - - return MultipartBody.Part.createFormData("file", file.name, requestFile) - } - - suspend fun convertFeedImageUrl( - context: Context, - urlString: String, - ): MultipartBody.Part { - return withContext(Dispatchers.IO) { - try { - val url = URL(urlString) - val connection = url.openConnection() - val inputStream = BufferedInputStream(connection.getInputStream()) - val file = File(context.cacheDir, "downloadedFile") - inputStream.use { input -> - file.outputStream().use { output -> - input.copyTo(output) - } - } - - val requestFile = file.asRequestBody("multipart/form-data".toMediaTypeOrNull()) - MultipartBody.Part.createFormData("file", file.name, requestFile) - } catch (e: Exception) { - Log.d("사료 이미지 URL 변환 실패", e.message.toString()) - - val emptyFile = "".toRequestBody("multipart/form-data".toMediaTypeOrNull()) - MultipartBody.Part.createFormData("file", "", emptyFile) - } - } - } - fun calculateRecommendDailyIntake( weight: Double, feedKcal: Double, diff --git a/app/src/main/java/com/project/meongcare/feed/view/FeedAddFragment.kt b/app/src/main/java/com/project/meongcare/feed/view/FeedAddFragment.kt index caa6febab..649fc090d 100644 --- a/app/src/main/java/com/project/meongcare/feed/view/FeedAddFragment.kt +++ b/app/src/main/java/com/project/meongcare/feed/view/FeedAddFragment.kt @@ -18,19 +18,21 @@ import androidx.navigation.fragment.findNavController import com.archit.calendardaterangepicker.customviews.CalendarListener import com.archit.calendardaterangepicker.customviews.DateRangeCalendarView import com.bumptech.glide.Glide +import com.project.meongcare.BuildConfig import com.project.meongcare.R +import com.project.meongcare.aws.util.AWSS3ImageUtils.convertUriToFile +import com.project.meongcare.aws.util.FEED_FOLDER_PATH +import com.project.meongcare.aws.util.PARENT_FOLDER_PATH +import com.project.meongcare.aws.viewmodel.AWSS3ViewModel import com.project.meongcare.databinding.FragmentFeedAddEditBinding import com.project.meongcare.excreta.utils.SUCCESS import com.project.meongcare.feed.model.data.local.FeedPhotoListener -import com.project.meongcare.feed.model.entities.FeedInfo -import com.project.meongcare.feed.model.entities.FeedUploadRequest +import com.project.meongcare.feed.model.entities.FeedPostRequest import com.project.meongcare.feed.model.utils.END_DATE import com.project.meongcare.feed.model.utils.FEED_POST_FAILURE import com.project.meongcare.feed.model.utils.FEED_POST_SUCCESS import com.project.meongcare.feed.model.utils.FeedDateUtils.convertDateFormat import com.project.meongcare.feed.model.utils.FeedInfoUtils.calculateRecommendDailyIntake -import com.project.meongcare.feed.model.utils.FeedInfoUtils.convertFeedFile -import com.project.meongcare.feed.model.utils.FeedInfoUtils.convertFeedPostDto import com.project.meongcare.feed.model.utils.FeedInfoUtils.initRecommendDailyIntake import com.project.meongcare.feed.model.utils.FeedInfoUtils.showFailureSnackBar import com.project.meongcare.feed.model.utils.FeedInfoUtils.showSuccessSnackBar @@ -45,6 +47,9 @@ import com.project.meongcare.feed.viewmodel.DogViewModel import com.project.meongcare.feed.viewmodel.FeedPostViewModel import com.project.meongcare.feed.viewmodel.UserViewModel import dagger.hilt.android.AndroidEntryPoint +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody.Companion.asRequestBody +import java.io.File import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -56,14 +61,17 @@ class FeedAddFragment : Fragment(), FeedPhotoListener { val binding get() = _binding!! private lateinit var inputMethodManager: InputMethodManager + private lateinit var imageFile: File + private lateinit var filePath: String + private val feedPostViewModel: FeedPostViewModel by viewModels() private val dogViewModel: DogViewModel by viewModels() private val userViewModel: UserViewModel by viewModels() + private val awsS3ViewModel: AWSS3ViewModel by viewModels() private var recommendIntake = 0.0 var selectedStartDate = "" private var selectedEndDate: String? = null - private lateinit var feedInfo: FeedInfo private var imageUri: Uri? = null private var proteinValue = 0.0 @@ -103,6 +111,12 @@ class FeedAddFragment : Fragment(), FeedPhotoListener { dogViewModel.dogWeight.observe(viewLifecycleOwner) { response -> weight = response } + awsS3ViewModel.uploadImageResponse.observe(viewLifecycleOwner) { response -> + if (response == 200) { + val imageURL = BuildConfig.AWS_S3_BASE_URL + filePath + postFeedInfo(imageURL) + } + } initInputMethodManager() initToolbar() initPhotoAttachModalBottomSheet() @@ -305,12 +319,12 @@ class FeedAddFragment : Fragment(), FeedPhotoListener { } } - private fun createFeedInfo() { + private fun createFeedInfo(imageURL: String?): FeedPostRequest { binding.apply { val brand = edittextFeedaddeditBrand.text.toString() val feedName = edittextFeedaddeditName.text.toString() - feedInfo = - FeedInfo( + val feedPostRequest = + FeedPostRequest( dogId, brand, feedName, @@ -323,8 +337,9 @@ class FeedAddFragment : Fragment(), FeedPhotoListener { recommendIntake.toInt(), selectedStartDate, selectedEndDate, + imageURL, ) - imageUri = feedPostViewModel.feedImage.value + return feedPostRequest } } @@ -457,25 +472,21 @@ class FeedAddFragment : Fragment(), FeedPhotoListener { } if (isValid) { - postFeedInfo() + val uri = feedPostViewModel.feedImage.value + if (uri == null) { + postFeedInfo(null) + } else { + getPreSignedUrl(uri) + } } } } } - private fun postFeedInfo() { - createFeedInfo() - val dto = convertFeedPostDto(feedInfo) - val file = - convertFeedFile( - requireContext(), - imageUri ?: Uri.EMPTY, - ) - val uploadRequest = FeedUploadRequest(dto, file) - + private fun postFeedInfo(imageURL: String?) { feedPostViewModel.postFeed( accessToken, - uploadRequest, + createFeedInfo(imageURL), ) feedPostViewModel.feedPosted.observe(viewLifecycleOwner) { response -> if (response == SUCCESS) { @@ -493,6 +504,18 @@ class FeedAddFragment : Fragment(), FeedPhotoListener { } } + private fun getPreSignedUrl(uri: Uri) { + imageFile = convertUriToFile(requireContext(), uri) + filePath = "$PARENT_FOLDER_PATH$FEED_FOLDER_PATH${imageFile.name}" + awsS3ViewModel.getPreSignedUrl(accessToken, filePath) + awsS3ViewModel.preSignedUrl.observe(viewLifecycleOwner) { response -> + if (response != null) { + val requestBody = imageFile.asRequestBody("image/*".toMediaTypeOrNull()) + awsS3ViewModel.uploadImageToS3(response.preSignedUrl, requestBody) + } + } + } + private fun initInputMethodManager() { thread { SystemClock.sleep(300) diff --git a/app/src/main/java/com/project/meongcare/feed/view/FeedEditFragment.kt b/app/src/main/java/com/project/meongcare/feed/view/FeedEditFragment.kt index 59c0b5e1c..060f23a82 100644 --- a/app/src/main/java/com/project/meongcare/feed/view/FeedEditFragment.kt +++ b/app/src/main/java/com/project/meongcare/feed/view/FeedEditFragment.kt @@ -15,27 +15,27 @@ import android.widget.TextView import androidx.core.widget.doAfterTextChanged import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController import com.archit.calendardaterangepicker.customviews.CalendarListener import com.archit.calendardaterangepicker.customviews.DateRangeCalendarView import com.bumptech.glide.Glide +import com.project.meongcare.BuildConfig import com.project.meongcare.R +import com.project.meongcare.aws.util.AWSS3ImageUtils.convertUriToFile +import com.project.meongcare.aws.util.FEED_FOLDER_PATH +import com.project.meongcare.aws.util.PARENT_FOLDER_PATH +import com.project.meongcare.aws.viewmodel.AWSS3ViewModel import com.project.meongcare.databinding.FragmentFeedAddEditBinding import com.project.meongcare.excreta.utils.SUCCESS import com.project.meongcare.feed.model.data.local.FeedPhotoListener import com.project.meongcare.feed.model.entities.FeedDetailGetResponse -import com.project.meongcare.feed.model.entities.FeedPutInfo -import com.project.meongcare.feed.model.entities.FeedUploadRequest +import com.project.meongcare.feed.model.entities.FeedPutRequest import com.project.meongcare.feed.model.utils.END_DATE import com.project.meongcare.feed.model.utils.FEED_PUT_FAILURE import com.project.meongcare.feed.model.utils.FEED_PUT_SUCCESS import com.project.meongcare.feed.model.utils.FeedDateUtils.convertDateFormat import com.project.meongcare.feed.model.utils.FeedInfoUtils.calculateRecommendDailyIntake -import com.project.meongcare.feed.model.utils.FeedInfoUtils.convertFeedFile -import com.project.meongcare.feed.model.utils.FeedInfoUtils.convertFeedImageUrl -import com.project.meongcare.feed.model.utils.FeedInfoUtils.convertFeedPutDto import com.project.meongcare.feed.model.utils.FeedInfoUtils.initRecommendDailyIntake import com.project.meongcare.feed.model.utils.FeedInfoUtils.showFailureSnackBar import com.project.meongcare.feed.model.utils.FeedInfoUtils.showSuccessSnackBar @@ -50,8 +50,10 @@ import com.project.meongcare.feed.viewmodel.DogViewModel import com.project.meongcare.feed.viewmodel.FeedPutViewModel import com.project.meongcare.feed.viewmodel.UserViewModel import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch -import okhttp3.MultipartBody +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import java.io.File import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -66,11 +68,14 @@ class FeedEditFragment : Fragment(), FeedPhotoListener { private var feedId = 0L private var feedRecordId = 0L private lateinit var feedInfo: FeedDetailGetResponse - private lateinit var feedPutInfo: FeedPutInfo + private lateinit var feedPutInfo: FeedPutRequest + private lateinit var imageFile: File + private lateinit var filePath: String private var recommendIntake = 0.0 private var selectedStartDate = "" private var selectedEndDate: String? = null + private val awsS3ViewModel: AWSS3ViewModel by viewModels() private val feedPutViewModel: FeedPutViewModel by viewModels() private val dogViewModel: DogViewModel by viewModels() private val userViewModel: UserViewModel by viewModels() @@ -124,7 +129,7 @@ class FeedEditFragment : Fragment(), FeedPhotoListener { private fun fetchFeedInfo() { binding.apply { recommendIntake = feedInfo.recommendIntake.toDouble() - if (feedInfo.imageURL.isNotEmpty()) { + if (!feedInfo.imageURL.isNullOrEmpty()) { Glide.with(this@FeedEditFragment) .load(feedInfo.imageURL) .into(imageviewFeedaddeditPicture) @@ -339,12 +344,12 @@ class FeedEditFragment : Fragment(), FeedPhotoListener { } } - private fun createFeedInfo() { + private fun createFeedInfo(imageURL: String?) { binding.apply { val brand = edittextFeedaddeditBrand.text.toString() val feedName = edittextFeedaddeditName.text.toString() feedPutInfo = - FeedPutInfo( + FeedPutRequest( feedId, brand, feedName, @@ -358,6 +363,7 @@ class FeedEditFragment : Fragment(), FeedPhotoListener { selectedStartDate, selectedEndDate, feedRecordId, + imageURL, ) } } @@ -491,49 +497,53 @@ class FeedEditFragment : Fragment(), FeedPhotoListener { } if (isValid) { - editFeedInfo() + val uri = feedPutViewModel.feedImage.value + if (uri == null) { // 기존 이미지 + if (feedInfo.imageURL == null) { + createFeedInfo(null) + putFeed() + } else { + createFeedInfo(feedInfo.imageURL) + putFeed() + } + } else { // 새 이미지 + getPreSignedUrl(uri) + } } } } } - private fun editFeedInfo() { - binding.apply { - createFeedInfo() - val imageUri = feedPutViewModel.feedImage.value - - if (imageUri == null) { - lifecycleScope.launch { - val file = - convertFeedImageUrl( - requireContext(), - feedInfo.imageURL, - ) - val feedUploadRequest = createFeedPutRequest(file) - putFeed(feedUploadRequest) - } - } else { - val file = - convertFeedFile( - requireContext(), - imageUri, - ) - val feedUploadRequest = createFeedPutRequest(file) - putFeed(feedUploadRequest) + private fun getPreSignedUrl(uri: Uri) { + imageFile = convertUriToFile(requireContext(), uri) + filePath = "$PARENT_FOLDER_PATH$FEED_FOLDER_PATH${imageFile.name}" + awsS3ViewModel.getPreSignedUrl(accessToken, filePath) + awsS3ViewModel.preSignedUrl.observe(viewLifecycleOwner) { response -> + if (response != null) { + val requestBody = imageFile.asRequestBody("image/*".toMediaTypeOrNull()) + uploadImage(response.preSignedUrl, requestBody) } } } - private fun createFeedPutRequest(file: MultipartBody.Part): FeedUploadRequest { - val dto = convertFeedPutDto(feedPutInfo) - - return FeedUploadRequest(dto, file) + private fun uploadImage( + preSignedUrl: String, + requestBody: RequestBody, + ) { + awsS3ViewModel.uploadImageToS3(preSignedUrl, requestBody) + awsS3ViewModel.uploadImageResponse.observe(viewLifecycleOwner) { response -> + if (response == 200) { + val imageURL = BuildConfig.AWS_S3_BASE_URL + filePath + createFeedInfo(imageURL) + putFeed() + } + } } - private fun putFeed(feedUploadRequest: FeedUploadRequest) { + private fun putFeed() { feedPutViewModel.putFeed( accessToken, - feedUploadRequest, + feedPutInfo, ) feedPutViewModel.feedPut.observe(viewLifecycleOwner) { response -> if (response == SUCCESS) { diff --git a/app/src/main/java/com/project/meongcare/feed/view/FeedInfoFragment.kt b/app/src/main/java/com/project/meongcare/feed/view/FeedInfoFragment.kt index 3745578aa..21ea36c63 100644 --- a/app/src/main/java/com/project/meongcare/feed/view/FeedInfoFragment.kt +++ b/app/src/main/java/com/project/meongcare/feed/view/FeedInfoFragment.kt @@ -84,7 +84,7 @@ class FeedInfoFragment : Fragment() { response.startDate, response.endDate, ) - if (response.imageURL.isNotEmpty()) { + if (!response.imageURL.isNullOrEmpty()) { Glide.with(this@FeedInfoFragment) .load(response.imageURL) .into(imageviewFeedinfo) diff --git a/app/src/main/java/com/project/meongcare/feed/view/FeedPartAdapter.kt b/app/src/main/java/com/project/meongcare/feed/view/FeedPartAdapter.kt index 981605af6..504075873 100644 --- a/app/src/main/java/com/project/meongcare/feed/view/FeedPartAdapter.kt +++ b/app/src/main/java/com/project/meongcare/feed/view/FeedPartAdapter.kt @@ -14,7 +14,7 @@ class FeedPartAdapter : ListAdapter - @Multipart @PUT("dog/{dogId}") suspend fun putDogInfo( @Path("dogId") dogId: Long, @Header("AccessToken") accessToken: String, - @Part file: MultipartBody.Part, - @Part("dto") dto: RequestBody, + @Body dogPutRequest: DogPutRequest, ): Response @POST("weight") @@ -81,10 +77,9 @@ interface ProfileApi { @Header("AccessToken") accessToken: String, ): Response - @Multipart @PATCH("member/profile") suspend fun patchProfileImage( @Header("AccessToken") accessToken: String, - @Part file: MultipartBody.Part, + @Body profilePatchRequest: ProfilePatchRequest, ): Response } diff --git a/app/src/main/java/com/project/meongcare/info/model/data/repository/ProfileRepository.kt b/app/src/main/java/com/project/meongcare/info/model/data/repository/ProfileRepository.kt index 4a8df6b2f..c8f97e67f 100644 --- a/app/src/main/java/com/project/meongcare/info/model/data/repository/ProfileRepository.kt +++ b/app/src/main/java/com/project/meongcare/info/model/data/repository/ProfileRepository.kt @@ -1,12 +1,11 @@ package com.project.meongcare.info.model.data.repository -import com.project.meongcare.home.model.entities.DogProfile import com.project.meongcare.home.model.entities.GetDogListResponse import com.project.meongcare.home.model.entities.GetUserProfileResponse +import com.project.meongcare.info.model.entities.DogPutRequest import com.project.meongcare.info.model.entities.GetDogInfoResponse +import com.project.meongcare.info.model.entities.ProfilePatchRequest import com.project.meongcare.weight.model.entities.WeightPostRequest -import okhttp3.MultipartBody -import okhttp3.RequestBody import retrofit2.Response interface ProfileRepository { @@ -27,8 +26,7 @@ interface ProfileRepository { suspend fun putDogInfo( dogId: Long, accessToken: String, - file: MultipartBody.Part, - dto: RequestBody, + dogPutRequest: DogPutRequest, ): Int? suspend fun postDogWeight( @@ -54,6 +52,6 @@ interface ProfileRepository { suspend fun patchProfileImage( accessToken: String, - file: MultipartBody.Part, + profilePatchRequest: ProfilePatchRequest, ): Int? } diff --git a/app/src/main/java/com/project/meongcare/info/model/data/repository/ProfileRepositoryImpl.kt b/app/src/main/java/com/project/meongcare/info/model/data/repository/ProfileRepositoryImpl.kt index 1e9d13afd..9842faa77 100644 --- a/app/src/main/java/com/project/meongcare/info/model/data/repository/ProfileRepositoryImpl.kt +++ b/app/src/main/java/com/project/meongcare/info/model/data/repository/ProfileRepositoryImpl.kt @@ -1,14 +1,13 @@ package com.project.meongcare.info.model.data.repository import android.util.Log -import com.project.meongcare.home.model.entities.DogProfile import com.project.meongcare.home.model.entities.GetDogListResponse import com.project.meongcare.home.model.entities.GetUserProfileResponse import com.project.meongcare.info.model.data.remote.ProfileRetrofitClient +import com.project.meongcare.info.model.entities.DogPutRequest import com.project.meongcare.info.model.entities.GetDogInfoResponse +import com.project.meongcare.info.model.entities.ProfilePatchRequest import com.project.meongcare.weight.model.entities.WeightPostRequest -import okhttp3.MultipartBody -import okhttp3.RequestBody import retrofit2.Response import javax.inject.Inject @@ -88,11 +87,10 @@ class ProfileRepositoryImpl override suspend fun putDogInfo( dogId: Long, accessToken: String, - file: MultipartBody.Part, - dto: RequestBody, + dogPutRequest: DogPutRequest, ): Int? { return try { - val response = profileRetrofitClient.profileApi.putDogInfo(dogId, accessToken, file, dto) + val response = profileRetrofitClient.profileApi.putDogInfo(dogId, accessToken, dogPutRequest) if (response.code() == 200) { Log.d("ProfileRepo-PutDog", "통신 성공 : ${response.code()}") response.code() @@ -199,10 +197,10 @@ class ProfileRepositoryImpl override suspend fun patchProfileImage( accessToken: String, - file: MultipartBody.Part, + profilePatchRequest: ProfilePatchRequest, ): Int? { return try { - val response = profileRetrofitClient.profileApi.patchProfileImage(accessToken, file) + val response = profileRetrofitClient.profileApi.patchProfileImage(accessToken, profilePatchRequest) if (response.code() == 200) { Log.d("ProfileRepo-PatchProfile", "통신 성공") response.code() diff --git a/app/src/main/java/com/project/meongcare/info/model/entities/DogPutRequest.kt b/app/src/main/java/com/project/meongcare/info/model/entities/DogPutRequest.kt new file mode 100644 index 000000000..34a27f791 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/info/model/entities/DogPutRequest.kt @@ -0,0 +1,14 @@ +package com.project.meongcare.info.model.entities + +data class DogPutRequest( + val name: String, + val type: String, + val sex: String, + val birthDate: String, + val castrate: Boolean, + val weight: Double, + val backRound: Double?, + val neckRound: Double?, + val chestRound: Double?, + val imageURL: String?, +) diff --git a/app/src/main/java/com/project/meongcare/info/model/entities/ProfilePatchRequest.kt b/app/src/main/java/com/project/meongcare/info/model/entities/ProfilePatchRequest.kt new file mode 100644 index 000000000..eecdfa904 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/info/model/entities/ProfilePatchRequest.kt @@ -0,0 +1,5 @@ +package com.project.meongcare.info.model.entities + +data class ProfilePatchRequest( + val imageURL: String?, +) diff --git a/app/src/main/java/com/project/meongcare/info/view/PetEditFragment.kt b/app/src/main/java/com/project/meongcare/info/view/PetEditFragment.kt index b257216c5..aa7f41f73 100644 --- a/app/src/main/java/com/project/meongcare/info/view/PetEditFragment.kt +++ b/app/src/main/java/com/project/meongcare/info/view/PetEditFragment.kt @@ -13,52 +13,48 @@ import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.bumptech.glide.Glide -import com.google.gson.Gson import com.project.meongcare.BirthdayBottomSheetFragment -import com.project.meongcare.MainActivity +import com.project.meongcare.BuildConfig import com.project.meongcare.R +import com.project.meongcare.aws.util.AWSS3ImageUtils.convertUriToFile +import com.project.meongcare.aws.util.DOG_FOLDER_PATH +import com.project.meongcare.aws.util.PARENT_FOLDER_PATH +import com.project.meongcare.aws.viewmodel.AWSS3ViewModel import com.project.meongcare.databinding.FragmentPetEditBinding +import com.project.meongcare.info.model.entities.DogPutRequest import com.project.meongcare.home.util.HomeDateUtil.getCurrentDate import com.project.meongcare.info.model.entities.GetDogInfoResponse import com.project.meongcare.info.viewmodel.ProfileViewModel -import com.project.meongcare.login.model.data.local.UserPreferences -import com.project.meongcare.login.model.data.repository.LoginRepository +import com.project.meongcare.medicalRecord.viewmodel.UserViewModel import com.project.meongcare.onboarding.model.data.local.DateSubmitListener import com.project.meongcare.onboarding.model.data.local.PhotoMenuListener -import com.project.meongcare.onboarding.model.entities.Dog -import com.project.meongcare.onboarding.view.Gender +import com.project.meongcare.onboarding.model.entities.Gender +import com.project.meongcare.onboarding.util.DogAddOnBoardingDateUtils.dateFormat +import com.project.meongcare.onboarding.util.DogAddOnBoardingInfoUtils.bodySizeCheck +import com.project.meongcare.onboarding.util.DogAddOnBoardingInfoUtils.getCheckedGender import com.project.meongcare.onboarding.view.PhotoSelectBottomSheetFragment -import com.project.meongcare.onboarding.view.bodySizeCheck -import com.project.meongcare.onboarding.view.createMultipartBody -import com.project.meongcare.onboarding.view.dateFormat -import com.project.meongcare.onboarding.view.getCheckedGender import com.project.meongcare.onboarding.viewmodel.DogTypeSharedViewModel import com.project.meongcare.snackbar.view.CustomSnackBar import com.project.meongcare.weight.model.entities.WeightPostRequest import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.MultipartBody import okhttp3.RequestBody import okhttp3.RequestBody.Companion.asRequestBody -import okhttp3.RequestBody.Companion.toRequestBody import java.io.File -import java.net.URL -import javax.inject.Inject @AndroidEntryPoint class PetEditFragment : Fragment(), PhotoMenuListener, DateSubmitListener { private lateinit var binding: FragmentPetEditBinding - private lateinit var mainActivity: MainActivity private lateinit var dogInfo: GetDogInfoResponse - private lateinit var currentAccessToken: String + private lateinit var accessToken: String + private lateinit var refreshToken: String + private lateinit var filePath: String + private lateinit var imageFile: File + private val awsS3ViewModel: AWSS3ViewModel by viewModels() + private val userViewModel: UserViewModel by viewModels() private val petEditViewModel: ProfileViewModel by viewModels() private val dogTypeSharedViewModel: DogTypeSharedViewModel by activityViewModels() @@ -66,16 +62,9 @@ class PetEditFragment : Fragment(), PhotoMenuListener, DateSubmitListener { private var isInitialized = false private var isImageUpdated = false - @Inject - lateinit var userPreferences: UserPreferences - - @Inject - lateinit var loginRepository: LoginRepository - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) dogInfo = getDogInfo() - getAccessToken() } override fun onCreateView( @@ -84,13 +73,69 @@ class PetEditFragment : Fragment(), PhotoMenuListener, DateSubmitListener { savedInstanceState: Bundle?, ): View? { binding = FragmentPetEditBinding.inflate(inflater) - mainActivity = activity as MainActivity + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) if (!isInitialized) { initDogInfo(dogInfo) isInitialized = true } + getAccessToken() + initObserves() + initViews() + } + + private fun getAccessToken() { + userViewModel.accessTokenPreferencesLiveData.observe(viewLifecycleOwner) { accessToken -> + if (accessToken != null) { + this.accessToken = accessToken + getRefreshToken() + } + } + } + + private fun getRefreshToken() { + userViewModel.refreshTokenPreferencesLiveData.observe(viewLifecycleOwner) { refreshToken -> + if (refreshToken != null) { + this.refreshToken = refreshToken + } + } + } + + private fun reissueAccessToken() { + userViewModel.getNewAccessToken(refreshToken) + userViewModel.reissueResponse.observe(viewLifecycleOwner) { response -> + if (response != null) { + when (response.code()) { + 200 -> { + CustomSnackBar.make( + requireView(), + R.drawable.snackbar_error_16dp, + getString(R.string.snack_bar_info_edit_failure), + ).show() + userViewModel.setAccessToken(response.body()?.accessToken) + } + 401 -> { + CustomSnackBar.make( + requireView(), + R.drawable.snackbar_error_16dp, + getString(R.string.snack_bar_refresh_expire), + ).show() + findNavController().navigate(R.id.action_petEditFragment_to_loginFragment) + } + } + } + } + } + + private fun initObserves() { petEditViewModel.dogProfile.observe(viewLifecycleOwner) { uri -> if (uri != null) { Glide.with(this@PetEditFragment) @@ -116,34 +161,11 @@ class PetEditFragment : Fragment(), PhotoMenuListener, DateSubmitListener { getCurrentDate(), null, ) - petEditViewModel.postDogWeight(currentAccessToken, dogPostRequest) + petEditViewModel.postDogWeight(accessToken, dogPostRequest) } 401 -> { - lifecycleScope.launch { - val refreshToken = userPreferences.getRefreshToken() - if (refreshToken.isNotEmpty()) { - val response = loginRepository.getNewAccessToken(refreshToken) - if (response != null) { - when (response.code()) { - 200 -> { - CustomSnackBar.make( - requireView(), - R.drawable.snackbar_error_16dp, - getString(R.string.snack_bar_info_edit_failure), - ).show() - userPreferences.setAccessToken(response.body()?.accessToken!!) - } - 401 -> { - CustomSnackBar.make( - requireView(), - R.drawable.snackbar_error_16dp, - getString(R.string.snack_bar_refresh_expire), - ).show() - findNavController().navigate(R.id.action_petEditFragment_to_loginFragment) - } - } - } - } + if (refreshToken.isNotEmpty()) { + reissueAccessToken() } } else -> { @@ -166,36 +188,12 @@ class PetEditFragment : Fragment(), PhotoMenuListener, DateSubmitListener { dogInfo.dogId, binding.edittextPeteditWeight.text.toString().toDouble(), getCurrentDate(), - currentAccessToken, + accessToken, ) } 401 -> { - // refresh reissue 호출 - lifecycleScope.launch { - val refreshToken = userPreferences.getRefreshToken() - if (refreshToken.isNotEmpty()) { - val response = loginRepository.getNewAccessToken(refreshToken) - if (response != null) { - when (response.code()) { - 200 -> { - CustomSnackBar.make( - requireView(), - R.drawable.snackbar_error_16dp, - getString(R.string.snack_bar_info_edit_failure), - ).show() - userPreferences.setAccessToken(response.body()?.accessToken!!) - } - 401 -> { - CustomSnackBar.make( - requireView(), - R.drawable.snackbar_error_16dp, - getString(R.string.snack_bar_refresh_expire), - ).show() - findNavController().navigate(R.id.action_petEditFragment_to_loginFragment) - } - } - } - } + if (refreshToken.isNotEmpty()) { + reissueAccessToken() } } else -> { @@ -228,31 +226,8 @@ class PetEditFragment : Fragment(), PhotoMenuListener, DateSubmitListener { findNavController().popBackStack() } 401 -> { - lifecycleScope.launch { - val refreshToken = userPreferences.getRefreshToken() - if (refreshToken.isNotEmpty()) { - val response = loginRepository.getNewAccessToken(refreshToken) - if (response != null) { - when (response.code()) { - 200 -> { - CustomSnackBar.make( - requireView(), - R.drawable.snackbar_error_16dp, - getString(R.string.snack_bar_info_edit_failure), - ).show() - userPreferences.setAccessToken(response.body()?.accessToken!!) - } - 401 -> { - CustomSnackBar.make( - requireView(), - R.drawable.snackbar_error_16dp, - getString(R.string.snack_bar_refresh_expire), - ).show() - findNavController().navigate(R.id.action_petEditFragment_to_loginFragment) - } - } - } - } + if (refreshToken.isNotEmpty()) { + reissueAccessToken() } } else -> { @@ -277,118 +252,129 @@ class PetEditFragment : Fragment(), PhotoMenuListener, DateSubmitListener { binding.edittextPeteditType.setText(dogType) } } + } - binding.run { - editTextWatcher(edittextPeteditName, edittextPeteditName, "이름을 입력해주세요") - editTextWatcher(edittextPeteditType, edittextPeteditType, "품종을 입력해주세요") - editTextWatcher(edittextPeteditSelectBirthday, edittextPeteditSelectBirthday, "날짜를 선택해주세요") - editTextWatcher(edittextPeteditWeight, viewPeteditWeight, "") - - cardviewPeteditImage.setOnClickListener { - val modalBottomSheet = PhotoSelectBottomSheetFragment() - modalBottomSheet.setPhotoMenuListener(this@PetEditFragment) - modalBottomSheet.setStyle(DialogFragment.STYLE_NORMAL, R.style.RoundCornerPhotoDialogTheme) - modalBottomSheet.show(mainActivity.supportFragmentManager, modalBottomSheet.tag) - } + private fun initViews() { + editTextWatcher(binding.edittextPeteditName, binding.edittextPeteditName, "이름을 입력해주세요") + editTextWatcher(binding.edittextPeteditType, binding.edittextPeteditType, "품종을 입력해주세요") + editTextWatcher(binding.edittextPeteditSelectBirthday, binding.edittextPeteditSelectBirthday, "날짜를 선택해주세요") + editTextWatcher(binding.edittextPeteditWeight, binding.viewPeteditWeight, "") + + binding.cardviewPeteditImage.setOnClickListener { + val modalBottomSheet = PhotoSelectBottomSheetFragment() + modalBottomSheet.setPhotoMenuListener(this@PetEditFragment) + modalBottomSheet.setStyle(DialogFragment.STYLE_NORMAL, R.style.RoundCornerPhotoDialogTheme) + modalBottomSheet.show(requireActivity().supportFragmentManager, modalBottomSheet.tag) + } - edittextPeteditType.setOnClickListener { - findNavController().navigate(R.id.action_petEditFragment_to_dogVarietySearchFragment) - } + binding.edittextPeteditType.setOnClickListener { + findNavController().navigate(R.id.action_petEditFragment_to_dogVarietySearchFragment) + } - checkboxPeteditNeuterStatus.setOnCheckedChangeListener { buttonView, isChecked -> - isCbxChecked = isChecked - } + binding.checkboxPeteditNeuterStatus.setOnCheckedChangeListener { buttonView, isChecked -> + isCbxChecked = isChecked + } - textviewPeteditNeuterStatus.setOnClickListener { - checkboxPeteditNeuterStatus.isChecked = !isCbxChecked - } + binding.textviewPeteditNeuterStatus.setOnClickListener { + binding.checkboxPeteditNeuterStatus.isChecked = !isCbxChecked + } - edittextPeteditSelectBirthday.setOnClickListener { - val birthdayBottomSheet = - BirthdayBottomSheetFragment( - binding.root, - petEditViewModel.dogBirth.value, - ) - birthdayBottomSheet.setDateSubmitListener(this@PetEditFragment) - birthdayBottomSheet.setStyle(DialogFragment.STYLE_NORMAL, R.style.RoundCornerBirthdayDialogTheme) - birthdayBottomSheet.show(requireActivity().supportFragmentManager, birthdayBottomSheet.tag) - } + binding.edittextPeteditSelectBirthday.setOnClickListener { + val birthdayBottomSheet = + BirthdayBottomSheetFragment( + binding.root, + petEditViewModel.dogBirth.value, + ) + birthdayBottomSheet.setDateSubmitListener(this@PetEditFragment) + birthdayBottomSheet.setStyle(DialogFragment.STYLE_NORMAL, R.style.RoundCornerBirthdayDialogTheme) + birthdayBottomSheet.show(requireActivity().supportFragmentManager, birthdayBottomSheet.tag) + } - buttonPeteditCancel.setOnClickListener { - findNavController().popBackStack() - } + binding.buttonPeteditCancel.setOnClickListener { + findNavController().popBackStack() + } - buttonPeteditSubmit.setOnClickListener { - if (edittextPeteditName.text.isEmpty()) { - return@setOnClickListener - } - if (edittextPeteditType.text.isEmpty()) { - return@setOnClickListener - } - if (edittextPeteditSelectBirthday.text.isEmpty()) { - return@setOnClickListener - } - if (edittextPeteditWeight.text.isEmpty()) { - return@setOnClickListener - } + binding.buttonPeteditSubmit.setOnClickListener { + if (binding.edittextPeteditName.text.isEmpty()) { + return@setOnClickListener + } + if (binding.edittextPeteditType.text.isEmpty()) { + return@setOnClickListener + } + if (binding.edittextPeteditSelectBirthday.text.isEmpty()) { + return@setOnClickListener + } + if (binding.edittextPeteditWeight.text.isEmpty()) { + return@setOnClickListener + } - val dogName = edittextPeteditName.text.toString() - val dogType = edittextPeteditType.text.toString() - val dogGender = getCheckedGender(binding.root, chipgroupPeteditGroupGender.checkedChipId) - val dogBirth = petEditViewModel.dogBirth.value!! - val dogWeight = edittextPeteditWeight.text.toString().toDouble() - val dogBack = bodySizeCheck(edittextPeteditBackLength.text.toString()) - val dogNeck = bodySizeCheck(edittextPeteditNeckCircumference.text.toString()) - val dogChest = bodySizeCheck(edittextPeteditChestCircumference.text.toString()) - val dog = - Dog( - dogName, - dogType, - dogGender, - dogBirth, - isCbxChecked, - dogWeight, - dogBack, - dogNeck, - dogChest, - ) - - val json = Gson().toJson(dog) - val requestBody: RequestBody = json.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) - if (isImageUpdated) { - // 새 이미지 등록된 경우 - val filePart: MultipartBody.Part = createMultipartBody(mainActivity, petEditViewModel.dogProfile.value) - petEditViewModel.putDogInfo(dogInfo.dogId, currentAccessToken, filePart, requestBody) - } else { - // 기존 이미지인 경우 - lifecycleScope.launch { - val filePart: MultipartBody.Part = createMultipartFromUrl(dogInfo.imageUrl) - petEditViewModel.putDogInfo(dogInfo.dogId, currentAccessToken, filePart, requestBody) - } - } + if (isImageUpdated) { // 새 이미지 등록 + getPreSignedURL(petEditViewModel.dogProfile.value!!) + } else { // 기존 이미지 + putDogInfo(dogInfo.imageUrl) } } + } - return binding.root + private fun getPreSignedURL(uri: Uri) { + imageFile = convertUriToFile(requireContext(), uri) + filePath = "$PARENT_FOLDER_PATH$DOG_FOLDER_PATH${imageFile.name}" + awsS3ViewModel.getPreSignedUrl(accessToken, filePath) + awsS3ViewModel.preSignedUrl.observe(viewLifecycleOwner) { response -> + if (response != null) { + val requestBody = imageFile.asRequestBody("image/*".toMediaTypeOrNull()) + uploadImage(response.preSignedUrl, requestBody) + } + } } - private fun getAccessToken() { - lifecycleScope.launch { - userPreferences.accessToken.collectLatest { accessToken -> - if (accessToken != null) { - currentAccessToken = accessToken - } + private fun uploadImage( + preSignedURL: String, + requestBody: RequestBody, + ) { + awsS3ViewModel.uploadImageToS3(preSignedURL, requestBody) + awsS3ViewModel.uploadImageResponse.observe(viewLifecycleOwner) { response -> + if (response == 200) { + val imageURL = BuildConfig.AWS_S3_BASE_URL + filePath + putDogInfo(imageURL) } } } + private fun putDogInfo(imageURL: String?) { + val dogName = binding.edittextPeteditName.text.toString() + val dogType = binding.edittextPeteditType.text.toString() + val dogGender = getCheckedGender(binding.root, binding.chipgroupPeteditGroupGender.checkedChipId) + val dogBirth = petEditViewModel.dogBirth.value!! + val dogCastrate = binding.checkboxPeteditNeuterStatus.isChecked + val dogWeight = binding.edittextPeteditWeight.text.toString().toDouble() + val dogBack = bodySizeCheck(binding.edittextPeteditBackLength.text.toString()) + val dogNeck = bodySizeCheck(binding.edittextPeteditNeckCircumference.text.toString()) + val dogChest = bodySizeCheck(binding.edittextPeteditChestCircumference.text.toString()) + val dogPutRequest = + DogPutRequest( + dogName, + dogType, + dogGender, + dogBirth, + dogCastrate, + dogWeight, + dogBack, + dogNeck, + dogChest, + imageURL, + ) + + petEditViewModel.putDogInfo(dogInfo.dogId, accessToken, dogPutRequest) + } + private fun editTextWatcher( editText: EditText, targetView: View, hint: String, ) { editText.addTextChangedListener { - editText.doAfterTextChanged { editable -> + editText.doAfterTextChanged { updateEditTextStyle(editText, targetView, hint) } } @@ -445,25 +431,6 @@ class PetEditFragment : Fragment(), PhotoMenuListener, DateSubmitListener { } } - private suspend fun createMultipartFromUrl(url: String?): MultipartBody.Part { - return withContext(Dispatchers.IO) { - if (url.isNullOrEmpty()) { - val emptyBody = "".toRequestBody("multipart/form-data".toMediaTypeOrNull()) - MultipartBody.Part.createFormData("file", "", emptyBody) - } else { - val inputStream = URL(url).openStream() - val file = File(requireContext().cacheDir, "url_image.jpg") - inputStream.use { input -> - file.outputStream().use { output -> - input.copyTo(output) - } - } - val requestFile = file.asRequestBody("multipart/form-data".toMediaTypeOrNull()) - MultipartBody.Part.createFormData("file", file.name, requestFile) - } - } - } - override fun onUriPassed(uri: Uri) { petEditViewModel.setDogProfile(uri) isImageUpdated = true diff --git a/app/src/main/java/com/project/meongcare/info/view/PetInfoFragment.kt b/app/src/main/java/com/project/meongcare/info/view/PetInfoFragment.kt index 89c4e64aa..a980fdf64 100644 --- a/app/src/main/java/com/project/meongcare/info/view/PetInfoFragment.kt +++ b/app/src/main/java/com/project/meongcare/info/view/PetInfoFragment.kt @@ -15,8 +15,8 @@ import com.project.meongcare.info.model.entities.GetDogInfoResponse import com.project.meongcare.info.viewmodel.ProfileViewModel import com.project.meongcare.login.model.data.local.UserPreferences import com.project.meongcare.login.model.data.repository.LoginRepository -import com.project.meongcare.onboarding.view.Gender -import com.project.meongcare.onboarding.view.dateFormat +import com.project.meongcare.onboarding.model.entities.Gender +import com.project.meongcare.onboarding.util.DogAddOnBoardingDateUtils.dateFormat import com.project.meongcare.snackbar.view.CustomSnackBar import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest diff --git a/app/src/main/java/com/project/meongcare/info/view/ProfileFragment.kt b/app/src/main/java/com/project/meongcare/info/view/ProfileFragment.kt index aa7529150..210fe88e6 100644 --- a/app/src/main/java/com/project/meongcare/info/view/ProfileFragment.kt +++ b/app/src/main/java/com/project/meongcare/info/view/ProfileFragment.kt @@ -10,7 +10,6 @@ import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import com.bumptech.glide.Glide @@ -19,44 +18,39 @@ import com.google.android.gms.auth.api.signin.GoogleSignInOptions import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.kakao.sdk.user.UserApiClient import com.navercorp.nid.NaverIdLoginSDK -import com.project.meongcare.MainActivity +import com.project.meongcare.BuildConfig import com.project.meongcare.R +import com.project.meongcare.aws.util.AWSS3ImageUtils.convertUriToFile +import com.project.meongcare.aws.util.MEMBER_FOLDER_PATH +import com.project.meongcare.aws.util.PARENT_FOLDER_PATH +import com.project.meongcare.aws.viewmodel.AWSS3ViewModel import com.project.meongcare.databinding.FragmentProfileBinding import com.project.meongcare.databinding.LayoutLogoutDialogBinding import com.project.meongcare.databinding.LayoutMedicalRecordDialogBinding +import com.project.meongcare.info.model.entities.ProfilePatchRequest import com.project.meongcare.info.viewmodel.ProfileViewModel -import com.project.meongcare.login.model.data.local.UserPreferences -import com.project.meongcare.login.model.data.repository.LoginRepository +import com.project.meongcare.medicalRecord.viewmodel.UserViewModel import com.project.meongcare.onboarding.model.data.local.PhotoMenuListener -import com.project.meongcare.onboarding.view.createMultipartBody import com.project.meongcare.snackbar.view.CustomSnackBar import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch -import javax.inject.Inject +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import java.io.File @AndroidEntryPoint class ProfileFragment : Fragment(), PhotoMenuListener { private lateinit var binding: FragmentProfileBinding - private lateinit var mainActivity: MainActivity + private lateinit var imageFile: File + private lateinit var filePath: String private val profileViewModel: ProfileViewModel by viewModels() - private val logoutCoroutineJob = Job() - private lateinit var profileUri: Uri - private lateinit var currentAccessToken: String + private val awsS3ViewModel: AWSS3ViewModel by viewModels() + private val userViewModel: UserViewModel by viewModels() - @Inject - lateinit var loginRepository: LoginRepository - - @Inject - lateinit var userPreferences: UserPreferences - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - getAccessToken() - } + private var currentAccessToken = "" + private var currentRefreshToken = "" + private var currentProvider = "" override fun onCreateView( inflater: LayoutInflater, @@ -64,10 +58,18 @@ class ProfileFragment : Fragment(), PhotoMenuListener { savedInstanceState: Bundle?, ): View { binding = FragmentProfileBinding.inflate(inflater) - mainActivity = activity as MainActivity + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) - profileViewModel.getUserProfile(currentAccessToken) - profileViewModel.getDogList(currentAccessToken) + getAccessToken() + getProvider() + getEmail() profileViewModel.userProfile.observe(viewLifecycleOwner) { profileResponse -> if (profileResponse != null) { @@ -78,30 +80,11 @@ class ProfileFragment : Fragment(), PhotoMenuListener { .load(profileResponse.body()?.imageUrl) .error(R.drawable.profile_default_image) .into(imageviewProfileImage) - textviewProfileEmail.text = profileResponse.body()?.email } } 401 -> { - lifecycleScope.launch { - val refreshToken = userPreferences.getRefreshToken() - if (refreshToken.isNotEmpty()) { - val response = loginRepository.getNewAccessToken(refreshToken) - if (response != null) { - when (response.code()) { - 200 -> { - userPreferences.setAccessToken(response.body()?.accessToken!!) - } - 401 -> { - CustomSnackBar.make( - requireView(), - R.drawable.snackbar_error_16dp, - getString(R.string.snack_bar_refresh_expire), - ).show() - findNavController().navigate(R.id.action_profileFragment_to_loginFragment) - } - } - } - } + if (currentRefreshToken.isNotEmpty()) { + reissueAccessToken() } } else -> { @@ -125,26 +108,8 @@ class ProfileFragment : Fragment(), PhotoMenuListener { adapter.updateDogList(dogListResponse.body()?.dogs!!) } 401 -> { - lifecycleScope.launch { - val refreshToken = userPreferences.getRefreshToken() - if (refreshToken.isNotEmpty()) { - val response = loginRepository.getNewAccessToken(refreshToken) - if (response != null) { - when (response.code()) { - 200 -> { - userPreferences.setAccessToken(response.body()?.accessToken!!) - } - 401 -> { - CustomSnackBar.make( - requireView(), - R.drawable.snackbar_error_16dp, - getString(R.string.snack_bar_refresh_expire), - ).show() - findNavController().navigate(R.id.action_profileFragment_to_loginFragment) - } - } - } - } + if (currentRefreshToken.isNotEmpty()) { + reissueAccessToken() } } else -> { @@ -164,14 +129,10 @@ class ProfileFragment : Fragment(), PhotoMenuListener { profileViewModel.logoutResponse.observe(viewLifecycleOwner) { response -> if (response != null) { - lifecycleScope.launch(logoutCoroutineJob) { - userPreferences.provider.collect { provider -> - when (provider) { - "kakao" -> kakaoLogout() - "naver" -> naverLogout() - "google" -> googleLogout() - } - } + when (currentProvider) { + "kakao" -> kakaoLogout() + "naver" -> naverLogout() + "google" -> googleLogout() } } else { CustomSnackBar.make( @@ -184,22 +145,13 @@ class ProfileFragment : Fragment(), PhotoMenuListener { profileViewModel.patchProfileResponse.observe(viewLifecycleOwner) { response -> if (response == 200) { - binding.run { - Glide.with(this@ProfileFragment) - .load(profileUri) - .into(imageviewProfileImage) - } + profileViewModel.getUserProfile(currentAccessToken) CustomSnackBar.make( requireView(), R.drawable.snackbar_success_16dp, getString(R.string.snack_bar_profile_update_complete), ).show() } else { - binding.run { - Glide.with(this@ProfileFragment) - .load(R.drawable.profile_default_image) - .into(imageviewProfileImage) - } CustomSnackBar.make( requireView(), R.drawable.snackbar_error_16dp, @@ -208,50 +160,137 @@ class ProfileFragment : Fragment(), PhotoMenuListener { } } - binding.run { - imagebuttonProfileBack.setOnClickListener { - findNavController().popBackStack() + initPetListRecyclerView() + initProfileImageView() + initBackButton() + initShareButton() + initSettingButton() + initLogoutButton() + } + + override fun onUriPassed(uri: Uri) { + imageFile = convertUriToFile(requireContext(), uri) + filePath = "$PARENT_FOLDER_PATH$MEMBER_FOLDER_PATH${imageFile.name}" + getPreSignedURL() + } + + private fun getPreSignedURL() { + awsS3ViewModel.getPreSignedUrl(currentAccessToken, filePath) + awsS3ViewModel.preSignedUrl.observe(viewLifecycleOwner) { response -> + if (response != null) { + val requestBody = imageFile.asRequestBody("image/*".toMediaTypeOrNull()) + uploadImage(response.preSignedUrl, requestBody) } + } + } - imageviewProfileImage.setOnClickListener { - val modalBottomSheet = UserProfileSelectBottomSheetFragment() - modalBottomSheet.setPhotoMenuListener(this@ProfileFragment) - modalBottomSheet.setStyle(DialogFragment.STYLE_NORMAL, R.style.RoundCornerPhotoDialogTheme) - modalBottomSheet.show(mainActivity.supportFragmentManager, modalBottomSheet.tag) + private fun uploadImage( + preSignedURL: String, + requestBody: RequestBody, + ) { + awsS3ViewModel.uploadImageToS3(preSignedURL, requestBody) + awsS3ViewModel.uploadImageResponse.observe(viewLifecycleOwner) { response -> + if (response == 200) { + val profilePatchRequest = ProfilePatchRequest(BuildConfig.AWS_S3_BASE_URL + filePath) + profileViewModel.patchProfileImage(currentAccessToken, profilePatchRequest) } + } + } - recyclerviewProfilePetList.run { - adapter = ProfileDogAdapter(layoutInflater, context) - layoutManager = LinearLayoutManager(mainActivity, LinearLayoutManager.HORIZONTAL, false) + private fun reissueAccessToken() { + userViewModel.getNewAccessToken(currentRefreshToken) + userViewModel.reissueResponse.observe(viewLifecycleOwner) { response -> + if (response != null) { + when (response.code()) { + 200 -> { + userViewModel.setAccessToken(response.body()?.accessToken) + } + 401 -> { + CustomSnackBar.make( + requireView(), + R.drawable.snackbar_error_16dp, + getString(R.string.snack_bar_refresh_expire), + ).show() + findNavController().navigate(R.id.action_profileFragment_to_loginFragment) + } + } } + } + } - buttonProfileShare.setOnClickListener { - showUpdateDialog() + private fun getAccessToken() { + userViewModel.accessTokenPreferencesLiveData.observe(viewLifecycleOwner) { accessToken -> + if (accessToken != null) { + currentAccessToken = accessToken + getRefreshToken() } + } + } - buttonProfileSetting.setOnClickListener { - val bundle = Bundle() - bundle.putBoolean("pushAgreement", profileViewModel.userProfile.value?.body()?.pushAgreement!!) - findNavController().navigate(R.id.action_profileFragment_to_settingFragment, bundle) + private fun getRefreshToken() { + userViewModel.refreshTokenPreferencesLiveData.observe(viewLifecycleOwner) { refreshToken -> + if (refreshToken != null) { + currentRefreshToken = refreshToken + profileViewModel.getUserProfile(currentAccessToken) + profileViewModel.getDogList(currentAccessToken) } + } + } - buttonProfileLogout.setOnClickListener { - showLogoutDialog() + private fun getProvider() { + userViewModel.providerPreferencesLiveData.observe(viewLifecycleOwner) { provider -> + if (provider != null) { + currentProvider = provider } } + } - return binding.root + private fun getEmail() { + userViewModel.emailPreferencesLiveData.observe(viewLifecycleOwner) { email -> + binding.textviewProfileEmail.text = email ?: "" + } } - override fun onDestroyView() { - super.onDestroyView() - logoutCoroutineJob.cancel() + private fun initPetListRecyclerView() { + binding.recyclerviewProfilePetList.run { + adapter = ProfileDogAdapter(layoutInflater, context) + layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + } } - override fun onUriPassed(uri: Uri) { - profileUri = uri - val multipartBody = createMultipartBody(requireContext(), uri) - profileViewModel.patchProfileImage(currentAccessToken, multipartBody) + private fun initProfileImageView() { + binding.imageviewProfileImage.setOnClickListener { + val modalBottomSheet = UserProfileSelectBottomSheetFragment() + modalBottomSheet.setPhotoMenuListener(this@ProfileFragment) + modalBottomSheet.setStyle(DialogFragment.STYLE_NORMAL, R.style.RoundCornerPhotoDialogTheme) + modalBottomSheet.show(requireActivity().supportFragmentManager, modalBottomSheet.tag) + } + } + + private fun initBackButton() { + binding.imagebuttonProfileBack.setOnClickListener { + findNavController().popBackStack() + } + } + + private fun initShareButton() { + binding.buttonProfileShare.setOnClickListener { + showUpdateDialog() + } + } + + private fun initSettingButton() { + binding.buttonProfileSetting.setOnClickListener { + val bundle = Bundle() + bundle.putBoolean("pushAgreement", profileViewModel.userProfile.value?.body()?.pushAgreement!!) + findNavController().navigate(R.id.action_profileFragment_to_settingFragment, bundle) + } + } + + private fun initLogoutButton() { + binding.buttonProfileLogout.setOnClickListener { + showLogoutDialog() + } } private fun showLogoutDialog() { @@ -264,13 +303,8 @@ class ProfileFragment : Fragment(), PhotoMenuListener { dialogBinding.run { buttonLogoutDialogLogout.setOnClickListener { - lifecycleScope.launch(logoutCoroutineJob) { - val refreshToken = userPreferences.getRefreshToken() - if (refreshToken != null) { - dialog.dismiss() - profileViewModel.logoutUser(refreshToken) - } - } + dialog.dismiss() + profileViewModel.logoutUser(currentRefreshToken) } buttonLogoutDialogCancel.setOnClickListener { @@ -296,24 +330,14 @@ class ProfileFragment : Fragment(), PhotoMenuListener { dialog.show() } - private fun getAccessToken() { - lifecycleScope.launch { - userPreferences.accessToken.collectLatest { accessToken -> - if (accessToken != null) { - currentAccessToken = accessToken - } - } - } - } - private fun kakaoLogout() { UserApiClient.instance.logout { error -> if (error != null) { Log.e("Logout-kakao", "로그아웃 실패, SDK에서 토큰 삭제됨\n $error") } else { Log.i("Logout-kakao", "로그아웃 성공, SDK에서 토큰 삭제됨") - userPreferences.setProvider(null) - userPreferences.setAccessToken(null) + userViewModel.setProvider(null) + userViewModel.setAccessToken(null) findNavController().navigate(R.id.action_profileFragment_to_loginFragment) } } @@ -321,8 +345,8 @@ class ProfileFragment : Fragment(), PhotoMenuListener { private fun naverLogout() { NaverIdLoginSDK.logout() - userPreferences.setProvider(null) - userPreferences.setAccessToken(null) + userViewModel.setProvider(null) + userViewModel.setAccessToken(null) findNavController().navigate(R.id.action_profileFragment_to_loginFragment) } @@ -340,8 +364,8 @@ class ProfileFragment : Fragment(), PhotoMenuListener { .addOnCompleteListener { task -> if (task.isSuccessful) { Log.d("Logout-google", "로그아웃 성공") - userPreferences.setProvider(null) - userPreferences.setAccessToken(null) + userViewModel.setProvider(null) + userViewModel.setAccessToken(null) findNavController().navigate(R.id.action_profileFragment_to_loginFragment) } else { Log.e("Logout-google", "로그아웃 실패") diff --git a/app/src/main/java/com/project/meongcare/info/view/SettingFragment.kt b/app/src/main/java/com/project/meongcare/info/view/SettingFragment.kt index 79b52a1c5..320097d56 100644 --- a/app/src/main/java/com/project/meongcare/info/view/SettingFragment.kt +++ b/app/src/main/java/com/project/meongcare/info/view/SettingFragment.kt @@ -1,15 +1,10 @@ package com.project.meongcare.info.view -import android.content.Context import android.os.Bundle import android.util.Log -import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels diff --git a/app/src/main/java/com/project/meongcare/info/viewmodel/ProfileViewModel.kt b/app/src/main/java/com/project/meongcare/info/viewmodel/ProfileViewModel.kt index 98dbf9e54..13206595b 100644 --- a/app/src/main/java/com/project/meongcare/info/viewmodel/ProfileViewModel.kt +++ b/app/src/main/java/com/project/meongcare/info/viewmodel/ProfileViewModel.kt @@ -8,12 +8,12 @@ import androidx.lifecycle.viewModelScope import com.project.meongcare.home.model.entities.GetDogListResponse import com.project.meongcare.home.model.entities.GetUserProfileResponse import com.project.meongcare.info.model.data.repository.ProfileRepository +import com.project.meongcare.info.model.entities.DogPutRequest import com.project.meongcare.info.model.entities.GetDogInfoResponse +import com.project.meongcare.info.model.entities.ProfilePatchRequest import com.project.meongcare.weight.model.entities.WeightPostRequest import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import okhttp3.MultipartBody -import okhttp3.RequestBody import retrofit2.Response import javax.inject.Inject @@ -114,11 +114,10 @@ class ProfileViewModel fun putDogInfo( dogId: Long, accessToken: String, - file: MultipartBody.Part, - dto: RequestBody, + dogPutRequest: DogPutRequest, ) { viewModelScope.launch { - _dogPutResponse.value = profileRepository.putDogInfo(dogId, accessToken, file, dto) + _dogPutResponse.value = profileRepository.putDogInfo(dogId, accessToken, dogPutRequest) } } @@ -165,10 +164,10 @@ class ProfileViewModel fun patchProfileImage( accessToken: String, - file: MultipartBody.Part, + profilePatchRequest: ProfilePatchRequest, ) { viewModelScope.launch { - _patchProfileResponse.value = profileRepository.patchProfileImage(accessToken, file) + _patchProfileResponse.value = profileRepository.patchProfileImage(accessToken, profilePatchRequest) } } } diff --git a/app/src/main/java/com/project/meongcare/login/view/LoginFragment.kt b/app/src/main/java/com/project/meongcare/login/view/LoginFragment.kt index c177436f9..8d68a1a9b 100644 --- a/app/src/main/java/com/project/meongcare/login/view/LoginFragment.kt +++ b/app/src/main/java/com/project/meongcare/login/view/LoginFragment.kt @@ -8,7 +8,6 @@ import android.view.ViewGroup import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignInAccount @@ -26,23 +25,20 @@ import com.navercorp.nid.oauth.OAuthLoginCallback import com.navercorp.nid.profile.NidProfileCallback import com.navercorp.nid.profile.data.NidProfileResponse import com.project.meongcare.BuildConfig -import com.project.meongcare.MainActivity import com.project.meongcare.R import com.project.meongcare.databinding.FragmentLoginBinding -import com.project.meongcare.login.model.data.local.UserPreferences import com.project.meongcare.login.model.data.repository.FirebaseCloudMessagingService import com.project.meongcare.login.model.entities.LoginRequest import com.project.meongcare.login.viewmodel.LoginViewModel +import com.project.meongcare.medicalRecord.viewmodel.UserViewModel import com.project.meongcare.snackbar.view.CustomSnackBar import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import javax.inject.Inject @AndroidEntryPoint class LoginFragment : Fragment() { - lateinit var fragmentLoginBinding: FragmentLoginBinding - lateinit var mainActivity: MainActivity + private lateinit var binding: FragmentLoginBinding + private lateinit var provider: String private val googleSignInClient: GoogleSignInClient by lazy { getGoogleClient() } val googleAuthLauncher = @@ -51,19 +47,30 @@ class LoginFragment : Fragment() { getGoogleResult(task) } private val loginViewModel: LoginViewModel by viewModels() + private val userViewModel: UserViewModel by viewModels() private val myFirebaseMessagingService = FirebaseCloudMessagingService() - @Inject - lateinit var userPreferences: UserPreferences - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View? { - fragmentLoginBinding = FragmentLoginBinding.inflate(inflater) - mainActivity = activity as MainActivity + binding = FragmentLoginBinding.inflate(inflater) + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + getProvider() + addListeners() + loginResponseProcess() + } + + private fun loginResponseProcess() { loginViewModel.loginResponse.observe(viewLifecycleOwner) { loginResponse -> if (loginResponse != null) { when (loginResponse.code()) { @@ -71,9 +78,9 @@ class LoginFragment : Fragment() { // 로그인 성공 if (loginResponse.body() != null) { Log.e("isFirstLogin", loginResponse.body()?.isFirstLogin.toString()) - userPreferences.setAccessToken(loginResponse.body()?.accessToken) - userPreferences.setRefreshToken(loginResponse.body()?.refreshToken) - userPreferences.setIsFirstLogin(loginResponse.body()?.isFirstLogin) + userViewModel.setAccessToken(loginResponse.body()?.accessToken) + userViewModel.setRefreshToken(loginResponse.body()?.refreshToken) + userViewModel.setIsFirstLogin(loginResponse.body()?.isFirstLogin) when (loginResponse.body()?.isFirstLogin) { true -> { val bundle = Bundle() @@ -92,13 +99,10 @@ class LoginFragment : Fragment() { R.drawable.snackbar_error_16dp, getString(R.string.snack_bar_login_failure_deleted), ) - lifecycleScope.launch { - val provider = userPreferences.getProvider() - when (provider) { - "kakao" -> deleteKakaoAccount() - "naver" -> deleteNaverAccount() - "google" -> deleteGoogleAccount() - } + when (provider) { + "kakao" -> deleteKakaoAccount() + "naver" -> deleteNaverAccount() + "google" -> deleteGoogleAccount() } } else -> { @@ -107,25 +111,32 @@ class LoginFragment : Fragment() { R.drawable.snackbar_error_16dp, getString(R.string.snack_bar_login_failure), ) - Log.d("Login", "통신 실패") } } } } + } - fragmentLoginBinding.run { - buttonKakaoLogin.setOnClickListener { - kakaoLogin() - } - buttonNaverLogin.setOnClickListener { - naverLogin() - } - buttonGoogleLogin.setOnClickListener { - googleLogin() + private fun getProvider() { + userViewModel.providerPreferencesLiveData.observe(viewLifecycleOwner) { provider -> + if (provider != null) { + this.provider = provider } } + } + + private fun addListeners() { + binding.buttonKakaoLogin.setOnClickListener { + kakaoLogin() + } + + binding.buttonNaverLogin.setOnClickListener { + naverLogin() + } - return fragmentLoginBinding.root + binding.buttonGoogleLogin.setOnClickListener { + googleLogin() + } } private fun kakaoLogin() { @@ -141,8 +152,8 @@ class LoginFragment : Fragment() { } // 카카오톡이 설치되어 있으면 카카오톡으로 로그인, 아니면 카카오계정으로 로그인 - if (UserApiClient.instance.isKakaoTalkLoginAvailable(mainActivity)) { - UserApiClient.instance.loginWithKakaoTalk(mainActivity) { token, error -> + if (UserApiClient.instance.isKakaoTalkLoginAvailable(requireContext())) { + UserApiClient.instance.loginWithKakaoTalk(requireContext()) { token, error -> if (error != null) { Log.e("Login-kakao", "카카오톡으로 로그인 실패", error) @@ -153,14 +164,14 @@ class LoginFragment : Fragment() { } // 카카오톡에 연결된 카카오계정이 없는 경우, 카카오계정으로 로그인 시도 - UserApiClient.instance.loginWithKakaoAccount(mainActivity, callback = callback) + UserApiClient.instance.loginWithKakaoAccount(requireContext(), callback = callback) } else if (token != null) { Log.i("Login-kakao", "카카오톡으로 로그인 성공") getKakaoLoginInfo() } } } else { - UserApiClient.instance.loginWithKakaoAccount(mainActivity, callback = callback) + UserApiClient.instance.loginWithKakaoAccount(requireContext(), callback = callback) } } @@ -174,8 +185,8 @@ class LoginFragment : Fragment() { val deviceToken = getDeviceToken() // data store에 저장 - userPreferences.setEmail(user.kakaoAccount?.email!!) - userPreferences.setProvider("kakao") + userViewModel.setEmail(user.kakaoAccount?.email!!) + userViewModel.setProvider("kakao") val loginRequest = LoginRequest( @@ -216,8 +227,8 @@ class LoginFragment : Fragment() { val deviceToken = getDeviceToken() // data store에 저장 - userPreferences.setEmail(result.profile?.email!!) - userPreferences.setProvider("naver") + userViewModel.setEmail(result.profile?.email!!) + userViewModel.setProvider("naver") // 서버에 로그인 정보 전송 val loginRequest = @@ -256,7 +267,7 @@ class LoginFragment : Fragment() { NidOAuthLogin().callProfileApi(nidProfileCallback) } } - NaverIdLoginSDK.authenticate(mainActivity, oauthLoginCallback) + NaverIdLoginSDK.authenticate(requireContext(), oauthLoginCallback) } private fun googleLogin() { @@ -273,7 +284,7 @@ class LoginFragment : Fragment() { .requestServerAuthCode(BuildConfig.GOOGLE_CLIENT_ID) .build() - return GoogleSignIn.getClient(mainActivity, googleSignInOptions) + return GoogleSignIn.getClient(requireActivity(), googleSignInOptions) } private fun getGoogleResult(task: Task) { @@ -282,8 +293,8 @@ class LoginFragment : Fragment() { val deviceToken = getDeviceToken() // data store에 저장 - userPreferences.setEmail(account.email!!) - userPreferences.setProvider("google") + userViewModel.setEmail(account.email!!) + userViewModel.setProvider("google") val loginRequest = LoginRequest( diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/data/local/MedicalRecordItemCheckListener.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/data/local/MedicalRecordItemCheckListener.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/data/local/MedicalRecordItemCheckListener.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/data/local/MedicalRecordItemCheckListener.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/data/local/MedicalRecordItemClickListener.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/data/local/MedicalRecordItemClickListener.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/data/local/MedicalRecordItemClickListener.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/data/local/MedicalRecordItemClickListener.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/data/local/OnPictureChangedListener.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/data/local/OnPictureChangedListener.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/data/local/OnPictureChangedListener.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/data/local/OnPictureChangedListener.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/data/remote/MedicalRecordApi.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/data/remote/MedicalRecordApi.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/data/remote/MedicalRecordApi.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/data/remote/MedicalRecordApi.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/data/remote/MedicalRecordRetrofitClient.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/data/remote/MedicalRecordRetrofitClient.kt similarity index 96% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/data/remote/MedicalRecordRetrofitClient.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/data/remote/MedicalRecordRetrofitClient.kt index ede05c344..4e2007284 100644 --- a/app/src/main/java/com/project/meongcare/medicalrecord/model/data/remote/MedicalRecordRetrofitClient.kt +++ b/app/src/main/java/com/project/meongcare/medicalRecord/model/data/remote/MedicalRecordRetrofitClient.kt @@ -1,4 +1,4 @@ -package com.project.meongcare.medicalrecord.model.data.remote +package com.project.meongcare.medicalRecord.model.data.remote import com.project.meongcare.BuildConfig import okhttp3.ResponseBody diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/data/repository/MedicalRecordModule.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/data/repository/MedicalRecordModule.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/data/repository/MedicalRecordModule.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/data/repository/MedicalRecordModule.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/data/repository/MedicalRecordRepository.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/data/repository/MedicalRecordRepository.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/data/repository/MedicalRecordRepository.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/data/repository/MedicalRecordRepository.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/data/repository/MedicalRecordRepositoryImpl.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/data/repository/MedicalRecordRepositoryImpl.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/data/repository/MedicalRecordRepositoryImpl.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/data/repository/MedicalRecordRepositoryImpl.kt index 52eb2cbb8..b87297f4f 100644 --- a/app/src/main/java/com/project/meongcare/medicalrecord/model/data/repository/MedicalRecordRepositoryImpl.kt +++ b/app/src/main/java/com/project/meongcare/medicalRecord/model/data/repository/MedicalRecordRepositoryImpl.kt @@ -2,9 +2,9 @@ package com.project.meongcare.medicalRecord.model.data.repository import android.util.Log import com.project.meongcare.medicalRecord.model.entities.MedicalRecordGet -import com.project.meongcare.medicalRecord.model.data.remote.MedicalRecordRetrofitClient import com.project.meongcare.medicalRecord.model.entities.MedicalRecordGetResponse import com.project.meongcare.medicalRecord.model.entities.RequestMedicalRecord +import com.project.meongcare.medicalRecord.model.data.remote.MedicalRecordRetrofitClient import org.json.JSONObject import retrofit2.Response import javax.inject.Inject diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/entities/MedicalRecord.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/entities/MedicalRecord.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/entities/MedicalRecord.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/entities/MedicalRecord.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/entities/MedicalRecordDto.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/entities/MedicalRecordDto.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/entities/MedicalRecordDto.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/entities/MedicalRecordDto.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/entities/MedicalRecordGet.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/entities/MedicalRecordGet.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/entities/MedicalRecordGet.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/entities/MedicalRecordGet.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/entities/MedicalRecordGetResponse.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/entities/MedicalRecordGetResponse.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/entities/MedicalRecordGetResponse.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/entities/MedicalRecordGetResponse.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/entities/RequestMedicalRecord.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/entities/RequestMedicalRecord.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/entities/RequestMedicalRecord.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/entities/RequestMedicalRecord.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/utils/MedicalRecordDateUtils.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/utils/MedicalRecordDateUtils.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/utils/MedicalRecordDateUtils.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/utils/MedicalRecordDateUtils.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/model/utils/MedicalRecordUtils.kt b/app/src/main/java/com/project/meongcare/medicalRecord/model/utils/MedicalRecordUtils.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/model/utils/MedicalRecordUtils.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/model/utils/MedicalRecordUtils.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/view/MedicalRecordAddFragment.kt b/app/src/main/java/com/project/meongcare/medicalRecord/view/MedicalRecordAddFragment.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/view/MedicalRecordAddFragment.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/view/MedicalRecordAddFragment.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/view/MedicalRecordEditFragment.kt b/app/src/main/java/com/project/meongcare/medicalRecord/view/MedicalRecordEditFragment.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/view/MedicalRecordEditFragment.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/view/MedicalRecordEditFragment.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/view/MedicalRecordEditListAdapter.kt b/app/src/main/java/com/project/meongcare/medicalRecord/view/MedicalRecordEditListAdapter.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/view/MedicalRecordEditListAdapter.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/view/MedicalRecordEditListAdapter.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/view/MedicalRecordFragment.kt b/app/src/main/java/com/project/meongcare/medicalRecord/view/MedicalRecordFragment.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/view/MedicalRecordFragment.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/view/MedicalRecordFragment.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/view/MedicalRecordInfoFragment.kt b/app/src/main/java/com/project/meongcare/medicalRecord/view/MedicalRecordInfoFragment.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/view/MedicalRecordInfoFragment.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/view/MedicalRecordInfoFragment.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/view/MedicalRecordListAdapter.kt b/app/src/main/java/com/project/meongcare/medicalRecord/view/MedicalRecordListAdapter.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/view/MedicalRecordListAdapter.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/view/MedicalRecordListAdapter.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/view/bottomSheet/MedicalRecordDateBottomSheetDialogFragment.kt b/app/src/main/java/com/project/meongcare/medicalRecord/view/bottomSheet/MedicalRecordDateBottomSheetDialogFragment.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/view/bottomSheet/MedicalRecordDateBottomSheetDialogFragment.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/view/bottomSheet/MedicalRecordDateBottomSheetDialogFragment.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/view/bottomSheet/MedicalRecordPictureBottomSheetDialogFragment.kt b/app/src/main/java/com/project/meongcare/medicalRecord/view/bottomSheet/MedicalRecordPictureBottomSheetDialogFragment.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/view/bottomSheet/MedicalRecordPictureBottomSheetDialogFragment.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/view/bottomSheet/MedicalRecordPictureBottomSheetDialogFragment.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/viewmodel/DogViewModel.kt b/app/src/main/java/com/project/meongcare/medicalRecord/viewmodel/DogViewModel.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/viewmodel/DogViewModel.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/viewmodel/DogViewModel.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/viewmodel/MedicalRecordViewModel.kt b/app/src/main/java/com/project/meongcare/medicalRecord/viewmodel/MedicalRecordViewModel.kt similarity index 100% rename from app/src/main/java/com/project/meongcare/medicalrecord/viewmodel/MedicalRecordViewModel.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/viewmodel/MedicalRecordViewModel.kt diff --git a/app/src/main/java/com/project/meongcare/medicalrecord/viewmodel/UserViewModel.kt b/app/src/main/java/com/project/meongcare/medicalRecord/viewmodel/UserViewModel.kt similarity index 78% rename from app/src/main/java/com/project/meongcare/medicalrecord/viewmodel/UserViewModel.kt rename to app/src/main/java/com/project/meongcare/medicalRecord/viewmodel/UserViewModel.kt index f0c2be61b..6c145e4c6 100644 --- a/app/src/main/java/com/project/meongcare/medicalrecord/viewmodel/UserViewModel.kt +++ b/app/src/main/java/com/project/meongcare/medicalRecord/viewmodel/UserViewModel.kt @@ -46,4 +46,22 @@ class UserViewModel userPreferences.editProvider(provider) } } + + fun setRefreshToken(refreshToken: String?) { + viewModelScope.launch { + userPreferences.editRefreshToken(refreshToken) + } + } + + fun setIsFirstLogin(isFirstLogin: Boolean?) { + viewModelScope.launch { + userPreferences.editIsFirstLogin(isFirstLogin) + } + } + + fun setEmail(email: String?) { + viewModelScope.launch { + userPreferences.editEmail(email) + } + } } diff --git a/app/src/main/java/com/project/meongcare/onboarding/model/data/remote/DogAddApi.kt b/app/src/main/java/com/project/meongcare/onboarding/model/data/remote/DogAddApi.kt index 6464f12d8..06ec6aa77 100644 --- a/app/src/main/java/com/project/meongcare/onboarding/model/data/remote/DogAddApi.kt +++ b/app/src/main/java/com/project/meongcare/onboarding/model/data/remote/DogAddApi.kt @@ -1,19 +1,15 @@ package com.project.meongcare.onboarding.model.data.remote -import okhttp3.MultipartBody -import okhttp3.RequestBody +import com.project.meongcare.onboarding.model.entities.DogPostRequest import retrofit2.Response +import retrofit2.http.Body import retrofit2.http.Header -import retrofit2.http.Multipart import retrofit2.http.POST -import retrofit2.http.Part interface DogAddApi { - @Multipart @POST("dog") suspend fun postDogInfo( @Header("AccessToken") accessToken: String, - @Part file: MultipartBody.Part, - @Part("dto") dto: RequestBody, + @Body dogPostRequest: DogPostRequest, ): Response } diff --git a/app/src/main/java/com/project/meongcare/onboarding/model/data/repository/DogAddRepository.kt b/app/src/main/java/com/project/meongcare/onboarding/model/data/repository/DogAddRepository.kt index 61c3a97d0..b6506560a 100644 --- a/app/src/main/java/com/project/meongcare/onboarding/model/data/repository/DogAddRepository.kt +++ b/app/src/main/java/com/project/meongcare/onboarding/model/data/repository/DogAddRepository.kt @@ -1,12 +1,10 @@ package com.project.meongcare.onboarding.model.data.repository -import okhttp3.MultipartBody -import okhttp3.RequestBody +import com.project.meongcare.onboarding.model.entities.DogPostRequest interface DogAddRepository { suspend fun postDogInfo( accessToken: String, - file: MultipartBody.Part, - dto: RequestBody, + dogPostRequest: DogPostRequest, ): Int } diff --git a/app/src/main/java/com/project/meongcare/onboarding/model/data/repository/DogAddRepositoryImpl.kt b/app/src/main/java/com/project/meongcare/onboarding/model/data/repository/DogAddRepositoryImpl.kt index 301eb524d..7894b0e00 100644 --- a/app/src/main/java/com/project/meongcare/onboarding/model/data/repository/DogAddRepositoryImpl.kt +++ b/app/src/main/java/com/project/meongcare/onboarding/model/data/repository/DogAddRepositoryImpl.kt @@ -2,8 +2,7 @@ package com.project.meongcare.onboarding.model.data.repository import android.util.Log import com.project.meongcare.onboarding.model.data.remote.DogAddRetrofitClient -import okhttp3.MultipartBody -import okhttp3.RequestBody +import com.project.meongcare.onboarding.model.entities.DogPostRequest import javax.inject.Inject class DogAddRepositoryImpl @@ -11,11 +10,10 @@ class DogAddRepositoryImpl constructor(private val dogAddRetrofitClient: DogAddRetrofitClient) : DogAddRepository { override suspend fun postDogInfo( accessToken: String, - file: MultipartBody.Part, - dto: RequestBody, + dogPostRequest: DogPostRequest, ): Int { try { - val response = dogAddRetrofitClient.dogAddApi.postDogInfo(accessToken, file, dto) + val response = dogAddRetrofitClient.dogAddApi.postDogInfo(accessToken, dogPostRequest) if (response.isSuccessful) { Log.d("DogAddRepository", "통신 성공(code-${response.code()})") return response.code() diff --git a/app/src/main/java/com/project/meongcare/onboarding/model/entities/Dog.kt b/app/src/main/java/com/project/meongcare/onboarding/model/entities/DogPostRequest.kt similarity index 84% rename from app/src/main/java/com/project/meongcare/onboarding/model/entities/Dog.kt rename to app/src/main/java/com/project/meongcare/onboarding/model/entities/DogPostRequest.kt index c9453783c..dec0e7adf 100644 --- a/app/src/main/java/com/project/meongcare/onboarding/model/entities/Dog.kt +++ b/app/src/main/java/com/project/meongcare/onboarding/model/entities/DogPostRequest.kt @@ -1,6 +1,6 @@ package com.project.meongcare.onboarding.model.entities -data class Dog( +data class DogPostRequest( val name: String, val type: String, val sex: String, @@ -10,4 +10,5 @@ data class Dog( val backRound: Double?, val neckRound: Double?, val chestRound: Double?, + val imageURL: String?, ) diff --git a/app/src/main/java/com/project/meongcare/onboarding/model/entities/Gender.kt b/app/src/main/java/com/project/meongcare/onboarding/model/entities/Gender.kt new file mode 100644 index 000000000..f0ce1672d --- /dev/null +++ b/app/src/main/java/com/project/meongcare/onboarding/model/entities/Gender.kt @@ -0,0 +1,6 @@ +package com.project.meongcare.onboarding.model.entities + +enum class Gender(val korean: String, val english: String) { + MALE("남성", "male"), + FEMALE("여성", "female"), +} diff --git a/app/src/main/java/com/project/meongcare/onboarding/util/DogAddOnBoardingDateUtils.kt b/app/src/main/java/com/project/meongcare/onboarding/util/DogAddOnBoardingDateUtils.kt new file mode 100644 index 000000000..52c420848 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/onboarding/util/DogAddOnBoardingDateUtils.kt @@ -0,0 +1,13 @@ +package com.project.meongcare.onboarding.util + +import java.text.SimpleDateFormat + +object DogAddOnBoardingDateUtils { + fun dateFormat(str: String): String { + val inputDateFormat = SimpleDateFormat("yyyy-MM-dd") + val outputDateFormat = SimpleDateFormat("yyyy년 MM월 dd일") + + val parsedDate = inputDateFormat.parse(str) + return outputDateFormat.format(parsedDate) + } +} diff --git a/app/src/main/java/com/project/meongcare/onboarding/util/DogAddOnBoardingInfoUtils.kt b/app/src/main/java/com/project/meongcare/onboarding/util/DogAddOnBoardingInfoUtils.kt new file mode 100644 index 000000000..ae2cf8574 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/onboarding/util/DogAddOnBoardingInfoUtils.kt @@ -0,0 +1,19 @@ +package com.project.meongcare.onboarding.util + +import android.view.View +import com.google.android.material.chip.Chip +import com.project.meongcare.onboarding.model.entities.Gender + +object DogAddOnBoardingInfoUtils { + fun getCheckedGender( + view: View, + checkedChipId: Int, + ): String { + val checkedChip = view.findViewById(checkedChipId) + return if (checkedChip.text.toString() == Gender.FEMALE.korean) Gender.FEMALE.english else Gender.MALE.english + } + + fun bodySizeCheck(str: String): Double? { + return if (str.isEmpty()) null else str.toDouble() + } +} diff --git a/app/src/main/java/com/project/meongcare/onboarding/view/DogAddOnBoardingFragment.kt b/app/src/main/java/com/project/meongcare/onboarding/view/DogAddOnBoardingFragment.kt index 7b97783c6..891d293c8 100644 --- a/app/src/main/java/com/project/meongcare/onboarding/view/DogAddOnBoardingFragment.kt +++ b/app/src/main/java/com/project/meongcare/onboarding/view/DogAddOnBoardingFragment.kt @@ -1,6 +1,5 @@ package com.project.meongcare.onboarding.view -import android.content.Context import android.net.Uri import android.os.Bundle import android.view.LayoutInflater @@ -10,55 +9,48 @@ import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.bumptech.glide.Glide -import com.google.android.material.chip.Chip -import com.google.gson.Gson import com.project.meongcare.BirthdayBottomSheetFragment -import com.project.meongcare.MainActivity +import com.project.meongcare.BuildConfig import com.project.meongcare.R +import com.project.meongcare.aws.util.AWSS3ImageUtils.convertUriToFile +import com.project.meongcare.aws.util.DOG_FOLDER_PATH +import com.project.meongcare.aws.util.PARENT_FOLDER_PATH +import com.project.meongcare.aws.viewmodel.AWSS3ViewModel import com.project.meongcare.databinding.FragmentDogAddOnBoardingBinding -import com.project.meongcare.login.model.data.local.UserPreferences -import com.project.meongcare.login.model.data.repository.LoginRepository +import com.project.meongcare.medicalRecord.viewmodel.UserViewModel import com.project.meongcare.onboarding.model.data.local.DateSubmitListener import com.project.meongcare.onboarding.model.data.local.PhotoMenuListener -import com.project.meongcare.onboarding.model.entities.Dog +import com.project.meongcare.onboarding.model.entities.DogPostRequest +import com.project.meongcare.onboarding.util.DogAddOnBoardingDateUtils.dateFormat +import com.project.meongcare.onboarding.util.DogAddOnBoardingInfoUtils.bodySizeCheck +import com.project.meongcare.onboarding.util.DogAddOnBoardingInfoUtils.getCheckedGender import com.project.meongcare.onboarding.viewmodel.DogAddViewModel import com.project.meongcare.onboarding.viewmodel.DogTypeSharedViewModel import com.project.meongcare.snackbar.view.CustomSnackBar import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.MultipartBody -import okhttp3.RequestBody import okhttp3.RequestBody.Companion.asRequestBody -import okhttp3.RequestBody.Companion.toRequestBody import java.io.File -import java.text.SimpleDateFormat -import javax.inject.Inject @AndroidEntryPoint class DogAddOnBoardingFragment : Fragment(), PhotoMenuListener, DateSubmitListener { - lateinit var fragmentDogAddOnBoardingBinding: FragmentDogAddOnBoardingBinding - lateinit var mainActivity: MainActivity + lateinit var binding: FragmentDogAddOnBoardingBinding private val dogAddViewModel: DogAddViewModel by viewModels() + private val userViewModel: UserViewModel by viewModels() + private val awsS3ViewModel: AWSS3ViewModel by viewModels() private val dogTypeSharedViewModel: DogTypeSharedViewModel by activityViewModels() - private val postDogCoroutineJob = Job() - private val dogAddCoroutineJob = Job() - - @Inject - lateinit var userPreferences: UserPreferences - - @Inject - lateinit var loginRepository: LoginRepository private var isCbxChecked = false private var isFirstRegister: Boolean? = null + private lateinit var accessToken: String + private lateinit var refreshToken: String + private lateinit var imageFile: File + private lateinit var filePath: String + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) isFirstRegister = arguments?.getBoolean("isFirstRegister") @@ -69,12 +61,133 @@ class DogAddOnBoardingFragment : Fragment(), PhotoMenuListener, DateSubmitListen container: ViewGroup?, savedInstanceState: Bundle?, ): View? { - fragmentDogAddOnBoardingBinding = FragmentDogAddOnBoardingBinding.inflate(inflater) - mainActivity = activity as MainActivity + binding = FragmentDogAddOnBoardingBinding.inflate(inflater) + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + + getAccessToken() + initObserves() + initViews() + } + + private fun getAccessToken() { + userViewModel.accessTokenPreferencesLiveData.observe(viewLifecycleOwner) { accessToken -> + if (accessToken != null) { + this.accessToken = accessToken + getRefreshToken() + } + } + } + + private fun getRefreshToken() { + userViewModel.refreshTokenPreferencesLiveData.observe(viewLifecycleOwner) { refreshToken -> + if (refreshToken != null) { + this.refreshToken = refreshToken + } + } + } + + private fun reissueAccessToken() { + userViewModel.getNewAccessToken(refreshToken) + userViewModel.reissueResponse.observe(viewLifecycleOwner) { response -> + if (response != null) { + when (response.code()) { + 200 -> { + CustomSnackBar.make( + requireView(), + R.drawable.snackbar_error_16dp, + getString(R.string.snack_bar_info_add_failure), + ).show() + userViewModel.setAccessToken(response.body()?.accessToken) + } + 401 -> { + CustomSnackBar.make( + requireView(), + R.drawable.snackbar_error_16dp, + getString(R.string.snack_bar_refresh_expire), + ).show() + findNavController().navigate(R.id.action_dogAddOnBoardingFragment_to_loginFragment) + } + } + } + } + } + + override fun onUriPassed(uri: Uri) { + dogAddViewModel.getDogProfileImage(uri) + } + + override fun onDateSubmit(str: String) { + dogAddViewModel.getDogBirthDate(str) + } + + private fun initViews() { + // 사진 등록 + binding.cardviewPetaddImage.setOnClickListener { + val modalBottomSheet = PhotoSelectBottomSheetFragment() + modalBottomSheet.setPhotoMenuListener(this@DogAddOnBoardingFragment) + modalBottomSheet.setStyle(DialogFragment.STYLE_NORMAL, R.style.RoundCornerPhotoDialogTheme) + modalBottomSheet.show(requireActivity().supportFragmentManager, modalBottomSheet.tag) + } + + // 품종 등록 + binding.viewPetaddType.setOnClickListener { + findNavController().navigate(R.id.action_dogAddOnBoardingFragment_to_dogVarietySearchFragment) + } + + // 날짜 등록 + binding.textviewPetaddSelectBirthday.setOnClickListener { + val birthdayBottomSheet = + BirthdayBottomSheetFragment( + binding.root, + dogAddViewModel.dogBirthDate.value, + ) + birthdayBottomSheet.setDateSubmitListener(this@DogAddOnBoardingFragment) + birthdayBottomSheet.setStyle(DialogFragment.STYLE_NORMAL, R.style.RoundCornerBirthdayDialogTheme) + birthdayBottomSheet.show(requireActivity().supportFragmentManager, birthdayBottomSheet.tag) + } + + binding.checkboxPetaddNeuterStatus.setOnCheckedChangeListener { buttonView, isChecked -> + isCbxChecked = isChecked + } + + // 중성화 여부 텍스트 클릭 시 체크박스 반전 + binding.textviewPetaddNeuterStatus.setOnClickListener { + binding.checkboxPetaddNeuterStatus.isChecked = !isCbxChecked + } + + binding.edittextPetaddNameError.setOnClickListener { + it.visibility = View.INVISIBLE + binding.edittextPetaddName.requestFocus() + } + + binding.edittextPetaddWeightError.setOnClickListener { + it.visibility = View.INVISIBLE + binding.edittextPetaddWeight.requestFocus() + } + + binding.edittextPetaddSelectTypeError.setOnClickListener { + findNavController().navigate(R.id.action_dogAddOnBoardingFragment_to_dogVarietySearchFragment) + } + + binding.buttonCancel.setOnClickListener { + findNavController().popBackStack() + } + + initCancelButtonVisibility() + initCompleteButton() + } + private fun initObserves() { dogAddViewModel.dogProfileImage.observe(viewLifecycleOwner) { uri -> if (uri != null) { - fragmentDogAddOnBoardingBinding.run { + binding.run { Glide.with(this@DogAddOnBoardingFragment) .load(uri) .into(imageviewPetaddImage) @@ -86,8 +199,8 @@ class DogAddOnBoardingFragment : Fragment(), PhotoMenuListener, DateSubmitListen dogAddViewModel.dogBirthDate.observe(viewLifecycleOwner) { date -> if (date != null) { - fragmentDogAddOnBoardingBinding.textviewPetaddSelectBirthday.run { - fragmentDogAddOnBoardingBinding.edittextPetaddSelectBirthdayError.visibility = View.INVISIBLE + binding.textviewPetaddSelectBirthday.run { + binding.edittextPetaddSelectBirthdayError.visibility = View.INVISIBLE text = dateFormat(date) setTextAppearance(R.style.Typography_Body1_Medium) } @@ -96,8 +209,8 @@ class DogAddOnBoardingFragment : Fragment(), PhotoMenuListener, DateSubmitListen dogTypeSharedViewModel.selectedDogType.observe(viewLifecycleOwner) { dogType -> if (dogType != null) { - fragmentDogAddOnBoardingBinding.edittextPetaddSelectType.run { - fragmentDogAddOnBoardingBinding.edittextPetaddSelectTypeError.visibility = View.GONE + binding.edittextPetaddSelectType.run { + binding.edittextPetaddSelectTypeError.visibility = View.GONE text = dogType setTextAppearance(R.style.Typography_Body1_Medium) } @@ -105,36 +218,17 @@ class DogAddOnBoardingFragment : Fragment(), PhotoMenuListener, DateSubmitListen } dogAddViewModel.dogAddResponse.observe(viewLifecycleOwner) { response -> - postDogCoroutineJob.cancel() if (response == 200) { CustomSnackBar.make( requireView(), R.drawable.snackbar_success_16dp, getString(R.string.snack_bar_dog_create_complete), ).show() - userPreferences.setIsFirstLogin(false) + userViewModel.setIsFirstLogin(false) findNavController().navigate(R.id.action_dogAddOnBoardingFragment_to_completeOnBoardingFragment) } else if (response == 401) { - lifecycleScope.launch(dogAddCoroutineJob) { - val refreshToken = userPreferences.getRefreshToken() - if (refreshToken.isNotEmpty()) { - val reissueResponse = loginRepository.getNewAccessToken(refreshToken) - if (reissueResponse != null && reissueResponse.code() == 200) { - CustomSnackBar.make( - requireView(), - R.drawable.snackbar_error_16dp, - getString(R.string.snack_bar_info_add_failure), - ).show() - userPreferences.setAccessToken(reissueResponse.body()?.accessToken!!) - } else if (reissueResponse != null && reissueResponse.code() == 401) { - CustomSnackBar.make( - requireView(), - R.drawable.snackbar_error_16dp, - getString(R.string.snack_bar_refresh_expire), - ).show() - findNavController().navigate(R.id.action_dogAddOnBoardingFragment_to_loginFragment) - } - } + if (refreshToken.isNotEmpty()) { + reissueAccessToken() } } else { CustomSnackBar.make( @@ -145,186 +239,95 @@ class DogAddOnBoardingFragment : Fragment(), PhotoMenuListener, DateSubmitListen } } - fragmentDogAddOnBoardingBinding.run { - when (isFirstRegister) { - true -> buttonCancel.visibility = View.GONE - false, null -> buttonCancel.visibility = View.VISIBLE + awsS3ViewModel.preSignedUrl.observe(viewLifecycleOwner) { response -> + if (response != null) { + val requestBody = imageFile.asRequestBody("image/*".toMediaTypeOrNull()) + awsS3ViewModel.uploadImageToS3(response.preSignedUrl, requestBody) } + } - // 사진 등록 - cardviewPetaddImage.setOnClickListener { - val modalBottomSheet = PhotoSelectBottomSheetFragment() - modalBottomSheet.setPhotoMenuListener(this@DogAddOnBoardingFragment) - // 둥근 모서리 지정 - modalBottomSheet.setStyle(DialogFragment.STYLE_NORMAL, R.style.RoundCornerPhotoDialogTheme) - modalBottomSheet.show(mainActivity.supportFragmentManager, modalBottomSheet.tag) + awsS3ViewModel.uploadImageResponse.observe(viewLifecycleOwner) { response -> + if (response == 200) { + val imageURL = BuildConfig.AWS_S3_BASE_URL + filePath + postDogInfo(imageURL) } + } + } - // 품종 등록 - viewPetaddType.setOnClickListener { - findNavController().navigate(R.id.action_dogAddOnBoardingFragment_to_dogVarietySearchFragment) - } + private fun postDogInfo(imageURL: String?) { + val name = binding.edittextPetaddName.text.toString() + val type = binding.edittextPetaddSelectType.text.toString() + val sex = getCheckedGender(binding.root, binding.chipgroupPetaddGroupGender.checkedChipId) + val birthDate = dogAddViewModel.dogBirthDate.value!! + val castrate = binding.checkboxPetaddNeuterStatus.isChecked + val weight: Double = binding.edittextPetaddWeight.text.toString().toDouble() + val backRound: Double? = bodySizeCheck(binding.edittextPetaddBackLength.text.toString()) + val neckRound: Double? = bodySizeCheck(binding.edittextPetaddNeckCircumference.text.toString()) + val chestRound: Double? = bodySizeCheck(binding.edittextPetaddChestCircumference.text.toString()) + + val dogPostRequest = + DogPostRequest( + name, + type, + sex, + birthDate, + castrate, + weight, + backRound, + neckRound, + chestRound, + imageURL, + ) + + if (accessToken.isNotEmpty()) { + dogAddViewModel.postDogInfo( + accessToken, + dogPostRequest, + ) + } + } - // 날짜 등록 - textviewPetaddSelectBirthday.setOnClickListener { - val birthdayBottomSheet = - BirthdayBottomSheetFragment( - fragmentDogAddOnBoardingBinding.root, - dogAddViewModel.dogBirthDate.value, - ) - birthdayBottomSheet.setDateSubmitListener(this@DogAddOnBoardingFragment) - birthdayBottomSheet.setStyle(DialogFragment.STYLE_NORMAL, R.style.RoundCornerBirthdayDialogTheme) - birthdayBottomSheet.show(mainActivity.supportFragmentManager, birthdayBottomSheet.tag) - } + private fun initCancelButtonVisibility() { + when (isFirstRegister) { + true -> binding.buttonCancel.visibility = View.GONE + false, null -> binding.buttonCancel.visibility = View.VISIBLE + } + } - checkboxPetaddNeuterStatus.setOnCheckedChangeListener { buttonView, isChecked -> - isCbxChecked = isChecked + private fun initCompleteButton() { + binding.buttonComplete.setOnClickListener { + // 입력 검사 + if (binding.edittextPetaddName.text.isEmpty()) { + binding.edittextPetaddNameError.visibility = View.VISIBLE + return@setOnClickListener } - // 중성화 여부 텍스트 클릭 시 체크박스 반전 - textviewPetaddNeuterStatus.setOnClickListener { - checkboxPetaddNeuterStatus.isChecked = !isCbxChecked + if (binding.edittextPetaddSelectType.text.isEmpty()) { + binding.edittextPetaddSelectTypeError.visibility = View.VISIBLE + return@setOnClickListener } - edittextPetaddNameError.setOnClickListener { - it.visibility = View.INVISIBLE - edittextPetaddName.requestFocus() - } - edittextPetaddWeightError.setOnClickListener { - it.visibility = View.INVISIBLE - edittextPetaddWeight.requestFocus() - } - edittextPetaddSelectTypeError.setOnClickListener { - findNavController().navigate(R.id.action_dogAddOnBoardingFragment_to_dogVarietySearchFragment) + if (binding.chipgroupPetaddGroupGender.checkedChipId == View.NO_ID) { + return@setOnClickListener } - buttonCancel.setOnClickListener { - findNavController().popBackStack() + if (binding.textviewPetaddSelectBirthday.text.isEmpty()) { + binding.edittextPetaddSelectBirthdayError.visibility = View.VISIBLE + return@setOnClickListener } - // 완료 - buttonComplete.setOnClickListener { - // 입력 검사 - if (edittextPetaddName.text.isEmpty()) { - edittextPetaddNameError.visibility = View.VISIBLE - return@setOnClickListener - } - - if (edittextPetaddSelectType.text.isEmpty()) { - edittextPetaddSelectTypeError.visibility = View.VISIBLE - return@setOnClickListener - } - - if (chipgroupPetaddGroupGender.checkedChipId == View.NO_ID) { - return@setOnClickListener - } - - if (textviewPetaddSelectBirthday.text.isEmpty()) { - edittextPetaddSelectBirthdayError.visibility = View.VISIBLE - return@setOnClickListener - } - - if (edittextPetaddWeight.text.isEmpty()) { - edittextPetaddWeightError.visibility = View.VISIBLE - return@setOnClickListener - } - - val dogName = edittextPetaddName.text.toString() - val dogType = edittextPetaddSelectType.text.toString() - val dogGender = getCheckedGender(fragmentDogAddOnBoardingBinding.root, chipgroupPetaddGroupGender.checkedChipId) - val dogBirth = dogAddViewModel.dogBirthDate.value!! - val dogWeight: Double = edittextPetaddWeight.text.toString().toDouble() - val dogBack: Double? = bodySizeCheck(edittextPetaddBackLength.text.toString()) - val dogNeck: Double? = bodySizeCheck(edittextPetaddNeckCircumference.text.toString()) - val dogChest: Double? = bodySizeCheck(edittextPetaddChestCircumference.text.toString()) - val dog = - Dog( - dogName, - dogType, - dogGender, - dogBirth, - isCbxChecked, - dogWeight, - dogBack, - dogNeck, - dogChest, - ) - val json = Gson().toJson(dog) - val requestBody: RequestBody = json.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) - val filePart = createMultipartBody(mainActivity, dogAddViewModel.dogProfileImage.value) - - lifecycleScope.launch(postDogCoroutineJob) { - userPreferences.accessToken.collectLatest { accessToken -> - if (accessToken != null) { - dogAddViewModel.postDogInfo( - accessToken, - filePart, - requestBody, - ) - } - } - } + if (binding.edittextPetaddWeight.text.isEmpty()) { + binding.edittextPetaddWeightError.visibility = View.VISIBLE + return@setOnClickListener } - } - - return fragmentDogAddOnBoardingBinding.root - } - override fun onDestroy() { - super.onDestroy() - dogAddCoroutineJob.cancel() - } - - override fun onUriPassed(uri: Uri) { - dogAddViewModel.getDogProfileImage(uri) - } - - override fun onDateSubmit(str: String) { - dogAddViewModel.getDogBirthDate(str) - } -} - -enum class Gender(val korean: String, val english: String) { - MALE("남성", "male"), - FEMALE("여성", "female"), -} - -fun dateFormat(str: String): String { - val inputDateFormat = SimpleDateFormat("yyyy-MM-dd") - val outputDateFormat = SimpleDateFormat("yyyy년 MM월 dd일") - - val parsedDate = inputDateFormat.parse(str) - return outputDateFormat.format(parsedDate) -} - -fun createMultipartBody( - context: Context, - uri: Uri?, -): MultipartBody.Part { - if (uri != null) { - val inputStream = context.contentResolver.openInputStream(uri) - val file = File(context.cacheDir, "tempFile") - inputStream.use { input -> - file.outputStream().use { output -> - input?.copyTo(output) + if (dogAddViewModel.dogProfileImage.value == null) { + postDogInfo(null) + } else { + imageFile = convertUriToFile(requireContext(), dogAddViewModel.dogProfileImage.value!!) + filePath = "$PARENT_FOLDER_PATH$DOG_FOLDER_PATH${imageFile.name}" + awsS3ViewModel.getPreSignedUrl(accessToken, filePath) } } - val requestFile = file.asRequestBody("multipart/form-data".toMediaTypeOrNull()) - - return MultipartBody.Part.createFormData("file", file.name, requestFile) } - val emptyBody = "".toRequestBody("multipart/form-data".toMediaTypeOrNull()) - return MultipartBody.Part.createFormData("file", "", emptyBody) -} - -fun getCheckedGender( - view: View, - checkedChipId: Int, -): String { - val checkedChip = view.findViewById(checkedChipId) - return if (checkedChip.text.toString() == Gender.FEMALE.korean) Gender.FEMALE.english else Gender.MALE.english -} - -fun bodySizeCheck(str: String): Double? { - return if (str.isEmpty()) null else str.toDouble() } diff --git a/app/src/main/java/com/project/meongcare/onboarding/viewmodel/DogAddViewModel.kt b/app/src/main/java/com/project/meongcare/onboarding/viewmodel/DogAddViewModel.kt index 9718f62fc..83f6f6d3c 100644 --- a/app/src/main/java/com/project/meongcare/onboarding/viewmodel/DogAddViewModel.kt +++ b/app/src/main/java/com/project/meongcare/onboarding/viewmodel/DogAddViewModel.kt @@ -6,10 +6,9 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.project.meongcare.onboarding.model.data.repository.DogAddRepository +import com.project.meongcare.onboarding.model.entities.DogPostRequest import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import okhttp3.MultipartBody -import okhttp3.RequestBody import javax.inject.Inject @HiltViewModel @@ -40,11 +39,10 @@ class DogAddViewModel fun postDogInfo( accessToken: String, - file: MultipartBody.Part, - dto: RequestBody, + dogPostRequest: DogPostRequest, ) { viewModelScope.launch { - _dogAddResponse.value = dogAddRepository.postDogInfo(accessToken, file, dto) + _dogAddResponse.value = dogAddRepository.postDogInfo(accessToken, dogPostRequest) } } } diff --git a/app/src/main/java/com/project/meongcare/supplement/model/data/remote/SupplementAPI.kt b/app/src/main/java/com/project/meongcare/supplement/model/data/remote/SupplementAPI.kt index 16115b72d..d22cb927f 100644 --- a/app/src/main/java/com/project/meongcare/supplement/model/data/remote/SupplementAPI.kt +++ b/app/src/main/java/com/project/meongcare/supplement/model/data/remote/SupplementAPI.kt @@ -3,17 +3,15 @@ package com.project.meongcare.supplement.model.data.remote import com.project.meongcare.supplement.model.entities.DetailSupplement import com.project.meongcare.supplement.model.entities.DogSupplement import com.project.meongcare.supplement.model.entities.ResultSupplement -import okhttp3.MultipartBody -import okhttp3.RequestBody +import com.project.meongcare.supplement.model.entities.SupplementPostRequest import okhttp3.ResponseBody import retrofit2.Response +import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.Header -import retrofit2.http.Multipart import retrofit2.http.PATCH import retrofit2.http.POST -import retrofit2.http.Part import retrofit2.http.Path import retrofit2.http.Query @@ -37,12 +35,10 @@ interface SupplementAPI { @Path("dogId") dogId: Long?, ): Response - @Multipart @POST("supplements") suspend fun addSupplement( @Header("AccessToken") accessToken: String?, - @Part filePart: MultipartBody.Part, - @Part("dto") supplementDto: RequestBody, + @Body supplementPostRequest: SupplementPostRequest, ): Response @PATCH("supplements/check") diff --git a/app/src/main/java/com/project/meongcare/supplement/model/data/repository/SupplementRepository.kt b/app/src/main/java/com/project/meongcare/supplement/model/data/repository/SupplementRepository.kt index 68933a7bf..7f3b0b559 100644 --- a/app/src/main/java/com/project/meongcare/supplement/model/data/repository/SupplementRepository.kt +++ b/app/src/main/java/com/project/meongcare/supplement/model/data/repository/SupplementRepository.kt @@ -4,8 +4,8 @@ import com.project.meongcare.supplement.model.data.remote.SupplementAPI import com.project.meongcare.supplement.model.data.remote.SupplementRetrofitInstance import com.project.meongcare.supplement.model.entities.DetailSupplement import com.project.meongcare.supplement.model.entities.DogSupplement -import com.project.meongcare.supplement.model.entities.RequestSupplement import com.project.meongcare.supplement.model.entities.ResultSupplement +import com.project.meongcare.supplement.model.entities.SupplementPostRequest import okhttp3.ResponseBody import javax.inject.Inject @@ -162,13 +162,12 @@ class SupplementRepository suspend fun addSupplement( accessToken: String?, - requestSupplement: RequestSupplement, + supplementsPostRequest: SupplementPostRequest, ): Int { val response = supplementAPI.addSupplement( accessToken, - requestSupplement.file, - requestSupplement.dto, + supplementsPostRequest, ) return response.code() } diff --git a/app/src/main/java/com/project/meongcare/supplement/model/entities/SupplementData.kt b/app/src/main/java/com/project/meongcare/supplement/model/entities/SupplementData.kt index 56114b47d..818fc2546 100644 --- a/app/src/main/java/com/project/meongcare/supplement/model/entities/SupplementData.kt +++ b/app/src/main/java/com/project/meongcare/supplement/model/entities/SupplementData.kt @@ -1,17 +1,9 @@ package com.project.meongcare.supplement.model.entities -import okhttp3.MultipartBody -import okhttp3.RequestBody - data class ResultSupplement( val routines: List, ) -data class RequestSupplement( - val dto: RequestBody, - val file: MultipartBody.Part, -) - data class DogSupplement( val supplementsInfos: List, ) @@ -37,12 +29,13 @@ data class DetailSupplement( val intakeInfos: List, ) -data class SupplementDto( +data class SupplementPostRequest( val dogId: Long, val brand: String, val name: String, val intakeCycle: Int, val intakeUnit: String, + val imageURL: String?, val intakeInfos: List, ) diff --git a/app/src/main/java/com/project/meongcare/supplement/utils/SupplementUtils.kt b/app/src/main/java/com/project/meongcare/supplement/utils/SupplementUtils.kt index 37066285c..69193e1ff 100644 --- a/app/src/main/java/com/project/meongcare/supplement/utils/SupplementUtils.kt +++ b/app/src/main/java/com/project/meongcare/supplement/utils/SupplementUtils.kt @@ -1,22 +1,13 @@ package com.project.meongcare.supplement.utils import android.content.Context -import android.net.Uri import android.view.View import android.view.inputmethod.InputMethodManager import androidx.fragment.app.FragmentManager -import com.google.gson.Gson import com.project.meongcare.supplement.model.entities.IntakeInfo -import com.project.meongcare.supplement.model.entities.SupplementDto import com.project.meongcare.supplement.view.bottomSheet.SupplementCycleBottomSheetDialogFragment import com.project.meongcare.supplement.view.bottomSheet.SupplementTimeBottomSheetDialogFragment import com.project.meongcare.supplement.viewmodel.SupplementViewModel -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.MultipartBody -import okhttp3.RequestBody -import okhttp3.RequestBody.Companion.asRequestBody -import okhttp3.RequestBody.Companion.toRequestBody -import java.io.File import java.time.Instant import java.time.LocalTime import java.time.ZoneId @@ -50,32 +41,6 @@ class SupplementUtils { return localDateTime.format(formatter) } - fun convertSupplementDto(supplementDto: SupplementDto): RequestBody { - val json = Gson().toJson(supplementDto) - return json.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) - } - - fun convertPictureToFile( - context: Context, - uri: Uri, - ): MultipartBody.Part { - if (uri.toString().isEmpty()) { - val emptyFile = "".toRequestBody("multipart/form-data".toMediaTypeOrNull()) - return MultipartBody.Part.createFormData("file", "", emptyFile) - } - - val inputStream = context.contentResolver.openInputStream(uri) - val file = File(context.cacheDir, "tempFile") - inputStream.use { input -> - file.outputStream().use { output -> - input?.copyTo(output) - } - } - val requestFile = file.asRequestBody("multipart/form-data".toMediaTypeOrNull()) - - return MultipartBody.Part.createFormData("file", file.name, requestFile) - } - fun showCycleBottomSheet( parentFragmentManager: FragmentManager, supplementViewModel: SupplementViewModel, diff --git a/app/src/main/java/com/project/meongcare/supplement/view/SupplementAddFragment.kt b/app/src/main/java/com/project/meongcare/supplement/view/SupplementAddFragment.kt index 943f5af54..6f52da784 100644 --- a/app/src/main/java/com/project/meongcare/supplement/view/SupplementAddFragment.kt +++ b/app/src/main/java/com/project/meongcare/supplement/view/SupplementAddFragment.kt @@ -13,20 +13,28 @@ import android.widget.ImageView import android.widget.TextView import androidx.core.content.ContextCompat.getColor import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider import androidx.navigation.NavController import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide +import com.project.meongcare.BuildConfig import com.project.meongcare.MainActivity import com.project.meongcare.R +import com.project.meongcare.aws.util.AWSS3ImageUtils.convertUriToFile +import com.project.meongcare.aws.util.PARENT_FOLDER_PATH +import com.project.meongcare.aws.util.SUPPLEMENTS_FOLDER_PATH +import com.project.meongcare.aws.viewmodel.AWSS3ViewModel import com.project.meongcare.databinding.FragmentSupplementAddBinding import com.project.meongcare.databinding.ItemSupplementAddTimeBinding +import com.project.meongcare.medicalRecord.viewmodel.DogViewModel +import com.project.meongcare.medicalRecord.viewmodel.UserViewModel import com.project.meongcare.snackbar.view.CustomSnackBar import com.project.meongcare.supplement.model.data.local.OnPictureChangedListener import com.project.meongcare.supplement.model.data.repository.SupplementRepository -import com.project.meongcare.supplement.model.entities.SupplementDto +import com.project.meongcare.supplement.model.entities.SupplementPostRequest import com.project.meongcare.supplement.utils.SupplementUtils.Companion.convertDateToTime import com.project.meongcare.supplement.utils.SupplementUtils.Companion.hideKeyboard import com.project.meongcare.supplement.utils.SupplementUtils.Companion.showCycleBottomSheet @@ -34,13 +42,29 @@ import com.project.meongcare.supplement.utils.SupplementUtils.Companion.showTime import com.project.meongcare.supplement.view.bottomSheet.SupplementPictureBottomSheetDialogFragment import com.project.meongcare.supplement.viewmodel.SupplementViewModel import com.project.meongcare.supplement.viewmodel.SupplementViewModelFactory +import dagger.hilt.android.AndroidEntryPoint +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import java.io.File +@AndroidEntryPoint class SupplementAddFragment : Fragment(), OnPictureChangedListener { lateinit var fragmentSupplementAddBinding: FragmentSupplementAddBinding lateinit var mainActivity: MainActivity lateinit var supplementViewModel: SupplementViewModel lateinit var navController: NavController + private val awsS3ViewModel: AWSS3ViewModel by viewModels() + private val userViewModel: UserViewModel by viewModels() + private val dogViewModel: DogViewModel by viewModels() + + private lateinit var imageFile: File + private lateinit var filePath: String + + private var accessToken = "" + private var dogId = 0L + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -49,6 +73,7 @@ class SupplementAddFragment : Fragment(), OnPictureChangedListener { fragmentSupplementAddBinding = FragmentSupplementAddBinding.inflate(layoutInflater) mainActivity = activity as MainActivity navController = findNavController() + getAccessToken() val factory = SupplementViewModelFactory(SupplementRepository()) supplementViewModel = ViewModelProvider(this, factory)[SupplementViewModel::class.java] @@ -184,15 +209,12 @@ class SupplementAddFragment : Fragment(), OnPictureChangedListener { buttonSupplementAddComplete.setOnClickListener { checkInput() if (checkInput()) { - val brandName = editTextSupplementAddBrandName.text.toString() - val name = editTextSupplementAddName.text.toString() - val imgUri = supplementViewModel.supplementAddImg.value - - supplementViewModel.addSupplement( - brandName, - name, - imgUri ?: Uri.EMPTY, - ) + val uri = supplementViewModel.supplementAddImg.value + if (uri == null) { + postSupplement(null) + } else { + getPreSignedUrl(uri) + } } } } @@ -341,6 +363,64 @@ class SupplementAddFragment : Fragment(), OnPictureChangedListener { } } + private fun getAccessToken() { + userViewModel.accessTokenPreferencesLiveData.observe(viewLifecycleOwner) { accessToken -> + if (accessToken != null) { + this.accessToken = accessToken + getDogId() + } + } + } + + private fun getDogId() { + dogViewModel.dogIdPreferencesLiveData.observe(viewLifecycleOwner) { dogId -> + if (dogId != null) { + this.dogId = dogId + } + } + } + + private fun getPreSignedUrl(uri: Uri) { + imageFile = convertUriToFile(requireContext(), uri) + filePath = "$PARENT_FOLDER_PATH$SUPPLEMENTS_FOLDER_PATH${imageFile.name}" + awsS3ViewModel.getPreSignedUrl(accessToken, filePath) + awsS3ViewModel.preSignedUrl.observe(viewLifecycleOwner) { response -> + if (response != null) { + val requestBody = imageFile.asRequestBody("image/*".toMediaTypeOrNull()) + uploadImage(response.preSignedUrl, requestBody) + } + } + } + + private fun uploadImage( + preSignedUrl: String, + requestBody: RequestBody, + ) { + awsS3ViewModel.uploadImageToS3(preSignedUrl, requestBody) + awsS3ViewModel.uploadImageResponse.observe(viewLifecycleOwner) { response -> + if (response == 200) { + val imageURL = BuildConfig.AWS_S3_BASE_URL + filePath + postSupplement(imageURL) + } + } + } + + private fun postSupplement(imageURL: String?) { + supplementViewModel.addSupplement( + accessToken, + createSupplementInfo(imageURL), + ) + } + + private fun createSupplementInfo(imageURL: String?): SupplementPostRequest { + val brandName = fragmentSupplementAddBinding.editTextSupplementAddBrandName.text.toString() + val name = fragmentSupplementAddBinding.editTextSupplementAddName.text.toString() + val intakeCycle = supplementViewModel.supplementCycle.value!! + val intakeUnit = supplementViewModel.intakeTimeUnit.value!! + val intakeInfos = supplementViewModel.intakeTimeList.value!! + return SupplementPostRequest(dogId, brandName, name, intakeCycle, intakeUnit, imageURL, intakeInfos) + } + private fun showPictureBottomSheet() { val bottomSheetFragment = SupplementPictureBottomSheetDialogFragment() diff --git a/app/src/main/java/com/project/meongcare/supplement/viewmodel/SupplementViewModel.kt b/app/src/main/java/com/project/meongcare/supplement/viewmodel/SupplementViewModel.kt index c48694ff2..87517f6be 100644 --- a/app/src/main/java/com/project/meongcare/supplement/viewmodel/SupplementViewModel.kt +++ b/app/src/main/java/com/project/meongcare/supplement/viewmodel/SupplementViewModel.kt @@ -1,30 +1,22 @@ package com.project.meongcare.supplement.viewmodel -import android.app.Activity -import android.content.Context import android.net.Uri import android.util.Log import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView -import androidx.core.content.ContextCompat import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.navigation.NavController -import com.project.meongcare.R import com.project.meongcare.home.model.data.local.DogPreferences import com.project.meongcare.login.model.data.local.UserPreferences import com.project.meongcare.login.view.GlobalApplication import com.project.meongcare.supplement.model.data.repository.SupplementRepository import com.project.meongcare.supplement.model.entities.DetailSupplement import com.project.meongcare.supplement.model.entities.IntakeInfo -import com.project.meongcare.supplement.model.entities.RequestSupplement import com.project.meongcare.supplement.model.entities.Supplement import com.project.meongcare.supplement.model.entities.SupplementDog -import com.project.meongcare.supplement.model.entities.SupplementDto -import com.project.meongcare.supplement.utils.SupplementUtils.Companion.convertPictureToFile -import com.project.meongcare.supplement.utils.SupplementUtils.Companion.convertSupplementDto +import com.project.meongcare.supplement.model.entities.SupplementPostRequest import com.project.meongcare.supplement.utils.SupplementUtils.Companion.convertToDateToDate import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers.Main @@ -146,27 +138,11 @@ class SupplementViewModel } fun addSupplement( - brandName: String, - name: String, - uri: Uri, + accessToken: String, + supplementPostRequest: SupplementPostRequest, ) { viewModelScope.launch { - val accessToken: String? = - UserPreferences(GlobalApplication.applicationContext()).accessToken.first() - val dogId: Long? = DogPreferences(GlobalApplication.applicationContext()).dogId.first() - - val supplementDto = - SupplementDto(dogId!!, brandName, name, supplementCycle.value!!, intakeTimeUnit.value!!, intakeTimeList.value!!) - val dto = convertSupplementDto(supplementDto) - val file = convertPictureToFile(GlobalApplication.applicationContext(), uri) - - val requestSupplement = - RequestSupplement( - dto, - file, - ) - Log.d("영양제 추가 확인", supplementDto.toString()) - supplementCode.value = repository.addSupplement(accessToken, requestSupplement) + supplementCode.value = repository.addSupplement(accessToken, supplementPostRequest) Log.d("영양제 추가 확인2", supplementCode.value.toString()) } } diff --git a/app/src/main/res/layout/fragment_medical_record_edit.xml b/app/src/main/res/layout/fragment_medical_record_edit.xml index be258f323..d0980d159 100644 --- a/app/src/main/res/layout/fragment_medical_record_edit.xml +++ b/app/src/main/res/layout/fragment_medical_record_edit.xml @@ -10,7 +10,7 @@ + tools:context=".medicalRecord.view.MedicalRecordEditFragment">