From dd894d6372f552c230dbff19344672b5a4ad3831 Mon Sep 17 00:00:00 2001 From: iiolo Date: Tue, 16 Jan 2024 23:54:28 +0900 Subject: [PATCH 01/10] merge: develop merge --- app/src/main/AndroidManifest.xml | 2 +- .../com/project/meongcare/FeedAddFragment.kt | 17 - .../com/project/meongcare/FeedFragment.kt | 117 ------ .../com/project/meongcare/FeedInfoFragment.kt | 17 - .../com/project/meongcare/MainActivity.kt | 67 +++- .../com/project/meongcare/OldFeedFragment.kt | 17 - .../project/meongcare/SearchFeedFragment.kt | 17 - .../data/local/FeedItemSelectionListener.kt | 5 + .../model/data/local/FeedPhotoListener.kt | 7 + .../feed/model/data/remote/FeedClient.kt | 52 +++ .../model/data/remote/FeedRemoteDataSource.kt | 233 +++++++++++ .../feed/model/data/remote/FeedService.kt | 85 ++++ .../feed/model/data/repository/FeedModule.kt | 17 + .../model/data/repository/FeedRepository.kt | 32 ++ .../data/repository/FeedRepositoryImpl.kt | 33 ++ .../meongcare/feed/model/entities/Feed.kt | 8 + .../model/entities/FeedDetailGetResponse.kt | 19 + .../feed/model/entities/FeedGetResponse.kt | 14 + .../meongcare/feed/model/entities/FeedInfo.kt | 15 + .../feed/model/entities/FeedPartRecord.kt | 10 + .../feed/model/entities/FeedPartRecords.kt | 5 + .../feed/model/entities/FeedPatchRequest.kt | 6 + .../feed/model/entities/FeedPutInfo.kt | 16 + .../feed/model/entities/FeedRecord.kt | 11 + .../feed/model/entities/FeedRecords.kt | 5 + .../feed/model/entities/FeedUploadRequest.kt | 9 + .../meongcare/feed/model/entities/Feeds.kt | 5 + .../feed/model/utils/FeedDateUtils.kt | 13 + .../feed/model/utils/FeedInfoUtils.kt | 47 +++ .../meongcare/feed/view/FeedAddFragment.kt | 320 +++++++++++++++ .../meongcare/feed/view/FeedEditFragment.kt | 371 ++++++++++++++++++ .../meongcare/feed/view/FeedFragment.kt | 243 ++++++++++++ .../meongcare/feed/view/FeedInfoFragment.kt | 144 +++++++ .../meongcare/feed/view/FeedPartAdapter.kt | 69 ++++ ...FeedPhotoAttachModalBottomSheetFragment.kt | 122 ++++++ .../meongcare/feed/view/FeedsAdapter.kt | 75 ++++ .../meongcare/feed/view/OldFeedFragment.kt | 66 ++++ .../feed/view/PreviousFeedAdapter.kt | 78 ++++ .../meongcare/feed/view/SearchFeedFragment.kt | 107 +++++ .../feed/viewmodel/FeedDeleteViewModel.kt | 27 ++ .../feed/viewmodel/FeedDetailGetViewModel.kt | 34 ++ .../feed/viewmodel/FeedGetViewModel.kt | 28 ++ .../feed/viewmodel/FeedPartGetViewModel.kt | 28 ++ .../feed/viewmodel/FeedPatchViewModel.kt | 28 ++ .../feed/viewmodel/FeedPostViewModel.kt | 37 ++ .../feed/viewmodel/FeedPutViewModel.kt | 39 ++ .../feed/viewmodel/FeedsGetViewModel.kt | 38 ++ .../viewmodel/PreviousFeedGetViewModel.kt | 28 ++ .../meongcare/home/view/HomeFragment.kt | 41 +- .../meongcare/info/view/PetEditFragment.kt | 30 +- .../meongcare/info/view/PetInfoFragment.kt | 33 +- .../meongcare/info/view/ProfileFragment.kt | 32 +- .../meongcare/info/view/SettingFragment.kt | 28 +- .../meongcare/login/view/LoginFragment.kt | 5 +- .../meongcare/notice/view/NoticeFragment.kt | 3 +- .../view/CompleteOnBoardingFragment.kt | 13 +- .../view/DogAddOnBoardingFragment.kt | 33 +- .../onboarding/view/OnBoardingFragment.kt | 5 +- .../onboarding/viewmodel/DogAddViewModel.kt | 23 +- .../model/entities/SupplementData.kt | 2 + .../supplement/view/SupplementFragment.kt | 7 +- .../supplement/view/SupplementInfoFragment.kt | 68 +--- .../SupplementInfoTimeRecyclerViewAdapter.kt | 60 +++ .../res/drawable/meongcare_logo_login.xml | 24 -- .../res/drawable/meongcare_logo_splash.xml | 21 - app/src/main/res/drawable/semoban_logo.xml | 18 + .../main/res/drawable/semoban_logo_splash.xml | 18 + .../res/drawable/snackbar_complete_icon.xml | 16 + .../res/drawable/snackbar_failure_icon.xml | 23 ++ app/src/main/res/layout/activity_main.xml | 7 + app/src/main/res/layout/activity_splash.xml | 13 +- .../layout/fragment_dog_add_on_boarding.xml | 29 +- app/src/main/res/layout/fragment_feed.xml | 4 +- .../res/layout/fragment_feed_add_edit.xml | 165 ++++++-- .../main/res/layout/fragment_feed_info.xml | 87 +++- app/src/main/res/layout/fragment_home.xml | 8 +- app/src/main/res/layout/fragment_login.xml | 26 +- app/src/main/res/layout/fragment_old_feed.xml | 2 +- .../main/res/layout/fragment_search_feed.xml | 2 +- .../res/layout/fragment_supplement_info.xml | 2 + .../res/layout/fragment_third_on_boarding.xml | 2 +- .../main/res/layout/layout_delete_dialog.xml | 8 +- .../res/layout/layout_feed_attach_photo.xml | 61 ++- .../layout/layout_medical_record_dialog.xml | 60 +++ app/src/main/res/navigation/nav_graph.xml | 99 ++++- app/src/main/res/xml/file_paths.xml | 5 +- 86 files changed, 3372 insertions(+), 494 deletions(-) delete mode 100644 app/src/main/java/com/project/meongcare/FeedAddFragment.kt delete mode 100644 app/src/main/java/com/project/meongcare/FeedFragment.kt delete mode 100644 app/src/main/java/com/project/meongcare/FeedInfoFragment.kt delete mode 100644 app/src/main/java/com/project/meongcare/OldFeedFragment.kt delete mode 100644 app/src/main/java/com/project/meongcare/SearchFeedFragment.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/data/local/FeedItemSelectionListener.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/data/local/FeedPhotoListener.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedClient.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedRemoteDataSource.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedService.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedModule.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedRepository.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedRepositoryImpl.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/entities/Feed.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/entities/FeedDetailGetResponse.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/entities/FeedGetResponse.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/entities/FeedInfo.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/entities/FeedPartRecord.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/entities/FeedPartRecords.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/entities/FeedPatchRequest.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/entities/FeedPutInfo.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/entities/FeedRecord.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/entities/FeedRecords.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/entities/FeedUploadRequest.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/entities/Feeds.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/utils/FeedDateUtils.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/model/utils/FeedInfoUtils.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/view/FeedAddFragment.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/view/FeedEditFragment.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/view/FeedFragment.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/view/FeedInfoFragment.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/view/FeedPartAdapter.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/view/FeedPhotoAttachModalBottomSheetFragment.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/view/FeedsAdapter.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/view/OldFeedFragment.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/view/PreviousFeedAdapter.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/view/SearchFeedFragment.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/viewmodel/FeedDeleteViewModel.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/viewmodel/FeedDetailGetViewModel.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/viewmodel/FeedGetViewModel.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPartGetViewModel.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPatchViewModel.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPostViewModel.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPutViewModel.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/viewmodel/FeedsGetViewModel.kt create mode 100644 app/src/main/java/com/project/meongcare/feed/viewmodel/PreviousFeedGetViewModel.kt create mode 100644 app/src/main/java/com/project/meongcare/supplement/view/adapter/SupplementInfoTimeRecyclerViewAdapter.kt delete mode 100644 app/src/main/res/drawable/meongcare_logo_login.xml delete mode 100644 app/src/main/res/drawable/meongcare_logo_splash.xml create mode 100644 app/src/main/res/drawable/semoban_logo.xml create mode 100644 app/src/main/res/drawable/semoban_logo_splash.xml create mode 100644 app/src/main/res/drawable/snackbar_complete_icon.xml create mode 100644 app/src/main/res/drawable/snackbar_failure_icon.xml create mode 100644 app/src/main/res/layout/layout_medical_record_dialog.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ace2d8b19..a5c848955 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,7 +32,7 @@ + android:resource="@drawable/semoban_logo" /> diff --git a/app/src/main/java/com/project/meongcare/FeedAddFragment.kt b/app/src/main/java/com/project/meongcare/FeedAddFragment.kt deleted file mode 100644 index 6e7af2eb8..000000000 --- a/app/src/main/java/com/project/meongcare/FeedAddFragment.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.project.meongcare - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment - -class FeedAddFragment : Fragment() { - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_feed_add_edit, container, false) - } -} diff --git a/app/src/main/java/com/project/meongcare/FeedFragment.kt b/app/src/main/java/com/project/meongcare/FeedFragment.kt deleted file mode 100644 index 6f65ad97c..000000000 --- a/app/src/main/java/com/project/meongcare/FeedFragment.kt +++ /dev/null @@ -1,117 +0,0 @@ -package com.project.meongcare - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.content.ContextCompat -import androidx.fragment.app.Fragment -import com.github.mikephil.charting.animation.Easing -import com.github.mikephil.charting.data.PieData -import com.github.mikephil.charting.data.PieDataSet -import com.github.mikephil.charting.data.PieEntry -import com.project.meongcare.databinding.FragmentFeedBinding -import com.project.meongcare.databinding.LayoutFeedNutrientBinding - -class FeedFragment : Fragment() { - private var _binding: FragmentFeedBinding? = null - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View { - _binding = FragmentFeedBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - initNutrientPieChart() - initNutrientTable() - } - - private fun initNutrientPieChart() { - val nutrientRatio = - listOf( - PieEntry(25f), - PieEntry(15f), - PieEntry(35f), - PieEntry(25f), - ) - - val pieColors = - listOf( - ContextCompat.getColor(requireContext(), R.color.main3), - ContextCompat.getColor(requireContext(), R.color.sub7), - ContextCompat.getColor(requireContext(), R.color.sub6), - ContextCompat.getColor(requireContext(), R.color.sub8), - ) - - val dataSet = PieDataSet(nutrientRatio, "") - dataSet.colors = pieColors - dataSet.setDrawValues(false) - - binding.piechartFeedNutrient.apply { - data = PieData(dataSet) - description.isEnabled = false - legend.isEnabled = false - isRotationEnabled = true - holeRadius = 60f - setTouchEnabled(false) - animateY(1200, Easing.EaseInOutCubic) - animate() - } - } - - private fun initNutrientTable() { - binding.run { - initNutrientRow( - includeFeedNutrientCrudeProtein, - R.drawable.feed_rect_crude_protein_r5, - "조단백", - "25%", - ) - initNutrientRow( - includeFeedNutrientCrudeFat, - R.drawable.feed_rect_crude_fat_r5, - "조지방", - "15%", - ) - initNutrientRow( - includeFeedNutrientCrudeAsh, - R.drawable.feed_rect_crude_ash_r5, - "조회분", - "35%", - ) - initNutrientRow( - includeFeedNutrientMoisture, - R.drawable.feed_rect_moisture_r5, - "수분", - "25%", - ) - } - } - - private fun initNutrientRow( - nutrientRow: LayoutFeedNutrientBinding, - nutrientColorLabel: Int, - nutrientType: String, - nutrientPercentage: String, - ) { - nutrientRow.apply { - viewFeedNutrientColorLabel.setBackgroundResource(nutrientColorLabel) - textviewFeedNutrientType.text = nutrientType - textviewFeedNutrientPercentage.text = nutrientPercentage - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} diff --git a/app/src/main/java/com/project/meongcare/FeedInfoFragment.kt b/app/src/main/java/com/project/meongcare/FeedInfoFragment.kt deleted file mode 100644 index 4cbadfdf4..000000000 --- a/app/src/main/java/com/project/meongcare/FeedInfoFragment.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.project.meongcare - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment - -class FeedInfoFragment : Fragment() { - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_feed_info, container, false) - } -} diff --git a/app/src/main/java/com/project/meongcare/MainActivity.kt b/app/src/main/java/com/project/meongcare/MainActivity.kt index 06140d2af..cca0a14a9 100644 --- a/app/src/main/java/com/project/meongcare/MainActivity.kt +++ b/app/src/main/java/com/project/meongcare/MainActivity.kt @@ -4,11 +4,11 @@ import android.Manifest import android.animation.AnimatorInflater import android.animation.AnimatorSet import android.os.Bundle -import android.util.Log import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment import com.project.meongcare.databinding.ActivityMainBinding import com.project.meongcare.login.model.data.local.UserPreferences @@ -69,20 +69,28 @@ class MainActivity : AppCompatActivity() { when (it.itemId) { R.id.menuMainBottomNavHome -> { - Log.d("Test", "Home") + fragmentContainerView.findNavController().navigate(R.id.homeFragment) } - - R.id.menuMainBottomNavMedicalRecord -> { - Log.d("Test", "Record") + else -> { + includeMedicalRecordDialog.root.visibility = View.VISIBLE } - - else -> Log.d("Test", "else") } true } } + includeMedicalRecordDialog.run { + constraintlayoutBg.setOnClickListener { + includeMedicalRecordDialog.root.visibility = View.GONE + bottomNavigationViewMain.selectedItemId = R.id.menuMainBottomNavHome + } + buttonOk.setOnClickListener { + includeMedicalRecordDialog.root.visibility = View.GONE + bottomNavigationViewMain.selectedItemId = R.id.menuMainBottomNavHome + } + } + fabMain.setOnClickListener { if (overlayLayout.visibility == View.GONE) { overlayLayout.visibility = View.VISIBLE @@ -98,6 +106,51 @@ class MainActivity : AppCompatActivity() { } } + menuWeightEdit.setOnClickListener { + CoroutineScope(Main).launch { + floatingButtonMenuIn(activityMainBinding) + delay(550L) + overlayLayout.visibility = View.GONE + fragmentContainerView.findNavController().navigate(R.id.weightFragment) + } + } + + menuNutritionAdd.setOnClickListener { + CoroutineScope(Main).launch { + floatingButtonMenuIn(activityMainBinding) + delay(550L) + overlayLayout.visibility = View.GONE + fragmentContainerView.findNavController().navigate(R.id.supplementFragment) + } + } + + menuSymptomAdd.setOnClickListener { + CoroutineScope(Main).launch { + floatingButtonMenuIn(activityMainBinding) + delay(550L) + overlayLayout.visibility = View.GONE + fragmentContainerView.findNavController().navigate(R.id.symptomFragment) + } + } + + menuFecesAdd.setOnClickListener { + CoroutineScope(Main).launch { + floatingButtonMenuIn(activityMainBinding) + delay(550L) + overlayLayout.visibility = View.GONE + fragmentContainerView.findNavController().navigate(R.id.excretaFragment) + } + } + + menuDogFoodAdd.setOnClickListener { + CoroutineScope(Main).launch { + floatingButtonMenuIn(activityMainBinding) + delay(550L) + overlayLayout.visibility = View.GONE + fragmentContainerView.findNavController().navigate(R.id.feedFragment) + } + } + overlayLayout.setOnClickListener { it.visibility = View.GONE } diff --git a/app/src/main/java/com/project/meongcare/OldFeedFragment.kt b/app/src/main/java/com/project/meongcare/OldFeedFragment.kt deleted file mode 100644 index 621279151..000000000 --- a/app/src/main/java/com/project/meongcare/OldFeedFragment.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.project.meongcare - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment - -class OldFeedFragment : Fragment() { - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_old_feed, container, false) - } -} diff --git a/app/src/main/java/com/project/meongcare/SearchFeedFragment.kt b/app/src/main/java/com/project/meongcare/SearchFeedFragment.kt deleted file mode 100644 index 4654fd36f..000000000 --- a/app/src/main/java/com/project/meongcare/SearchFeedFragment.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.project.meongcare - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment - -class SearchFeedFragment : Fragment() { - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_search_feed, container, false) - } -} diff --git a/app/src/main/java/com/project/meongcare/feed/model/data/local/FeedItemSelectionListener.kt b/app/src/main/java/com/project/meongcare/feed/model/data/local/FeedItemSelectionListener.kt new file mode 100644 index 000000000..5bcd5ac28 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/data/local/FeedItemSelectionListener.kt @@ -0,0 +1,5 @@ +package com.project.meongcare.feed.model.data.local + +interface FeedItemSelectionListener { + fun onItemSelection(feedId: Long) +} diff --git a/app/src/main/java/com/project/meongcare/feed/model/data/local/FeedPhotoListener.kt b/app/src/main/java/com/project/meongcare/feed/model/data/local/FeedPhotoListener.kt new file mode 100644 index 000000000..a515b2e5c --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/data/local/FeedPhotoListener.kt @@ -0,0 +1,7 @@ +package com.project.meongcare.feed.model.data.local + +import android.net.Uri + +interface FeedPhotoListener { + fun onUriPassed(uri: Uri) +} diff --git a/app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedClient.kt b/app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedClient.kt new file mode 100644 index 000000000..6a8dc32f6 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedClient.kt @@ -0,0 +1,52 @@ +package com.project.meongcare.feed.model.data.remote + +import okhttp3.OkHttpClient +import okhttp3.ResponseBody +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Converter +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.lang.reflect.Type + +object FeedClient { + private const val BASE_URL = "https://dev.meongcare.com/" + + private val logging = + HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + + // cURL을 확인 하기 위해 사용 + private val okHttpClient = + OkHttpClient.Builder() + .addInterceptor(logging) + .build() + + private fun getRetrofit(): Retrofit { + return Retrofit.Builder() + .baseUrl(BASE_URL) + .client(okHttpClient) + .addConverterFactory(nullOnEmptyConverterFactory) + .addConverterFactory(GsonConverterFactory.create()) + .build() + } + + // 비어있는 응답을 null로 처리 + 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 + } + } + + val feedService: FeedService = getRetrofit().create(FeedService::class.java) +} 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 new file mode 100644 index 000000000..9a16532b3 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedRemoteDataSource.kt @@ -0,0 +1,233 @@ +package com.project.meongcare.feed.model.data.remote + +import android.util.Log +import com.project.meongcare.excreta.utils.SUCCESS +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.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 + +class FeedRemoteDataSource + @Inject + constructor() { + private val accessToken = + "Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6NiwiZXhwIjoxNzAzMzY1MjQ2fQ.qbSYeabyBpAni3yISWDUGYgFkQdKYfdFqPlMlz7DKCs" + private val feedApiService = FeedClient.feedService + + suspend fun postFeed(feedUploadRequest: FeedUploadRequest): Int? { + try { + val postFeedResponse = + feedApiService.postFeed( + accessToken, + feedUploadRequest.dto, + feedUploadRequest.file, + ) + + if (postFeedResponse.code() != SUCCESS) { + val stringToJson = JSONObject(postFeedResponse.code().toString()) + Log.d("FeedPostFailure", postFeedResponse.code().toString()) + Log.d("FeedPostFailure", "$stringToJson") + return null + } + + Log.d("FeedPostSuccess", postFeedResponse.code().toString()) + return postFeedResponse.code() + } catch (e: Exception) { + Log.e("FeedPostException", e.toString()) + return null + } + } + + suspend fun getFeed(): FeedGetResponse? { + try { + val getFeedResponse = + feedApiService.getFeed( + accessToken, + 2L, + ) + return if (getFeedResponse.code() == SUCCESS) { + Log.d("FeedGetSuccess", getFeedResponse.code().toString()) + getFeedResponse.body() + } else { + val stringToJson = JSONObject(getFeedResponse.errorBody()?.string()!!) + Log.d("FeedGetFailure", getFeedResponse.code().toString()) + Log.d("FeedGetFailure", "$stringToJson") + null + } + } catch (e: Exception) { + Log.e("FeedGetException", e.toString()) + return null + } + } + + suspend fun getFeedPart(feedRecordId: Long): FeedPartRecords? { + try { + val getFeedPartResponse = + feedApiService.getFeedPart( + accessToken, + 2L, + feedRecordId, + ) + return if (getFeedPartResponse.code() == SUCCESS) { + Log.d("FeedPartGetSuccess", getFeedPartResponse.code().toString()) + getFeedPartResponse.body() + } else { + val stringToJson = JSONObject(getFeedPartResponse.errorBody()?.string()!!) + Log.d("FeedPartGetFailure", getFeedPartResponse.code().toString()) + Log.d("FeedPartGetFailure", "$stringToJson") + null + } + } catch (e: Exception) { + Log.e("FeedPartGetException", e.toString()) + return null + } + } + + suspend fun getFeeds(): Feeds? { + try { + val getFeedsResponse = + feedApiService.getFeeds( + accessToken, + 2L, + ) + return if (getFeedsResponse.code() == SUCCESS) { + Log.d("FeedsGetSuccess", getFeedsResponse.code().toString()) + getFeedsResponse.body() + } else { + val stringToJson = JSONObject(getFeedsResponse.errorBody()?.string()!!) + Log.d("FeedsGetFailure", getFeedsResponse.code().toString()) + Log.d("FeedsGetFailure", "$stringToJson") + null + } + } catch (e: Exception) { + Log.d("FeedsGetException", e.toString()) + return null + } + } + + suspend fun getPreviousFeed(feedRecordId: Long): FeedRecords? { + try { + val getPreviousFeedResponse = + feedApiService.getPreviousFeed( + accessToken, + 2L, + feedRecordId, + ) + + if (getPreviousFeedResponse.code() != SUCCESS) { + val stringToJson = JSONObject(getPreviousFeedResponse.errorBody()?.string()!!) + Log.d("PreviousFeedGetFailure", getPreviousFeedResponse.code().toString()) + Log.d("PreviousFeedGetFailure", "$stringToJson") + return null + } + + Log.d("PreviousFeedGetSuccess", getPreviousFeedResponse.code().toString()) + return getPreviousFeedResponse.body() + } catch (e: Exception) { + Log.d("PreviousFeedGetException", e.toString()) + return null + } + } + + suspend fun getFeedDetail( + feedId: Long, + feedRecordId: Long, + ): FeedDetailGetResponse? { + try { + val getFeedDetailResponse = + feedApiService.getDetailFeed( + accessToken, + feedId, + feedRecordId, + ) + + if (getFeedDetailResponse.code() != SUCCESS) { + val stringToJson = JSONObject(getFeedDetailResponse.errorBody()?.string()!!) + Log.d("FeedDetailGetFailure", getFeedDetailResponse.code().toString()) + Log.d("FeedDetailGetFailure", "$stringToJson") + return null + } + + Log.d("FeedDetailGetSuccess", getFeedDetailResponse.code().toString()) + return getFeedDetailResponse.body() + } catch (e: Exception) { + Log.d("FeedDetailGetException", e.toString()) + return null + } + } + + suspend fun patchFeed(feedPatchRequest: FeedPatchRequest): Int? { + try { + val patchFeedResponse = + feedApiService.patchFeed( + accessToken, + feedPatchRequest, + ) + + if (patchFeedResponse.code() != SUCCESS) { + val stringToJson = JSONObject(patchFeedResponse.errorBody()?.string()!!) + Log.d("FeedPatchFailure", patchFeedResponse.code().toString()) + Log.d("FeedPatchFailure", "$stringToJson") + return null + } + + Log.d("FeedPatchSuccess", patchFeedResponse.code().toString()) + return patchFeedResponse.code() + } catch (e: Exception) { + Log.d("FeedPatchException", e.toString()) + return null + } + } + + suspend fun putFeed(feedUploadRequest: FeedUploadRequest): Int? { + try { + val putFeedResponse = + feedApiService.putFeed( + accessToken, + feedUploadRequest.dto, + feedUploadRequest.file, + ) + + if (putFeedResponse.code() != SUCCESS) { + val stringToJson = JSONObject(putFeedResponse.code().toString()) + Log.d("FeedPutFailure", putFeedResponse.code().toString()) + Log.d("FeedPutFailure", "$stringToJson") + return null + } + + Log.d("FeedPutSuccess", putFeedResponse.code().toString()) + return putFeedResponse.code() + } catch (e: Exception) { + Log.e("FeedPutException", e.toString()) + return null + } + } + + suspend fun deleteFeed(feedId: Long): Int? { + try { + val deleteFeedResponse = + feedApiService.deleteFeed( + accessToken, + feedId, + ) + + if (deleteFeedResponse.code() != SUCCESS) { + val stringToJson = JSONObject(deleteFeedResponse.errorBody()?.string()!!) + Log.d("FeedDeleteFailure", deleteFeedResponse.code().toString()) + Log.d("FeedDeleteFailure", "$stringToJson") + return null + } + + Log.d("FeedDeleteSuccess", deleteFeedResponse.code().toString()) + return deleteFeedResponse.code() + } catch (e: Exception) { + Log.e("FeedDeleteException", e.toString()) + return null + } + } + } 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 new file mode 100644 index 000000000..2e9802ee1 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/data/remote/FeedService.kt @@ -0,0 +1,85 @@ +package com.project.meongcare.feed.model.data.remote + +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.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, + ): Response + + @GET("feed/{dogId}") + suspend fun getFeed( + @Header("AccessToken") accessToken: String, + @Path("dogId") dogId: Long, + ): Response + + @GET("feed/part/{dogId}") + suspend fun getFeedPart( + @Header("AccessToken") accessToken: String, + @Path("dogId") dogId: Long, + @Query("feedRecordId") feedRecordId: Long, + ): Response + + @GET("feed/list/{dogId}") + suspend fun getFeeds( + @Header("AccessToken") accessToken: String, + @Path("dogId") dogId: Long, + ): Response + + @GET("feed/list/before/{dogId}") + suspend fun getPreviousFeed( + @Header("AccessToken") accessToken: String, + @Path("dogId") dogId: Long, + @Query("feedRecordId") feedRecordId: Long, + ): Response + + @GET("feed/detail/{feedId}") + suspend fun getDetailFeed( + @Header("AccessToken") accessToken: String, + @Path("feedId") feedId: Long, + @Query("feedRecordId") feedRecordId: Long, + ): Response + + @PATCH("feed") + suspend fun patchFeed( + @Header("AccessToken") accessToken: String, + @Body requestBody: FeedPatchRequest, + ): Response + + @Multipart + @PUT("feed") + suspend fun putFeed( + @Header("AccessToken") accessToken: String, + @Part("dto") dto: RequestBody, + @Part file: MultipartBody.Part, + ): Response + + @DELETE("feed/{feedId}") + suspend fun deleteFeed( + @Header("AccessToken") accessToken: String, + @Path("feedId") feedId: Long, + ): Response +} diff --git a/app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedModule.kt b/app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedModule.kt new file mode 100644 index 000000000..cb55b1051 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedModule.kt @@ -0,0 +1,17 @@ +package com.project.meongcare.feed.model.data.repository + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object FeedModule { + @Provides + @Singleton + fun provideFeedRepository(feedRepositoryImpl: FeedRepositoryImpl): FeedRepository { + return feedRepositoryImpl + } +} 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 new file mode 100644 index 000000000..9fc7784b8 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedRepository.kt @@ -0,0 +1,32 @@ +package com.project.meongcare.feed.model.data.repository + +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.FeedRecords +import com.project.meongcare.feed.model.entities.FeedUploadRequest +import com.project.meongcare.feed.model.entities.Feeds + +interface FeedRepository { + suspend fun getFeed(): FeedGetResponse? + + suspend fun postFeed(feedUploadRequest: FeedUploadRequest): Int? + + suspend fun getFeedPart(feedRecordId: Long): FeedPartRecords? + + suspend fun getFeeds(): Feeds? + + suspend fun getPreviousFeed(feedRecordId: Long): FeedRecords? + + suspend fun getDetailFeed( + feedId: Long, + feedRecordId: Long, + ): FeedDetailGetResponse? + + suspend fun patchFeed(feedPatchRequest: FeedPatchRequest): Int? + + suspend fun putFeed(feedUploadRequest: FeedUploadRequest): Int? + + suspend fun deleteFeed(feedId: Long): Int? +} 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 new file mode 100644 index 000000000..9399c4d19 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/data/repository/FeedRepositoryImpl.kt @@ -0,0 +1,33 @@ +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 javax.inject.Inject + +class FeedRepositoryImpl + @Inject + constructor( + private val feedRemoteDataSource: FeedRemoteDataSource, + ) : FeedRepository { + override suspend fun getFeed() = feedRemoteDataSource.getFeed() + + override suspend fun postFeed(feedUploadRequest: FeedUploadRequest) = feedRemoteDataSource.postFeed(feedUploadRequest) + + override suspend fun getFeedPart(feedRecordId: Long) = feedRemoteDataSource.getFeedPart(feedRecordId) + + override suspend fun getFeeds() = feedRemoteDataSource.getFeeds() + + override suspend fun getPreviousFeed(feedRecordId: Long) = feedRemoteDataSource.getPreviousFeed(feedRecordId) + + override suspend fun getDetailFeed( + feedId: Long, + feedRecordId: Long, + ) = feedRemoteDataSource.getFeedDetail(feedId, feedRecordId) + + override suspend fun patchFeed(feedPatchRequest: FeedPatchRequest) = feedRemoteDataSource.patchFeed(feedPatchRequest) + + override suspend fun putFeed(feedUploadRequest: FeedUploadRequest) = feedRemoteDataSource.putFeed(feedUploadRequest) + + override suspend fun deleteFeed(feedId: Long) = feedRemoteDataSource.deleteFeed(feedId) + } 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 new file mode 100644 index 000000000..8b18ae0df --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/Feed.kt @@ -0,0 +1,8 @@ +package com.project.meongcare.feed.model.entities + +data class Feed( + val feedId: Long, + val brandName: String, + val feedName: 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 new file mode 100644 index 000000000..1484401b4 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedDetailGetResponse.kt @@ -0,0 +1,19 @@ +package com.project.meongcare.feed.model.entities + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class FeedDetailGetResponse( + val brand: String, + val feedName: String, + val protein: Double, + val fat: Double, + val crudeAsh: Double, + val moisture: Double, + val kcal: Double, + val recommendIntake: Long, + val imageURL: String, + val startDate: String, + val endDate: String?, +) : Parcelable diff --git a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedGetResponse.kt b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedGetResponse.kt new file mode 100644 index 000000000..a48d032bc --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedGetResponse.kt @@ -0,0 +1,14 @@ +package com.project.meongcare.feed.model.entities + +data class FeedGetResponse( + val brand: String? = null, + val feedName: String? = null, + val protein: Double, + val fat: Double, + val crudeAsh: Double, + val moisture: Double, + val days: Long, + val recommendIntake: Int, + val feedId: Long, + 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/FeedInfo.kt new file mode 100644 index 000000000..209db2a98 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedInfo.kt @@ -0,0 +1,15 @@ +package com.project.meongcare.feed.model.entities + +data class FeedInfo( + val dogId: Long, + val brand: String, + val feedName: String, + val protein: Double, + val fat: Double, + val crudeAsh: Double, + val moisture: Double, + val kcal: Double, + val recommendIntake: Int, + val startDate: String, + val endDate: String? = null, +) 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 new file mode 100644 index 000000000..6992ea3e7 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPartRecord.kt @@ -0,0 +1,10 @@ +package com.project.meongcare.feed.model.entities + +data class FeedPartRecord( + val brandName: String, + val feedName: String, + val startDate: String, + val endDate: String, + val feedImageURL: String, + val feedRecordId: Long, +) diff --git a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPartRecords.kt b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPartRecords.kt new file mode 100644 index 000000000..e8aec2562 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPartRecords.kt @@ -0,0 +1,5 @@ +package com.project.meongcare.feed.model.entities + +data class FeedPartRecords( + val feedPartRecords: List, +) diff --git a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPatchRequest.kt b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPatchRequest.kt new file mode 100644 index 000000000..a90c4907c --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPatchRequest.kt @@ -0,0 +1,6 @@ +package com.project.meongcare.feed.model.entities + +data class FeedPatchRequest( + val dogId: Long, + val newFeedId: Long, +) 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/FeedPutInfo.kt new file mode 100644 index 000000000..fdf3d4dc0 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedPutInfo.kt @@ -0,0 +1,16 @@ +package com.project.meongcare.feed.model.entities + +data class FeedPutInfo( + val feedId: Long, + val brand: String, + val feedName: String, + val protein: Double, + val fat: Double, + val crudeAsh: Double, + val moisture: Double, + val kcal: Double, + val recommendIntake: Int, + val startDate: String, + val endDate: String, + val feedRecordId: Long, +) 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 new file mode 100644 index 000000000..aa5a1db10 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedRecord.kt @@ -0,0 +1,11 @@ +package com.project.meongcare.feed.model.entities + +data class FeedRecord( + val brand: String, + val feedName: String, + val startDate: String, + val endDate: String, + val feedRecordId: Long, + val imageURL: String, + val feedId: Long, +) diff --git a/app/src/main/java/com/project/meongcare/feed/model/entities/FeedRecords.kt b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedRecords.kt new file mode 100644 index 000000000..19a24621b --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedRecords.kt @@ -0,0 +1,5 @@ +package com.project.meongcare.feed.model.entities + +data class FeedRecords( + val feedRecords: List, +) 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 new file mode 100644 index 000000000..37baaca1e --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/FeedUploadRequest.kt @@ -0,0 +1,9 @@ +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/entities/Feeds.kt b/app/src/main/java/com/project/meongcare/feed/model/entities/Feeds.kt new file mode 100644 index 000000000..8c52801a3 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/entities/Feeds.kt @@ -0,0 +1,5 @@ +package com.project.meongcare.feed.model.entities + +data class Feeds( + val feeds: List, +) diff --git a/app/src/main/java/com/project/meongcare/feed/model/utils/FeedDateUtils.kt b/app/src/main/java/com/project/meongcare/feed/model/utils/FeedDateUtils.kt new file mode 100644 index 000000000..35e7491e8 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/utils/FeedDateUtils.kt @@ -0,0 +1,13 @@ +package com.project.meongcare.feed.model.utils + +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +object FeedDateUtils { + fun convertDateFormat(date: String?): String { + val outputFormat = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일") + val parsedDate = LocalDate.parse(date) + + return outputFormat.format(parsedDate) + } +} 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 new file mode 100644 index 000000000..3e0a2a628 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/model/utils/FeedInfoUtils.kt @@ -0,0 +1,47 @@ +package com.project.meongcare.feed.model.utils + +import android.content.Context +import android.net.Uri +import com.google.gson.Gson +import com.project.meongcare.feed.model.entities.FeedInfo +import com.project.meongcare.feed.model.entities.FeedPutInfo +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 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) + } +} 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 new file mode 100644 index 000000000..97226c2be --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/view/FeedAddFragment.kt @@ -0,0 +1,320 @@ +package com.project.meongcare.feed.view + +import android.content.Context +import android.net.Uri +import android.os.Bundle +import android.os.SystemClock +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.CheckBox +import android.widget.EditText +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import com.archit.calendardaterangepicker.customviews.CalendarListener +import com.archit.calendardaterangepicker.customviews.DateRangeCalendarView +import com.bumptech.glide.Glide +import com.google.android.material.snackbar.Snackbar +import com.project.meongcare.R +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.utils.FeedInfoUtils.convertFeedFile +import com.project.meongcare.feed.model.utils.FeedInfoUtils.convertFeedPostDto +import com.project.meongcare.feed.viewmodel.FeedPostViewModel +import dagger.hilt.android.AndroidEntryPoint +import java.text.SimpleDateFormat +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.util.Calendar +import java.util.Locale +import kotlin.concurrent.thread + +@AndroidEntryPoint +class FeedAddFragment : Fragment(), FeedPhotoListener { + private var _binding: FragmentFeedAddEditBinding? = null + val binding get() = _binding!! + + private lateinit var inputMethodManager: InputMethodManager + private val feedPostViewModel: FeedPostViewModel by viewModels() + + private var recommendIntake = 0.0 + var selectedStartDate = "" + var selectedEndDate = "" + private lateinit var feedInfo: FeedInfo + private var imageUri: Uri? = null + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = FragmentFeedAddEditBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + initInputMethodManager() + initToolbar() + initPhotoAttachModalBottomSheet() + applyKeyboardHidingAction() + applyKcalContentEditorBehavior() + updateCalendarVisibility() + updateSelectedIntakePeriod() + createFeedInfo() + postFeedInfo() + } + + private fun applyKcalContentEditorBehavior() { + binding.edittextFeedaddeditKcalContent.apply { + setOnEditorActionListener { _, _, _ -> + initRecommendDailyIntake(text.toString().toDouble()) + hideSoftKeyboard() + true + } + } + } + + private fun initToolbar() { + binding.toolbarFeedaddedit.setNavigationOnClickListener { + findNavController().popBackStack() + } + } + + private fun initPhotoAttachModalBottomSheet() { + binding.cardviewFeedaddeditImage.setOnClickListener { + val photoAttachModalBottomSheet = FeedPhotoAttachModalBottomSheetFragment() + photoAttachModalBottomSheet.setPhotoListener(this@FeedAddFragment) + photoAttachModalBottomSheet.show( + requireActivity().supportFragmentManager, + FeedPhotoAttachModalBottomSheetFragment.TAG, + ) + } + } + + private fun hideKeyboardOnAction(editText: EditText) { + editText.apply { + setOnEditorActionListener { _, _, _ -> + hideSoftKeyboard() + true + } + } + } + + private fun applyKeyboardHidingAction() { + binding.apply { + hideKeyboardOnAction(edittextFeedaddeditBrand) + hideKeyboardOnAction(edittextFeedaddeditName) + hideKeyboardOnAction(edittextFeedaddeditCrudeProteinPercentage) + hideKeyboardOnAction(edittextFeedaddeditCrudeFatPercent) + hideKeyboardOnAction(edittextFeedaddeditCrudeAshPercent) + hideKeyboardOnAction(edittextFeedaddeditMoisturePercent) + } + } + + private fun initRecommendDailyIntake(feedKcal: Double) { + val weight = 15.0 + recommendIntake = calculateRecommendDailyIntake(weight, feedKcal) + binding.textviewFeedaddeditDailyIntakeContent.text = "${recommendIntake}g" + } + + private fun calculateRecommendDailyIntake( + weight: Double, + feedKcal: Double, + ): Double { + val dailyEnergyRequirement = 1.6 * (30 * weight + 70) + val recommendDailyIntake = dailyEnergyRequirement * 1000 / feedKcal + return String.format("%.2f", recommendDailyIntake).toDouble() + } + + private fun updateCalendarVisibility() { + binding.apply { + textviewFeedaddeditIntakePeriodStart.apply { + setOnClickListener { + setTextColor(resources.getColor(R.color.black, null)) + calendarviewFeedaddeditStartDate.visibility = View.VISIBLE + calendarviewFeedaddeditEndDate.visibility = View.GONE + checkboxFeedaddeditDoNotKnowEndDate.visibility = View.GONE + textviewFeedaddeditDoNotKnowEndDate.visibility = View.GONE + textviewFeedaddeditIntakePeriodEnd.setTextColor(resources.getColor(R.color.gray4, null)) + } + } + textviewFeedaddeditIntakePeriodEnd.apply { + setOnClickListener { + setTextColor(resources.getColor(R.color.black, null)) + calendarviewFeedaddeditEndDate.visibility = View.VISIBLE + calendarviewFeedaddeditStartDate.visibility = View.INVISIBLE + checkboxFeedaddeditDoNotKnowEndDate.visibility = View.VISIBLE + textviewFeedaddeditDoNotKnowEndDate.visibility = View.VISIBLE + textviewFeedaddeditIntakePeriodStart.setTextColor(resources.getColor(R.color.gray4, null)) + } + } + } + } + + private fun updateSelectedIntakePeriodStartDate( + calendar: DateRangeCalendarView, + date: TextView, + ) { + calendar.setCalendarListener( + object : CalendarListener { + override fun onDateRangeSelected( + startDate: Calendar, + endDate: Calendar, + ) { + calendar.resetAllSelectedViews() + } + + override fun onFirstDateSelected(startDate: Calendar) { + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + selectedStartDate = dateFormat.format(startDate.time) + + date.text = convertDateFormat(selectedStartDate) + } + }, + ) + } + + private fun updateSelectedIntakePeriodEndDate( + calendar: DateRangeCalendarView, + date: TextView, + checkBox: CheckBox, + ) { + calendar.setCalendarListener( + object : CalendarListener { + override fun onDateRangeSelected( + startDate: Calendar, + endDate: Calendar, + ) { + calendar.resetAllSelectedViews() + } + + override fun onFirstDateSelected(startDate: Calendar) { + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + selectedEndDate = dateFormat.format(startDate.time) + date.text = convertDateFormat(selectedEndDate) + + checkBox.setOnClickListener { + calendar.resetAllSelectedViews() + selectedEndDate = null.toString() + date.text = "모름" + } + + if (date.text != "모름") { + checkBox.isChecked = false + } + } + }, + ) + } + + private fun updateSelectedIntakePeriod() { + binding.apply { + updateSelectedIntakePeriodStartDate( + calendarviewFeedaddeditStartDate, + textviewFeedaddeditIntakePeriodStart, + ) + updateSelectedIntakePeriodEndDate( + calendarviewFeedaddeditEndDate, + textviewFeedaddeditIntakePeriodEnd, + checkboxFeedaddeditDoNotKnowEndDate, + ) + } + } + + private fun initInputMethodManager() { + thread { + SystemClock.sleep(1000) + inputMethodManager = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + hideSoftKeyboard() + } + } + + private fun hideSoftKeyboard() { + if (requireActivity().currentFocus != null) { + inputMethodManager.hideSoftInputFromWindow(requireActivity().currentFocus!!.windowToken, 0) + requireActivity().currentFocus!!.clearFocus() + } + } + + private fun createFeedInfo() { + binding.apply { + val brand = edittextFeedaddeditBrand.text.toString() + val feedName = edittextFeedaddeditName.text.toString() + val protein = edittextFeedaddeditCrudeProteinPercentage.text.toString() + val fat = edittextFeedaddeditCrudeFatPercent.text.toString() + val crudeAsh = edittextFeedaddeditCrudeAshPercent.text.toString() + val moisture = edittextFeedaddeditMoisturePercent.text.toString() + val kcal = edittextFeedaddeditKcalContent.text.toString() + feedInfo = + FeedInfo( + 2L, + brand, + feedName, + protein.toDouble(), + fat.toDouble(), + crudeAsh.toDouble(), + moisture.toDouble(), + kcal.toDouble(), + recommendIntake.toInt(), + selectedStartDate, + selectedEndDate, + ) + imageUri = feedPostViewModel.feedImage.value + } + } + + private fun postFeedInfo() { + binding.buttonFeedaddeditCompletion.setOnClickListener { + createFeedInfo() + val dto = convertFeedPostDto(feedInfo) + val file = + convertFeedFile( + requireContext(), + imageUri ?: Uri.EMPTY, + ) + val uploadRequest = FeedUploadRequest(dto, file) + + feedPostViewModel.postFeed( + uploadRequest, + ) + feedPostViewModel.feedPosted.observe(viewLifecycleOwner) { response -> + if (response == SUCCESS) { + findNavController().popBackStack() + Snackbar.make(requireView(), "사료가 등록되었습니다!", Snackbar.LENGTH_SHORT).show() + } + } + } + } + + override fun onUriPassed(uri: Uri) { + feedPostViewModel.getFeedImage(uri) + binding.apply { + Glide.with(this@FeedAddFragment) + .load(uri) + .into(imageviewFeedaddeditPicture) + layoutFeedaddeditImage.root.visibility = View.INVISIBLE + } + } + + fun convertDateFormat(date: String): String { + val outputFormat = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일") + + val parsedDate = LocalDate.parse(date) + return outputFormat.format(parsedDate) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} 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 new file mode 100644 index 000000000..c8a4be6ac --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/view/FeedEditFragment.kt @@ -0,0 +1,371 @@ +package com.project.meongcare.feed.view + +import android.content.Context +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.SystemClock +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.CheckBox +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +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.google.android.material.snackbar.Snackbar +import com.project.meongcare.R +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.utils.FeedDateUtils.convertDateFormat +import com.project.meongcare.feed.model.utils.FeedInfoUtils.convertFeedFile +import com.project.meongcare.feed.model.utils.FeedInfoUtils.convertFeedPutDto +import com.project.meongcare.feed.viewmodel.FeedPutViewModel +import dagger.hilt.android.AndroidEntryPoint +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale +import kotlin.concurrent.thread + +@AndroidEntryPoint +class FeedEditFragment : Fragment(), FeedPhotoListener { + private var _binding: FragmentFeedAddEditBinding? = null + val binding + get() = _binding!! + + private var feedId = 0L + private var feedRecordId = 0L + private lateinit var feedInfo: FeedDetailGetResponse + private lateinit var feedPutInfo: FeedPutInfo + private var recommendIntake = 0.0 + var selectedStartDate = "" + var selectedEndDate = "" + + private val feedPutViewModel: FeedPutViewModel by viewModels() + private lateinit var inputMethodManager: InputMethodManager + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = FragmentFeedAddEditBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + initInputMethodManager() + feedId = getFeedId() + feedRecordId = getFeedRecordId() + feedInfo = getFeedInfo() + initToolbar() + fetchFeedInfo() + initPhotoAttachModalBottomSheet() + applyKcalContentEditorBehavior() + updateCalendarVisibility() + updateSelectedIntakePeriod() + editFeedInfo() + } + + private fun fetchFeedInfo() { + binding.apply { + if (feedInfo.imageURL.isNotEmpty()) { + Glide.with(this@FeedEditFragment) + .load(feedInfo.imageURL) + .into(imageviewFeedaddeditPicture) + layoutFeedaddeditImage.root.visibility = View.INVISIBLE + } + edittextFeedaddeditBrand.setText(feedInfo.brand) + edittextFeedaddeditName.setText(feedInfo.feedName) + edittextFeedaddeditCrudeProteinPercentage.setText(feedInfo.protein.toString()) + edittextFeedaddeditCrudeFatPercent.setText(feedInfo.fat.toString()) + edittextFeedaddeditCrudeAshPercent.setText(feedInfo.crudeAsh.toString()) + edittextFeedaddeditMoisturePercent.setText(feedInfo.moisture.toString()) + edittextFeedaddeditKcalContent.setText(feedInfo.kcal.toString()) + textviewFeedaddeditDailyIntakeContent.text = "${feedInfo.recommendIntake}g" + textviewFeedaddeditIntakePeriodStart.apply { + text = convertDateFormat(feedInfo.startDate) + setTextColor(resources.getColor(R.color.black, null)) + } + textviewFeedaddeditIntakePeriodEnd.apply { + text = + if (feedInfo.endDate != null) { + convertDateFormat(feedInfo.endDate) + } else { + "모름" + } + setTextColor(resources.getColor(R.color.black, null)) + } + } + } + + private fun initToolbar() { + binding.toolbarFeedaddedit.apply { + setNavigationOnClickListener { + findNavController().popBackStack() + } + } + } + + private fun initPhotoAttachModalBottomSheet() { + binding.cardviewFeedaddeditImage.setOnClickListener { + val photoAttachModalBottomSheet = FeedPhotoAttachModalBottomSheetFragment() + photoAttachModalBottomSheet.setPhotoListener(this@FeedEditFragment) + photoAttachModalBottomSheet.show( + requireActivity().supportFragmentManager, + FeedPhotoAttachModalBottomSheetFragment.TAG, + ) + } + } + + private fun applyKcalContentEditorBehavior() { + binding.edittextFeedaddeditKcalContent.apply { + setOnEditorActionListener { _, _, _ -> + initRecommendDailyIntake(text.toString().toDouble()) + hideSoftKeyboard() + true + } + } + } + + private fun initRecommendDailyIntake(feedKcal: Double) { + val weight = 15.0 + recommendIntake = calculateRecommendDailyIntake(weight, feedKcal) + binding.textviewFeedaddeditDailyIntakeContent.text = "${recommendIntake}g" + } + + private fun calculateRecommendDailyIntake( + weight: Double, + feedKcal: Double, + ): Double { + val dailyEnergyRequirement = 1.6 * (30 * weight + 70) + val recommendDailyIntake = dailyEnergyRequirement * 1000 / feedKcal + return String.format("%.2f", recommendDailyIntake).toDouble() + } + + private fun updateCalendarVisibility() { + binding.apply { + textviewFeedaddeditIntakePeriodStart.apply { + setOnClickListener { + setTextColor(resources.getColor(R.color.black, null)) + calendarviewFeedaddeditStartDate.visibility = View.VISIBLE + calendarviewFeedaddeditEndDate.visibility = View.GONE + checkboxFeedaddeditDoNotKnowEndDate.visibility = View.GONE + textviewFeedaddeditDoNotKnowEndDate.visibility = View.GONE + textviewFeedaddeditIntakePeriodEnd.setTextColor( + resources.getColor( + R.color.gray4, + null, + ), + ) + } + } + textviewFeedaddeditIntakePeriodEnd.apply { + setOnClickListener { + setTextColor(resources.getColor(R.color.black, null)) + calendarviewFeedaddeditEndDate.visibility = View.VISIBLE + calendarviewFeedaddeditStartDate.visibility = View.INVISIBLE + checkboxFeedaddeditDoNotKnowEndDate.visibility = View.VISIBLE + textviewFeedaddeditDoNotKnowEndDate.visibility = View.VISIBLE + textviewFeedaddeditIntakePeriodStart.setTextColor( + resources.getColor( + R.color.gray4, + null, + ), + ) + } + } + } + } + + private fun updateSelectedIntakePeriodStartDate( + calendar: DateRangeCalendarView, + date: TextView, + ) { + calendar.setCalendarListener( + object : CalendarListener { + override fun onDateRangeSelected( + startDate: Calendar, + endDate: Calendar, + ) { + calendar.resetAllSelectedViews() + } + + override fun onFirstDateSelected(startDate: Calendar) { + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + selectedStartDate = dateFormat.format(startDate.time) + + date.text = convertDateFormat(selectedStartDate) + } + }, + ) + } + + private fun updateSelectedIntakePeriodEndDate( + calendar: DateRangeCalendarView, + date: TextView, + checkBox: CheckBox, + ) { + calendar.setCalendarListener( + object : CalendarListener { + override fun onDateRangeSelected( + startDate: Calendar, + endDate: Calendar, + ) { + calendar.resetAllSelectedViews() + } + + override fun onFirstDateSelected(startDate: Calendar) { + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + selectedEndDate = dateFormat.format(startDate.time) + date.text = convertDateFormat(selectedEndDate) + + checkBox.setOnClickListener { + calendar.resetAllSelectedViews() + selectedEndDate = null.toString() + date.text = "모름" + } + + if (date.text != "모름") { + checkBox.isChecked = false + } + } + }, + ) + } + + private fun updateSelectedIntakePeriod() { + binding.apply { + updateSelectedIntakePeriodStartDate( + calendarviewFeedaddeditStartDate, + textviewFeedaddeditIntakePeriodStart, + ) + updateSelectedIntakePeriodEndDate( + calendarviewFeedaddeditEndDate, + textviewFeedaddeditIntakePeriodEnd, + checkboxFeedaddeditDoNotKnowEndDate, + ) + } + } + + private fun createFeedInfo() { + binding.apply { + val brand = edittextFeedaddeditBrand.text.toString() + val feedName = edittextFeedaddeditName.text.toString() + val protein = edittextFeedaddeditCrudeProteinPercentage.text.toString() + val fat = edittextFeedaddeditCrudeFatPercent.text.toString() + val crudeAsh = edittextFeedaddeditCrudeAshPercent.text.toString() + val moisture = edittextFeedaddeditMoisturePercent.text.toString() + val kcal = edittextFeedaddeditKcalContent.text.toString() + feedPutInfo = + FeedPutInfo( + feedId, + brand, + feedName, + protein.toDouble(), + fat.toDouble(), + crudeAsh.toDouble(), + moisture.toDouble(), + kcal.toDouble(), + recommendIntake.toInt(), + selectedStartDate, + selectedEndDate, + feedRecordId, + ) + } + } + + private fun editFeedInfo() { + binding.apply { + buttonFeedaddeditCompletion.setOnClickListener { + createFeedInfo() + val imageUri = feedPutViewModel.feedImage.value + + val dto = convertFeedPutDto(feedPutInfo) + val file = + convertFeedFile( + requireContext(), + imageUri ?: Uri.EMPTY, + ) + + val feedUploadRequest = + FeedUploadRequest( + dto, + file, + ) + feedPutViewModel.putFeed(feedUploadRequest) + feedPutViewModel.feedPut.observe(viewLifecycleOwner) { response -> + if (response == SUCCESS) { + findNavController().popBackStack() + Snackbar.make(requireView(), "사료 정보가 수정되었습니다!", Snackbar.LENGTH_SHORT) + .show() + } else { + Snackbar.make( + requireView(), + "서버가 불안정 하여 사료 정보 수정에 실패하였습니다.\n잠시 후 다시 시도해 주세요.", + Snackbar.LENGTH_SHORT, + ).show() + } + } + } + } + } + + private fun initInputMethodManager() { + thread { + SystemClock.sleep(1000) + inputMethodManager = + requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + hideSoftKeyboard() + } + } + + private fun hideSoftKeyboard() { + if (requireActivity().currentFocus != null) { + inputMethodManager.hideSoftInputFromWindow( + requireActivity().currentFocus!!.windowToken, + 0, + ) + requireActivity().currentFocus!!.clearFocus() + } + } + + private fun getFeedId() = arguments?.getLong("feedId")!! + + private fun getFeedRecordId() = arguments?.getLong("feedRecordId")!! + + private fun getFeedInfo() = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + arguments?.getParcelable("feedInfo", FeedDetailGetResponse::class.java)!! + } else { + arguments?.getParcelable("feedInfo")!! + } + + override fun onUriPassed(uri: Uri) { + feedPutViewModel.getImageFeed(uri) + binding.apply { + Glide.with(this@FeedEditFragment) + .load(uri) + .into(imageviewFeedaddeditPicture) + layoutFeedaddeditImage.root.visibility = View.INVISIBLE + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/app/src/main/java/com/project/meongcare/feed/view/FeedFragment.kt b/app/src/main/java/com/project/meongcare/feed/view/FeedFragment.kt new file mode 100644 index 000000000..3eb86d14b --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/view/FeedFragment.kt @@ -0,0 +1,243 @@ +package com.project.meongcare.feed.view + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.LinearLayoutManager +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.data.PieData +import com.github.mikephil.charting.data.PieDataSet +import com.github.mikephil.charting.data.PieEntry +import com.project.meongcare.R +import com.project.meongcare.databinding.FragmentFeedBinding +import com.project.meongcare.databinding.LayoutFeedNutrientBinding +import com.project.meongcare.feed.model.entities.FeedGetResponse +import com.project.meongcare.feed.viewmodel.FeedGetViewModel +import com.project.meongcare.feed.viewmodel.FeedPartGetViewModel +import dagger.hilt.android.AndroidEntryPoint +import kotlin.math.roundToInt + +@AndroidEntryPoint +class FeedFragment : Fragment() { + private var _binding: FragmentFeedBinding? = null + val binding get() = _binding!! + + private val feedGetViewModel: FeedGetViewModel by viewModels() + private val feedPartGetViewModel: FeedPartGetViewModel by viewModels() + private lateinit var feedPartAdapter: FeedPartAdapter + private lateinit var feedGetResponse: FeedGetResponse + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = FragmentFeedBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + feedPartAdapter = FeedPartAdapter() + feedGetViewModel.getFeed() + feedGetViewModel.feedGet.observe(viewLifecycleOwner) { response -> + feedGetResponse = + FeedGetResponse( + response.brand, + response.feedName, + response.protein, + response.fat, + response.crudeAsh, + response.moisture, + response.days, + response.recommendIntake, + response.feedId, + response.feedRecordId, + ) + updateViewVisibilityBasedOnFeedExist(feedGetResponse.feedId) + initFeedInfo(feedGetResponse.brand!!, feedGetResponse.feedName!!) + initNutrientPieChart( + feedGetResponse.protein, + feedGetResponse.fat, + feedGetResponse.crudeAsh, + feedGetResponse.moisture, + ) + initNutrientTable(feedGetResponse) + initIntakePeriod(feedGetResponse.days) + initDailyRecommendIntake(feedGetResponse.recommendIntake) + updateViewVisibilityBasedOnOldFeedPartExist(feedGetResponse.feedRecordId) + initOldFeedSeeMoreButton(feedGetResponse.feedRecordId) + } + initFeedAddButton() + initOldFeedPartRecyclerView() + initChangeButton() + } + + private fun updateViewVisibilityBasedOnFeedExist(feedId: Long) { + binding.apply { + if (feedId == 0L) { + textviewFeedBrand.visibility = View.GONE + textviewFeedName.visibility = View.GONE + piechartFeedNutrient.visibility = View.GONE + } else { + imageviewFeedBowlIllustration.visibility = View.GONE + buttonFeedInputGuide.visibility = View.GONE + } + } + } + + private fun initFeedAddButton() { + binding.buttonFeedInputGuide.setOnClickListener { + findNavController().navigate(R.id.action_feedFragment_to_feedAddFragment) + } + } + + private fun initFeedInfo( + brand: String, + feedName: String, + ) { + binding.textviewFeedBrand.text = brand + binding.textviewFeedName.text = feedName + } + + private fun initNutrientPieChart( + protein: Double, + fat: Double, + crudeAsh: Double, + moisture: Double, + ) { + val nutrientRatio = + listOf( + PieEntry(protein.toFloat()), + PieEntry(fat.toFloat()), + PieEntry(crudeAsh.toFloat()), + PieEntry(moisture.toFloat()), + ) + + val pieColors = + listOf( + ContextCompat.getColor(requireContext(), R.color.main3), + ContextCompat.getColor(requireContext(), R.color.sub7), + ContextCompat.getColor(requireContext(), R.color.sub6), + ContextCompat.getColor(requireContext(), R.color.sub8), + ) + + val dataSet = PieDataSet(nutrientRatio, "") + dataSet.colors = pieColors + dataSet.setDrawValues(false) + + binding.piechartFeedNutrient.apply { + data = PieData(dataSet) + description.isEnabled = false + legend.isEnabled = false + isRotationEnabled = true + holeRadius = 60f + setTouchEnabled(false) + animateY(1200, Easing.EaseInOutCubic) + animate() + } + } + + private fun initNutrientTable(feedInfo: FeedGetResponse) { + binding.run { + initNutrientRow( + includeFeedNutrientCrudeProtein, + R.drawable.feed_rect_crude_protein_r5, + "조단백", + feedInfo.protein, + ) + initNutrientRow( + includeFeedNutrientCrudeFat, + R.drawable.feed_rect_crude_fat_r5, + "조지방", + feedInfo.fat, + ) + initNutrientRow( + includeFeedNutrientCrudeAsh, + R.drawable.feed_rect_crude_ash_r5, + "조회분", + feedInfo.crudeAsh, + ) + initNutrientRow( + includeFeedNutrientMoisture, + R.drawable.feed_rect_moisture_r5, + "수분", + feedInfo.moisture, + ) + } + } + + private fun initNutrientRow( + nutrientRow: LayoutFeedNutrientBinding, + nutrientColorLabel: Int, + nutrientType: String, + nutrientPercentage: Double, + ) { + nutrientRow.apply { + viewFeedNutrientColorLabel.setBackgroundResource(nutrientColorLabel) + textviewFeedNutrientType.text = nutrientType + textviewFeedNutrientPercentage.text = convertNutrientPercentage(nutrientPercentage.roundToInt()) + } + } + + private fun initIntakePeriod(days: Long) { + binding.textviewFeedIntakePeriodContent.text = convertIntakePeriod(days) + } + + private fun initDailyRecommendIntake(recommendIntake: Int) { + binding.textviewFeedDailyIntakeContent.text = convertDailyRecommendIntake(recommendIntake) + } + + private fun updateViewVisibilityBasedOnOldFeedPartExist(feedRecordId: Long) { + feedPartGetViewModel.getFeedPart(feedRecordId) + feedPartGetViewModel.feedPartGet.observe(viewLifecycleOwner) { response -> + if (response.feedPartRecords.isEmpty()) { + binding.apply { + textviewFeedOldFeedSeeMore.visibility = View.GONE + buttonFeedChange.visibility = View.GONE + } + } + feedPartAdapter.submitList(response.feedPartRecords) + } + } + + private fun initOldFeedSeeMoreButton(feedRecordId: Long) { + binding.textviewFeedOldFeedSeeMore.setOnClickListener { + val bundle = Bundle() + bundle.putLong("feedRecordId", feedRecordId) + findNavController().navigate(R.id.action_feedFragment_to_oldFeedFragment, bundle) + } + } + + private fun initOldFeedPartRecyclerView() { + binding.recyclerviewFeedOldfeed.run { + adapter = feedPartAdapter + layoutManager = LinearLayoutManager(context) + } + } + + private fun initChangeButton() { + binding.buttonFeedChange.setOnClickListener { + findNavController().navigate(R.id.action_feedFragment_to_searchFeedFragment) + } + } + + private fun convertNutrientPercentage(nutrientPercentage: Int) = String.format("%d%%", nutrientPercentage) + + private fun convertIntakePeriod(days: Long) = String.format("%d일", days) + + private fun convertDailyRecommendIntake(recommendIntake: Int) = String.format("%dg", recommendIntake) + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} 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 new file mode 100644 index 000000000..051ba3d91 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/view/FeedInfoFragment.kt @@ -0,0 +1,144 @@ +package com.project.meongcare.feed.view + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import com.bumptech.glide.Glide +import com.google.android.material.snackbar.Snackbar +import com.project.meongcare.R +import com.project.meongcare.databinding.FragmentFeedInfoBinding +import com.project.meongcare.excreta.utils.SUCCESS +import com.project.meongcare.feed.model.entities.FeedDetailGetResponse +import com.project.meongcare.feed.model.utils.FeedDateUtils.convertDateFormat +import com.project.meongcare.feed.viewmodel.FeedDeleteViewModel +import com.project.meongcare.feed.viewmodel.FeedDetailGetViewModel +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class FeedInfoFragment : Fragment() { + private var _binding: FragmentFeedInfoBinding? = null + val binding get() = _binding!! + + private val feedInfoFeedDetailGetViewModel: FeedDetailGetViewModel by viewModels() + private val feedDeleteViewModel: FeedDeleteViewModel by viewModels() + + private var feedId = 0L + private var feedRecordId = 0L + private lateinit var feedInfo: FeedDetailGetResponse + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = FragmentFeedInfoBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + feedId = getFeedId() + feedRecordId = getFeedRecordId() + feedInfoFeedDetailGetViewModel.getFeedDetail( + feedId, + feedRecordId, + ) + initToolbar() + fetchFeedInfo() + } + + private fun fetchFeedInfo() { + feedInfoFeedDetailGetViewModel.feedDetailGet.observe(viewLifecycleOwner) { response -> + binding.apply { + feedInfo = + FeedDetailGetResponse( + response.brand, + response.feedName, + response.protein, + response.fat, + response.crudeAsh, + response.moisture, + response.kcal, + response.recommendIntake, + response.imageURL, + response.startDate, + response.endDate, + ) + if (response.imageURL.isNotEmpty()) { + Glide.with(this@FeedInfoFragment) + .load(response.imageURL) + .into(imageviewFeedinfo) + } + textviewFeedinfoBrandContent.text = feedInfo.brand + textviewFeedinfoNameContent.text = feedInfo.feedName + textviewFeedinfoCrudeProteinRatio.text = feedInfo.protein.toString() + textviewFeedinfoCrudeFatRatio.text = feedInfo.fat.toString() + textviewFeedinfoMoistureRatio.text = feedInfo.moisture.toString() + textviewFeedinfoCrudeAshRatio.text = feedInfo.crudeAsh.toString() + textviewFeedinfoKcalContent.text = feedInfo.kcal.toString() + textviewFeedinfoDailyIntakeContent.text = feedInfo.recommendIntake.toString() + textviewFeedinfoIntakePeriodStart.text = convertDateFormat(feedInfo.startDate) + if (feedInfo.endDate != null) { + textviewFeedinfoIntakePeriodEnd.text = convertDateFormat(feedInfo.endDate) + } else { + textviewFeedinfoIntakePeriodEnd.text = "모름" + } + } + } + } + + private fun initToolbar() { + binding.toolbarFeedinfo.apply { + setNavigationOnClickListener { + findNavController().popBackStack() + } + setOnMenuItemClickListener { menu -> + when (menu.itemId) { + R.id.menu_info_edit -> { + val bundle = + bundleOf( + "feedId" to feedId, + "feedRecordId" to feedRecordId, + "feedInfo" to feedInfo, + ) + findNavController().navigate(R.id.action_feedInfoFragment_to_feedEditFragment, bundle) + } + R.id.menu_info_delete -> { + // 삭제 다이얼로그 추가 필요 + feedDeleteViewModel.deleteFeed(feedId) + feedDeleteViewModel.feedDeleted.observe(viewLifecycleOwner) { response -> + if (response == SUCCESS) { + findNavController().popBackStack() + Snackbar.make(requireView(), "사료 정보를 삭제하였습니다!", Snackbar.LENGTH_SHORT).show() + } else { + Snackbar.make( + requireView(), + "서버가 불안정 하여 사료 정보 삭제에 실패하였습니다.\n잠시 후 다시 시도해 주세요.", + Snackbar.LENGTH_SHORT, + ).show() + } + } + } + } + false + } + } + } + + private fun getFeedId() = arguments?.getLong("feedId")!! + + private fun getFeedRecordId() = arguments?.getLong("feedRecordId")!! + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} 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 new file mode 100644 index 000000000..69437af01 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/view/FeedPartAdapter.kt @@ -0,0 +1,69 @@ +package com.project.meongcare.feed.view + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.project.meongcare.databinding.ItemFeedBinding +import com.project.meongcare.feed.model.entities.FeedPartRecord + +class FeedPartAdapter : ListAdapter(diffUtil) { + inner class FeedPartViewHolder(private val binding: ItemFeedBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(item: FeedPartRecord) { + binding.run { + if (item.feedImageURL.isNotEmpty()) { + Glide.with(itemView) + .load(item.feedImageURL) + .into(imageviewFeed) + } + val period = "${item.startDate}~ ${item.endDate}" + textviewFeedBrand.text = item.brandName + textviewFeedName.text = item.feedName + textviewFeedDate.text = period + } + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): FeedPartViewHolder { + val itemFeedBinding = + ItemFeedBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ) + + return FeedPartViewHolder(itemFeedBinding) + } + + override fun onBindViewHolder( + holder: FeedPartViewHolder, + position: Int, + ) { + holder.bind(currentList[position]) + } + + companion object { + val diffUtil = + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: FeedPartRecord, + newItem: FeedPartRecord, + ): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame( + oldItem: FeedPartRecord, + newItem: FeedPartRecord, + ): Boolean { + return oldItem == newItem + } + } + } +} diff --git a/app/src/main/java/com/project/meongcare/feed/view/FeedPhotoAttachModalBottomSheetFragment.kt b/app/src/main/java/com/project/meongcare/feed/view/FeedPhotoAttachModalBottomSheetFragment.kt new file mode 100644 index 000000000..003812d0f --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/view/FeedPhotoAttachModalBottomSheetFragment.kt @@ -0,0 +1,122 @@ +package com.project.meongcare.feed.view + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.os.Environment +import android.provider.MediaStore +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.FileProvider +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.project.meongcare.databinding.FragmentPhotoSelectBottomSheetBinding +import com.project.meongcare.feed.model.data.local.FeedPhotoListener +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class FeedPhotoAttachModalBottomSheetFragment : BottomSheetDialogFragment() { + private var _binding: FragmentPhotoSelectBottomSheetBinding? = null + val binding get() = _binding!! + + private var photoListener: FeedPhotoListener? = null + private lateinit var photoURI: Uri + lateinit var file: File + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = FragmentPhotoSelectBottomSheetBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + binding.apply { + textviewSelectCamera.setOnClickListener { + openCamera() + } + textviewSelectAlbum.setOnClickListener { + openAlbum() + } + } + } + + private val cameraLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + sendUri(photoURI) + dismiss() + } + } + + private val albumLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + result.data?.data?.let { uri -> + sendUri(uri) + dismiss() + } + } + } + + private fun photoFile(): File { + val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val storageDir: File = + requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES)?.let { + File(it, "Feed") + } ?: throw Exception("Cannot access external storage.") + + storageDir.mkdirs() + return File.createTempFile( + "FEED_${timeStamp}_", + ".jpg", + storageDir, + ) + } + + private fun openCamera() { + val file = photoFile() + photoURI = + FileProvider.getUriForFile( + requireContext(), + "com.project.meongcare", + file, + ) + val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) + intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI) + cameraLauncher.launch(intent) + } + + private fun openAlbum() { + val intent = Intent(Intent.ACTION_PICK) + intent.type = "image/*" + albumLauncher.launch(intent) + } + + private fun sendUri(uri: Uri) { + photoListener?.onUriPassed(uri) + } + + fun setPhotoListener(listener: FeedPhotoListener) { + this.photoListener = listener + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + companion object { + const val TAG = "ModalBottomSheet" + } +} diff --git a/app/src/main/java/com/project/meongcare/feed/view/FeedsAdapter.kt b/app/src/main/java/com/project/meongcare/feed/view/FeedsAdapter.kt new file mode 100644 index 000000000..a7ff1901e --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/view/FeedsAdapter.kt @@ -0,0 +1,75 @@ +package com.project.meongcare.feed.view + +import android.util.Log +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.project.meongcare.databinding.ItemSearchFeedBinding +import com.project.meongcare.feed.model.data.local.FeedItemSelectionListener +import com.project.meongcare.feed.model.entities.Feed + +class FeedsAdapter( + private val feedItemSelectionListener: FeedItemSelectionListener, +) : ListAdapter(diffUtil) { + inner class FeedsViewHolder(private val binding: ItemSearchFeedBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(item: Feed) { + binding.run { + if (item.imageURL.isNotEmpty()) { + Glide.with(itemView) + .load(item.imageURL) + .into(imageviewOldfeed) + } + textviewSearchfeedBrand.text = item.brandName + textviewSearchfeedName.text = item.feedName + root.setOnClickListener { + Log.d("feedId", item.feedId.toString()) + feedItemSelectionListener.onItemSelection(item.feedId) + } + } + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): FeedsViewHolder { + val itemSearchFeedBinding = + ItemSearchFeedBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ) + + return FeedsViewHolder(itemSearchFeedBinding) + } + + override fun onBindViewHolder( + holder: FeedsViewHolder, + position: Int, + ) { + holder.bind(currentList[position]) + } + + companion object { + val diffUtil = + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: Feed, + newItem: Feed, + ): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame( + oldItem: Feed, + newItem: Feed, + ): Boolean { + return oldItem == newItem + } + } + } +} diff --git a/app/src/main/java/com/project/meongcare/feed/view/OldFeedFragment.kt b/app/src/main/java/com/project/meongcare/feed/view/OldFeedFragment.kt new file mode 100644 index 000000000..802dcb028 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/view/OldFeedFragment.kt @@ -0,0 +1,66 @@ +package com.project.meongcare.feed.view + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.LinearLayoutManager +import com.project.meongcare.databinding.FragmentOldFeedBinding +import com.project.meongcare.feed.viewmodel.PreviousFeedGetViewModel +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class OldFeedFragment : Fragment() { + private var _binding: FragmentOldFeedBinding? = null + val binding get() = _binding!! + + private val previousFeedGetViewModel: PreviousFeedGetViewModel by viewModels() + private lateinit var previousFeedAdapter: PreviousFeedAdapter + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = FragmentOldFeedBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + val feedRecordId = getFeedRecordId() + previousFeedAdapter = PreviousFeedAdapter() + previousFeedGetViewModel.getPreviousFeed(feedRecordId) + previousFeedGetViewModel.previousFeedGet.observe(viewLifecycleOwner) { response -> + previousFeedAdapter.submitList(response.feedRecords) + } + initToolbar() + initPreviousFeedRecyclerView() + } + + private fun initToolbar() { + binding.toolbarOldfeed.setNavigationOnClickListener { + findNavController().popBackStack() + } + } + + private fun initPreviousFeedRecyclerView() { + binding.recyclerviewOldfeed.run { + adapter = previousFeedAdapter + layoutManager = LinearLayoutManager(context) + } + } + + private fun getFeedRecordId() = arguments?.getLong("feedRecordId")!! + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/app/src/main/java/com/project/meongcare/feed/view/PreviousFeedAdapter.kt b/app/src/main/java/com/project/meongcare/feed/view/PreviousFeedAdapter.kt new file mode 100644 index 000000000..cb10cd541 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/view/PreviousFeedAdapter.kt @@ -0,0 +1,78 @@ +package com.project.meongcare.feed.view + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.navigation.findNavController +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.project.meongcare.R +import com.project.meongcare.databinding.ItemOldFeedBinding +import com.project.meongcare.feed.model.entities.FeedRecord + +class PreviousFeedAdapter : ListAdapter(diffUtil) { + inner class PreviousFeedViewHolder(private val binding: ItemOldFeedBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(item: FeedRecord) { + binding.run { + if (item.imageURL.isNotEmpty()) { + Glide.with(itemView) + .load(item.imageURL) + .into(imageviewOldfeed) + } + val period = "${item.startDate}~ ${item.endDate}" + textviewOldfeedBrand.text = item.brand + textviewOldfeedName.text = item.feedName + textviewOldfeedDate.text = period + root.setOnClickListener { + val bundle = Bundle() + bundle.putLong("feedId", item.feedId) + bundle.putLong("feedRecordId", item.feedRecordId) + it.findNavController().navigate(R.id.action_oldFeedFragment_to_feedInfoFragment, bundle) + } + } + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): PreviousFeedViewHolder { + val itemOldFeedBinding = + ItemOldFeedBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ) + + return PreviousFeedViewHolder(itemOldFeedBinding) + } + + override fun onBindViewHolder( + holder: PreviousFeedViewHolder, + position: Int, + ) { + holder.bind(currentList[position]) + } + + companion object { + val diffUtil = + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: FeedRecord, + newItem: FeedRecord, + ): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame( + oldItem: FeedRecord, + newItem: FeedRecord, + ): Boolean { + return oldItem == newItem + } + } + } +} diff --git a/app/src/main/java/com/project/meongcare/feed/view/SearchFeedFragment.kt b/app/src/main/java/com/project/meongcare/feed/view/SearchFeedFragment.kt new file mode 100644 index 000000000..38f117328 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/view/SearchFeedFragment.kt @@ -0,0 +1,107 @@ +package com.project.meongcare.feed.view + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.widget.doAfterTextChanged +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.LinearLayoutManager +import com.project.meongcare.R +import com.project.meongcare.databinding.FragmentSearchFeedBinding +import com.project.meongcare.excreta.utils.SUCCESS +import com.project.meongcare.feed.model.data.local.FeedItemSelectionListener +import com.project.meongcare.feed.model.entities.FeedPatchRequest +import com.project.meongcare.feed.viewmodel.FeedPatchViewModel +import com.project.meongcare.feed.viewmodel.FeedsGetViewModel +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class SearchFeedFragment : Fragment() { + private var _binding: FragmentSearchFeedBinding? = null + val binding get() = _binding!! + + private val feedsGetViewModel: FeedsGetViewModel by viewModels() + private val feedPatchViewModel: FeedPatchViewModel by viewModels() + private lateinit var feedsAdapter: FeedsAdapter + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = FragmentSearchFeedBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + feedsAdapter = + FeedsAdapter( + object : FeedItemSelectionListener { + override fun onItemSelection(feedId: Long) { + patchFeed(feedId) + } + }, + ) + + initToolbar() + initFeedsRecyclerView() + initDirectInputButton() + updateSearchResult() + feedsGetViewModel.getFeeds() + feedsGetViewModel.feedsGet.observe(viewLifecycleOwner) { response -> + feedsAdapter.submitList(response.feeds) + } + } + + private fun initToolbar() { + binding.toolbarSearchfeed.setNavigationOnClickListener { + findNavController().popBackStack() + } + } + + private fun updateSearchResult() { + binding.edittextSearchfeedSearch.doAfterTextChanged { + val searchText = it.toString() + feedsGetViewModel.filterFeeds(searchText) + } + } + + private fun initFeedsRecyclerView() { + binding.recyclerviewSearchfeedResult.apply { + adapter = feedsAdapter + layoutManager = LinearLayoutManager(context) + } + } + + private fun patchFeed(newFeedId: Long) { + val feedPatchRequest = + FeedPatchRequest( + 2L, + newFeedId, + ) + feedPatchViewModel.patchFeed(feedPatchRequest) + feedPatchViewModel.feedPatched.observe(viewLifecycleOwner) { response -> + if (response == SUCCESS) { + findNavController().popBackStack() + } + } + } + + private fun initDirectInputButton() { + binding.extendedfloatingbuttonSearchfeedDirectInput.setOnClickListener { + findNavController().navigate(R.id.action_searchFeedFragment_to_feedAddFragment) + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedDeleteViewModel.kt b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedDeleteViewModel.kt new file mode 100644 index 000000000..feb9056fa --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedDeleteViewModel.kt @@ -0,0 +1,27 @@ +package com.project.meongcare.feed.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.project.meongcare.feed.model.data.repository.FeedRepositoryImpl +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class FeedDeleteViewModel + @Inject + constructor( + private val feedRepositoryImpl: FeedRepositoryImpl, + ) : ViewModel() { + private var _feedDeleted = MutableLiveData() + val feedDeleted + get() = _feedDeleted + + fun deleteFeed(feedId: Long) { + viewModelScope.launch { + _feedDeleted.value = + feedRepositoryImpl.deleteFeed(feedId) + } + } + } diff --git a/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedDetailGetViewModel.kt b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedDetailGetViewModel.kt new file mode 100644 index 000000000..4acbf2b88 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedDetailGetViewModel.kt @@ -0,0 +1,34 @@ +package com.project.meongcare.feed.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.project.meongcare.feed.model.data.repository.FeedRepositoryImpl +import com.project.meongcare.feed.model.entities.FeedDetailGetResponse +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class FeedDetailGetViewModel + @Inject + constructor( + private val feedRepositoryImpl: FeedRepositoryImpl, + ) : ViewModel() { + private var _feedDetailGet = MutableLiveData() + val feedDetailGet + get() = _feedDetailGet + + fun getFeedDetail( + feedId: Long, + feedRecordId: Long, + ) { + viewModelScope.launch { + _feedDetailGet.value = + feedRepositoryImpl.getDetailFeed( + feedId, + feedRecordId, + ) + } + } + } diff --git a/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedGetViewModel.kt b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedGetViewModel.kt new file mode 100644 index 000000000..e3337348f --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedGetViewModel.kt @@ -0,0 +1,28 @@ +package com.project.meongcare.feed.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.project.meongcare.feed.model.data.repository.FeedRepositoryImpl +import com.project.meongcare.feed.model.entities.FeedGetResponse +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class FeedGetViewModel + @Inject + constructor( + private val feedRepositoryImpl: FeedRepositoryImpl, + ) : ViewModel() { + private var _feedGet = MutableLiveData() + val feedGet + get() = _feedGet + + fun getFeed() { + viewModelScope.launch { + _feedGet.value = + feedRepositoryImpl.getFeed() + } + } + } diff --git a/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPartGetViewModel.kt b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPartGetViewModel.kt new file mode 100644 index 000000000..2f7b73e11 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPartGetViewModel.kt @@ -0,0 +1,28 @@ +package com.project.meongcare.feed.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.project.meongcare.feed.model.data.repository.FeedRepositoryImpl +import com.project.meongcare.feed.model.entities.FeedPartRecords +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class FeedPartGetViewModel + @Inject + constructor( + private val feedRepositoryImpl: FeedRepositoryImpl, + ) : ViewModel() { + private var _feedPartGet = MutableLiveData() + val feedPartGet + get() = _feedPartGet + + fun getFeedPart(feedRecordId: Long) { + viewModelScope.launch { + feedPartGet.value = + feedRepositoryImpl.getFeedPart(feedRecordId) + } + } + } diff --git a/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPatchViewModel.kt b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPatchViewModel.kt new file mode 100644 index 000000000..86d48bf0d --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPatchViewModel.kt @@ -0,0 +1,28 @@ +package com.project.meongcare.feed.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.project.meongcare.feed.model.data.repository.FeedRepositoryImpl +import com.project.meongcare.feed.model.entities.FeedPatchRequest +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class FeedPatchViewModel + @Inject + constructor( + private val feedRepositoryImpl: FeedRepositoryImpl, + ) : ViewModel() { + private var _feedPatched = MutableLiveData() + val feedPatched + get() = _feedPatched + + fun patchFeed(feedPatchRequest: FeedPatchRequest) { + viewModelScope.launch { + _feedPatched.value = + feedRepositoryImpl.patchFeed(feedPatchRequest) + } + } + } diff --git a/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPostViewModel.kt b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPostViewModel.kt new file mode 100644 index 000000000..c96674134 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPostViewModel.kt @@ -0,0 +1,37 @@ +package com.project.meongcare.feed.viewmodel + +import android.net.Uri +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.project.meongcare.feed.model.data.repository.FeedRepositoryImpl +import com.project.meongcare.feed.model.entities.FeedUploadRequest +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class FeedPostViewModel + @Inject + constructor( + private val feedRepositoryImpl: FeedRepositoryImpl, + ) : ViewModel() { + private var _feedImage = MutableLiveData() + val feedImage + get() = _feedImage + + private var _feedPosted = MutableLiveData() + val feedPosted + get() = _feedPosted + + fun getFeedImage(uri: Uri) { + feedImage.value = uri + } + + fun postFeed(feedUploadRequest: FeedUploadRequest) { + viewModelScope.launch { + _feedPosted.value = + feedRepositoryImpl.postFeed(feedUploadRequest) + } + } + } diff --git a/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPutViewModel.kt b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPutViewModel.kt new file mode 100644 index 000000000..144b4097a --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedPutViewModel.kt @@ -0,0 +1,39 @@ +package com.project.meongcare.feed.viewmodel + +import android.net.Uri +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.project.meongcare.feed.model.data.repository.FeedRepositoryImpl +import com.project.meongcare.feed.model.entities.FeedUploadRequest +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class FeedPutViewModel + @Inject + constructor( + private val feedRepositoryImpl: FeedRepositoryImpl, + ) : ViewModel() { + private var _feedImage = MutableLiveData() + val feedImage + get() = _feedImage + + private var _feedPut = MutableLiveData() + val feedPut + get() = _feedPut + + fun getImageFeed(uri: Uri) { + viewModelScope.launch { + _feedImage.value = uri + } + } + + fun putFeed(feedUploadRequest: FeedUploadRequest) { + viewModelScope.launch { + _feedPut.value = + feedRepositoryImpl.putFeed(feedUploadRequest) + } + } + } diff --git a/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedsGetViewModel.kt b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedsGetViewModel.kt new file mode 100644 index 000000000..24671ca37 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/viewmodel/FeedsGetViewModel.kt @@ -0,0 +1,38 @@ +package com.project.meongcare.feed.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.project.meongcare.feed.model.data.repository.FeedRepositoryImpl +import com.project.meongcare.feed.model.entities.Feeds +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class FeedsGetViewModel + @Inject + constructor( + private val feedRepositoryImpl: FeedRepositoryImpl, + ) : ViewModel() { + private var _feedsGet = MutableLiveData() + val feedsGet + get() = _feedsGet + + private var allFeeds: Feeds? = null + + fun getFeeds() { + viewModelScope.launch { + allFeeds = feedRepositoryImpl.getFeeds() + _feedsGet.value = allFeeds!! + } + } + + fun filterFeeds(searchText: String) { + val filteredFeeds = + allFeeds?.feeds?.filter { feed -> + feed.brandName.contains(searchText) || feed.feedName.contains(searchText) + } + _feedsGet.value = Feeds(filteredFeeds ?: listOf()) + } + } diff --git a/app/src/main/java/com/project/meongcare/feed/viewmodel/PreviousFeedGetViewModel.kt b/app/src/main/java/com/project/meongcare/feed/viewmodel/PreviousFeedGetViewModel.kt new file mode 100644 index 000000000..3d69ab561 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/feed/viewmodel/PreviousFeedGetViewModel.kt @@ -0,0 +1,28 @@ +package com.project.meongcare.feed.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.project.meongcare.feed.model.data.repository.FeedRepositoryImpl +import com.project.meongcare.feed.model.entities.FeedRecords +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class PreviousFeedGetViewModel + @Inject + constructor( + private val feedRepositoryImpl: FeedRepositoryImpl, + ) : ViewModel() { + private var _previousFeedGet = MutableLiveData() + val previousFeedGet + get() = _previousFeedGet + + fun getPreviousFeed(feedRecordId: Long) { + viewModelScope.launch { + _previousFeedGet.value = + feedRepositoryImpl.getPreviousFeed(feedRecordId) + } + } + } diff --git a/app/src/main/java/com/project/meongcare/home/view/HomeFragment.kt b/app/src/main/java/com/project/meongcare/home/view/HomeFragment.kt index 09538c0dc..9ad173f72 100644 --- a/app/src/main/java/com/project/meongcare/home/view/HomeFragment.kt +++ b/app/src/main/java/com/project/meongcare/home/view/HomeFragment.kt @@ -8,6 +8,8 @@ import android.view.ViewGroup 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.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import com.bumptech.glide.Glide @@ -23,7 +25,8 @@ import com.project.meongcare.login.model.data.local.UserPreferences import com.project.meongcare.onboarding.model.data.local.DateSubmitListener import com.project.meongcare.weight.model.entities.WeightPostRequest import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import java.text.SimpleDateFormat import java.time.LocalDate import java.time.format.DateTimeFormatter @@ -36,6 +39,7 @@ class HomeFragment : Fragment(), DateSubmitListener, DogProfileClickListener, Ho private lateinit var mainActivity: MainActivity private val homeViewModel: HomeViewModel by viewModels() + private lateinit var currentAccessToken: String @Inject lateinit var userPreferences: UserPreferences @@ -43,7 +47,10 @@ class HomeFragment : Fragment(), DateSubmitListener, DogProfileClickListener, Ho @Inject lateinit var dogPreferences: DogPreferences - lateinit var currentAccessToken: String + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + getAccessToken() + } override fun onCreateView( inflater: LayoutInflater, @@ -53,9 +60,6 @@ class HomeFragment : Fragment(), DateSubmitListener, DogProfileClickListener, Ho fragmentHomeBinding = FragmentHomeBinding.inflate(inflater) mainActivity = activity as MainActivity - currentAccessToken = "Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNzAyOTc0NzE4fQ.nkypezwWCEiomJSQz2t24cyijjtBtyBqoZsOyi2uudo" -// getAccessToken() - homeViewModel.homeProfileResponse.observe(viewLifecycleOwner) { homeProfileResponse -> if (homeProfileResponse != null) { Glide.with(this) @@ -119,7 +123,7 @@ class HomeFragment : Fragment(), DateSubmitListener, DogProfileClickListener, Ho } homeViewModel.homeDogList.observe(viewLifecycleOwner) { dogList -> - if (dogList != null) { + if (!dogList.isNullOrEmpty()) { fragmentHomeBinding.recyclerviewHomeDog.visibility = View.VISIBLE fragmentHomeBinding.linearlayoutDogExist.visibility = View.VISIBLE fragmentHomeBinding.linearlayoutDogNotExist.visibility = View.GONE @@ -227,9 +231,6 @@ class HomeFragment : Fragment(), DateSubmitListener, DogProfileClickListener, Ho } fragmentHomeBinding.run { - homeViewModel.getUserProfile(currentAccessToken) - homeViewModel.getDogList(currentAccessToken) - imageviewHomeCalendar.setOnClickListener { val modalBottomSheet = CalendarBottomSheetFragment() modalBottomSheet.setDateSubmitListener(this@HomeFragment) @@ -238,15 +239,15 @@ class HomeFragment : Fragment(), DateSubmitListener, DogProfileClickListener, Ho } imageviewHomeAlert.setOnClickListener { - // 알림 화면으로 전환 + findNavController().navigate(R.id.action_homeFragment_to_noticeFragment) } imageviewHomeProfile.setOnClickListener { - // 내정보 화면으로 전환 + findNavController().navigate(R.id.action_homeFragment_to_profileFragment) } imageviewHomeAddDog.setOnClickListener { - // 강아지 정보 등록 화면으로 전환 + findNavController().navigate(R.id.action_homeFragment_to_dogAddOnBoardingFragment) } recyclerviewHomeDog.run { @@ -265,23 +266,23 @@ class HomeFragment : Fragment(), DateSubmitListener, DogProfileClickListener, Ho } constraintlayoutHomeSymptom.setOnClickListener { - // 이상 증상 홈 화면으로 전환 + findNavController().navigate(R.id.action_homeFragment_to_symptomFragment) } constraintlayoutHomeFeces.setOnClickListener { - // 대소변 홈 화면으로 전환 + findNavController().navigate(R.id.action_homeFragment_to_excretaFragment) } constraintlayoutHomeNutrition.setOnClickListener { - // 영양제 홈 화면으로 전환 + findNavController().navigate(R.id.action_homeFragment_to_supplementFragment) } constraintlayoutHomeWeight.setOnClickListener { - // 체중 홈 화면으로 전환 + findNavController().navigate(R.id.action_homeFragment_to_weightFragment) } constraintlayoutHomeFeed.setOnClickListener { - // 사료 홈 화면으로 전환 + findNavController().navigate(R.id.action_homeFragment_to_feedFragment) } } @@ -289,10 +290,12 @@ class HomeFragment : Fragment(), DateSubmitListener, DogProfileClickListener, Ho } private fun getAccessToken() { - runBlocking { - userPreferences.accessToken.collect { accessToken -> + lifecycleScope.launch { + userPreferences.accessToken.collectLatest { accessToken -> if (accessToken != null) { currentAccessToken = accessToken + homeViewModel.getUserProfile(currentAccessToken) + homeViewModel.getDogList(currentAccessToken) } } } 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 35080e2eb..4ccb89f19 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,6 +13,7 @@ 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 @@ -22,6 +23,7 @@ import com.project.meongcare.R import com.project.meongcare.databinding.FragmentPetEditBinding 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.onboarding.model.data.local.DateSubmitListener import com.project.meongcare.onboarding.model.data.local.PhotoMenuListener import com.project.meongcare.onboarding.model.entities.Dog @@ -33,15 +35,19 @@ import com.project.meongcare.onboarding.view.dateFormat import com.project.meongcare.onboarding.view.getCheckedGender import com.project.meongcare.onboarding.viewmodel.DogTypeSharedViewModel import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody import okhttp3.RequestBody.Companion.toRequestBody +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 val petEditViewModel: ProfileViewModel by viewModels() private val dogTypeSharedViewModel: DogTypeSharedViewModel by activityViewModels() @@ -49,6 +55,15 @@ class PetEditFragment : Fragment(), PhotoMenuListener, DateSubmitListener { private var isCbxChecked = false private var isInitialized = false + @Inject + lateinit var userPreferences: UserPreferences + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + dogInfo = getDogInfo() + getAccessToken() + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -57,8 +72,6 @@ class PetEditFragment : Fragment(), PhotoMenuListener, DateSubmitListener { binding = FragmentPetEditBinding.inflate(inflater) mainActivity = activity as MainActivity - dogInfo = getDogInfo() - if (!isInitialized) { initDogInfo(dogInfo) isInitialized = true @@ -166,14 +179,23 @@ class PetEditFragment : Fragment(), PhotoMenuListener, DateSubmitListener { val requestBody: RequestBody = json.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) val filePart = createMultipartBody(mainActivity, petEditViewModel.dogProfile.value) - val accessToken = "" - petEditViewModel.putDogInfo(dogInfo.dogId, accessToken, filePart, requestBody) + petEditViewModel.putDogInfo(dogInfo.dogId, currentAccessToken, filePart, requestBody) } } return binding.root } + private fun getAccessToken() { + lifecycleScope.launch { + userPreferences.accessToken.collectLatest { accessToken -> + if (accessToken != null) { + currentAccessToken = accessToken + } + } + } + } + private fun editTextWatcher( editText: EditText, targetView: View, 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 935f29cea..6822b47f5 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 @@ -6,21 +6,37 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.bumptech.glide.Glide import com.project.meongcare.R import com.project.meongcare.databinding.FragmentPetAddEditBinding 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.onboarding.view.Gender import com.project.meongcare.onboarding.view.dateFormat import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import javax.inject.Inject @AndroidEntryPoint class PetInfoFragment : Fragment() { private lateinit var binding: FragmentPetAddEditBinding private val petInfoViewModel: ProfileViewModel by viewModels() + private lateinit var currentAccessToken: String + private var dogId: Long = 0 + + @Inject + lateinit var userPreferences: UserPreferences + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + dogId = arguments?.getLong("dogId")!! + getAccessToken() + } override fun onCreateView( inflater: LayoutInflater, @@ -35,10 +51,6 @@ class PetInfoFragment : Fragment() { } } - val accessToken = "" - val dogId = arguments?.getLong("dogId")!! - petInfoViewModel.getDogInfo(dogId, accessToken) - binding.run { imagebuttonPetaddBack.setOnClickListener { findNavController().popBackStack() @@ -61,7 +73,7 @@ class PetInfoFragment : Fragment() { includeDeleteDialog.root.visibility = View.GONE } buttonDeleteDialogDelete.setOnClickListener { - petInfoViewModel.deleteDog(dogId, accessToken) + petInfoViewModel.deleteDog(dogId, currentAccessToken) petInfoViewModel.dogDeleteResponse.observe(viewLifecycleOwner) { response -> if (response == 200) findNavController().popBackStack() } @@ -73,6 +85,17 @@ class PetInfoFragment : Fragment() { return binding.root } + private fun getAccessToken() { + lifecycleScope.launch { + userPreferences.accessToken.collectLatest { accessToken -> + if (accessToken != null) { + currentAccessToken = accessToken + petInfoViewModel.getDogInfo(dogId, accessToken) + } + } + } + } + private fun initDogInfo(dogInfo: GetDogInfoResponse) { binding.run { if (dogInfo.imageUrl != null) { 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 4c1ae22c5..25893cad9 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 @@ -25,6 +25,7 @@ import com.project.meongcare.login.model.data.local.UserPreferences import com.project.meongcare.onboarding.model.data.local.PhotoMenuListener import com.project.meongcare.onboarding.view.createMultipartBody import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject @@ -35,10 +36,17 @@ class ProfileFragment : Fragment(), PhotoMenuListener { private val profileViewModel: ProfileViewModel by viewModels() private lateinit var profileUri: Uri + private lateinit var currentAccessToken: String @Inject lateinit var userPreferences: UserPreferences + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + getAccessToken() + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -47,9 +55,6 @@ class ProfileFragment : Fragment(), PhotoMenuListener { binding = FragmentProfileBinding.inflate(inflater) mainActivity = activity as MainActivity - // 임시 설정 - userPreferences.setProvider("kakao") - profileViewModel.userProfile.observe(viewLifecycleOwner) { response -> if (response != null) { binding.run { @@ -63,7 +68,7 @@ class ProfileFragment : Fragment(), PhotoMenuListener { } profileViewModel.dogList.observe(viewLifecycleOwner) { dogList -> - if (dogList.isNotEmpty()) { + if (!dogList.isNullOrEmpty()) { binding.textViewNoDog.visibility = View.GONE binding.recyclerviewProfilePetList.visibility = View.VISIBLE val adapter = binding.recyclerviewProfilePetList.adapter as ProfileDogAdapter @@ -106,10 +111,6 @@ class ProfileFragment : Fragment(), PhotoMenuListener { } } - val accessToken = "Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MywiZXhwIjoxNzA0NzY4MzU0fQ.cN4yZ3Ou9YcUHusdd8Z_IsmA7KF-gzZ3kVc5fljELTM" - profileViewModel.getUserProfile(accessToken) - profileViewModel.getDogList(accessToken) - binding.run { imagebuttonProfileBack.setOnClickListener { findNavController().popBackStack() @@ -157,10 +158,21 @@ class ProfileFragment : Fragment(), PhotoMenuListener { } override fun onUriPassed(uri: Uri) { - val accessToken = "Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MywiZXhwIjoxNzA0NzY4MzU0fQ.cN4yZ3Ou9YcUHusdd8Z_IsmA7KF-gzZ3kVc5fljELTM" profileUri = uri val multipartBody = createMultipartBody(requireContext(), uri) - profileViewModel.patchProfileImage(accessToken, multipartBody) + profileViewModel.patchProfileImage(currentAccessToken, multipartBody) + } + + private fun getAccessToken() { + lifecycleScope.launch { + userPreferences.accessToken.collectLatest { accessToken -> + if (accessToken != null) { + currentAccessToken = accessToken + profileViewModel.getUserProfile(accessToken) + profileViewModel.getDogList(accessToken) + } + } + } } private fun kakaoLogout() { 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 0557a5c7a..35629c09e 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 @@ -11,6 +11,7 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.google.android.material.snackbar.Snackbar import com.project.meongcare.R @@ -18,17 +19,28 @@ import com.project.meongcare.databinding.FragmentSettingBinding import com.project.meongcare.info.viewmodel.ProfileViewModel import com.project.meongcare.login.model.data.local.UserPreferences import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import javax.inject.Inject @AndroidEntryPoint class SettingFragment : Fragment() { private lateinit var binding: FragmentSettingBinding + private lateinit var currentAccessToken: String private val settingViewModel: ProfileViewModel by viewModels() + private var pushAgreement = false @Inject lateinit var userPreferences: UserPreferences + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + pushAgreement = arguments?.getBoolean("pushAgreement")!! + getAccessToken() + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -52,8 +64,6 @@ class SettingFragment : Fragment() { } } - val accessToken = "" - val pushAgreement = arguments?.getBoolean("pushAgreement")!! binding.run { switchSettingNotification.run { isChecked = pushAgreement @@ -78,7 +88,7 @@ class SettingFragment : Fragment() { includeDeleteAccountDialog.root.visibility = View.GONE } buttonDeleteAccountDialogDelete.setOnClickListener { - settingViewModel.deleteUser(accessToken) + settingViewModel.deleteUser(currentAccessToken) } } @@ -87,12 +97,22 @@ class SettingFragment : Fragment() { } switchSettingNotification.setOnCheckedChangeListener { buttonView, isChecked -> - settingViewModel.patchPushAgreement(isChecked, accessToken) + settingViewModel.patchPushAgreement(isChecked, currentAccessToken) } } return binding.root } + + private fun getAccessToken() { + lifecycleScope.launch { + userPreferences.accessToken.collectLatest { accessToken -> + if (accessToken != null) { + currentAccessToken = accessToken + } + } + } + } } fun makeSnackBar( 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 5a63b6261..8904aba50 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,6 +8,7 @@ import android.view.ViewGroup import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.auth.api.signin.GoogleSignInClient @@ -25,6 +26,7 @@ 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 @@ -63,7 +65,8 @@ class LoginFragment : Fragment() { if (loginResponse != null) { userPreferences.setAccessToken(loginResponse.accessToken) userPreferences.setRefreshToken(loginResponse.refreshToken) - // DogAddOnBoardingFragment로 이동 + + findNavController().navigate(R.id.action_loginFragment_to_dogAddOnBoardingFragment) } else { Log.d("Login-viewmodel", "통신 실패") } diff --git a/app/src/main/java/com/project/meongcare/notice/view/NoticeFragment.kt b/app/src/main/java/com/project/meongcare/notice/view/NoticeFragment.kt index d73129a7b..37a4fec96 100644 --- a/app/src/main/java/com/project/meongcare/notice/view/NoticeFragment.kt +++ b/app/src/main/java/com/project/meongcare/notice/view/NoticeFragment.kt @@ -5,6 +5,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController import androidx.viewpager2.adapter.FragmentStateAdapter import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator @@ -43,7 +44,7 @@ class NoticeFragment : Fragment() { ) imageviewNoticeBack.setOnClickListener { - // 이전 화면으로 back + findNavController().popBackStack() } } diff --git a/app/src/main/java/com/project/meongcare/onboarding/view/CompleteOnBoardingFragment.kt b/app/src/main/java/com/project/meongcare/onboarding/view/CompleteOnBoardingFragment.kt index 5f85bc536..fd5c4a740 100644 --- a/app/src/main/java/com/project/meongcare/onboarding/view/CompleteOnBoardingFragment.kt +++ b/app/src/main/java/com/project/meongcare/onboarding/view/CompleteOnBoardingFragment.kt @@ -5,15 +5,24 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import com.project.meongcare.databinding.FragmentCompleteOnBoardingBinding import com.project.meongcare.R class CompleteOnBoardingFragment : Fragment() { + private lateinit var binding: FragmentCompleteOnBoardingBinding + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View? { - // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_complete_on_boarding, container, false) + binding = FragmentCompleteOnBoardingBinding.inflate(inflater) + + binding.buttonStart.setOnClickListener { + findNavController().navigate(R.id.action_completeOnBoardingFragment_to_homeFragment) + } + + return binding.root } } 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 e00a15826..24eafc75a 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 @@ -10,6 +10,7 @@ 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 @@ -21,12 +22,12 @@ import com.project.meongcare.databinding.FragmentDogAddOnBoardingBinding import com.project.meongcare.login.model.data.local.UserPreferences import com.project.meongcare.onboarding.model.data.local.DateSubmitListener import com.project.meongcare.onboarding.model.data.local.PhotoMenuListener -import com.project.meongcare.onboarding.model.data.repository.DogAddRepository import com.project.meongcare.onboarding.model.entities.Dog import com.project.meongcare.onboarding.viewmodel.DogAddViewModel import com.project.meongcare.onboarding.viewmodel.DogTypeSharedViewModel import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody @@ -44,9 +45,6 @@ class DogAddOnBoardingFragment : Fragment(), PhotoMenuListener, DateSubmitListen private val dogAddViewModel: DogAddViewModel by viewModels() private val dogTypeSharedViewModel: DogTypeSharedViewModel by activityViewModels() - @Inject - lateinit var dogAddRepository: DogAddRepository - @Inject lateinit var userPreferences: UserPreferences @@ -80,6 +78,12 @@ class DogAddOnBoardingFragment : Fragment(), PhotoMenuListener, DateSubmitListen } } + dogAddViewModel.dogAddResponse.observe(viewLifecycleOwner) { response -> + if (response == 200) { + findNavController().navigate(R.id.action_dogAddOnBoardingFragment_to_completeOnBoardingFragment) + } + } + fragmentDogAddOnBoardingBinding.run { // 사진 등록 cardviewPetaddImage.setOnClickListener { @@ -175,16 +179,15 @@ class DogAddOnBoardingFragment : Fragment(), PhotoMenuListener, DateSubmitListen val requestBody: RequestBody = json.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) val filePart = createMultipartBody(mainActivity, dogAddViewModel.dogProfileImage.value) - // 서버로 전송 - runBlocking { - val dogAddResponse = - dogAddRepository.postDogInfo( - "", - filePart, - requestBody, - ) - if (dogAddResponse == 200) { - findNavController().navigate(R.id.action_dogAddOnBoardingFragment_to_completeOnBoardingFragment) + lifecycleScope.launch { + userPreferences.accessToken.collectLatest { accessToken -> + if (accessToken != null) { + dogAddViewModel.postDogInfo( + accessToken, + filePart, + requestBody, + ) + } } } } diff --git a/app/src/main/java/com/project/meongcare/onboarding/view/OnBoardingFragment.kt b/app/src/main/java/com/project/meongcare/onboarding/view/OnBoardingFragment.kt index d13180a13..4f49c7642 100644 --- a/app/src/main/java/com/project/meongcare/onboarding/view/OnBoardingFragment.kt +++ b/app/src/main/java/com/project/meongcare/onboarding/view/OnBoardingFragment.kt @@ -1,16 +1,17 @@ package com.project.meongcare.onboarding.view -import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController import androidx.viewpager2.adapter.FragmentStateAdapter import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout.Tab import com.google.android.material.tabs.TabLayoutMediator import com.project.meongcare.MainActivity +import com.project.meongcare.R import com.project.meongcare.databinding.FragmentOnBoardingBinding class OnBoardingFragment : Fragment() { @@ -62,7 +63,7 @@ class OnBoardingFragment : Fragment() { } private fun moveToLogin() { - // LoginFragment로 이동 + findNavController().navigate(R.id.action_onBoardingFragment_to_loginFragment) } } 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 a4094c713..9718f62fc 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 @@ -4,13 +4,20 @@ import android.net.Uri import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.project.meongcare.onboarding.model.data.repository.DogAddRepository import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import okhttp3.MultipartBody +import okhttp3.RequestBody import javax.inject.Inject @HiltViewModel class DogAddViewModel @Inject - constructor() : ViewModel() { + constructor( + private val dogAddRepository: DogAddRepository, + ) : ViewModel() { private val _dogProfileImage = MutableLiveData() val dogProfileImage: LiveData get() = _dogProfileImage @@ -19,6 +26,10 @@ class DogAddViewModel val dogBirthDate: LiveData get() = _dogBirthDate + private val _dogAddResponse = MutableLiveData() + val dogAddResponse + get() = _dogAddResponse + fun getDogProfileImage(uri: Uri) { _dogProfileImage.value = uri } @@ -26,4 +37,14 @@ class DogAddViewModel fun getDogBirthDate(str: String) { _dogBirthDate.value = str } + + fun postDogInfo( + accessToken: String, + file: MultipartBody.Part, + dto: RequestBody, + ) { + viewModelScope.launch { + _dogAddResponse.value = dogAddRepository.postDogInfo(accessToken, file, dto) + } + } } 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 68f9d1e13..3d88ae46d 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 @@ -18,6 +18,7 @@ data class DogSupplement( data class Supplement( val supplementsRecordId: Int, + val supplementsId: Int, val name: String, val intakeTime: String, val intakeCount: Int, @@ -27,6 +28,7 @@ data class Supplement( data class DetailSupplement( val supplementsId: Int, + val imageUrl: String, val brand: String, val name: String, val intakeCycle: Int, diff --git a/app/src/main/java/com/project/meongcare/supplement/view/SupplementFragment.kt b/app/src/main/java/com/project/meongcare/supplement/view/SupplementFragment.kt index 3b40695e1..90bd22e3c 100644 --- a/app/src/main/java/com/project/meongcare/supplement/view/SupplementFragment.kt +++ b/app/src/main/java/com/project/meongcare/supplement/view/SupplementFragment.kt @@ -230,9 +230,10 @@ class SupplementFragment : Fragment() { } holder.itemSupplementLayout.setOnClickListener { -// val bundle = Bundle() -// bundle.putInt("supplementsId",supplementsId) -// navController.navigate(R.id.action_supplement_to_supplementInfo,bundle) + val supplementsId = supplementViewModel.supplementList.value!![position].supplementsId + val bundle = Bundle() + bundle.putInt("supplementsId", supplementsId) + navController.navigate(R.id.action_supplement_to_supplementInfo, bundle) } } diff --git a/app/src/main/java/com/project/meongcare/supplement/view/SupplementInfoFragment.kt b/app/src/main/java/com/project/meongcare/supplement/view/SupplementInfoFragment.kt index 770385233..a42bcefd1 100644 --- a/app/src/main/java/com/project/meongcare/supplement/view/SupplementInfoFragment.kt +++ b/app/src/main/java/com/project/meongcare/supplement/view/SupplementInfoFragment.kt @@ -4,21 +4,19 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.TextView import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment 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.MainActivity import com.project.meongcare.R import com.project.meongcare.databinding.FragmentSupplementInfoBinding -import com.project.meongcare.databinding.ItemSupplementAddTimeBinding import com.project.meongcare.supplement.model.data.repository.SupplementRepository import com.project.meongcare.supplement.model.entities.DetailSupplement -import com.project.meongcare.supplement.utils.SupplementUtils +import com.project.meongcare.supplement.view.adapter.SupplementInfoTimeRecyclerViewAdapter import com.project.meongcare.supplement.viewmodel.SupplementViewModel import com.project.meongcare.supplement.viewmodel.SupplementViewModelFactory @@ -45,7 +43,15 @@ class SupplementInfoFragment : Fragment() { getSupplementDetail(supplementId!!) supplementDetail.observe(viewLifecycleOwner) { fragmentSupplementInfoBinding.run { - textViewSupplementInfoName.text = it.name + if (!it.imageUrl.isNullOrBlank()) { + layoutSupplementInfoDefault.visibility = View.GONE + imageViewSupplementInfo.visibility = View.VISIBLE + Glide.with(this@SupplementInfoFragment) + .load(it.imageUrl) + .into(imageViewSupplementInfo) + textViewSupplementInfoName.text = it.name + } + textViewSupplementInfoBrandName.text = it.brand textViewSupplementInfoCycleCount.text = it.intakeCycle.toString() textViewSupplementInfoTimeListCount.text = "${it.intakeInfos.size}회" @@ -53,7 +59,7 @@ class SupplementInfoFragment : Fragment() { setButtonSelected(it) recyclerViewSupplementInfoTimeList.run { - adapter = SupplementInfoTimeRecyclerViewAdapter() + adapter = SupplementInfoTimeRecyclerViewAdapter(supplementViewModel) layoutManager = LinearLayoutManager(context) } } @@ -115,56 +121,6 @@ class SupplementInfoFragment : Fragment() { return fragmentSupplementInfoBinding.root } - inner class SupplementInfoTimeRecyclerViewAdapter : - RecyclerView.Adapter() { - val intakeList = - supplementViewModel.supplementDetail.value!!.intakeInfos.sortedBy { it.intakeTime } - - inner class SupplementInfoTimeViewHolder(itemSupplementInfoTimeBinding: ItemSupplementAddTimeBinding) : - RecyclerView.ViewHolder(itemSupplementInfoTimeBinding.root) { - val itemSupplementInfoTimeTime: TextView - val itemSupplementInfoTimeAmount: TextView - - init { - itemSupplementInfoTimeTime = - itemSupplementInfoTimeBinding.textViewItemSupplementAddTime - itemSupplementInfoTimeAmount = - itemSupplementInfoTimeBinding.textViewItemSupplementAddTimeAmount - } - } - - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int, - ): SupplementInfoTimeViewHolder { - val itemSupplementInfoTimeBinding = ItemSupplementAddTimeBinding.inflate(layoutInflater) - val allViewHolder = SupplementInfoTimeViewHolder(itemSupplementInfoTimeBinding) - - itemSupplementInfoTimeBinding.root.layoutParams = - ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - ) - - return allViewHolder - } - - override fun getItemCount(): Int { - return intakeList.size - } - - override fun onBindViewHolder( - holder: SupplementInfoTimeViewHolder, - position: Int, - ) { - val intakeCountString = intakeList[position].intakeCount - val intakeUnitString = supplementViewModel.supplementDetail.value!!.intakeUnit - holder.itemSupplementInfoTimeTime.text = - SupplementUtils.convertDateToTime(intakeList[position].intakeTime) - holder.itemSupplementInfoTimeAmount.text = "$intakeCountString$intakeUnitString" - } - } - private fun FragmentSupplementInfoBinding.setButtonSelected(it: DetailSupplement) { val selected = R.drawable.all_rect_main1_r5_outline_main3 val selectedTextColor = ContextCompat.getColor(mainActivity, R.color.main4) diff --git a/app/src/main/java/com/project/meongcare/supplement/view/adapter/SupplementInfoTimeRecyclerViewAdapter.kt b/app/src/main/java/com/project/meongcare/supplement/view/adapter/SupplementInfoTimeRecyclerViewAdapter.kt new file mode 100644 index 000000000..017dc6916 --- /dev/null +++ b/app/src/main/java/com/project/meongcare/supplement/view/adapter/SupplementInfoTimeRecyclerViewAdapter.kt @@ -0,0 +1,60 @@ +package com.project.meongcare.supplement.view.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.project.meongcare.databinding.ItemSupplementAddTimeBinding +import com.project.meongcare.supplement.utils.SupplementUtils.Companion.convertDateToTime +import com.project.meongcare.supplement.viewmodel.SupplementViewModel + +class SupplementInfoTimeRecyclerViewAdapter(private val supplementViewModel: SupplementViewModel) : + RecyclerView.Adapter() { + val intakeList = + supplementViewModel.supplementDetail.value!!.intakeInfos.sortedBy { it.intakeTime } + + inner class SupplementInfoTimeViewHolder(itemSupplementInfoTimeBinding: ItemSupplementAddTimeBinding) : + RecyclerView.ViewHolder(itemSupplementInfoTimeBinding.root) { + val itemSupplementInfoTimeTime: TextView + val itemSupplementInfoTimeAmount: TextView + + init { + itemSupplementInfoTimeTime = + itemSupplementInfoTimeBinding.textViewItemSupplementAddTime + itemSupplementInfoTimeAmount = + itemSupplementInfoTimeBinding.textViewItemSupplementAddTimeAmount + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): SupplementInfoTimeViewHolder { + val itemSupplementInfoTimeBinding = + ItemSupplementAddTimeBinding.inflate(LayoutInflater.from(parent.context), parent, false) + val allViewHolder = SupplementInfoTimeViewHolder(itemSupplementInfoTimeBinding) + + itemSupplementInfoTimeBinding.root.layoutParams = + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + ) + + return allViewHolder + } + + override fun getItemCount(): Int { + return intakeList.size + } + + override fun onBindViewHolder( + holder: SupplementInfoTimeViewHolder, + position: Int, + ) { + val intakeCountString = intakeList[position].intakeCount + val intakeUnitString = supplementViewModel.supplementDetail.value!!.intakeUnit + holder.itemSupplementInfoTimeTime.text = + convertDateToTime(intakeList[position].intakeTime) + holder.itemSupplementInfoTimeAmount.text = "$intakeCountString$intakeUnitString" + } +} diff --git a/app/src/main/res/drawable/meongcare_logo_login.xml b/app/src/main/res/drawable/meongcare_logo_login.xml deleted file mode 100644 index d6846bd84..000000000 --- a/app/src/main/res/drawable/meongcare_logo_login.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/meongcare_logo_splash.xml b/app/src/main/res/drawable/meongcare_logo_splash.xml deleted file mode 100644 index d07b8eeb4..000000000 --- a/app/src/main/res/drawable/meongcare_logo_splash.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/semoban_logo.xml b/app/src/main/res/drawable/semoban_logo.xml new file mode 100644 index 000000000..97b30ce0b --- /dev/null +++ b/app/src/main/res/drawable/semoban_logo.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/app/src/main/res/drawable/semoban_logo_splash.xml b/app/src/main/res/drawable/semoban_logo_splash.xml new file mode 100644 index 000000000..faa6ca84c --- /dev/null +++ b/app/src/main/res/drawable/semoban_logo_splash.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/app/src/main/res/drawable/snackbar_complete_icon.xml b/app/src/main/res/drawable/snackbar_complete_icon.xml new file mode 100644 index 000000000..ff503e3a0 --- /dev/null +++ b/app/src/main/res/drawable/snackbar_complete_icon.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/drawable/snackbar_failure_icon.xml b/app/src/main/res/drawable/snackbar_failure_icon.xml new file mode 100644 index 000000000..bf660be9c --- /dev/null +++ b/app/src/main/res/drawable/snackbar_failure_icon.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 3902c5c68..85c39a797 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -263,4 +263,11 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml index 5d0b0ded4..6982cfd76 100644 --- a/app/src/main/res/layout/activity_splash.xml +++ b/app/src/main/res/layout/activity_splash.xml @@ -11,22 +11,23 @@ android:id="@+id/textView22" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginBottom="8dp" android:letterSpacing="-0.02" - android:text="반려견을 위한 특별한 케어" + android:text="세상 모든 반려동물을 위해" android:textAppearance="@style/Typography.Body1.Medium" android:textColor="@color/white" + app:layout_constraintBottom_toTopOf="@+id/imageView17" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@+id/imageView17" /> + app:layout_constraintStart_toStartOf="parent" /> + app:srcCompat="@drawable/semoban_logo_splash" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dog_add_on_boarding.xml b/app/src/main/res/layout/fragment_dog_add_on_boarding.xml index e0d59111c..244c492bc 100644 --- a/app/src/main/res/layout/fragment_dog_add_on_boarding.xml +++ b/app/src/main/res/layout/fragment_dog_add_on_boarding.xml @@ -455,14 +455,18 @@ + app:layout_constraintTop_toBottomOf="@id/textview_petadd_size" + app:layout_constraintWidth_percent="0.2" /> + app:layout_constraintEnd_toStartOf="@+id/view_petadd_neck_circumference" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintHorizontal_chainStyle="spread_inside" + app:layout_constraintStart_toEndOf="@+id/view_petadd_back_length" + app:layout_constraintTop_toBottomOf="@id/textview_petadd_size" + app:layout_constraintWidth_percent="0.3" /> + app:layout_constraintTop_toBottomOf="@+id/textview_petadd_size" + app:layout_constraintWidth_percent="0.25" /> + tools:context=".feed.view.FeedFragment"> diff --git a/app/src/main/res/layout/fragment_feed_add_edit.xml b/app/src/main/res/layout/fragment_feed_add_edit.xml index 160b64c30..36c503cc8 100644 --- a/app/src/main/res/layout/fragment_feed_add_edit.xml +++ b/app/src/main/res/layout/fragment_feed_add_edit.xml @@ -1,14 +1,15 @@ + tools:context=".feed.view.FeedAddFragment"> - + app:layout_constraintTop_toTopOf="parent"> + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/cardview_feedaddedit_image" /> - - + app:layout_constraintStart_toEndOf="@id/textview_feedaddedit_intake_period_start" + app:layout_constraintTop_toTopOf="@id/textview_feedaddedit_intake_period_start" /> - + + + + + app:layout_constraintTop_toBottomOf="@id/calendarview_feedaddedit_start_date" + tools:visibility="visible" /> + + + app:layout_constraintTop_toBottomOf="@id/checkbox_feedaddedit_do_not_know_end_date" /> diff --git a/app/src/main/res/layout/fragment_feed_info.xml b/app/src/main/res/layout/fragment_feed_info.xml index 297815a8c..c93de2564 100644 --- a/app/src/main/res/layout/fragment_feed_info.xml +++ b/app/src/main/res/layout/fragment_feed_info.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".FeedInfoFragment"> + tools:context=".feed.view.FeedInfoFragment"> + + + + + + + + @@ -280,7 +324,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginHorizontal="8dp" - android:background="@drawable/all_rect_gray3_r5" + android:background="@drawable/feed_rect_gray3_r5" android:paddingHorizontal="7dp" android:paddingVertical="6dp" android:text="000.00" @@ -295,7 +339,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="95dp" - android:text="Kcal" + android:text="kcal/kg" android:textAppearance="@style/Typography.Body2.Regular" app:layout_constraintBottom_toBottomOf="@id/textview_feedinfo_kcal_content" app:layout_constraintEnd_toEndOf="@id/view_feedinfo_kcal" @@ -331,13 +375,24 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" - android:text="0mg" + android:text="0" android:textAppearance="@style/Typography.Body2.Medium" android:textColor="@color/main4" app:layout_constraintBottom_toBottomOf="@id/textview_feedinfo_daily_intake_title" app:layout_constraintStart_toEndOf="@id/textview_feedinfo_daily_intake_title" app:layout_constraintTop_toTopOf="@id/textview_feedinfo_daily_intake_title" /> + + diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml index f07900db1..6b23ccdc3 100644 --- a/app/src/main/res/layout/fragment_login.xml +++ b/app/src/main/res/layout/fragment_login.xml @@ -8,10 +8,10 @@ @@ -21,7 +21,7 @@ style="@style/Widget.Material3.Button" android:layout_width="300dp" android:layout_height="50dp" - android:layout_marginTop="180dp" + android:layout_marginBottom="4dp" android:backgroundTint="#FEE500" android:drawableLeft="@drawable/login_kakao_talk" android:letterSpacing="-0.02" @@ -29,16 +29,16 @@ android:textAppearance="@style/Typography.Body1.Medium" android:textColor="@color/black" app:cornerRadius="6dp" + app:layout_constraintBottom_toTopOf="@+id/buttonNaverLogin" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/imageView" /> + app:layout_constraintStart_toStartOf="parent" />