diff --git a/app/build.gradle b/app/build.gradle index ba0ec53..361f435 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,6 +3,12 @@ plugins { id 'org.jetbrains.kotlin.android' id 'com.google.dagger.hilt.android' id 'kotlin-kapt' + id 'com.google.gms.google-services' + id 'com.google.firebase.crashlytics' +} + +kapt { + correctErrorTypes true } android { @@ -21,8 +27,8 @@ android { applicationId "com.konkuk.mocacong" minSdk 25 targetSdk 33 - versionCode 2 - versionName "1.0.1" + versionCode 3 + versionName "2.0.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -100,7 +106,7 @@ dependencies { //hilt implementation 'com.google.dagger:hilt-android:2.48.1' - annotationProcessor 'com.google.dagger:hilt-compiler:2.48.1' + kapt 'com.google.dagger:hilt-compiler:2.48.1' //DataStore implementation "androidx.datastore:datastore-preferences:1.0.0" @@ -110,4 +116,9 @@ dependencies { implementation("com.squareup.okhttp3:okhttp") implementation("com.squareup.okhttp3:logging-interceptor") + //Firebase BoM + implementation platform('com.google.firebase:firebase-bom:32.7.0') + implementation 'com.google.firebase:firebase-analytics' + implementation 'com.google.firebase:firebase-crashlytics' + } \ No newline at end of file diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..6a8f5a2 --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "922499845818", + "project_id": "mocacong-54f51", + "storage_bucket": "mocacong-54f51.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:922499845818:android:dfbbb72ee44af09b07bc83", + "android_client_info": { + "package_name": "com.konkuk.mocacong" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyDq9jay2XQk27KqIMegBHWp28Au6jGd7uk" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/objects/KakaoLocalClient.kt b/app/src/main/java/com/konkuk/mocacong/objects/KakaoLocalClient.kt deleted file mode 100644 index 3e0964c..0000000 --- a/app/src/main/java/com/konkuk/mocacong/objects/KakaoLocalClient.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.konkuk.mocacong.objects - -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.Response -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory - -object KakaoLocalClient { - // Base URL 설정 - private const val BASE_URL = "https://dapi.kakao.com/" - - // Retrofit 객체 생성 - private val retrofit: Retrofit = Retrofit.Builder() - .baseUrl(BASE_URL) - .client(provideOKHttpClient(AppInterceptor())) - .addConverterFactory(GsonConverterFactory.create()) - .build() - - // API 인터페이스 반환 - fun create(service: Class): T { - return retrofit.create(service) - } - - private fun provideOKHttpClient(interceptor: AppInterceptor): OkHttpClient = - OkHttpClient.Builder().run { - addInterceptor(interceptor) - build() - } - - class AppInterceptor : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response = with(chain) { - val newRequest = request().newBuilder() - .addHeader("Authorization", "KakaoAK 3e7c72e35c239c324a59419fa82812a4") - .build() - proceed(newRequest) - } - - } -} \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/objects/NetworkManager.kt b/app/src/main/java/com/konkuk/mocacong/objects/NetworkManager.kt deleted file mode 100644 index e6252d4..0000000 --- a/app/src/main/java/com/konkuk/mocacong/objects/NetworkManager.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.konkuk.mocacong.objects - -import android.content.Context -import android.net.ConnectivityManager -import android.net.NetworkCapabilities -import androidx.fragment.app.FragmentManager - -class NetworkManager { - - companion object{ - fun Context.isNetworkConnected(context: Context): Boolean { - val connectivityManager = context.getSystemService(ConnectivityManager::class.java) - val currentNetwork = connectivityManager.activeNetwork ?: return false - val actNetwork = connectivityManager.getNetworkCapabilities(currentNetwork) ?: return false - return (actNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) - || actNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) - - } - - fun Context.showCheckDialog(supportFragmentManager : FragmentManager){ -// MessageDialog("인터넷 연결 상태를 확인해주세요!").show(supportFragmentManager, "MessageDialog") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/objects/NetworkUtil.kt b/app/src/main/java/com/konkuk/mocacong/objects/NetworkUtil.kt deleted file mode 100644 index 500c26a..0000000 --- a/app/src/main/java/com/konkuk/mocacong/objects/NetworkUtil.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.konkuk.mocacong.objects - -import com.konkuk.mocacong.remote.models.response.ErrorResponse -import com.konkuk.mocacong.util.RetrofitClient -import okhttp3.ResponseBody - -object NetworkUtil { - fun getErrorResponse(errorBody: ResponseBody): ErrorResponse? { - return RetrofitClient.retrofit.responseBodyConverter( - ErrorResponse::class.java, - ErrorResponse::class.java.annotations - ).convert(errorBody) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/objects/Utils.kt b/app/src/main/java/com/konkuk/mocacong/objects/Utils.kt deleted file mode 100644 index 4381061..0000000 --- a/app/src/main/java/com/konkuk/mocacong/objects/Utils.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.konkuk.mocacong.objects - -import android.app.AlertDialog -import android.content.Context -import android.content.DialogInterface -import android.content.Intent -import android.os.Build -import android.os.Bundle -import android.view.KeyEvent -import android.view.inputmethod.InputMethodManager -import android.widget.EditText -import android.widget.Toast -import androidx.core.content.ContextCompat -import java.io.Serializable - -object Utils { - private var toast: Toast? = null - - fun showConfirmDialog( - context: Context, - msg: String, - confirmAction: () -> Unit, - cancelAction: () -> Unit - ) { - AlertDialog.Builder(context).apply { - setCancelable(false) - setMessage(msg) - setPositiveButton("확인") { _: DialogInterface, _: Int -> - confirmAction() - - } - setNegativeButton("취소") { _: DialogInterface, _: Int -> - cancelAction() - } - create() - }.show() - } - - fun EditText.handleEnterKey(onEnter: ()->(Unit) = {}) { - val inputMethodManager = - ContextCompat.getSystemService(context, InputMethodManager::class.java) - this.setOnKeyListener { _, code, keyEvent -> - if ((keyEvent.action == KeyEvent.ACTION_DOWN) && (code == KeyEvent.KEYCODE_ENTER)) { - (inputMethodManager)?.hideSoftInputFromWindow( - this.windowToken, - 0 - ) - onEnter() - true - } - false - } - } - - fun EditText.showKeyboard() { - isFocusableInTouchMode = true - requestFocus() - postDelayed({ - val inputMethodManager = - ContextCompat.getSystemService(context, InputMethodManager::class.java) - inputMethodManager?.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) - }, 30) - } - - fun Intent.intentSerializable(key: String, clazz: Class): T? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - this.getSerializableExtra(key, clazz) - } else { - this.getSerializableExtra(key) as T? - } - } - - fun Bundle.bundleSerializable(key: String, clazz: Class): T? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - this.getSerializable(key, clazz) - } else { - this.getSerializable(key) as T? - } - } - - -} \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/base/BaseActivity.kt b/app/src/main/java/com/konkuk/mocacong/presentation/base/BaseActivity.kt index 121fbdf..5d03fb4 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/base/BaseActivity.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/base/BaseActivity.kt @@ -20,7 +20,6 @@ abstract class BaseActivity : AppCompatActivity() { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, layoutRes) binding.lifecycleOwner = this - initViewModel() afterViewCreated() } @@ -31,8 +30,6 @@ abstract class BaseActivity : AppCompatActivity() { startActivity(intent) } - - open fun initViewModel() {} abstract fun afterViewCreated() // 토스트 생성 diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeDetailFragment.kt b/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeDetailFragment.kt index fa1b19e..7b74bad 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeDetailFragment.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeDetailFragment.kt @@ -4,17 +4,21 @@ import androidx.fragment.app.activityViewModels import com.konkuk.mocacong.R import com.konkuk.mocacong.databinding.FragmentCafeDetailBinding import com.konkuk.mocacong.presentation.base.BaseFragment +import com.konkuk.mocacong.presentation.detail.comment.WriteCommentFragment import com.konkuk.mocacong.presentation.main.MainPage import com.konkuk.mocacong.presentation.main.MainViewModel +import com.konkuk.mocacong.presentation.main.mypage.MypageViewModel class CafeDetailFragment : BaseFragment() { override val TAG: String = "DetailFragment" override val layoutRes: Int = R.layout.fragment_cafe_detail private val mainViewModel: MainViewModel by activityViewModels() private val detailViewModel: CafeDetailViewModel by activityViewModels() + private val mypageViewModel: MypageViewModel by activityViewModels() override fun afterViewCreated() { binding.vm = detailViewModel + binding.mypageVm = mypageViewModel detailViewModel.requestCafeDetailInfo() initLayout() } @@ -48,6 +52,8 @@ class CafeDetailFragment : BaseFragment() { mainViewModel.goto(MainPage.COMMENTS) } + binding + } diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeDetailViewModel.kt b/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeDetailViewModel.kt index 9ae3235..39e53ec 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeDetailViewModel.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeDetailViewModel.kt @@ -9,16 +9,20 @@ import com.konkuk.mocacong.data.entities.BasicPlaceInfo import com.konkuk.mocacong.presentation.models.CafeCommentsUiModel import com.konkuk.mocacong.presentation.models.CafeDetailUiModel import com.konkuk.mocacong.remote.models.request.ReviewRequest +import com.konkuk.mocacong.remote.models.response.CafeImage import com.konkuk.mocacong.remote.models.response.CafeImageResponse import com.konkuk.mocacong.remote.models.response.MyReviewResponse import com.konkuk.mocacong.remote.repositories.CafeDetailRepository import com.konkuk.mocacong.util.ApiState +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.MultipartBody +import javax.inject.Inject -class CafeDetailViewModel(private val cafeDetailRepository: CafeDetailRepository) : ViewModel() { +@HiltViewModel +class CafeDetailViewModel @Inject constructor(private val cafeDetailRepository: CafeDetailRepository) : ViewModel() { val TAG = "CafeDetailViewModel" lateinit var cafeId: String @@ -175,6 +179,7 @@ class CafeDetailViewModel(private val cafeDetailRepository: CafeDetailRepository } } + var currentImage: CafeImage? = null fun deleteComment(id: Long) { diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/detail/ReviewButtonGroup.kt b/app/src/main/java/com/konkuk/mocacong/presentation/detail/ReviewButtonGroup.kt index bbd5315..d02a3fa 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/detail/ReviewButtonGroup.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/detail/ReviewButtonGroup.kt @@ -5,6 +5,7 @@ import android.content.Context import android.util.AttributeSet import android.util.Log import android.view.LayoutInflater +import android.view.View import android.widget.LinearLayout import com.konkuk.mocacong.R import com.konkuk.mocacong.data.entities.Review @@ -34,10 +35,19 @@ class ReviewButtonGroup @JvmOverloads constructor( binding = LayoutReviewBtnBinding.inflate(LayoutInflater.from(context), this, true) setLevelText() + setRequiredIcon() setClickListeners() } + private fun setRequiredIcon() { + if (category == "desk" || category == "power") { + binding.requiredIcon.visibility = View.VISIBLE + invalidate() + requestLayout() + } + } + @SuppressLint("DiscouragedApi") private fun setLevelText() { val resourceId = resources.getIdentifier(category, "array", context.packageName) diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeCommentView.kt b/app/src/main/java/com/konkuk/mocacong/presentation/detail/comment/CafeCommentView.kt similarity index 81% rename from app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeCommentView.kt rename to app/src/main/java/com/konkuk/mocacong/presentation/detail/comment/CafeCommentView.kt index 10a42c9..90be415 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeCommentView.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/detail/comment/CafeCommentView.kt @@ -1,4 +1,4 @@ -package com.konkuk.mocacong.presentation.detail +package com.konkuk.mocacong.presentation.detail.comment import android.content.Context import android.util.AttributeSet @@ -17,6 +17,7 @@ class CafeCommentView @JvmOverloads constructor( ) : ConstraintLayout(context, attrs, defStyleAttr) { private var binding: LayoutCafeCommentBinding + init { binding = LayoutCafeCommentBinding.bind( LayoutInflater.from(context).inflate(R.layout.layout_cafe_comment, this, false) @@ -24,14 +25,12 @@ class CafeCommentView @JvmOverloads constructor( addView(binding.root) } - fun setComment(comment: Comment?){ - if(comment==null) binding.root.visibility = View.GONE + fun setComment(comment: Comment?) { + if (comment == null) binding.root.visibility = View.GONE else { binding.root.visibility = View.VISIBLE - if(comment.isMe) binding.commentMenuBtn.visibility = View.VISIBLE - else binding.commentMenuBtn.visibility = View.GONE - binding.profileImg.clipToOutline = true + binding.commentMenuBtn.visibility = View.GONE if (comment.imgUrl.isNullOrBlank()) binding.profileImg.setImageResource(R.drawable.img_no_profile) else Glide.with(context).load(comment.imgUrl).into(binding.profileImg) diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeCommentsAdapter.kt b/app/src/main/java/com/konkuk/mocacong/presentation/detail/comment/CafeCommentsAdapter.kt similarity index 98% rename from app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeCommentsAdapter.kt rename to app/src/main/java/com/konkuk/mocacong/presentation/detail/comment/CafeCommentsAdapter.kt index cdf31d9..91512a4 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeCommentsAdapter.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/detail/comment/CafeCommentsAdapter.kt @@ -1,4 +1,4 @@ -package com.konkuk.mocacong.presentation.detail +package com.konkuk.mocacong.presentation.detail.comment import android.view.LayoutInflater import android.view.View diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeCommentsFragment.kt b/app/src/main/java/com/konkuk/mocacong/presentation/detail/comment/CafeCommentsFragment.kt similarity index 90% rename from app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeCommentsFragment.kt rename to app/src/main/java/com/konkuk/mocacong/presentation/detail/comment/CafeCommentsFragment.kt index 097e081..fc29ab4 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeCommentsFragment.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/detail/comment/CafeCommentsFragment.kt @@ -1,4 +1,4 @@ -package com.konkuk.mocacong.presentation.detail +package com.konkuk.mocacong.presentation.detail.comment import android.view.MenuItem import android.view.View @@ -11,14 +11,18 @@ import com.konkuk.mocacong.R import com.konkuk.mocacong.data.entities.Comment import com.konkuk.mocacong.databinding.FragmentCafeCommentsBinding import com.konkuk.mocacong.presentation.base.BaseBottomSheet +import com.konkuk.mocacong.presentation.detail.CafeDetailViewModel +import com.konkuk.mocacong.presentation.main.mypage.MypageViewModel class CafeCommentsFragment : BaseBottomSheet() { override val TAG: String = "CafeCommentsFragment" override val layoutRes: Int = R.layout.fragment_cafe_comments private val detailViewModel: CafeDetailViewModel by activityViewModels() + private val mypageViewModel: MypageViewModel by activityViewModels() override fun afterViewCreated() { binding.vm = detailViewModel + binding.mypageVm = mypageViewModel val clickListener = object : CafeCommentsAdapter.ButtonClickListener { override fun onMenuClicked(comment: Comment, menu: View) { //메뉴 diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/detail/WriteCommentFragment.kt b/app/src/main/java/com/konkuk/mocacong/presentation/detail/comment/WriteCommentFragment.kt similarity index 93% rename from app/src/main/java/com/konkuk/mocacong/presentation/detail/WriteCommentFragment.kt rename to app/src/main/java/com/konkuk/mocacong/presentation/detail/comment/WriteCommentFragment.kt index afbf4e9..3e77e06 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/detail/WriteCommentFragment.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/detail/comment/WriteCommentFragment.kt @@ -1,4 +1,4 @@ -package com.konkuk.mocacong.presentation.detail +package com.konkuk.mocacong.presentation.detail.comment import android.app.Dialog import android.os.Bundle @@ -12,6 +12,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.konkuk.mocacong.R import com.konkuk.mocacong.databinding.FragmentWriteCommentBinding import com.konkuk.mocacong.presentation.base.BaseBottomSheet +import com.konkuk.mocacong.presentation.detail.CafeDetailViewModel class WriteCommentFragment : BaseBottomSheet() { override val TAG: String @@ -32,7 +33,8 @@ class WriteCommentFragment : BaseBottomSheet() { } override fun afterViewCreated() { - val bottomSheet = dialog?.findViewById(com.google.android.material.R.id.design_bottom_sheet) + val bottomSheet = + dialog?.findViewById(com.google.android.material.R.id.design_bottom_sheet) val behavior = BottomSheetBehavior.from(bottomSheet!!) behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeImagesFragment.kt b/app/src/main/java/com/konkuk/mocacong/presentation/detail/image/CafeImagesFragment.kt similarity index 89% rename from app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeImagesFragment.kt rename to app/src/main/java/com/konkuk/mocacong/presentation/detail/image/CafeImagesFragment.kt index ef2f539..7fad9d0 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/detail/CafeImagesFragment.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/detail/image/CafeImagesFragment.kt @@ -1,4 +1,4 @@ -package com.konkuk.mocacong.presentation.detail +package com.konkuk.mocacong.presentation.detail.image import android.database.Cursor import android.net.Uri @@ -8,6 +8,7 @@ import androidx.fragment.app.activityViewModels import androidx.recyclerview.widget.GridLayoutManager import com.konkuk.mocacong.databinding.FragmentCafeImagesBinding import com.konkuk.mocacong.presentation.base.BaseFragment +import com.konkuk.mocacong.presentation.detail.CafeDetailViewModel import com.konkuk.mocacong.remote.models.response.CafeImage import com.konkuk.mocacong.util.ApiState import gun0912.tedimagepicker.builder.TedImagePicker @@ -60,8 +61,11 @@ class CafeImagesFragment : BaseFragment() { binding.vm = detailViewModel val clickListener = object : ImageAdapter.ButtonClickListener { - override fun onMenuClicked(cafeImage: CafeImage) { + override fun onImageClicked(cafeImage: CafeImage) { //메뉴 +// detailViewModel.currentImage = cafeImage +// val dialog = FullImageDialog() +// dialog.show(childFragmentManager, dialog.tag) } override fun onMoreClicked() { @@ -72,7 +76,7 @@ class CafeImagesFragment : BaseFragment() { } adapter = ImageAdapter(clickListener) - binding.recyclerView.layoutManager = GridLayoutManager(requireContext(), 3) + binding.recyclerView.layoutManager = GridLayoutManager(requireContext(), 2) binding.recyclerView.adapter = adapter binding.plusBtn.setOnClickListener { diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/detail/image/FullImageDialog.kt b/app/src/main/java/com/konkuk/mocacong/presentation/detail/image/FullImageDialog.kt new file mode 100644 index 0000000..86d2436 --- /dev/null +++ b/app/src/main/java/com/konkuk/mocacong/presentation/detail/image/FullImageDialog.kt @@ -0,0 +1,55 @@ +package com.konkuk.mocacong.presentation.detail.image + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.Window +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.activityViewModels +import com.konkuk.mocacong.databinding.DialogFullImageBinding +import com.konkuk.mocacong.presentation.detail.CafeDetailViewModel + +class FullImageDialog : DialogFragment() { + internal lateinit var listener: ImageDialogListener + val binding: DialogFullImageBinding get() = _binding!! + private var _binding: DialogFullImageBinding? = null + + private val detailViewModel : CafeDetailViewModel by activityViewModels() + + interface ImageDialogListener { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + isCancelable = true + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = DialogFullImageBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) + return dialog + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.image = detailViewModel.currentImage + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/detail/ImageAdapter.kt b/app/src/main/java/com/konkuk/mocacong/presentation/detail/image/ImageAdapter.kt similarity index 88% rename from app/src/main/java/com/konkuk/mocacong/presentation/detail/ImageAdapter.kt rename to app/src/main/java/com/konkuk/mocacong/presentation/detail/image/ImageAdapter.kt index c22fa9f..1089fdd 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/detail/ImageAdapter.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/detail/image/ImageAdapter.kt @@ -1,4 +1,4 @@ -package com.konkuk.mocacong.presentation.detail +package com.konkuk.mocacong.presentation.detail.image import android.view.LayoutInflater import android.view.View @@ -23,7 +23,7 @@ class ImageAdapter( interface ButtonClickListener { fun onMoreClicked() - fun onMenuClicked(cafeImage: CafeImage) + fun onImageClicked(cafeImage: CafeImage) } var images: List = emptyList() @@ -32,8 +32,15 @@ class ImageAdapter( inner class ItemViewHolder(private val binding: ItemImageBinding) : ViewHolder(binding.root) { fun bind(image: CafeImage) { + binding.imageView.apply { + clipToOutline = true + setOnClickListener { + btnClickListener.onImageClicked(image) + } + } if (image.imageUrl == null) binding.imageView.setImageResource(R.drawable.img_nothing) else Glide.with(binding.imageView.context).load(image.imageUrl).into(binding.imageView) + } } diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/login/JoinFragment.kt b/app/src/main/java/com/konkuk/mocacong/presentation/login/JoinFragment.kt index 27770fb..e04f91e 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/login/JoinFragment.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/login/JoinFragment.kt @@ -7,10 +7,10 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import com.konkuk.mocacong.R import com.konkuk.mocacong.databinding.FragmentJoinBinding -import com.konkuk.mocacong.objects.Utils.handleEnterKey import com.konkuk.mocacong.presentation.base.BaseFragment import com.konkuk.mocacong.presentation.main.MainActivity import com.konkuk.mocacong.util.TokenManager +import com.konkuk.mocacong.util.handleEnterKey import kotlinx.coroutines.launch class JoinFragment : BaseFragment() { diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/login/LoginActivity.kt b/app/src/main/java/com/konkuk/mocacong/presentation/login/LoginActivity.kt index bd657f4..fcbbdc4 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/login/LoginActivity.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/login/LoginActivity.kt @@ -1,22 +1,22 @@ package com.konkuk.mocacong.presentation.login +import androidx.activity.viewModels import androidx.fragment.app.Fragment import androidx.fragment.app.commit import androidx.lifecycle.Lifecycle -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.konkuk.mocacong.R import com.konkuk.mocacong.databinding.ActivityLoginBinding import com.konkuk.mocacong.presentation.base.BaseActivity -import com.konkuk.mocacong.remote.repositories.LoginRepository -import com.konkuk.mocacong.util.ViewModelFactory +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch +@AndroidEntryPoint class LoginActivity : BaseActivity() { override val TAG: String = "LoginActivity" override val layoutRes: Int = R.layout.activity_login - lateinit var viewModel: LoginViewModel + private val viewModel: LoginViewModel by viewModels() private val loginFragment by lazy { @@ -36,10 +36,6 @@ class LoginActivity : BaseActivity() { } } - override fun initViewModel() { - viewModel = - ViewModelProvider(this, ViewModelFactory(LoginRepository()))[LoginViewModel::class.java] - } override fun afterViewCreated() { collectPage() diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/login/LoginViewModel.kt b/app/src/main/java/com/konkuk/mocacong/presentation/login/LoginViewModel.kt index 8e410f6..e763f4e 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/login/LoginViewModel.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/login/LoginViewModel.kt @@ -11,13 +11,16 @@ import com.konkuk.mocacong.remote.models.response.CheckDuplicateResponse import com.konkuk.mocacong.remote.models.response.KakaoLoginResponse import com.konkuk.mocacong.remote.repositories.LoginRepository import com.konkuk.mocacong.util.ApiState +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import javax.inject.Inject -class LoginViewModel(val repository: LoginRepository) : ViewModel() { +@HiltViewModel +class LoginViewModel @Inject constructor(private val repository: LoginRepository) : ViewModel() { val TAG = "LoginViewModel" var mKakaoLoginResponse: MutableLiveData> = MutableLiveData(ApiState.Loading()) diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/login/WebViewActivity.kt b/app/src/main/java/com/konkuk/mocacong/presentation/login/WebViewActivity.kt index 70824ba..5bf5229 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/login/WebViewActivity.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/login/WebViewActivity.kt @@ -2,9 +2,26 @@ package com.konkuk.mocacong.presentation.login import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import com.konkuk.mocacong.databinding.ActivityWebViewBinding class WebViewActivity : AppCompatActivity() { + private lateinit var binding: ActivityWebViewBinding + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + binding = ActivityWebViewBinding.inflate(layoutInflater) + setContentView(binding.root) + setWebView() + } + + private fun setWebView() { + val webView = binding.webView + val uri = intent.getStringExtra("urlString") + + webView.settings.javaScriptEnabled = true + webView.settings.domStorageEnabled = true + if (uri != null) { + webView.loadUrl(uri) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/main/MainActivity.kt b/app/src/main/java/com/konkuk/mocacong/presentation/main/MainActivity.kt index c83effd..0ac2dcd 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/main/MainActivity.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/main/MainActivity.kt @@ -1,21 +1,20 @@ package com.konkuk.mocacong.presentation.main -import android.util.Log import androidx.activity.OnBackPressedCallback +import androidx.activity.viewModels import androidx.fragment.app.Fragment import androidx.fragment.app.commit import androidx.lifecycle.Lifecycle -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.konkuk.mocacong.R import com.konkuk.mocacong.databinding.ActivityMainBinding import com.konkuk.mocacong.presentation.base.BaseActivity -import com.konkuk.mocacong.presentation.detail.CafeCommentsFragment import com.konkuk.mocacong.presentation.detail.CafeDetailFragment import com.konkuk.mocacong.presentation.detail.CafeDetailViewModel -import com.konkuk.mocacong.presentation.detail.CafeImagesFragment +import com.konkuk.mocacong.presentation.detail.comment.CafeCommentsFragment +import com.konkuk.mocacong.presentation.detail.image.CafeImagesFragment import com.konkuk.mocacong.presentation.main.map.HomeFragment import com.konkuk.mocacong.presentation.main.map.MapViewModel import com.konkuk.mocacong.presentation.main.map.SearchFragment @@ -23,20 +22,17 @@ import com.konkuk.mocacong.presentation.main.mypage.MyCommentsFragment import com.konkuk.mocacong.presentation.main.mypage.MyFavsFragment import com.konkuk.mocacong.presentation.main.mypage.MyReviewsFragment import com.konkuk.mocacong.presentation.main.mypage.MypageViewModel -import com.konkuk.mocacong.remote.repositories.CafeDetailRepository -import com.konkuk.mocacong.remote.repositories.MapRepository -import com.konkuk.mocacong.remote.repositories.MypageRepository -import com.konkuk.mocacong.util.ViewModelFactory +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.delay import kotlinx.coroutines.launch +@AndroidEntryPoint class MainActivity : BaseActivity() { - - private lateinit var mapViewModel: MapViewModel - private lateinit var detailViewModel: CafeDetailViewModel - private lateinit var mypageViewModel: MypageViewModel - private lateinit var mainViewModel: MainViewModel + private val mapViewModel: MapViewModel by viewModels() + private val detailViewModel: CafeDetailViewModel by viewModels() + private val mypageViewModel: MypageViewModel by viewModels() + private val mainViewModel: MainViewModel by viewModels() override val TAG: String = "MainActivity" @@ -104,30 +100,11 @@ class MainActivity : BaseActivity() { } } - override fun initViewModel() { - Log.d(TAG, "initViewModel") - mapViewModel = - ViewModelProvider(this, ViewModelFactory(MapRepository()))[MapViewModel::class.java] - detailViewModel = ViewModelProvider( - this, - ViewModelFactory(CafeDetailRepository()) - )[CafeDetailViewModel::class.java] - mypageViewModel = ViewModelProvider( - this, - ViewModelFactory(MypageRepository()) - )[MypageViewModel::class.java] - mainViewModel = ViewModelProvider(this)[MainViewModel::class.java] - } - override fun afterViewCreated() { onBackPressedDispatcher.addCallback(this, callback) collectPage() observeMyPlace() - getUserProfile() - } - - private fun getUserProfile() { - mypageViewModel.requestMyProfile() + getMemberProfile() } @@ -165,7 +142,6 @@ class MainActivity : BaseActivity() { } - private fun observeMyPlace() { mypageViewModel.selectedPlaces.observe(this) { mainViewModel.gotoMap(it) @@ -173,4 +149,11 @@ class MainActivity : BaseActivity() { } + private fun getMemberProfile() { + lifecycleScope.launch { + mypageViewModel.getMyProfile() + } + } + + } \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/main/MainViewModel.kt b/app/src/main/java/com/konkuk/mocacong/presentation/main/MainViewModel.kt index 93387be..1dbd0eb 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/main/MainViewModel.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/main/MainViewModel.kt @@ -4,12 +4,16 @@ import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.konkuk.mocacong.remote.models.response.Place +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import javax.inject.Inject -class MainViewModel : ViewModel() { +@HiltViewModel + +class MainViewModel @Inject constructor() : ViewModel() { private val _pageFlow = MutableStateFlow(MainPage.HOME) val pageFlow = _pageFlow @@ -33,5 +37,11 @@ class MainViewModel : ViewModel() { } } + fun consumeLocation(){ + viewModelScope.launch { + _locationFlow.emit(null) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/main/map/HomeFragment.kt b/app/src/main/java/com/konkuk/mocacong/presentation/main/map/HomeFragment.kt index abd1d57..3f13d3a 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/main/map/HomeFragment.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/main/map/HomeFragment.kt @@ -1,6 +1,7 @@ package com.konkuk.mocacong.presentation.main.map import android.Manifest +import android.content.Intent import android.content.pm.PackageManager import android.database.Cursor import android.net.Uri @@ -22,6 +23,7 @@ import com.konkuk.mocacong.R import com.konkuk.mocacong.data.entities.MapMarker import com.konkuk.mocacong.databinding.FragmentHomeBinding import com.konkuk.mocacong.presentation.base.BaseFragment +import com.konkuk.mocacong.presentation.login.WebViewActivity import com.konkuk.mocacong.presentation.main.MainPage import com.konkuk.mocacong.presentation.main.MainViewModel import com.konkuk.mocacong.presentation.main.mypage.MypageViewModel @@ -106,6 +108,21 @@ class HomeFragment : BaseFragment(), OnMapReadyCallback { mypageViewModel.requestMyComments() mainViewModel.goto(MainPage.MyComment) } + R.id.customerCenter -> { + val intent = Intent(requireContext(), WebViewActivity::class.java) + intent.putExtra("urlString", resources.getString(R.string.qnaUrl)) + startActivity(intent) + } + R.id.appInfo -> { + val intent = Intent(requireContext(), WebViewActivity::class.java) + intent.putExtra("urlString", resources.getString(R.string.termsUrl)) + startActivity(intent) + } + R.id.qna -> { + val intent = Intent(requireContext(), WebViewActivity::class.java) + intent.putExtra("urlString", resources.getString(R.string.qnaUrl)) + startActivity(intent) + } } return@setNavigationItemSelectedListener false @@ -317,8 +334,9 @@ class HomeFragment : BaseFragment(), OnMapReadyCallback { delay(500) mapViewModel.mapMarkers[place.id]?.marker?.performClick() + mainViewModel.consumeLocation() } else { - showToast("죄송합니다. 정보를 찾지 못했습니다.") + } } } diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/main/map/MapViewModel.kt b/app/src/main/java/com/konkuk/mocacong/presentation/main/map/MapViewModel.kt index ea2f81c..4d088ae 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/main/map/MapViewModel.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/main/map/MapViewModel.kt @@ -11,18 +11,25 @@ import com.konkuk.mocacong.remote.models.request.FilteringRequest import com.konkuk.mocacong.remote.models.request.PostCafeRequest import com.konkuk.mocacong.remote.models.response.CafePreviewResponse import com.konkuk.mocacong.remote.models.response.Place +import com.konkuk.mocacong.remote.repositories.KakaoRepository import com.konkuk.mocacong.remote.repositories.MapRepository import com.konkuk.mocacong.util.ApiState import com.naver.maps.geometry.LatLng import com.naver.maps.map.CameraPosition import com.naver.maps.map.overlay.Marker import com.naver.maps.map.util.MarkerIcons +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import javax.inject.Inject -class MapViewModel(val mapRepository: MapRepository) : ViewModel() { +@HiltViewModel +class MapViewModel @Inject constructor( + private val mapRepository: MapRepository, + private val kakaoRepository: KakaoRepository +) : ViewModel() { private val TAG = "MapViewModel" val mapMarkers = HashMap() @@ -35,7 +42,7 @@ class MapViewModel(val mapRepository: MapRepository) : ViewModel() { if (mapMarkers.size > 70) removeMarkers(mapMarkers.keys.toList()) } Log.d(TAG, "updateMarkers") - mapRepository.getPlaces(x, y, radius).byState({ response -> + kakaoRepository.getPlaces(x, y, radius).byState({ response -> response.documents.let { Log.d(TAG, "카카오 검색 결과: $it") _newPlaces.postValue(it.filter { place -> @@ -136,7 +143,8 @@ class MapViewModel(val mapRepository: MapRepository) : ViewModel() { mapMarkers[id] } return@byState markers - }) + } + ) }.await() private val _clickedMarker = MutableLiveData(null) @@ -185,7 +193,7 @@ class MapViewModel(val mapRepository: MapRepository) : ViewModel() { fun requestSearchByKeyword(keyword: String) { viewModelScope.launch { val response = withContext(Dispatchers.IO) { - mapRepository.getSearchResult( + kakaoRepository.getSearchResult( keyword = keyword, y = currentLocation.target.latitude.toString(), x = currentLocation.target.longitude.toString() diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/main/map/SearchFragment.kt b/app/src/main/java/com/konkuk/mocacong/presentation/main/map/SearchFragment.kt index edb6eca..94a5a95 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/main/map/SearchFragment.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/main/map/SearchFragment.kt @@ -9,11 +9,11 @@ import androidx.fragment.app.activityViewModels import androidx.recyclerview.widget.LinearLayoutManager import com.konkuk.mocacong.R import com.konkuk.mocacong.databinding.FragmentSearchBinding -import com.konkuk.mocacong.objects.Utils.handleEnterKey -import com.konkuk.mocacong.objects.Utils.showKeyboard import com.konkuk.mocacong.presentation.base.BaseFragment import com.konkuk.mocacong.presentation.main.MainViewModel import com.konkuk.mocacong.remote.models.response.Place +import com.konkuk.mocacong.util.handleEnterKey +import com.konkuk.mocacong.util.showKeyboard class SearchFragment : BaseFragment() { override val TAG: String diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/main/mypage/MyCommentsFragment.kt b/app/src/main/java/com/konkuk/mocacong/presentation/main/mypage/MyCommentsFragment.kt index 57ad369..16243cb 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/main/mypage/MyCommentsFragment.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/main/mypage/MyCommentsFragment.kt @@ -6,6 +6,8 @@ import com.konkuk.mocacong.R import com.konkuk.mocacong.databinding.FragmentMyCommentsBinding import com.konkuk.mocacong.presentation.base.BaseFragment import com.konkuk.mocacong.remote.models.response.MyComments +import dagger.hilt.android.AndroidEntryPoint + class MyCommentsFragment : BaseFragment() { override val TAG: String diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/main/mypage/MypageViewModel.kt b/app/src/main/java/com/konkuk/mocacong/presentation/main/mypage/MypageViewModel.kt index ce6d6e8..c5ceb5c 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/main/mypage/MypageViewModel.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/main/mypage/MypageViewModel.kt @@ -6,17 +6,23 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.konkuk.mocacong.remote.models.response.* +import com.konkuk.mocacong.remote.repositories.KakaoRepository import com.konkuk.mocacong.remote.repositories.MypageRepository +import com.konkuk.mocacong.remote.repositories.UserPreferencesRepository +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.MultipartBody +import javax.inject.Inject -enum class MyPage { - FAVS, REVIEWS, COMMENTS -} -class MypageViewModel(val repository: MypageRepository) : ViewModel() { +@HiltViewModel +class MypageViewModel @Inject constructor( + private val mypageRepository: MypageRepository, + private val userPreferencesRepository: UserPreferencesRepository, + private val kakaoRepository: KakaoRepository +) : ViewModel() { val favDescription = listOf("즐겨찾는 카페", "즐겨찾기 한 카페를 한 눈에 확인해보세요") val reviewsDescription = listOf("나의 리뷰", "카페별로 작성한 리뷰를 확인해볼까요?") @@ -29,7 +35,7 @@ class MypageViewModel(val repository: MypageRepository) : ViewModel() { fun requestMyFavs(page: Int = favPage) { viewModelScope.launch { val response = withContext(Dispatchers.IO) { - repository.getMyFavs(page) + mypageRepository.getMyFavs(page) } response.byState( onSuccess = { @@ -53,7 +59,7 @@ class MypageViewModel(val repository: MypageRepository) : ViewModel() { fun requestMyReviews(page: Int = reviewPage) { viewModelScope.launch { val response = withContext(Dispatchers.IO) { - repository.getMyReviews(page) + mypageRepository.getMyReviews(page) } response.byState( onSuccess = { @@ -78,7 +84,7 @@ class MypageViewModel(val repository: MypageRepository) : ViewModel() { fun requestMyComments(page: Int = commentPage) { viewModelScope.launch { val response = withContext(Dispatchers.IO) { - repository.getMyComments(page) + mypageRepository.getMyComments(page) } response.byState( onSuccess = { @@ -100,7 +106,7 @@ class MypageViewModel(val repository: MypageRepository) : ViewModel() { fun requestSearchAddress(keyword: String, id: String) { viewModelScope.launch { val response = withContext(Dispatchers.IO) { - repository.getSearchResult( + kakaoRepository.getMyPageSearchResult( keyword = keyword ) } @@ -121,16 +127,37 @@ class MypageViewModel(val repository: MypageRepository) : ViewModel() { private val _myProfile = MutableLiveData() val myProfile: LiveData = _myProfile + suspend fun getMyProfile(): ProfileResponse? { + val nickname = withContext(Dispatchers.IO) { + userPreferencesRepository.getMemberNickname() + } + val imgUrl = withContext(Dispatchers.IO) { + userPreferencesRepository.getMemberImage() + } + return if (nickname.isNullOrBlank()) { + requestMyProfile() + null + } else { + val pr = ProfileResponse(nickname, imgUrl, "") + _myProfile.value = pr + return pr + } + } + fun requestMyProfile() { Log.d("Profile", "requestMyProfile") viewModelScope.launch { val response = withContext(Dispatchers.IO) { - repository.getMyProfile() + mypageRepository.getMyProfile() } response.byState( onSuccess = { _myProfile.value = it Log.d("Profile", "requestMyProfile 성공. $it") + viewModelScope.launch { + userPreferencesRepository.saveMemberNickname(it.nickname) + userPreferencesRepository.saveMemberImage(it.imgUrl) + } } ) } @@ -140,7 +167,7 @@ class MypageViewModel(val repository: MypageRepository) : ViewModel() { Log.d("Profile", "putMyProfileImg 들어옴") viewModelScope.launch { val response = withContext(Dispatchers.IO) { - repository.putMyProfileImg(part) + mypageRepository.putMyProfileImg(part) } response.byState(onSuccess = { Log.d("Profile", "putMyProfileImg 성공") diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/models/CafePreviewUiModel.kt b/app/src/main/java/com/konkuk/mocacong/presentation/models/CafePreviewUiModel.kt index 60ae3e4..dbb2b06 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/models/CafePreviewUiModel.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/models/CafePreviewUiModel.kt @@ -28,7 +28,7 @@ data class CafePreviewUiModel( "group" -> { groupVisibility = View.VISIBLE } - else -> { + "both" -> { soloVisibility = View.VISIBLE groupVisibility = View.VISIBLE } diff --git a/app/src/main/java/com/konkuk/mocacong/presentation/splash/SplashActivity.kt b/app/src/main/java/com/konkuk/mocacong/presentation/splash/SplashActivity.kt index f184b86..2c95705 100644 --- a/app/src/main/java/com/konkuk/mocacong/presentation/splash/SplashActivity.kt +++ b/app/src/main/java/com/konkuk/mocacong/presentation/splash/SplashActivity.kt @@ -11,10 +11,13 @@ import com.konkuk.mocacong.presentation.main.MainActivity import com.konkuk.mocacong.remote.models.request.ReIssueRequest import com.konkuk.mocacong.remote.repositories.TokenRepository import com.konkuk.mocacong.util.TokenManager +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.* import kotlinx.coroutines.flow.first +import javax.inject.Inject @SuppressLint("CustomSplashScreen") +@AndroidEntryPoint class SplashActivity : AppCompatActivity() { val TAG = "Splash" @@ -23,17 +26,15 @@ class SplashActivity : AppCompatActivity() { checkToken() } - private val repository = TokenRepository() + @Inject lateinit var repository: TokenRepository private fun checkToken() = lifecycleScope.launch { val startTime = System.currentTimeMillis().toInt() - Log.d(TAG, "checkToken 들어옴") - val refreshToken = withContext(Dispatchers.Default) { + val refreshToken = withContext(Dispatchers.IO) { TokenManager.getRefreshToken().first() } if (refreshToken.isNullOrBlank()) { - Log.d(TAG, "refresh token is NULL") gotoActivity(LoginActivity::class.java, startTime) return@launch } @@ -41,10 +42,14 @@ class SplashActivity : AppCompatActivity() { val response = withContext(Dispatchers.IO) { postRefresh(refreshToken) } + response.byState( onSuccess = { + Log.d(TAG, "성공") CoroutineScope(Dispatchers.Default).launch { - TokenManager.saveAccessToken(it.accessToken) + withContext(Dispatchers.IO) { + TokenManager.saveAccessToken(it.accessToken) + } gotoActivity(MainActivity::class.java, startTime) } }, @@ -66,18 +71,18 @@ class SplashActivity : AppCompatActivity() { private suspend fun postRefresh(token: String) = repository.refresh(ReIssueRequest(token)) - private fun gotoActivity(activity: Class<*>?, startTime: Int) = lifecycleScope.launch { - val endTime = System.currentTimeMillis() // 종료 시간 기록 - val elapsedTime = endTime - startTime // 경과 시간 계산 + private fun gotoActivity(activity: Class<*>, startTime: Int) = + CoroutineScope(Dispatchers.Main).launch { + val endTime = System.currentTimeMillis() + val elapsedTime = endTime - startTime + if (elapsedTime < 2000) { + delay(2000 - elapsedTime) + } - if (elapsedTime < 2000) { - delay(2000 - elapsedTime) // 2초 동안 기다리기 + val intent = Intent(this@SplashActivity, activity) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + startActivity(intent) } - val intent = Intent(this@SplashActivity, activity) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - startActivity(intent) - } - } \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/remote/models/response/ProfileResponse.kt b/app/src/main/java/com/konkuk/mocacong/remote/models/response/ProfileResponse.kt index ebde9ca..7d02578 100644 --- a/app/src/main/java/com/konkuk/mocacong/remote/models/response/ProfileResponse.kt +++ b/app/src/main/java/com/konkuk/mocacong/remote/models/response/ProfileResponse.kt @@ -4,4 +4,6 @@ data class ProfileResponse( val nickname: String, val imgUrl: String?, val email: String -) \ No newline at end of file +){ + val nicknameString: String get() = "${nickname}님의 댓글 쓰기" +} \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/remote/repositories/BaseRepository.kt b/app/src/main/java/com/konkuk/mocacong/remote/repositories/BaseRepository.kt index 0f2fe66..5d42be0 100644 --- a/app/src/main/java/com/konkuk/mocacong/remote/repositories/BaseRepository.kt +++ b/app/src/main/java/com/konkuk/mocacong/remote/repositories/BaseRepository.kt @@ -3,11 +3,11 @@ package com.konkuk.mocacong.remote.repositories import android.util.Log import com.konkuk.mocacong.remote.models.response.ErrorResponse import com.konkuk.mocacong.util.ApiState -import com.konkuk.mocacong.util.RetrofitClient import okhttp3.ResponseBody import retrofit2.Response +import retrofit2.Retrofit -abstract class BaseRepository { +abstract class BaseRepository(private val retrofit: Retrofit) { protected suspend fun makeRequest(call: suspend () -> Response): ApiState { val response = call() @@ -23,7 +23,7 @@ abstract class BaseRepository { private fun getErrorResponse(errorBody: ResponseBody): ErrorResponse? { - return RetrofitClient.retrofit.responseBodyConverter( + return retrofit.responseBodyConverter( ErrorResponse::class.java, ErrorResponse::class.java.annotations ).convert(errorBody) } diff --git a/app/src/main/java/com/konkuk/mocacong/remote/repositories/CafeDetailRepository.kt b/app/src/main/java/com/konkuk/mocacong/remote/repositories/CafeDetailRepository.kt index bd511b5..45f547b 100644 --- a/app/src/main/java/com/konkuk/mocacong/remote/repositories/CafeDetailRepository.kt +++ b/app/src/main/java/com/konkuk/mocacong/remote/repositories/CafeDetailRepository.kt @@ -7,12 +7,13 @@ import com.konkuk.mocacong.remote.models.response.CafeResponse import com.konkuk.mocacong.remote.models.response.CommentsResponse import com.konkuk.mocacong.remote.models.response.MyReviewResponse import com.konkuk.mocacong.util.ApiState -import com.konkuk.mocacong.util.RetrofitClient +import com.konkuk.mocacong.util.MocacongRetrofit import okhttp3.MultipartBody +import retrofit2.Retrofit +import javax.inject.Inject -class CafeDetailRepository : BaseRepository() { - - private val api = RetrofitClient.create(CafeDetailAPI::class.java) +class CafeDetailRepository @Inject constructor(private val api: CafeDetailAPI,@MocacongRetrofit retrofit: Retrofit) : + BaseRepository(retrofit) { suspend fun getCafeDetailInfo(id: String): ApiState = makeRequest { api.getCafeResponse(cafeId = id) } diff --git a/app/src/main/java/com/konkuk/mocacong/remote/repositories/KakaoRepository.kt b/app/src/main/java/com/konkuk/mocacong/remote/repositories/KakaoRepository.kt new file mode 100644 index 0000000..4ac326a --- /dev/null +++ b/app/src/main/java/com/konkuk/mocacong/remote/repositories/KakaoRepository.kt @@ -0,0 +1,35 @@ +package com.konkuk.mocacong.remote.repositories + +import com.konkuk.mocacong.remote.apis.KakaoSearchAPI +import com.konkuk.mocacong.remote.models.response.LocalSearchResponse +import com.konkuk.mocacong.util.ApiState +import com.konkuk.mocacong.util.KakaoRetrofit +import retrofit2.Retrofit +import javax.inject.Inject + +class KakaoRepository @Inject constructor( + private val kakaoApi: KakaoSearchAPI, + @KakaoRetrofit retrofit: Retrofit +) : BaseRepository(retrofit) { + + + suspend fun getPlaces(x: String, y: String, radius: Int): ApiState = + makeRequest { kakaoApi.getLocalSearchResponse(x = x, y = y, radius = radius) } + + suspend fun getSearchResult( + x: String, + y: String, + keyword: String + ): ApiState = + makeRequest { kakaoApi.getKeywordSearchResponse(query = keyword, x = x, y = y) } + + suspend fun getMyPageSearchResult( + keyword: String + ): ApiState = + makeRequest { + kakaoApi.getKeywordSearchResponse( + query = keyword, + sort = "accuracy" + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/remote/repositories/LoginRepository.kt b/app/src/main/java/com/konkuk/mocacong/remote/repositories/LoginRepository.kt index 0785200..dd56eab 100644 --- a/app/src/main/java/com/konkuk/mocacong/remote/repositories/LoginRepository.kt +++ b/app/src/main/java/com/konkuk/mocacong/remote/repositories/LoginRepository.kt @@ -6,11 +6,12 @@ import com.konkuk.mocacong.remote.models.request.OAuthRequest import com.konkuk.mocacong.remote.models.response.CheckDuplicateResponse import com.konkuk.mocacong.remote.models.response.KakaoLoginResponse import com.konkuk.mocacong.util.ApiState -import com.konkuk.mocacong.util.RetrofitClient - -class LoginRepository : BaseRepository() { - val api = RetrofitClient.create(AuthAPI::class.java) +import com.konkuk.mocacong.util.MocacongRetrofit +import retrofit2.Retrofit +import javax.inject.Inject +class LoginRepository @Inject constructor(private val api: AuthAPI,@MocacongRetrofit retrofit: Retrofit) : + BaseRepository(retrofit) { suspend fun postKakaoLogin(kakaoRequest: KakaoRequest): ApiState = makeRequest { api.kakaoLoginPost(kakaoRequest) } diff --git a/app/src/main/java/com/konkuk/mocacong/remote/repositories/MapRepository.kt b/app/src/main/java/com/konkuk/mocacong/remote/repositories/MapRepository.kt index 320861c..004e57c 100644 --- a/app/src/main/java/com/konkuk/mocacong/remote/repositories/MapRepository.kt +++ b/app/src/main/java/com/konkuk/mocacong/remote/repositories/MapRepository.kt @@ -1,30 +1,19 @@ package com.konkuk.mocacong.remote.repositories -import com.konkuk.mocacong.objects.KakaoLocalClient -import com.konkuk.mocacong.util.RetrofitClient -import com.konkuk.mocacong.remote.apis.KakaoSearchAPI import com.konkuk.mocacong.remote.apis.MapApi import com.konkuk.mocacong.remote.models.request.FilteringRequest import com.konkuk.mocacong.remote.models.request.PostCafeRequest import com.konkuk.mocacong.remote.models.response.CafePreviewResponse import com.konkuk.mocacong.remote.models.response.FilteringResponse -import com.konkuk.mocacong.remote.models.response.LocalSearchResponse import com.konkuk.mocacong.util.ApiState - -class MapRepository : BaseRepository() { - - private val mapApi = RetrofitClient.create(MapApi::class.java) - private val kakaoApi = KakaoLocalClient.create(KakaoSearchAPI::class.java) - - suspend fun getPlaces(x: String, y: String, radius: Int): ApiState = - makeRequest { kakaoApi.getLocalSearchResponse(x = x, y = y, radius = radius) } - - suspend fun getSearchResult( - x: String, - y: String, - keyword: String - ): ApiState = - makeRequest { kakaoApi.getKeywordSearchResponse(query = keyword, x = x, y = y) } +import com.konkuk.mocacong.util.MocacongRetrofit +import retrofit2.Retrofit +import javax.inject.Inject + +class MapRepository @Inject constructor( + private val mapApi: MapApi, + @MocacongRetrofit retrofit: Retrofit +) : BaseRepository(retrofit) { suspend fun filterStudyType(type: String, fr: FilteringRequest): ApiState = makeRequest { mapApi.getFilteredCafes(studyType = type, filteringRequest = fr) } diff --git a/app/src/main/java/com/konkuk/mocacong/remote/repositories/MypageRepository.kt b/app/src/main/java/com/konkuk/mocacong/remote/repositories/MypageRepository.kt index 61859fc..2b64bc3 100644 --- a/app/src/main/java/com/konkuk/mocacong/remote/repositories/MypageRepository.kt +++ b/app/src/main/java/com/konkuk/mocacong/remote/repositories/MypageRepository.kt @@ -1,17 +1,15 @@ package com.konkuk.mocacong.remote.repositories -import com.konkuk.mocacong.objects.KakaoLocalClient -import com.konkuk.mocacong.remote.apis.KakaoSearchAPI import com.konkuk.mocacong.remote.apis.MyPageAPI -import com.konkuk.mocacong.remote.models.response.LocalSearchResponse -import com.konkuk.mocacong.util.ApiState -import com.konkuk.mocacong.util.RetrofitClient +import com.konkuk.mocacong.util.MocacongRetrofit import okhttp3.MultipartBody +import retrofit2.Retrofit +import javax.inject.Inject -class MypageRepository : BaseRepository() { - - val mypageApi = RetrofitClient.create(MyPageAPI::class.java) - private val kakaoApi = KakaoLocalClient.create(KakaoSearchAPI::class.java) +class MypageRepository @Inject constructor( + private val mypageApi: MyPageAPI, + @MocacongRetrofit retrofit: Retrofit +) : BaseRepository(retrofit) { suspend fun getMyComments(page: Int) = makeRequest { mypageApi.getMyComments(page = page) } @@ -22,16 +20,6 @@ class MypageRepository : BaseRepository() { suspend fun getMyFavs(page: Int) = makeRequest { mypageApi.getMyFavorites(page = page) } - suspend fun getSearchResult( - keyword: String - ): ApiState = - makeRequest { - kakaoApi.getKeywordSearchResponse( - query = keyword, - sort = "accuracy" - ) - } - suspend fun getMyProfile() = makeRequest { mypageApi.getMyProfile() } diff --git a/app/src/main/java/com/konkuk/mocacong/remote/repositories/TokenRepository.kt b/app/src/main/java/com/konkuk/mocacong/remote/repositories/TokenRepository.kt index c540444..8d73f78 100644 --- a/app/src/main/java/com/konkuk/mocacong/remote/repositories/TokenRepository.kt +++ b/app/src/main/java/com/konkuk/mocacong/remote/repositories/TokenRepository.kt @@ -4,10 +4,15 @@ import com.konkuk.mocacong.remote.apis.TokenAPI import com.konkuk.mocacong.remote.models.request.ReIssueRequest import com.konkuk.mocacong.remote.models.response.ReIssueResponse import com.konkuk.mocacong.util.ApiState -import com.konkuk.mocacong.util.RetrofitClient +import com.konkuk.mocacong.util.MocacongRetrofit +import retrofit2.Retrofit +import javax.inject.Inject -class TokenRepository: BaseRepository() { - val api = RetrofitClient.create(TokenAPI::class.java) +class TokenRepository @Inject constructor( + private val api: TokenAPI, + @MocacongRetrofit retrofit: Retrofit +) : + BaseRepository(retrofit) { suspend fun refresh(reIssueRequest: ReIssueRequest): ApiState = makeRequest { api.updateAccessToken(reIssueRequest) } diff --git a/app/src/main/java/com/konkuk/mocacong/remote/repositories/UserPreferencesRepository.kt b/app/src/main/java/com/konkuk/mocacong/remote/repositories/UserPreferencesRepository.kt new file mode 100644 index 0000000..ef49640 --- /dev/null +++ b/app/src/main/java/com/konkuk/mocacong/remote/repositories/UserPreferencesRepository.kt @@ -0,0 +1,45 @@ +package com.konkuk.mocacong.remote.repositories + +import android.content.Context +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import com.konkuk.mocacong.util.dataStore +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import javax.inject.Inject + + +class UserPreferencesRepository @Inject constructor( + @ApplicationContext private val context: Context +) { + + private object PreferenceKeys { + val MEMBER_NICKNAME = stringPreferencesKey("member_nickname") + val MEMBER_IMAGE = stringPreferencesKey("member_image") + } + + suspend fun saveMemberNickname(nickname: String) { + context.dataStore.edit { prefs -> + prefs[PreferenceKeys.MEMBER_NICKNAME] = nickname + } + } + + suspend fun getMemberNickname() = context.dataStore.data.map { pref-> + pref[PreferenceKeys.MEMBER_NICKNAME] + }.first() + + suspend fun getMemberImage() = context.dataStore.data.map { pref -> + pref[PreferenceKeys.MEMBER_IMAGE] + }.first() + + + suspend fun saveMemberImage(url: String?) { + url?.let { + context.dataStore.edit { prefs -> + prefs[PreferenceKeys.MEMBER_IMAGE] = it + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/util/ApplicatonModule.kt b/app/src/main/java/com/konkuk/mocacong/util/ApplicatonModule.kt new file mode 100644 index 0000000..30f8bff --- /dev/null +++ b/app/src/main/java/com/konkuk/mocacong/util/ApplicatonModule.kt @@ -0,0 +1,17 @@ +package com.konkuk.mocacong.util + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object ApplicatonModule { + + @Singleton + @Provides + fun appContext(application: MocacongApplication): Context = application.applicationContext +} \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/util/BindingAdapters.kt b/app/src/main/java/com/konkuk/mocacong/util/BindingAdapters.kt index 27f98c7..fc85b22 100644 --- a/app/src/main/java/com/konkuk/mocacong/util/BindingAdapters.kt +++ b/app/src/main/java/com/konkuk/mocacong/util/BindingAdapters.kt @@ -8,10 +8,10 @@ import com.bumptech.glide.Glide import com.google.android.material.button.MaterialButtonToggleGroup import com.konkuk.mocacong.R import com.konkuk.mocacong.data.entities.Comment -import com.konkuk.mocacong.presentation.detail.CafeCommentView -import com.konkuk.mocacong.presentation.detail.CafeCommentsAdapter -import com.konkuk.mocacong.presentation.detail.ImageAdapter import com.konkuk.mocacong.presentation.detail.ReviewButtonGroup +import com.konkuk.mocacong.presentation.detail.comment.CafeCommentView +import com.konkuk.mocacong.presentation.detail.comment.CafeCommentsAdapter +import com.konkuk.mocacong.presentation.detail.image.ImageAdapter import com.konkuk.mocacong.presentation.main.mypage.MyCommentsAdapter import com.konkuk.mocacong.presentation.main.mypage.MyFavsAdapter import com.konkuk.mocacong.presentation.main.mypage.MyReviewsAdapter @@ -26,6 +26,7 @@ object BindingAdapters { fun setProfileImageUrl(imageView: ImageView, url: String?) { if (url.isNullOrBlank()) imageView.setImageResource(R.drawable.img_no_profile) else Glide.with(imageView.context).load(url).into(imageView) + imageView.clipToOutline = true } diff --git a/app/src/main/java/com/konkuk/mocacong/util/Extentions.kt b/app/src/main/java/com/konkuk/mocacong/util/Extentions.kt new file mode 100644 index 0000000..d33e35a --- /dev/null +++ b/app/src/main/java/com/konkuk/mocacong/util/Extentions.kt @@ -0,0 +1,53 @@ +package com.konkuk.mocacong.util + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.util.Log +import android.view.KeyEvent +import android.view.inputmethod.InputMethodManager +import android.widget.EditText +import androidx.core.content.ContextCompat +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.preferencesDataStore +import com.konkuk.mocacong.presentation.splash.SplashActivity + +fun Context.forceRestart() { + if (this is Activity) { + Log.d("Restart","Force Restart") + this.finish() + val intent = Intent(this, SplashActivity::class.java) + this.startActivity(intent) + } +} + + +val Context.dataStore : DataStore by preferencesDataStore(name = "mocacong") + + +fun EditText.handleEnterKey(onEnter: ()->(Unit) = {}) { + val inputMethodManager = + ContextCompat.getSystemService(context, InputMethodManager::class.java) + this.setOnKeyListener { _, code, keyEvent -> + if ((keyEvent.action == KeyEvent.ACTION_DOWN) && (code == KeyEvent.KEYCODE_ENTER)) { + (inputMethodManager)?.hideSoftInputFromWindow( + this.windowToken, + 0 + ) + onEnter() + true + } + false + } +} + +fun EditText.showKeyboard() { + isFocusableInTouchMode = true + requestFocus() + postDelayed({ + val inputMethodManager = + ContextCompat.getSystemService(context, InputMethodManager::class.java) + inputMethodManager?.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) + }, 30) +} diff --git a/app/src/main/java/com/konkuk/mocacong/util/MocacongApplication.kt b/app/src/main/java/com/konkuk/mocacong/util/MocacongApplication.kt index 9f560da..6c28d26 100644 --- a/app/src/main/java/com/konkuk/mocacong/util/MocacongApplication.kt +++ b/app/src/main/java/com/konkuk/mocacong/util/MocacongApplication.kt @@ -1,20 +1,19 @@ package com.konkuk.mocacong.util import android.app.Application -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.preferencesDataStore import com.kakao.sdk.common.KakaoSdk +import dagger.hilt.android.HiltAndroidApp +@HiltAndroidApp class MocacongApplication : Application() { - private val authDataStore : DataStore by preferencesDataStore(name = "auth") + override fun onCreate() { super.onCreate() // 다른 초기화 코드들 // Kakao SDK 초기화 KakaoSdk.init(this, "eb2cc1ff484fdd2ba0934a003fd80b3b") - TokenManager.setDataStore(authDataStore) + TokenManager.setDataStore(dataStore) } } diff --git a/app/src/main/java/com/konkuk/mocacong/util/NetworkModule.kt b/app/src/main/java/com/konkuk/mocacong/util/NetworkModule.kt new file mode 100644 index 0000000..05bed5c --- /dev/null +++ b/app/src/main/java/com/konkuk/mocacong/util/NetworkModule.kt @@ -0,0 +1,239 @@ +package com.konkuk.mocacong.util + +import android.content.Context +import android.util.Log +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.kakao.sdk.common.Constants.AUTHORIZATION +import com.konkuk.mocacong.remote.apis.* +import com.konkuk.mocacong.remote.models.response.ReIssueResponse +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONException +import org.json.JSONObject +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Qualifier +import javax.inject.Singleton + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class MocacongRetrofit + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class KakaoRetrofit + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class MocacongClient + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class KakaoClient + +@Module +@InstallIn(SingletonComponent::class) + +object NetworkModule { + + private const val TAG = "NetworkModule" +// private const val BASE_URL = "http://3.37.64.38:8080/" + + private const val BASE_URL = "https://mocacong.com/" + private const val KAKAO_BASE_URL = "https://dapi.kakao.com/" + + @MocacongClient + @Provides + @Singleton + fun provideClient( + interceptor: Interceptor, authenticator: Authenticator + ): OkHttpClient = createClient(interceptor, authenticator) + + @KakaoClient + @Provides + @Singleton + fun provideKakaoClient( + interceptor: KakaoInterceptor + ): OkHttpClient = createClient(interceptor, null) + + private fun createClient( + interceptor: Interceptor?, authenticator: Authenticator? + ): OkHttpClient { + val client = OkHttpClient.Builder() + if (authenticator != null) client.authenticator(authenticator) + if (interceptor != null) client.addInterceptor(interceptor) + return client.connectTimeout(5, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS).build() + } + + + // Retrofit 객체 생성 + @MocacongRetrofit + @Provides + @Singleton + fun provideMocacongRetrofit(@MocacongClient client: OkHttpClient): Retrofit { + return Retrofit.Builder().baseUrl(BASE_URL).client(client).addConverterFactory( + GsonConverterFactory.create( + GsonBuilder().setLenient().create() + ) + ).build() + } + + @KakaoRetrofit + @Provides + @Singleton + fun provideKakaoRetrofit(@KakaoClient client: OkHttpClient): Retrofit { + return Retrofit.Builder().baseUrl(KAKAO_BASE_URL).client(client).addConverterFactory( + GsonConverterFactory.create( + GsonBuilder().setLenient().create() + ) + ).build() + } + + + @Provides + @Singleton + fun provideInterceptor(): Interceptor = Interceptor { chain -> + val token: String? = runBlocking { + TokenManager.getAccessToken().first() + } + Log.d("Network", "interceptor accessToken : $token") + + val request = chain.request().newBuilder().header(AUTHORIZATION, "Bearer $token").build() + Log.d("Network", "request proceed 전: $request") + + val response = chain.proceed(request) + if (!response.isSuccessful) { + val errorJson = JSONObject(response.peekBody(2048).string()) + try { + val code = errorJson.getInt("code") + val message = errorJson.getString("message") + Log.e("interceptor", "Network Error 발생! code = $code, message = $message") + } catch (e: JSONException) { + Log.e("interceptor", "Json Body is null $errorJson") + } + } + response + } + + + class KakaoInterceptor @Inject constructor() : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response = with(chain) { + val newRequest = request().newBuilder() + .addHeader("Authorization", "KakaoAK 3e7c72e35c239c324a59419fa82812a4").build() + proceed(newRequest) + } + } + + + @Provides + @Singleton + fun provideAuthenticator( + @ApplicationContext context: Context + ): Authenticator { + + val authenticatorClient = createClient(null, null) + + fun newRequestWithToken(token: String, request: Request): Request = + request.newBuilder().removeHeader(AUTHORIZATION) + .addHeader(AUTHORIZATION, "Bearer $token").build() + + fun refresh(response: Response): Request? { + val refreshToken = runBlocking { + TokenManager.getRefreshToken().first() + } + if (refreshToken.isNullOrBlank()) { + Log.d(TAG, "RefreshToken is NULL") + return null + } + + val refreshRequest = Request.Builder().url(BASE_URL + "login/reissue").post( + "{\"refreshToken\": \"${refreshToken}\"}".toRequestBody("application/json".toMediaType()) + ).build() + + val newRequest = runBlocking { + val refreshResponse = authenticatorClient.newCall(refreshRequest).execute() + if (refreshResponse.isSuccessful && refreshResponse.body != null) { + val newToken = refreshResponse.peekBody(2048).string() + val newTokenObj = Gson().fromJson(newToken, ReIssueResponse::class.java) + TokenManager.saveAccessToken(newTokenObj.accessToken) + return@runBlocking newRequestWithToken( + newTokenObj.accessToken, response.request + ) + } else throw java.lang.RuntimeException() + } + + return newRequest + } + + + return Authenticator { route, response -> + val TAG = "Authenticator" + val errorJson = JSONObject(response.peekBody(2048).string()) + val code = errorJson.getInt("code") + when (code) { + 1013 -> { + //로그인안됨 + } + 1014 -> { + //액세스 기한 만료 + val newRequest = refresh(response) + return@Authenticator newRequest + } + 1021 -> { + //올바르지 않은 리프레시 토큰 + context.forceRestart() + } + 1022 -> { + //액세스 만료되지 않았음 + response.request + } + } + + null + } + } + + + @Provides + @Singleton + fun provideDetailApi(@MocacongRetrofit retrofit: Retrofit): CafeDetailAPI = + retrofit.create(CafeDetailAPI::class.java) + + @Provides + @Singleton + fun provideMapApi(@MocacongRetrofit retrofit: Retrofit): MapApi = + retrofit.create(MapApi::class.java) + + + @Provides + @Singleton + fun provideMypageApi(@MocacongRetrofit retrofit: Retrofit): MyPageAPI = + retrofit.create(MyPageAPI::class.java) + + @Provides + @Singleton + fun provideTokenApi(@MocacongRetrofit retrofit: Retrofit): TokenAPI = + retrofit.create(TokenAPI::class.java) + + @Provides + @Singleton + fun provideAuthApi(@MocacongRetrofit retrofit: Retrofit): AuthAPI = + retrofit.create(AuthAPI::class.java) + + @Provides + @Singleton + fun provideKakaoSearchApi(@KakaoRetrofit retrofit: Retrofit): KakaoSearchAPI = + retrofit.create(KakaoSearchAPI::class.java) +} diff --git a/app/src/main/java/com/konkuk/mocacong/util/RetrofitClient.kt b/app/src/main/java/com/konkuk/mocacong/util/RetrofitClient.kt deleted file mode 100644 index f6ca082..0000000 --- a/app/src/main/java/com/konkuk/mocacong/util/RetrofitClient.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.konkuk.mocacong.util - -import android.util.Log -import com.google.gson.GsonBuilder -import com.kakao.sdk.common.Constants.AUTHORIZATION -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.Response -import org.json.JSONObject -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import java.util.concurrent.TimeUnit - -object RetrofitClient { - - private const val BASE_URL = "http://3.37.64.38:8080/" -// private const val BASE_URL = "https://mocacong.com/" - - //interceptor 생성 - private val interceptorClient = OkHttpClient().newBuilder() - .connectTimeout(5, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .writeTimeout(15, TimeUnit.SECONDS) - .authenticator(TokenAuthenticator()) - .addInterceptor(ResponseInterceptor()) - .build() - - // Retrofit 객체 생성 - val retrofit: Retrofit = Retrofit.Builder() - .baseUrl(BASE_URL) - .client(interceptorClient) - .addConverterFactory( - GsonConverterFactory.create( - GsonBuilder() - .setLenient() - .create() - ) - ) - .build() - - - // API 인터페이스 반환 - fun create(service: Class): T { - return retrofit.create(service) - } -} - -class ResponseInterceptor : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - - val token : String? = runBlocking { - TokenManager.getAccessToken().first() - } - - val request = chain.request().newBuilder().header(AUTHORIZATION, "Bearer $token").build() - - val response = chain.proceed(request) - return if (response.isSuccessful) response - else { - val errorJson = JSONObject(response.peekBody(2048).string()) - val code = errorJson.getInt("code") - val message = errorJson.getString("message") - Log.e("interceptor", "Network Error 발생! code = $code, message = $message") - - response - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/konkuk/mocacong/util/TokenAuthenticator.kt b/app/src/main/java/com/konkuk/mocacong/util/TokenAuthenticator.kt deleted file mode 100644 index 8747bf8..0000000 --- a/app/src/main/java/com/konkuk/mocacong/util/TokenAuthenticator.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.konkuk.mocacong.util - -import com.kakao.sdk.common.Constants.AUTHORIZATION -import com.konkuk.mocacong.remote.apis.TokenAPI -import com.konkuk.mocacong.remote.models.request.ReIssueRequest -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import okhttp3.Authenticator -import okhttp3.Request -import okhttp3.Response -import okhttp3.Route - -class TokenAuthenticator : Authenticator { - override fun authenticate(route: Route?, response: Response): Request? { - val refreshToken = runBlocking { - TokenManager.getRefreshToken().first() - } - if (refreshToken.isNullOrBlank()) { - return null - } - - val newToken = getUpdatedToken(refreshToken) - - return if (!newToken.isNullOrBlank()) { - CoroutineScope(Dispatchers.IO).launch { - TokenManager.saveAccessToken(newToken) - } - newRequestWithToken(newToken, response.request) - } else null - } - - private fun newRequestWithToken(token: String, request: Request): Request = - request.newBuilder() - .header(AUTHORIZATION, token) - .build() - - private fun getUpdatedToken(refreshToken: String): String? { - val response = runBlocking { - RetrofitClient.create(TokenAPI::class.java).updateAccessToken( - ReIssueRequest(refreshToken) - ) - } - - return if (response.isSuccessful && response.body() != null) { - response.body()!!.accessToken - } else null - } - -} \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_required.png b/app/src/main/res/drawable/icon_required.png new file mode 100644 index 0000000..73e0f14 Binary files /dev/null and b/app/src/main/res/drawable/icon_required.png differ diff --git a/app/src/main/res/drawable/logo.png b/app/src/main/res/drawable/logo.png deleted file mode 100644 index cd8e613..0000000 Binary files a/app/src/main/res/drawable/logo.png and /dev/null differ diff --git a/app/src/main/res/drawable/map_ic_none.xml b/app/src/main/res/drawable/map_ic_none.xml index 55e8077..17bae87 100644 --- a/app/src/main/res/drawable/map_ic_none.xml +++ b/app/src/main/res/drawable/map_ic_none.xml @@ -6,13 +6,13 @@ + android:fillColor="#5C4726"/> diff --git a/app/src/main/res/drawable/marker_fav.xml b/app/src/main/res/drawable/marker_fav.xml deleted file mode 100644 index e32da76..0000000 --- a/app/src/main/res/drawable/marker_fav.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/marker_origin.xml b/app/src/main/res/drawable/marker_origin.xml deleted file mode 100644 index 94e5147..0000000 --- a/app/src/main/res/drawable/marker_origin.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/preview_icon_both.xml b/app/src/main/res/drawable/preview_icon_both.xml deleted file mode 100644 index 666e9ab..0000000 --- a/app/src/main/res/drawable/preview_icon_both.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/preview_icon_empty_heart.xml b/app/src/main/res/drawable/preview_icon_empty_heart.xml deleted file mode 100644 index 8615390..0000000 --- a/app/src/main/res/drawable/preview_icon_empty_heart.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/preview_icon_fill_heart.xml b/app/src/main/res/drawable/preview_icon_fill_heart.xml deleted file mode 100644 index e563571..0000000 --- a/app/src/main/res/drawable/preview_icon_fill_heart.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/preview_icon_group.xml b/app/src/main/res/drawable/preview_icon_group.xml deleted file mode 100644 index 7c88715..0000000 --- a/app/src/main/res/drawable/preview_icon_group.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/preview_icon_solo.xml b/app/src/main/res/drawable/preview_icon_solo.xml deleted file mode 100644 index 21f8b6a..0000000 --- a/app/src/main/res/drawable/preview_icon_solo.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/profile_no_image.jpg b/app/src/main/res/drawable/profile_no_image.jpg deleted file mode 100644 index c4fdd18..0000000 Binary files a/app/src/main/res/drawable/profile_no_image.jpg and /dev/null differ diff --git a/app/src/main/res/drawable/round_btn.xml b/app/src/main/res/drawable/round_btn.xml deleted file mode 100644 index 748b8dd..0000000 --- a/app/src/main/res/drawable/round_btn.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/round_rectangle.xml b/app/src/main/res/drawable/round_rectangle.xml deleted file mode 100644 index 878a895..0000000 --- a/app/src/main/res/drawable/round_rectangle.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_btn.xml b/app/src/main/res/drawable/selector_btn.xml deleted file mode 100644 index 645189a..0000000 --- a/app/src/main/res/drawable/selector_btn.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_bordered_rect.xml b/app/src/main/res/drawable/shape_bordered_rect.xml deleted file mode 100644 index 4680288..0000000 --- a/app/src/main/res/drawable/shape_bordered_rect.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_full_image.xml b/app/src/main/res/layout/dialog_full_image.xml new file mode 100644 index 0000000..3a01940 --- /dev/null +++ b/app/src/main/res/layout/dialog_full_image.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_cafe_comments.xml b/app/src/main/res/layout/fragment_cafe_comments.xml index 3f8eebc..cc2a2ea 100644 --- a/app/src/main/res/layout/fragment_cafe_comments.xml +++ b/app/src/main/res/layout/fragment_cafe_comments.xml @@ -2,9 +2,12 @@ + tools:context=".presentation.detail.comment.CafeCommentsFragment"> + @@ -82,6 +85,7 @@ layout="@layout/layout_comment_input" android:layout_width="match_parent" android:layout_height="wrap_content" + profile="@{mypageVm.myProfile}" android:id="@+id/commentInputLayout" android:layout_margin="5dp" app:layout_constraintBottom_toBottomOf="parent" /> diff --git a/app/src/main/res/layout/fragment_cafe_detail.xml b/app/src/main/res/layout/fragment_cafe_detail.xml index 0539d6b..a31a229 100644 --- a/app/src/main/res/layout/fragment_cafe_detail.xml +++ b/app/src/main/res/layout/fragment_cafe_detail.xml @@ -3,6 +3,9 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> + @@ -236,24 +239,23 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="10dp" - android:layout_marginTop="40dp" android:orientation="vertical" android:paddingBottom="60dp" app:layout_constraintTop_toBottomOf="@id/divider"> - - - + tools:context=".presentation.detail.image.CafeImagesFragment"> @@ -67,9 +67,9 @@ comments="@{vm.cafeComments}" images="@{vm.cafeImages}" android:layout_width="match_parent" - android:background="@color/white" + android:background="@color/brownGray" android:layout_height="0dp" - android:paddingBottom="30dp" + android:padding="10dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/header" /> diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 5e1bd6d..932c27c 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -45,7 +45,7 @@ android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="0dp" - android:background="@color/stroke" + android:background="@color/ivory" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" diff --git a/app/src/main/res/layout/item_cafe_comment.xml b/app/src/main/res/layout/item_cafe_comment.xml index e35fd4f..2691d42 100644 --- a/app/src/main/res/layout/item_cafe_comment.xml +++ b/app/src/main/res/layout/item_cafe_comment.xml @@ -7,7 +7,7 @@ type="com.konkuk.mocacong.data.entities.Comment" /> - - + + android:background="@drawable/shape_rect_r10_filled" + android:backgroundTint="@color/mocaBrown" + android:layout_margin="4dp" + android:layout_height="300dp"> - diff --git a/app/src/main/res/layout/item_my_comments.xml b/app/src/main/res/layout/item_my_comments.xml index 7b6c14c..dae8f0e 100644 --- a/app/src/main/res/layout/item_my_comments.xml +++ b/app/src/main/res/layout/item_my_comments.xml @@ -33,7 +33,7 @@ diff --git a/app/src/main/res/layout/item_my_fav.xml b/app/src/main/res/layout/item_my_fav.xml index 4a26c52..0ad133d 100644 --- a/app/src/main/res/layout/item_my_fav.xml +++ b/app/src/main/res/layout/item_my_fav.xml @@ -24,7 +24,7 @@ + + + + android:scaleType="centerCrop" /> + + @@ -9,15 +11,23 @@ + android:paddingVertical="5dp"> + @@ -73,27 +83,27 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:textSize="11sp" android:gravity="center" - android:text="@{review.levelStrings[1]}" /> + android:text="@{review.levelStrings[1]}" + android:textSize="11sp" /> + android:text="@{review.levelStrings[2]}" + android:textSize="11sp" /> + android:text="@{review.levelStrings[3]}" + android:textSize="11sp" /> diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index d4e1bc9..df214de 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,4 +1,4 @@ - + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8a010f1..14635dd 100644 --- a/build.gradle +++ b/build.gradle @@ -4,4 +4,7 @@ plugins { id 'com.android.library' version '7.4.2' apply false id 'org.jetbrains.kotlin.android' version '1.9.20' apply false id 'com.google.dagger.hilt.android' version '2.48.1' apply false + id 'com.google.gms.google-services' version '4.4.0' apply false + id 'com.google.firebase.crashlytics' version '2.9.9' apply false + } \ No newline at end of file