From 20a62426611b59ee8491accde8acee214bd77deb Mon Sep 17 00:00:00 2001 From: ddiyooong Date: Fri, 5 Jan 2024 04:17:18 +0900 Subject: [PATCH] MOCACONG v2 RELEASED --- app/build.gradle | 17 +- app/google-services.json | 29 +++ .../mocacong/objects/KakaoLocalClient.kt | 40 --- .../konkuk/mocacong/objects/NetworkManager.kt | 24 -- .../konkuk/mocacong/objects/NetworkUtil.kt | 14 - .../java/com/konkuk/mocacong/objects/Utils.kt | 82 ------ .../presentation/base/BaseActivity.kt | 3 - .../presentation/detail/CafeDetailFragment.kt | 6 + .../detail/CafeDetailViewModel.kt | 7 +- .../presentation/detail/ReviewButtonGroup.kt | 10 + .../detail/{ => comment}/CafeCommentView.kt | 11 +- .../{ => comment}/CafeCommentsAdapter.kt | 2 +- .../{ => comment}/CafeCommentsFragment.kt | 6 +- .../{ => comment}/WriteCommentFragment.kt | 6 +- .../detail/{ => image}/CafeImagesFragment.kt | 10 +- .../detail/image/FullImageDialog.kt | 55 ++++ .../detail/{ => image}/ImageAdapter.kt | 11 +- .../presentation/login/JoinFragment.kt | 2 +- .../presentation/login/LoginActivity.kt | 12 +- .../presentation/login/LoginViewModel.kt | 5 +- .../presentation/login/WebViewActivity.kt | 17 ++ .../presentation/main/MainActivity.kt | 51 ++-- .../presentation/main/MainViewModel.kt | 12 +- .../presentation/main/map/HomeFragment.kt | 20 +- .../presentation/main/map/MapViewModel.kt | 16 +- .../presentation/main/map/SearchFragment.kt | 4 +- .../main/mypage/MyCommentsFragment.kt | 2 + .../main/mypage/MypageViewModel.kt | 47 +++- .../presentation/models/CafePreviewUiModel.kt | 2 +- .../presentation/splash/SplashActivity.kt | 35 +-- .../remote/models/response/ProfileResponse.kt | 4 +- .../remote/repositories/BaseRepository.kt | 6 +- .../repositories/CafeDetailRepository.kt | 9 +- .../remote/repositories/KakaoRepository.kt | 35 +++ .../remote/repositories/LoginRepository.kt | 9 +- .../remote/repositories/MapRepository.kt | 27 +- .../remote/repositories/MypageRepository.kt | 26 +- .../remote/repositories/TokenRepository.kt | 11 +- .../repositories/UserPreferencesRepository.kt | 45 ++++ .../konkuk/mocacong/util/ApplicatonModule.kt | 17 ++ .../konkuk/mocacong/util/BindingAdapters.kt | 7 +- .../com/konkuk/mocacong/util/Extentions.kt | 53 ++++ .../mocacong/util/MocacongApplication.kt | 9 +- .../com/konkuk/mocacong/util/NetworkModule.kt | 239 ++++++++++++++++++ .../konkuk/mocacong/util/RetrofitClient.kt | 70 ----- .../mocacong/util/TokenAuthenticator.kt | 52 ---- app/src/main/res/drawable/icon_required.png | Bin 0 -> 3822 bytes app/src/main/res/drawable/logo.png | Bin 14749 -> 0 bytes app/src/main/res/drawable/map_ic_none.xml | 4 +- app/src/main/res/drawable/marker_fav.xml | 8 - app/src/main/res/drawable/marker_origin.xml | 14 - .../main/res/drawable/preview_icon_both.xml | 13 - .../res/drawable/preview_icon_empty_heart.xml | 6 - .../res/drawable/preview_icon_fill_heart.xml | 6 - .../main/res/drawable/preview_icon_group.xml | 10 - .../main/res/drawable/preview_icon_solo.xml | 6 - .../main/res/drawable/profile_no_image.jpg | Bin 127948 -> 0 bytes app/src/main/res/drawable/round_btn.xml | 29 --- app/src/main/res/drawable/round_rectangle.xml | 17 -- app/src/main/res/drawable/selector_btn.xml | 27 -- .../main/res/drawable/shape_bordered_rect.xml | 11 - app/src/main/res/layout/dialog_full_image.xml | 38 +++ .../res/layout/fragment_cafe_comments.xml | 6 +- .../main/res/layout/fragment_cafe_detail.xml | 13 +- .../main/res/layout/fragment_cafe_images.xml | 6 +- app/src/main/res/layout/fragment_search.xml | 2 +- app/src/main/res/layout/item_cafe_comment.xml | 2 +- app/src/main/res/layout/item_image.xml | 23 +- app/src/main/res/layout/item_my_comments.xml | 2 +- app/src/main/res/layout/item_my_fav.xml | 2 +- app/src/main/res/layout/item_my_review.xml | 2 +- .../main/res/layout/layout_comment_input.xml | 11 +- app/src/main/res/layout/layout_review_btn.xml | 32 ++- app/src/main/res/values-night/themes.xml | 14 +- build.gradle | 3 + 75 files changed, 848 insertions(+), 636 deletions(-) create mode 100644 app/google-services.json delete mode 100644 app/src/main/java/com/konkuk/mocacong/objects/KakaoLocalClient.kt delete mode 100644 app/src/main/java/com/konkuk/mocacong/objects/NetworkManager.kt delete mode 100644 app/src/main/java/com/konkuk/mocacong/objects/NetworkUtil.kt delete mode 100644 app/src/main/java/com/konkuk/mocacong/objects/Utils.kt rename app/src/main/java/com/konkuk/mocacong/presentation/detail/{ => comment}/CafeCommentView.kt (81%) rename app/src/main/java/com/konkuk/mocacong/presentation/detail/{ => comment}/CafeCommentsAdapter.kt (98%) rename app/src/main/java/com/konkuk/mocacong/presentation/detail/{ => comment}/CafeCommentsFragment.kt (90%) rename app/src/main/java/com/konkuk/mocacong/presentation/detail/{ => comment}/WriteCommentFragment.kt (93%) rename app/src/main/java/com/konkuk/mocacong/presentation/detail/{ => image}/CafeImagesFragment.kt (89%) create mode 100644 app/src/main/java/com/konkuk/mocacong/presentation/detail/image/FullImageDialog.kt rename app/src/main/java/com/konkuk/mocacong/presentation/detail/{ => image}/ImageAdapter.kt (88%) create mode 100644 app/src/main/java/com/konkuk/mocacong/remote/repositories/KakaoRepository.kt create mode 100644 app/src/main/java/com/konkuk/mocacong/remote/repositories/UserPreferencesRepository.kt create mode 100644 app/src/main/java/com/konkuk/mocacong/util/ApplicatonModule.kt create mode 100644 app/src/main/java/com/konkuk/mocacong/util/Extentions.kt create mode 100644 app/src/main/java/com/konkuk/mocacong/util/NetworkModule.kt delete mode 100644 app/src/main/java/com/konkuk/mocacong/util/RetrofitClient.kt delete mode 100644 app/src/main/java/com/konkuk/mocacong/util/TokenAuthenticator.kt create mode 100644 app/src/main/res/drawable/icon_required.png delete mode 100644 app/src/main/res/drawable/logo.png delete mode 100644 app/src/main/res/drawable/marker_fav.xml delete mode 100644 app/src/main/res/drawable/marker_origin.xml delete mode 100644 app/src/main/res/drawable/preview_icon_both.xml delete mode 100644 app/src/main/res/drawable/preview_icon_empty_heart.xml delete mode 100644 app/src/main/res/drawable/preview_icon_fill_heart.xml delete mode 100644 app/src/main/res/drawable/preview_icon_group.xml delete mode 100644 app/src/main/res/drawable/preview_icon_solo.xml delete mode 100644 app/src/main/res/drawable/profile_no_image.jpg delete mode 100644 app/src/main/res/drawable/round_btn.xml delete mode 100644 app/src/main/res/drawable/round_rectangle.xml delete mode 100644 app/src/main/res/drawable/selector_btn.xml delete mode 100644 app/src/main/res/drawable/shape_bordered_rect.xml create mode 100644 app/src/main/res/layout/dialog_full_image.xml 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 0000000000000000000000000000000000000000..73e0f14996e10a0689003d71e44b10d857f61a46 GIT binary patch literal 3822 zcmVf4r%6f|Mm|Y)+5bGbG&(P-pu>_qtbNuuYWcDo$h|!zwSrKGluG`ZqtSO zdQ{O;sZ_B7XtMkZON%ErNH_)NVG!Dc;-|~n_jViUuhmTY<=BZ28dp|^RP>Ky3KynQ zZb|`+gMsISaNq6OzWc+J3g#%nY6py9$XE4OXQkegYU8H^0qa>sy8~-$DzaUd8Q8uD z81~gv1oY@$U9px<`BZy=^Ee_T)s*?WhSN2W3)sE~2tewY(c$-Vru~m zHCEj{rqo=^9AMmdwxzD#vic3c)&hty-)*%I1pbQEK4gNe1)wWlVzm!~vDRuIGQqY2 zU_eM_cMkaRDYo%s+O`5Xx_ZSNw%Uo0DLU@+d8~FKU2H3WP-$t-{OTwg%^6=(#kK-) zFa}2e1&$+t0>=?Rf#V3Ez;Ogn;5Y&(a2x>?IF0}c9G6jshMKAxplGdRo~CJZ0<3n< z@96I^%6=geZeG1WkzT17ZXpm70{ct1b-!+3XULfn1}M;6_cvu) z@BoM(Az*RbjKo&~^l!n)tk;9htE$qiFRca{YO1~~5LLg@8F3qaJd#-pDtX7-^%Z9h zH&))BPMw87=r2~)1zT!g0r3`qE>5!8j9d5X`Hr4X^N^tG<7muu|*{p`PWdWkWMa$Cu+LW#TvPiGP|*Vfx&xVfuY|cu*M5>}v6LztYO2~e z%IcC-D~X2!D5$HoM|FHO*jRY;gJ5&b=K@-29|PZGz-xp}0@epwYS*PwCh0GI-_17{ zg(d%U>TXSru2>t<nZ1H?(@?HQB1iD$cCCZdPDT-G30P*8!yTD@~EB z16ugbWHTovfJ2S7i$TpBtXTrL&J#8EmNhrxB@=>PpN)fVfnM=cLf?9n~X4ncBx_^~Ug>$M_fO*!;`RVKN1iQîsm9Bj!Ucv+c3^!HZJgU@H z+4sr8ObGaDynBr%2w+sIN34GAbI1yU6ds)L;ragz)>l=JDPwl~9C8l^tq(O{7t_8m z0$}3XonL=mqbbOk$e|bkC}4%T{y3Noi`b><*dBmUYOXsDW)@z>}?sCW@PDrU98vo%00nBgjeXqo&-Q{cq$)0p@q~|Da4OSzaQKPD2ja5Gl zN%b|!oHxg~@=O3H0D9OnYyP9Ob*oIKK+1EyBdb@;3u)RjVX2z(VZk6z1P}%2rxDV> zHLJDvOezJ_p6m}c)U1gh`pYrNm!@kY7qGtosFi_x%iFs4r&T)B>W3ODzY$jWZZ^*i zW)m9=-~cJQ&$FZJkC_4e72j19sFnbsh~zM6_rsXsNU)KcWo1R=)+z(n|sfV{u7wKwB~ zugPHgFLJP<>aWL`AB!lJ6SsflkO1~7=6k2L_q=RorKT_GYC`1PFpx7aoBrK6LqP8I z06;zt0nQ{aE%IX$=sX5yj6EL%orNHyVLudY@b2h-)o9DkQm`!q03er_g5US~N`yT7 zvyhaxn0cR|5!grof95n8n>@3Fk0iSHClw6X4z9RAtmt89JJ@vICc=H5*6tTFG2#S- z*jE7OFgYN^2?@(dvL6!W1clmy>;#1l)mPmXX7!^evmq}w$R+Yj0Ov3$EPQD7%4;G* zzA&oPeB+$UE%H+U000Kz*I?S{o#d5$3A^&a`sxRQh`yT7ji2Wb7+(hX)sg1f=0us| z3Sg+IYU7BKo9y;X?+}*@i13GzwKYxg5+@7+hkaE`0ugzjP#Qmvhk)^Xq@-Lr{)F&^ zMvmzGT%k06fiXvOY2P0IVN63GS-oQ3nBuF=^U1+f7=L%n=ZkrgixEIXb8k>EKPtt+ z92oF=Xf$R*7$X1)PUp3y4t7GQm{;<}UK9y6(_C*H%!Y|$+Bo*2NKn{ZcO1-ym52#3 z8+TD8439W*$T3L0asI!tk9!I~cV>WjjTE9NW`7_?0P}bE?=2GI19N?HFb&4Dp6$Co zJpZE!3qGZ8y3gqa@)8Nggtxy>5WuB7`kpBf;w^K0axe=9o`@G!9CyiAD=Ge>KrWr% zambLfB>ACu32_CmuyxOyr7rCcS#`<5JO~eY{XIwGB_-VS@rbco+(a3N9cK$SVa#eD z&f`t19hii-Ju`!kB}z;vfO-CXff7NRvg1?w2m*VA_B(g6xYBB0{>)ertpedz00|>+ ztb!D6ob3F-q}#p=I{Uv}EW~l+-AWw-P$slDJXEw~(XPHPim1q$-bsc_mAAEfOH@X# z0^whPT5I`z|m0Yv1qyNUayAO;^T`0Gq z-rlIEwd--|(yj#PM*xyKu#pK&{NS{&R#7bI^bD8lON%=P zE?dx<-Z{5iX8KF7obJtB$o==H!5y^6w z_V|)r1G6sO*}K(f`?*GW>z+5gZQY+H=9?JE!i1JKlHac(fG-}yWHe^@NUhg0WF zs$%xIv~3r-F=s(X-;cbbUi@nyuSC0V*Wy+_5R67 z>j22Yj=smH3-?!CN$wT6h-A6u`p+wN4NSjeSKlwvsk0C$Ywga|?>Q^ywmslxgDkGq~*H1)aTrPtFS^ri1|Ywsvm` zNzau4zA?!}aX&yCJhKDWW^#L&VXM3!G_1T{2d`o93_%=J;1?Hm3}o%z+d~c27X_rA z@r2Zfvw&g-TNuis2SaLBX`Y)W7GD5w=}M7y*DwI`{4du-hnm z4*$=&#y*q{+s+}-0>=?Rf#V3Ez;Ogn;5Y&(a2x>?IF0}c97g~Jjw65q$F>4cMXsFL zSD0IL(zdY-wiUplwyxt=hpV4s6x&`4S?xr+*j4}pD2*s;wI2Wu+c}!awgLbEx1e{d z_JP3Lto9)jY%Ktd=qFbDAQ?|t?L#Ky$S|5d@77lw9aTIp-SQd613d3->%J)y16E_} z9smGJT<&!)L0La+5r;nsNq1RLuc6pp0GIlE2E1-@i^(DwhQbYH{;pvY>&yza7Xa|_ zy+&S_d!zAoXr9L59O_0XXI|n-~a#s07*qoM6N<$f-(O+0{{R3 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/logo.png b/app/src/main/res/drawable/logo.png deleted file mode 100644 index cd8e6138598937d965b6ff45a3268dc9d1c2a06b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14749 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 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 c4fdd1836ac8efb0f560d9f08be052f6c0d76cdb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 127948 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

oUdhqoV>Tu?+A4i4hjyw-Dwrzk;i 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 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