From 56d694dab36eea5fc1e137719079c477cf4e4570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=99=ED=98=84?= Date: Fri, 8 Dec 2023 03:12:40 +0900 Subject: [PATCH 01/16] =?UTF-8?q?[ADD]=20#287=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=ED=88=B4=EB=B0=94=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20xml?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/layout/layout_common_toolbar.xml | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 app/src/main/res/layout/layout_common_toolbar.xml diff --git a/app/src/main/res/layout/layout_common_toolbar.xml b/app/src/main/res/layout/layout_common_toolbar.xml new file mode 100644 index 000000000..201a6a7ca --- /dev/null +++ b/app/src/main/res/layout/layout_common_toolbar.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file From d42ae9d6847c2eebbf7689fea6f4d82b7b133f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=99=ED=98=84?= Date: Fri, 8 Dec 2023 03:13:28 +0900 Subject: [PATCH 02/16] =?UTF-8?q?[ADD]=20#287=20CommonToolbarLayout=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Riffle 미작업 * 팝업 메뉴 추가 필요 --- .../runnect/util/CommonToolbarLayout.kt | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt diff --git a/app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt b/app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt new file mode 100644 index 000000000..884fd7d31 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt @@ -0,0 +1,244 @@ +package com.runnect.runnect.util + +import android.content.Context +import android.util.TypedValue +import android.view.View +import android.widget.LinearLayout +import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes +import androidx.annotation.IntDef +import androidx.annotation.Px +import androidx.annotation.StringRes +import androidx.appcompat.widget.AppCompatImageButton +import androidx.core.content.ContextCompat +import com.runnect.runnect.R +import com.runnect.runnect.databinding.LayoutCommonToolbarBinding +import com.runnect.runnect.util.extension.dpToPx + +interface CommonToolbarLayout { + + @IntDef(LEFT, RIGHT) + @Retention(AnnotationRetention.SOURCE) + annotation class Direction + + companion object { + const val LEFT = 0 + const val RIGHT = 1 + + private const val TOOLBAR_HEIGHT = 56 // Toolbar 기본 높이 + private const val TOOLBAR_TITLE_TEXT_SIZE = 18 // Title 텍스트 기본 크기(sp) + const val MENU_ITEM_SIZE = 48 // 메뉴 아이템 뷰 기본 크기 + const val MENU_ITEM_PADDING = 15 // 메뉴 아이템 뷰 기본 패딩(dp) + const val MENU_ITEM_RESOURCE_NONE = -1 // 메뉴 아이템 리소스(아이콘,텍스트) 없음 + private const val MENU_LIMIT_COUNT = 2 // 메뉴 최대 노출 갯수 + private const val VIEW_SIZE_NONE = -1 // 뷰 사이즈 없음 + + /** 리소스 관련 상수 */ + // 메뉴 뒤로가기 기본 아이콘 + private const val MENU_ICON_BACK = R.drawable.all_back_arrow + // Title 텍스트 기본 색상 + private const val TOOLBAR_TITLE_TEXT_COLOR = R.color.G1 + // Toolbar 기본 배경 색상 + private const val TOOLBAR_BACKGROUND_COLOR = R.color.white + } + + val toolbarBinding: LayoutCommonToolbarBinding + + /** + * ToolBar 초기화 메소드 + */ + fun initToolBarLayout() + + /** + * 툴바의 기본 형태를 설정 + * + * @param bgColorResId 툴바의 배경색 리소스 id + * @param textResId title 텍스트 리소스 id + * @param textSizeDip title 텍스트의 크기(sp) + * @param textColorResId title 텍스트 색상의 리소스 id + * @param backButtonResId back 버튼의 아이콘 리소스 id + * @param backButtonEvent back 버튼의 클릭 이벤트 핸들러 + */ + fun setToolbar ( + @ColorRes bgColorResId: Int = TOOLBAR_BACKGROUND_COLOR, + @StringRes textResId: Int? = null, + titleText: String? = null, + textSizeDip: Int = TOOLBAR_TITLE_TEXT_SIZE, + @ColorRes textColorResId: Int = TOOLBAR_TITLE_TEXT_COLOR, + @DrawableRes backButtonResId: Int = MENU_ICON_BACK, + backButtonEvent: ((View) -> Unit)? = null + ) { + // 백그라운드 영역 + setToolbarBackgroundColor(bgColorResId) + + // title 영역 text + if(textResId != null) { + setAppBarTitleText(textResId) + } else if (titleText != null) { + setAppBarTitleText(titleText) + } + + setAppBarTitleTextColor(textColorResId) + setAppBarTitleTextSize(textSizeDip) + + // 왼쪽 메뉴 추가 + addMenuTo( + LEFT, + ToolbarMenu.Icon( + resourceId = backButtonResId, + clickEvent = backButtonEvent, + ) + ) + } + + /** + * Toolbar의 배경 색상 지정 메소드 + * @param colorResId 배경 색상 Color Resource Id + */ + fun setToolbarBackgroundColor(@ColorRes colorResId: Int = TOOLBAR_BACKGROUND_COLOR) { + toolbarBinding.toolbar.run { + context?.let { + setBackgroundColor(ContextCompat.getColor(it, colorResId)) + } + } + } + + /** + * AppBar의 Title Text 설정 + * @param textResId title 텍스트 리소스 id + */ + fun setAppBarTitleText(@StringRes textResId: Int) { + toolbarBinding.tvTitle.run { + text = context?.getText(textResId) + } + } + + /** + * AppBar의 Title Text 설정 + * @param title title 텍스트 + */ + fun setAppBarTitleText(title: String) { + toolbarBinding.tvTitle.text = title + } + + /** + * Toolbar의 Title Text Color 설정 + * @param textColorResId 색상 리소스 id + */ + fun setAppBarTitleTextColor(@ColorRes textColorResId: Int = TOOLBAR_TITLE_TEXT_COLOR) { + toolbarBinding.tvTitle.run { + context?.let { + setTextColor(ContextCompat.getColor(it, textColorResId)) + } + } + } + + /** + * Toolbar의 Title Text Size 설정 + * @param textSize 텍스트 사이즈(sp) + */ + fun setAppBarTitleTextSize(textSize: Int = TOOLBAR_TITLE_TEXT_SIZE) { + toolbarBinding.tvTitle.run { + context?.let { + setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize.toFloat()) + } + } + } + + /** + * Toolbar 높이 설정 메소드 + * @param height 높이 값(dp) + */ + fun setAppBarHeight(height: Int = TOOLBAR_HEIGHT) { + toolbarBinding.toolbar.run { + context?.let { + setViewSize(toolbarBinding.toolbar, height = height.dpToPx(it)) + } + } + } + + /** + * Toolbar의 왼쪽 영역에 메뉴 아이템들을 추가합니다. + * + * @param direction 메뉴가 추가될 위치 (Toolbar의 왼쪽, 오른쪽) + * @param menu 추가할 메뉴 아이템 리스트(가변 인자) + */ + fun addMenuTo(@Direction direction: Int, vararg menu: ToolbarMenu) { + val menuLayout = when(direction) { + LEFT -> toolbarBinding.llLeftMenu + else -> toolbarBinding.llRightMenu + } + val context = menuLayout.context ?: return + + menu.forEach { item -> + if (canAddMenu(menuLayout)) { + addMenuView(context, menuLayout, item) + } + } + } + + /** + * 뷰 size 설정 + * @param view + * @param width + * @param height + */ + private fun setViewSize(view: View, width: Int = VIEW_SIZE_NONE, height: Int = VIEW_SIZE_NONE) { + view.layoutParams = view.layoutParams.apply { + this.width = if (width > VIEW_SIZE_NONE) width else this.width + this.height = if (height > VIEW_SIZE_NONE) height else this.height + } + } + + /** + * Toolbar에 메뉴 추가 + * + * @param parent 메뉴가 추가될 부모 뷰 + * @param toolbarMenu 추가할 메뉴 + */ + private fun addMenuView(context: Context?, parent: LinearLayout, toolbarMenu: ToolbarMenu) { + context ?: return + val layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.MATCH_PARENT + ).apply { + + } + + val menuView: View = when (toolbarMenu) { + is ToolbarMenu.Icon -> AppCompatImageButton(context).apply { + with(toolbarMenu) { + // 이미지뷰 패딩 설정 (기본 0dp) + setPadding(padding.dpToPx(context)) + setBackgroundColor( + ContextCompat.getColor(context, R.color.transparent_00) + ) + // 이미지 아이콘 설정 + setImageResource(resourceId) + // 클릭 이벤트 설정 + setOnClickListener { + clickEvent?.invoke(this@apply) + } + // 이미지뷰 사이즈 (기본 48dp * 48dp) + setLayoutParams( + layoutParams.apply { + width = toolbarMenu.width.dpToPx(context) + height = toolbarMenu.height.dpToPx(context) + } + ) + } + } + is ToolbarMenu.Popup -> AppCompatImageButton(context).apply { + + } + } + + parent.addView(menuView) + } + + private fun canAddMenu(parent: LinearLayout): Boolean = parent.childCount < MENU_LIMIT_COUNT + + private fun View.setPadding(@Px size: Int) { + setPadding(size, size, size, size) + } +} \ No newline at end of file From babe3be4105826f6471434b82ffa484402b4f01e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=99=ED=98=84?= Date: Fri, 8 Dec 2023 03:13:56 +0900 Subject: [PATCH 03/16] =?UTF-8?q?[ADD]=20#287=20=ED=88=B4=EB=B0=94=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/runnect/runnect/util/RippleEffect.kt | 14 ++++++++ .../com/runnect/runnect/util/ToolbarMenu.kt | 32 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 app/src/main/java/com/runnect/runnect/util/RippleEffect.kt create mode 100644 app/src/main/java/com/runnect/runnect/util/ToolbarMenu.kt diff --git a/app/src/main/java/com/runnect/runnect/util/RippleEffect.kt b/app/src/main/java/com/runnect/runnect/util/RippleEffect.kt new file mode 100644 index 000000000..170caf09f --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/util/RippleEffect.kt @@ -0,0 +1,14 @@ +package com.runnect.runnect.util + +import androidx.annotation.DrawableRes + +sealed class RippleEffect { + + object Ripple : RippleEffect() + + object CircleRipple: RippleEffect() + + data class CustomRipple( + @DrawableRes val rippleEffectResId: Int = 0 + ) : RippleEffect() +} diff --git a/app/src/main/java/com/runnect/runnect/util/ToolbarMenu.kt b/app/src/main/java/com/runnect/runnect/util/ToolbarMenu.kt new file mode 100644 index 000000000..292962e1f --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/util/ToolbarMenu.kt @@ -0,0 +1,32 @@ +package com.runnect.runnect.util + +import android.view.View + +sealed class ToolbarMenu( + open val resourceId: Int, + open val padding: Int, + open val useRippleEffect: Boolean, + open val rippleEffect: RippleEffect, + open val clickEvent: ((View) -> Unit)? +) { + + data class Icon ( + override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, + override val padding: Int = CommonToolbarLayout.MENU_ITEM_PADDING, + override val clickEvent: ((View) -> Unit)? = null, + override val useRippleEffect: Boolean = false, + override val rippleEffect: RippleEffect = RippleEffect.CircleRipple, + val width: Int = CommonToolbarLayout.MENU_ITEM_SIZE, + val height: Int = CommonToolbarLayout.MENU_ITEM_SIZE, + ) : ToolbarMenu(resourceId, padding, useRippleEffect, rippleEffect, clickEvent) + + data class Popup ( + override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, + override val padding: Int = CommonToolbarLayout.MENU_ITEM_PADDING, + override val clickEvent: ((View) -> Unit)? = null, + override val useRippleEffect: Boolean = false, + override val rippleEffect: RippleEffect = RippleEffect.CircleRipple, + val width: Int = CommonToolbarLayout.MENU_ITEM_SIZE, + val height: Int = CommonToolbarLayout.MENU_ITEM_SIZE, + ) : ToolbarMenu(resourceId, padding, useRippleEffect, rippleEffect, clickEvent) +} \ No newline at end of file From f5b0c5247231b0298dcb22f4288328975b2c0055 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 1 Jan 2024 22:41:29 +0900 Subject: [PATCH 04/16] =?UTF-8?q?[ADD]=20#287=20Text=20=EB=A9=94=EB=89=B4?= =?UTF-8?q?=20=EC=95=84=EC=9D=B4=ED=85=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runnect/util/CommonToolbarLayout.kt | 37 +++++++++++++++---- .../com/runnect/runnect/util/RippleEffect.kt | 14 ------- .../com/runnect/runnect/util/ToolbarMenu.kt | 17 +++++---- 3 files changed, 39 insertions(+), 29 deletions(-) delete mode 100644 app/src/main/java/com/runnect/runnect/util/RippleEffect.kt diff --git a/app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt b/app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt index 884fd7d31..4a91bbde5 100644 --- a/app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt +++ b/app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt @@ -1,9 +1,12 @@ package com.runnect.runnect.util import android.content.Context +import android.graphics.Typeface import android.util.TypedValue +import android.view.Gravity import android.view.View import android.widget.LinearLayout +import android.widget.TextView import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.IntDef @@ -11,6 +14,7 @@ import androidx.annotation.Px import androidx.annotation.StringRes import androidx.appcompat.widget.AppCompatImageButton import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat import com.runnect.runnect.R import com.runnect.runnect.databinding.LayoutCommonToolbarBinding import com.runnect.runnect.util.extension.dpToPx @@ -24,12 +28,13 @@ interface CommonToolbarLayout { companion object { const val LEFT = 0 const val RIGHT = 1 - - private const val TOOLBAR_HEIGHT = 56 // Toolbar 기본 높이 - private const val TOOLBAR_TITLE_TEXT_SIZE = 18 // Title 텍스트 기본 크기(sp) const val MENU_ITEM_SIZE = 48 // 메뉴 아이템 뷰 기본 크기 const val MENU_ITEM_PADDING = 15 // 메뉴 아이템 뷰 기본 패딩(dp) + const val MENU_ITEM_TEXT_SIZE = 18 // 텍스트 기본 크기 const val MENU_ITEM_RESOURCE_NONE = -1 // 메뉴 아이템 리소스(아이콘,텍스트) 없음 + + private const val TOOLBAR_HEIGHT = 56 // Toolbar 기본 높이 + private const val TOOLBAR_TITLE_TEXT_SIZE = 18 // Title 텍스트 기본 크기(sp) private const val MENU_LIMIT_COUNT = 2 // 메뉴 최대 노출 갯수 private const val VIEW_SIZE_NONE = -1 // 뷰 사이즈 없음 @@ -201,14 +206,12 @@ interface CommonToolbarLayout { val layoutParams = LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT - ).apply { - - } + ) val menuView: View = when (toolbarMenu) { is ToolbarMenu.Icon -> AppCompatImageButton(context).apply { with(toolbarMenu) { - // 이미지뷰 패딩 설정 (기본 0dp) + // 이미지뷰 패딩 설정 (기본 15dp) setPadding(padding.dpToPx(context)) setBackgroundColor( ContextCompat.getColor(context, R.color.transparent_00) @@ -228,6 +231,26 @@ interface CommonToolbarLayout { ) } } + + is ToolbarMenu.TextStyle -> TextView(context).apply { + with(toolbarMenu) { + // 텍스트뷰 패딩 설정 기본(0dp) + val padding = padding.dpToPx(context) + setPadding(padding, 0, padding, 0) + + text = context.getString(resourceId) + gravity = Gravity.CENTER + typeface = ResourcesCompat.getFont(context, R.font.pretendard_bold) + + setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize.toFloat()) + setTextColor( + getColor(context, TOOLBAR_TITLE_TEXT_COLOR) + ) + + setLayoutParams(layoutParams) + } + } + is ToolbarMenu.Popup -> AppCompatImageButton(context).apply { } diff --git a/app/src/main/java/com/runnect/runnect/util/RippleEffect.kt b/app/src/main/java/com/runnect/runnect/util/RippleEffect.kt deleted file mode 100644 index 170caf09f..000000000 --- a/app/src/main/java/com/runnect/runnect/util/RippleEffect.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.runnect.runnect.util - -import androidx.annotation.DrawableRes - -sealed class RippleEffect { - - object Ripple : RippleEffect() - - object CircleRipple: RippleEffect() - - data class CustomRipple( - @DrawableRes val rippleEffectResId: Int = 0 - ) : RippleEffect() -} diff --git a/app/src/main/java/com/runnect/runnect/util/ToolbarMenu.kt b/app/src/main/java/com/runnect/runnect/util/ToolbarMenu.kt index 292962e1f..b251d3309 100644 --- a/app/src/main/java/com/runnect/runnect/util/ToolbarMenu.kt +++ b/app/src/main/java/com/runnect/runnect/util/ToolbarMenu.kt @@ -5,8 +5,6 @@ import android.view.View sealed class ToolbarMenu( open val resourceId: Int, open val padding: Int, - open val useRippleEffect: Boolean, - open val rippleEffect: RippleEffect, open val clickEvent: ((View) -> Unit)? ) { @@ -14,19 +12,22 @@ sealed class ToolbarMenu( override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, override val padding: Int = CommonToolbarLayout.MENU_ITEM_PADDING, override val clickEvent: ((View) -> Unit)? = null, - override val useRippleEffect: Boolean = false, - override val rippleEffect: RippleEffect = RippleEffect.CircleRipple, val width: Int = CommonToolbarLayout.MENU_ITEM_SIZE, val height: Int = CommonToolbarLayout.MENU_ITEM_SIZE, - ) : ToolbarMenu(resourceId, padding, useRippleEffect, rippleEffect, clickEvent) + ) : ToolbarMenu(resourceId, padding, clickEvent) + + data class TextStyle( + override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, + override val padding: Int = 0, + override val clickEvent: ((View) -> Unit)? = null, + val textSize: Int = CommonToolbarLayout.MENU_ITEM_TEXT_SIZE, + ) : ToolbarMenu(resourceId, padding, clickEvent) data class Popup ( override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, override val padding: Int = CommonToolbarLayout.MENU_ITEM_PADDING, override val clickEvent: ((View) -> Unit)? = null, - override val useRippleEffect: Boolean = false, - override val rippleEffect: RippleEffect = RippleEffect.CircleRipple, val width: Int = CommonToolbarLayout.MENU_ITEM_SIZE, val height: Int = CommonToolbarLayout.MENU_ITEM_SIZE, - ) : ToolbarMenu(resourceId, padding, useRippleEffect, rippleEffect, clickEvent) + ) : ToolbarMenu(resourceId, padding, clickEvent) } \ No newline at end of file From 1ada388a5d1b19f42c139eb63ed7d21be87fd4bb Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 1 Jan 2024 22:41:54 +0900 Subject: [PATCH 05/16] =?UTF-8?q?[REFACTOR]=20#287=20color=20=EB=A6=AC?= =?UTF-8?q?=EC=86=8C=EC=8A=A4=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/runnect/runnect/util/CommonToolbarLayout.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt b/app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt index 4a91bbde5..2b827d6f6 100644 --- a/app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt +++ b/app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt @@ -103,7 +103,7 @@ interface CommonToolbarLayout { fun setToolbarBackgroundColor(@ColorRes colorResId: Int = TOOLBAR_BACKGROUND_COLOR) { toolbarBinding.toolbar.run { context?.let { - setBackgroundColor(ContextCompat.getColor(it, colorResId)) + setBackgroundColor(getColor(it, colorResId)) } } } @@ -133,7 +133,7 @@ interface CommonToolbarLayout { fun setAppBarTitleTextColor(@ColorRes textColorResId: Int = TOOLBAR_TITLE_TEXT_COLOR) { toolbarBinding.tvTitle.run { context?.let { - setTextColor(ContextCompat.getColor(it, textColorResId)) + setTextColor(getColor(it, textColorResId)) } } } @@ -214,7 +214,7 @@ interface CommonToolbarLayout { // 이미지뷰 패딩 설정 (기본 15dp) setPadding(padding.dpToPx(context)) setBackgroundColor( - ContextCompat.getColor(context, R.color.transparent_00) + getColor(context, R.color.transparent_00) ) // 이미지 아이콘 설정 setImageResource(resourceId) @@ -261,7 +261,12 @@ interface CommonToolbarLayout { private fun canAddMenu(parent: LinearLayout): Boolean = parent.childCount < MENU_LIMIT_COUNT + /* 유틸 메소드 */ private fun View.setPadding(@Px size: Int) { setPadding(size, size, size, size) } + + private fun getColor(context: Context, @ColorRes colorResId: Int): Int { + return ContextCompat.getColor(context, colorResId) + } } \ No newline at end of file From ebd8c2665755aeb2d0c59755c39daa5530825637 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 1 Jan 2024 22:54:21 +0900 Subject: [PATCH 06/16] =?UTF-8?q?[REFACTOR]=20#287=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EB=B0=8F=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EB=AA=85?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../toolbar}/CommonToolbarLayout.kt | 19 +++++++++---------- .../util/{ => custom/toolbar}/ToolbarMenu.kt | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) rename app/src/main/java/com/runnect/runnect/util/{ => custom/toolbar}/CommonToolbarLayout.kt (94%) rename app/src/main/java/com/runnect/runnect/util/{ => custom/toolbar}/ToolbarMenu.kt (96%) diff --git a/app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt similarity index 94% rename from app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt rename to app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt index 2b827d6f6..7ed29a2a5 100644 --- a/app/src/main/java/com/runnect/runnect/util/CommonToolbarLayout.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt @@ -1,7 +1,6 @@ -package com.runnect.runnect.util +package com.runnect.runnect.util.custom.toolbar import android.content.Context -import android.graphics.Typeface import android.util.TypedValue import android.view.Gravity import android.view.View @@ -78,13 +77,13 @@ interface CommonToolbarLayout { // title 영역 text if(textResId != null) { - setAppBarTitleText(textResId) + setToolBarTitleText(textResId) } else if (titleText != null) { - setAppBarTitleText(titleText) + setToolBarTitleText(titleText) } - setAppBarTitleTextColor(textColorResId) - setAppBarTitleTextSize(textSizeDip) + setToolBarTitleTextColor(textColorResId) + setToolbarTitleTextSize(textSizeDip) // 왼쪽 메뉴 추가 addMenuTo( @@ -112,7 +111,7 @@ interface CommonToolbarLayout { * AppBar의 Title Text 설정 * @param textResId title 텍스트 리소스 id */ - fun setAppBarTitleText(@StringRes textResId: Int) { + fun setToolBarTitleText(@StringRes textResId: Int) { toolbarBinding.tvTitle.run { text = context?.getText(textResId) } @@ -122,7 +121,7 @@ interface CommonToolbarLayout { * AppBar의 Title Text 설정 * @param title title 텍스트 */ - fun setAppBarTitleText(title: String) { + fun setToolBarTitleText(title: String) { toolbarBinding.tvTitle.text = title } @@ -130,7 +129,7 @@ interface CommonToolbarLayout { * Toolbar의 Title Text Color 설정 * @param textColorResId 색상 리소스 id */ - fun setAppBarTitleTextColor(@ColorRes textColorResId: Int = TOOLBAR_TITLE_TEXT_COLOR) { + fun setToolBarTitleTextColor(@ColorRes textColorResId: Int = TOOLBAR_TITLE_TEXT_COLOR) { toolbarBinding.tvTitle.run { context?.let { setTextColor(getColor(it, textColorResId)) @@ -142,7 +141,7 @@ interface CommonToolbarLayout { * Toolbar의 Title Text Size 설정 * @param textSize 텍스트 사이즈(sp) */ - fun setAppBarTitleTextSize(textSize: Int = TOOLBAR_TITLE_TEXT_SIZE) { + fun setToolbarTitleTextSize(textSize: Int = TOOLBAR_TITLE_TEXT_SIZE) { toolbarBinding.tvTitle.run { context?.let { setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize.toFloat()) diff --git a/app/src/main/java/com/runnect/runnect/util/ToolbarMenu.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt similarity index 96% rename from app/src/main/java/com/runnect/runnect/util/ToolbarMenu.kt rename to app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt index b251d3309..38bf695de 100644 --- a/app/src/main/java/com/runnect/runnect/util/ToolbarMenu.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt @@ -1,4 +1,4 @@ -package com.runnect.runnect.util +package com.runnect.runnect.util.custom.toolbar import android.view.View From 1c110c76fdd15e930d2bd56c74230fa7bd770d79 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 1 Jan 2024 23:15:47 +0900 Subject: [PATCH 07/16] =?UTF-8?q?[REFACTOR]=20#287=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/toolbar/CommonToolbarLayout.kt | 80 ++++++++++--------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt index 7ed29a2a5..59203c60e 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt @@ -25,27 +25,33 @@ interface CommonToolbarLayout { annotation class Direction companion object { + // 메뉴 추가 위치 const val LEFT = 0 const val RIGHT = 1 - const val MENU_ITEM_SIZE = 48 // 메뉴 아이템 뷰 기본 크기 - const val MENU_ITEM_PADDING = 15 // 메뉴 아이템 뷰 기본 패딩(dp) - const val MENU_ITEM_TEXT_SIZE = 18 // 텍스트 기본 크기 - const val MENU_ITEM_RESOURCE_NONE = -1 // 메뉴 아이템 리소스(아이콘,텍스트) 없음 - - private const val TOOLBAR_HEIGHT = 56 // Toolbar 기본 높이 - private const val TOOLBAR_TITLE_TEXT_SIZE = 18 // Title 텍스트 기본 크기(sp) - private const val MENU_LIMIT_COUNT = 2 // 메뉴 최대 노출 갯수 - private const val VIEW_SIZE_NONE = -1 // 뷰 사이즈 없음 - /** 리소스 관련 상수 */ - // 메뉴 뒤로가기 기본 아이콘 - private const val MENU_ICON_BACK = R.drawable.all_back_arrow - // Title 텍스트 기본 색상 - private const val TOOLBAR_TITLE_TEXT_COLOR = R.color.G1 - // Toolbar 기본 배경 색상 - private const val TOOLBAR_BACKGROUND_COLOR = R.color.white + // 메뉴 아이템 관련 + const val MENU_ITEM_SIZE = 48 // 메뉴 아이템 뷰 기본 크기 + const val MENU_ITEM_PADDING = 15 // 메뉴 아이템 뷰 기본 패딩(dp) + const val MENU_ITEM_TEXT_SIZE = 18 // 텍스트 기본 크기 + const val MENU_ITEM_RESOURCE_NONE = -1 // 메뉴 아이템 리소스(아이콘,텍스트) 없음 + + // Toolbar 관련 + private const val TOOLBAR_HEIGHT = 56 // Toolbar 기본 높이 + private const val TOOLBAR_TITLE_TEXT_SIZE = 18 // Title 텍스트 기본 크기(sp) + private const val TOOLBAR_MENU_LIMIT_COUNT = 2 // 메뉴 최대 노출 갯수 + + // 뷰 관련 + private const val VIEW_SIZE_NONE = -1 // 뷰 사이즈 없음 } + /** 기본 리소스 */ + private val menuIconBack: Int // 메뉴 뒤로가기 기본 아이콘 + get() = R.drawable.all_back_arrow + private val toolbarTitleTextColor: Int // Title 텍스트 기본 색상 + get() = R.color.G1 + private val toolbarBackgroundColor: Int // Toolbar 기본 배경 색상 + get() = R.color.white + val toolbarBinding: LayoutCommonToolbarBinding /** @@ -64,12 +70,12 @@ interface CommonToolbarLayout { * @param backButtonEvent back 버튼의 클릭 이벤트 핸들러 */ fun setToolbar ( - @ColorRes bgColorResId: Int = TOOLBAR_BACKGROUND_COLOR, + @ColorRes bgColorResId: Int = toolbarBackgroundColor, @StringRes textResId: Int? = null, + @ColorRes textColorResId: Int = toolbarTitleTextColor, + @DrawableRes backButtonResId: Int = menuIconBack, titleText: String? = null, textSizeDip: Int = TOOLBAR_TITLE_TEXT_SIZE, - @ColorRes textColorResId: Int = TOOLBAR_TITLE_TEXT_COLOR, - @DrawableRes backButtonResId: Int = MENU_ICON_BACK, backButtonEvent: ((View) -> Unit)? = null ) { // 백그라운드 영역 @@ -99,7 +105,7 @@ interface CommonToolbarLayout { * Toolbar의 배경 색상 지정 메소드 * @param colorResId 배경 색상 Color Resource Id */ - fun setToolbarBackgroundColor(@ColorRes colorResId: Int = TOOLBAR_BACKGROUND_COLOR) { + fun setToolbarBackgroundColor(@ColorRes colorResId: Int = toolbarBackgroundColor) { toolbarBinding.toolbar.run { context?.let { setBackgroundColor(getColor(it, colorResId)) @@ -129,7 +135,7 @@ interface CommonToolbarLayout { * Toolbar의 Title Text Color 설정 * @param textColorResId 색상 리소스 id */ - fun setToolBarTitleTextColor(@ColorRes textColorResId: Int = TOOLBAR_TITLE_TEXT_COLOR) { + fun setToolBarTitleTextColor(@ColorRes textColorResId: Int = toolbarTitleTextColor) { toolbarBinding.tvTitle.run { context?.let { setTextColor(getColor(it, textColorResId)) @@ -153,7 +159,7 @@ interface CommonToolbarLayout { * Toolbar 높이 설정 메소드 * @param height 높이 값(dp) */ - fun setAppBarHeight(height: Int = TOOLBAR_HEIGHT) { + fun setToolbarHeight(height: Int = TOOLBAR_HEIGHT) { toolbarBinding.toolbar.run { context?.let { setViewSize(toolbarBinding.toolbar, height = height.dpToPx(it)) @@ -161,6 +167,19 @@ interface CommonToolbarLayout { } } + /** + * 뷰 size 설정 + * @param view + * @param width + * @param height + */ + private fun setViewSize(view: View, width: Int = VIEW_SIZE_NONE, height: Int = VIEW_SIZE_NONE) { + view.layoutParams = view.layoutParams.apply { + this.width = if (width > VIEW_SIZE_NONE) width else this.width + this.height = if (height > VIEW_SIZE_NONE) height else this.height + } + } + /** * Toolbar의 왼쪽 영역에 메뉴 아이템들을 추가합니다. * @@ -181,19 +200,6 @@ interface CommonToolbarLayout { } } - /** - * 뷰 size 설정 - * @param view - * @param width - * @param height - */ - private fun setViewSize(view: View, width: Int = VIEW_SIZE_NONE, height: Int = VIEW_SIZE_NONE) { - view.layoutParams = view.layoutParams.apply { - this.width = if (width > VIEW_SIZE_NONE) width else this.width - this.height = if (height > VIEW_SIZE_NONE) height else this.height - } - } - /** * Toolbar에 메뉴 추가 * @@ -243,7 +249,7 @@ interface CommonToolbarLayout { setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize.toFloat()) setTextColor( - getColor(context, TOOLBAR_TITLE_TEXT_COLOR) + getColor(context, toolbarTitleTextColor) ) setLayoutParams(layoutParams) @@ -258,7 +264,7 @@ interface CommonToolbarLayout { parent.addView(menuView) } - private fun canAddMenu(parent: LinearLayout): Boolean = parent.childCount < MENU_LIMIT_COUNT + private fun canAddMenu(parent: LinearLayout): Boolean = parent.childCount < TOOLBAR_MENU_LIMIT_COUNT /* 유틸 메소드 */ private fun View.setPadding(@Px size: Int) { From 66efb2c01dea96926dfe467f07eb7940d60e392e Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Tue, 2 Jan 2024 00:05:12 +0900 Subject: [PATCH 08/16] =?UTF-8?q?[ADD]=20#287=20View=20=ED=99=95=EC=9E=A5?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/runnect/runnect/util/extension/ViewExt.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/com/runnect/runnect/util/extension/ViewExt.kt b/app/src/main/java/com/runnect/runnect/util/extension/ViewExt.kt index 80e66da42..da66e976c 100644 --- a/app/src/main/java/com/runnect/runnect/util/extension/ViewExt.kt +++ b/app/src/main/java/com/runnect/runnect/util/extension/ViewExt.kt @@ -1,6 +1,7 @@ package com.runnect.runnect.util.extension import android.view.View +import androidx.annotation.Px inline fun View.setOnSingleClickListener( delay: Long = 500L, @@ -15,3 +16,7 @@ inline fun View.setOnSingleClickListener( } } } + +fun View.setPadding(@Px size: Int) { + setPadding(size, size, size, size) +} \ No newline at end of file From 96ae2cc56a5cf72fdf38e90c918215b2f1abf43f Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Tue, 2 Jan 2024 00:05:45 +0900 Subject: [PATCH 09/16] =?UTF-8?q?[REFACTOR]=20#287=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B6=80=EB=B6=84=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/toolbar/CommonToolbarLayout.kt | 67 ++------------ .../util/custom/toolbar/ToolbarMenu.kt | 91 ++++++++++++++++++- 2 files changed, 94 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt index 59203c60e..6a1203ae1 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt @@ -2,18 +2,14 @@ package com.runnect.runnect.util.custom.toolbar import android.content.Context import android.util.TypedValue -import android.view.Gravity import android.view.View import android.widget.LinearLayout -import android.widget.TextView +import android.widget.LinearLayout.LayoutParams import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.IntDef -import androidx.annotation.Px import androidx.annotation.StringRes -import androidx.appcompat.widget.AppCompatImageButton import androidx.core.content.ContextCompat -import androidx.core.content.res.ResourcesCompat import com.runnect.runnect.R import com.runnect.runnect.databinding.LayoutCommonToolbarBinding import com.runnect.runnect.util.extension.dpToPx @@ -208,69 +204,20 @@ interface CommonToolbarLayout { */ private fun addMenuView(context: Context?, parent: LinearLayout, toolbarMenu: ToolbarMenu) { context ?: return - val layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.MATCH_PARENT + val layoutParams = LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.MATCH_PARENT ) - val menuView: View = when (toolbarMenu) { - is ToolbarMenu.Icon -> AppCompatImageButton(context).apply { - with(toolbarMenu) { - // 이미지뷰 패딩 설정 (기본 15dp) - setPadding(padding.dpToPx(context)) - setBackgroundColor( - getColor(context, R.color.transparent_00) - ) - // 이미지 아이콘 설정 - setImageResource(resourceId) - // 클릭 이벤트 설정 - setOnClickListener { - clickEvent?.invoke(this@apply) - } - // 이미지뷰 사이즈 (기본 48dp * 48dp) - setLayoutParams( - layoutParams.apply { - width = toolbarMenu.width.dpToPx(context) - height = toolbarMenu.height.dpToPx(context) - } - ) - } - } - - is ToolbarMenu.TextStyle -> TextView(context).apply { - with(toolbarMenu) { - // 텍스트뷰 패딩 설정 기본(0dp) - val padding = padding.dpToPx(context) - setPadding(padding, 0, padding, 0) - - text = context.getString(resourceId) - gravity = Gravity.CENTER - typeface = ResourcesCompat.getFont(context, R.font.pretendard_bold) - - setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize.toFloat()) - setTextColor( - getColor(context, toolbarTitleTextColor) - ) - - setLayoutParams(layoutParams) - } - } - - is ToolbarMenu.Popup -> AppCompatImageButton(context).apply { - - } + val menuView: View? = toolbarMenu.createMenu(context, layoutParams, toolbarMenu) + menuView?.let{ + parent.addView(it) } - - parent.addView(menuView) } private fun canAddMenu(parent: LinearLayout): Boolean = parent.childCount < TOOLBAR_MENU_LIMIT_COUNT /* 유틸 메소드 */ - private fun View.setPadding(@Px size: Int) { - setPadding(size, size, size, size) - } - private fun getColor(context: Context, @ColorRes colorResId: Int): Int { return ContextCompat.getColor(context, colorResId) } diff --git a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt index 38bf695de..398413fa1 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt @@ -1,12 +1,28 @@ package com.runnect.runnect.util.custom.toolbar +import android.content.Context +import android.util.TypedValue +import android.view.Gravity import android.view.View +import android.widget.LinearLayout +import androidx.appcompat.widget.AppCompatImageButton +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import com.runnect.runnect.R +import com.runnect.runnect.util.extension.dpToPx +import com.runnect.runnect.util.extension.setPadding sealed class ToolbarMenu( open val resourceId: Int, open val padding: Int, open val clickEvent: ((View) -> Unit)? ) { + abstract fun createMenu( + context: Context, + layoutParams: LinearLayout.LayoutParams, + menu: ToolbarMenu + ): View? data class Icon ( override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, @@ -14,14 +30,73 @@ sealed class ToolbarMenu( override val clickEvent: ((View) -> Unit)? = null, val width: Int = CommonToolbarLayout.MENU_ITEM_SIZE, val height: Int = CommonToolbarLayout.MENU_ITEM_SIZE, - ) : ToolbarMenu(resourceId, padding, clickEvent) + ) : ToolbarMenu(resourceId, padding, clickEvent) { + override fun createMenu( + context: Context, + layoutParams: LinearLayout.LayoutParams, + menu: ToolbarMenu + ): View? { + if (menu !is Icon) return null - data class TextStyle( + val padding = menu.padding.dpToPx(context) + val menuWidth = menu.width.dpToPx(context) + val menuHeight = menu.height.dpToPx(context) + val imgRes = menu.resourceId + val bgColor = ContextCompat.getColor(context, R.color.transparent_00) + val clickListener = View.OnClickListener { + menu.clickEvent?.invoke(it) + } + + return AppCompatImageButton(context).apply { + // 이미지 아이콘 설정 + setImageResource(imgRes) + setBackgroundColor(bgColor) + setPadding(padding) + + // 클릭 이벤트 설정 + setOnClickListener(clickListener) + + // 이미지뷰 사이즈 설정 (기본 48dp * 48dp) + layoutParams.apply { + width = menuWidth + height = menuHeight + }.let(::setLayoutParams) + } + } + } + + data class Text ( override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, override val padding: Int = 0, override val clickEvent: ((View) -> Unit)? = null, val textSize: Int = CommonToolbarLayout.MENU_ITEM_TEXT_SIZE, - ) : ToolbarMenu(resourceId, padding, clickEvent) + ) : ToolbarMenu(resourceId, padding, clickEvent) { + override fun createMenu( + context: Context, + layoutParams: LinearLayout.LayoutParams, + menu: ToolbarMenu + ): View? { + if (menu !is Text) return null + + val padding = menu.padding.dpToPx(context) + val titleText = context.getString(menu.resourceId) + val textSize = menu.textSize.toFloat() + val textColor = ContextCompat.getColor(context, R.color.G1) + + return AppCompatTextView(context).apply { + text = titleText + gravity = Gravity.CENTER + typeface = ResourcesCompat.getFont(context, R.font.pretendard_bold) + + // 텍스트뷰 패딩 설정 기본(0dp) + setPadding(padding, 0, padding, 0) + setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize) + setTextColor(textColor) + + setLayoutParams(layoutParams) + } + } + } data class Popup ( override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, @@ -29,5 +104,13 @@ sealed class ToolbarMenu( override val clickEvent: ((View) -> Unit)? = null, val width: Int = CommonToolbarLayout.MENU_ITEM_SIZE, val height: Int = CommonToolbarLayout.MENU_ITEM_SIZE, - ) : ToolbarMenu(resourceId, padding, clickEvent) + ) : ToolbarMenu(resourceId, padding, clickEvent) { + override fun createMenu( + context: Context, + layoutParams: LinearLayout.LayoutParams, + menu: ToolbarMenu + ): View? { + TODO("Not yet implemented") + } + } } \ No newline at end of file From a3746709bf83b8731a8743b3441b3e3d3bbaf6a9 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Sat, 6 Jan 2024 21:23:38 +0900 Subject: [PATCH 10/16] =?UTF-8?q?[feat]=20#287=20=ED=8C=9D=EC=97=85=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/custom/popup/RunnectPopupMenu.kt | 8 ++++++ .../util/custom/toolbar/ToolbarMenu.kt | 27 ++++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/util/custom/popup/RunnectPopupMenu.kt b/app/src/main/java/com/runnect/runnect/util/custom/popup/RunnectPopupMenu.kt index 1cd5566dc..0e388dfa0 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/popup/RunnectPopupMenu.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/popup/RunnectPopupMenu.kt @@ -2,10 +2,12 @@ package com.runnect.runnect.util.custom.popup import android.content.Context import android.util.TypedValue +import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.PopupWindow +import com.google.android.material.transition.platform.SlideDistanceProvider.GravityFlag import com.runnect.runnect.R import com.runnect.runnect.databinding.ItemRunnectPopupMenuBinding import com.runnect.runnect.databinding.LayoutRunnectPopupMenuBinding @@ -62,7 +64,13 @@ class RunnectPopupMenu( return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, dm).toInt() } + fun showCustomPosition(anchorView: View, @GravityFlag gravity: Int = Gravity.END) { + showAsDropDown(anchorView, POPUP_MENU_X_OFFSET, POPUP_MENU_Y_OFFSET, gravity) + } + companion object { private const val POPUP_MENU_WIDTH = 170F + private const val POPUP_MENU_X_OFFSET = 17 + private const val POPUP_MENU_Y_OFFSET = -10 } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt index 398413fa1..83c25494f 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt @@ -104,13 +104,28 @@ sealed class ToolbarMenu( override val clickEvent: ((View) -> Unit)? = null, val width: Int = CommonToolbarLayout.MENU_ITEM_SIZE, val height: Int = CommonToolbarLayout.MENU_ITEM_SIZE, - ) : ToolbarMenu(resourceId, padding, clickEvent) { + private val popupItems: List, + private val menuItemClickListener: (View, PopupItem, Int) -> Unit, + ) : ToolbarMenu(resourceId, padding) { + + private var popupMenu: RunnectPopupMenu? = null + override fun createMenu( - context: Context, - layoutParams: LinearLayout.LayoutParams, - menu: ToolbarMenu - ): View? { - TODO("Not yet implemented") + context: Context + ): View { + return createBaseImageButton(context, resourceId, padding.dpToPx(context), width, height) { + attachPopupItem(it) + } + } + + private fun attachPopupItem(anchorView: View){ + if(popupMenu == null) { + popupMenu = RunnectPopupMenu(anchorView.context, popupItems) { view, popupItem, pos -> + menuItemClickListener.invoke(view, popupItem, pos) + } + } + + popupMenu?.showCustomPosition(anchorView) } } } \ No newline at end of file From 85297a5ab2cfa83727fa8fb65c9daf24531e81b2 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Sat, 6 Jan 2024 21:23:53 +0900 Subject: [PATCH 11/16] =?UTF-8?q?[feat]=20#287=20=ED=88=B4=EB=B0=94=20Base?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/custom/toolbar/BaseToolbarMenu.kt | 66 +++++++++++++ .../custom/toolbar/CommonToolbarLayout.kt | 11 +-- .../util/custom/toolbar/ToolbarMenu.kt | 96 ++++--------------- 3 files changed, 89 insertions(+), 84 deletions(-) create mode 100644 app/src/main/java/com/runnect/runnect/util/custom/toolbar/BaseToolbarMenu.kt diff --git a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/BaseToolbarMenu.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/BaseToolbarMenu.kt new file mode 100644 index 000000000..8c9035f1e --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/BaseToolbarMenu.kt @@ -0,0 +1,66 @@ +package com.runnect.runnect.util.custom.toolbar + +import android.content.Context +import android.util.TypedValue +import android.view.Gravity +import android.view.View +import android.widget.LinearLayout +import androidx.appcompat.widget.AppCompatImageButton +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import com.runnect.runnect.R +import com.runnect.runnect.util.extension.dpToPx +import com.runnect.runnect.util.extension.setPadding + +open class BaseToolbarMenu { + protected fun createBaseImageButton( + context: Context, + resourceId: Int, + padding: Int, + width: Int, + height: Int, + clickEvent: ((View) -> Unit)? + ): AppCompatImageButton { + val paddingPx = padding.dpToPx(context) + val bgColor = ContextCompat.getColor(context, R.color.transparent_00) + val layoutParams = LinearLayout.LayoutParams( + width.dpToPx(context), + height.dpToPx(context) + ) + + return AppCompatImageButton(context).apply { + setImageResource(resourceId) + setBackgroundColor(bgColor) + setPadding(paddingPx) + setOnClickListener { view -> clickEvent?.invoke(view) } + setLayoutParams(layoutParams) + } + } + + protected fun createBaseTextView( + context: Context, + resourceId: Int, + padding: Int, + textSize: Float, + ): AppCompatTextView { + val paddingPx = padding.dpToPx(context) + val titleText = context.getString(resourceId) + val textColor = ContextCompat.getColor(context, R.color.G1) + val layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + + return AppCompatTextView(context).apply { + text = titleText + gravity = Gravity.CENTER + typeface = ResourcesCompat.getFont(context, R.font.pretendard_bold) + + setPadding(paddingPx, 0, paddingPx, 0) + setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize) + setTextColor(textColor) + setLayoutParams(layoutParams) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt index 6a1203ae1..246ca77ac 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt @@ -4,7 +4,6 @@ import android.content.Context import android.util.TypedValue import android.view.View import android.widget.LinearLayout -import android.widget.LinearLayout.LayoutParams import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.IntDef @@ -204,15 +203,9 @@ interface CommonToolbarLayout { */ private fun addMenuView(context: Context?, parent: LinearLayout, toolbarMenu: ToolbarMenu) { context ?: return - val layoutParams = LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.MATCH_PARENT - ) - val menuView: View? = toolbarMenu.createMenu(context, layoutParams, toolbarMenu) - menuView?.let{ - parent.addView(it) - } + val menuView: View = toolbarMenu.createMenu(context) + parent.addView(menuView) } private fun canAddMenu(parent: LinearLayout): Boolean = parent.childCount < TOOLBAR_MENU_LIMIT_COUNT diff --git a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt index 83c25494f..338c89e61 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt @@ -1,107 +1,53 @@ package com.runnect.runnect.util.custom.toolbar import android.content.Context -import android.util.TypedValue -import android.view.Gravity import android.view.View -import android.widget.LinearLayout -import androidx.appcompat.widget.AppCompatImageButton -import androidx.appcompat.widget.AppCompatTextView -import androidx.core.content.ContextCompat -import androidx.core.content.res.ResourcesCompat -import com.runnect.runnect.R +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import com.runnect.runnect.util.custom.popup.PopupItem +import com.runnect.runnect.util.custom.popup.RunnectPopupMenu import com.runnect.runnect.util.extension.dpToPx -import com.runnect.runnect.util.extension.setPadding sealed class ToolbarMenu( open val resourceId: Int, open val padding: Int, - open val clickEvent: ((View) -> Unit)? -) { +): BaseToolbarMenu() { + abstract fun createMenu( - context: Context, - layoutParams: LinearLayout.LayoutParams, - menu: ToolbarMenu - ): View? + context: Context + ): View data class Icon ( - override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, + @DrawableRes override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, override val padding: Int = CommonToolbarLayout.MENU_ITEM_PADDING, - override val clickEvent: ((View) -> Unit)? = null, val width: Int = CommonToolbarLayout.MENU_ITEM_SIZE, val height: Int = CommonToolbarLayout.MENU_ITEM_SIZE, - ) : ToolbarMenu(resourceId, padding, clickEvent) { + val clickEvent: ((View) -> Unit)? = null, + ) : ToolbarMenu(resourceId, padding) { + override fun createMenu( context: Context, - layoutParams: LinearLayout.LayoutParams, - menu: ToolbarMenu - ): View? { - if (menu !is Icon) return null - - val padding = menu.padding.dpToPx(context) - val menuWidth = menu.width.dpToPx(context) - val menuHeight = menu.height.dpToPx(context) - val imgRes = menu.resourceId - val bgColor = ContextCompat.getColor(context, R.color.transparent_00) - val clickListener = View.OnClickListener { - menu.clickEvent?.invoke(it) - } - - return AppCompatImageButton(context).apply { - // 이미지 아이콘 설정 - setImageResource(imgRes) - setBackgroundColor(bgColor) - setPadding(padding) - - // 클릭 이벤트 설정 - setOnClickListener(clickListener) - - // 이미지뷰 사이즈 설정 (기본 48dp * 48dp) - layoutParams.apply { - width = menuWidth - height = menuHeight - }.let(::setLayoutParams) - } + ): View { + return createBaseImageButton(context, resourceId, padding, width, height, clickEvent) } } data class Text ( - override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, + @StringRes override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, override val padding: Int = 0, - override val clickEvent: ((View) -> Unit)? = null, val textSize: Int = CommonToolbarLayout.MENU_ITEM_TEXT_SIZE, - ) : ToolbarMenu(resourceId, padding, clickEvent) { - override fun createMenu( - context: Context, - layoutParams: LinearLayout.LayoutParams, - menu: ToolbarMenu - ): View? { - if (menu !is Text) return null - - val padding = menu.padding.dpToPx(context) - val titleText = context.getString(menu.resourceId) - val textSize = menu.textSize.toFloat() - val textColor = ContextCompat.getColor(context, R.color.G1) - - return AppCompatTextView(context).apply { - text = titleText - gravity = Gravity.CENTER - typeface = ResourcesCompat.getFont(context, R.font.pretendard_bold) - - // 텍스트뷰 패딩 설정 기본(0dp) - setPadding(padding, 0, padding, 0) - setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize) - setTextColor(textColor) + ) : ToolbarMenu(resourceId, padding) { - setLayoutParams(layoutParams) - } + override fun createMenu( + context: Context + ): View { + return createBaseTextView(context, resourceId, padding, textSize.toFloat()) } } data class Popup ( - override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, + @DrawableRes override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, override val padding: Int = CommonToolbarLayout.MENU_ITEM_PADDING, - override val clickEvent: ((View) -> Unit)? = null, val width: Int = CommonToolbarLayout.MENU_ITEM_SIZE, val height: Int = CommonToolbarLayout.MENU_ITEM_SIZE, private val popupItems: List, From c22a665b569f30ba4b553fe6ab770b5d49263a9c Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Sun, 7 Jan 2024 22:32:43 +0900 Subject: [PATCH 12/16] =?UTF-8?q?[refactor]=20#287=20=EB=AF=B8=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A3=BC=EC=84=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/toolbar/CommonToolbarLayout.kt | 31 +++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt index 246ca77ac..8265d8b48 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt @@ -51,6 +51,7 @@ interface CommonToolbarLayout { /** * ToolBar 초기화 메소드 + * Activity or Fragment에서 구현 */ fun initToolBarLayout() @@ -109,7 +110,8 @@ interface CommonToolbarLayout { } /** - * AppBar의 Title Text 설정 + * Toolbar의 Title Text 설정 + * - Title Text는 Toolbar의 정가운데에 위치하는 TextView를 의미 * @param textResId title 텍스트 리소스 id */ fun setToolBarTitleText(@StringRes textResId: Int) { @@ -119,7 +121,8 @@ interface CommonToolbarLayout { } /** - * AppBar의 Title Text 설정 + * Toolbar의 Title Text 설정 + * - Title Text는 Toolbar의 정가운데에 위치하는 TextView를 의미 * @param title title 텍스트 */ fun setToolBarTitleText(title: String) { @@ -150,30 +153,6 @@ interface CommonToolbarLayout { } } - /** - * Toolbar 높이 설정 메소드 - * @param height 높이 값(dp) - */ - fun setToolbarHeight(height: Int = TOOLBAR_HEIGHT) { - toolbarBinding.toolbar.run { - context?.let { - setViewSize(toolbarBinding.toolbar, height = height.dpToPx(it)) - } - } - } - - /** - * 뷰 size 설정 - * @param view - * @param width - * @param height - */ - private fun setViewSize(view: View, width: Int = VIEW_SIZE_NONE, height: Int = VIEW_SIZE_NONE) { - view.layoutParams = view.layoutParams.apply { - this.width = if (width > VIEW_SIZE_NONE) width else this.width - this.height = if (height > VIEW_SIZE_NONE) height else this.height - } - } /** * Toolbar의 왼쪽 영역에 메뉴 아이템들을 추가합니다. From c2247fc1b2c039d0bdfb929d487b637b30a00df6 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 8 Jan 2024 00:42:09 +0900 Subject: [PATCH 13/16] =?UTF-8?q?[add]=20#287=20=ED=83=80=EC=9D=B4?= =?UTF-8?q?=ED=8B=80=20=ED=8F=B0=ED=8A=B8=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20&=20=EC=83=81?= =?UTF-8?q?=EC=88=98=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/custom/toolbar/BaseToolbarMenu.kt | 10 ++- .../custom/toolbar/CommonToolbarLayout.kt | 83 ++++++++++--------- .../util/custom/toolbar/ToolbarMenu.kt | 5 +- 3 files changed, 57 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/BaseToolbarMenu.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/BaseToolbarMenu.kt index 8c9035f1e..959115192 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/BaseToolbarMenu.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/BaseToolbarMenu.kt @@ -5,6 +5,9 @@ import android.util.TypedValue import android.view.Gravity import android.view.View import android.widget.LinearLayout +import androidx.annotation.DrawableRes +import androidx.annotation.FontRes +import androidx.annotation.StringRes import androidx.appcompat.widget.AppCompatImageButton import androidx.appcompat.widget.AppCompatTextView import androidx.core.content.ContextCompat @@ -16,7 +19,7 @@ import com.runnect.runnect.util.extension.setPadding open class BaseToolbarMenu { protected fun createBaseImageButton( context: Context, - resourceId: Int, + @DrawableRes resourceId: Int, padding: Int, width: Int, height: Int, @@ -40,9 +43,10 @@ open class BaseToolbarMenu { protected fun createBaseTextView( context: Context, - resourceId: Int, + @StringRes resourceId: Int, padding: Int, textSize: Float, + @FontRes fontRes: Int ): AppCompatTextView { val paddingPx = padding.dpToPx(context) val titleText = context.getString(resourceId) @@ -55,7 +59,7 @@ open class BaseToolbarMenu { return AppCompatTextView(context).apply { text = titleText gravity = Gravity.CENTER - typeface = ResourcesCompat.getFont(context, R.font.pretendard_bold) + typeface = ResourcesCompat.getFont(context, fontRes) setPadding(paddingPx, 0, paddingPx, 0) setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize) diff --git a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt index 8265d8b48..8978be2eb 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt @@ -6,12 +6,13 @@ import android.view.View import android.widget.LinearLayout import androidx.annotation.ColorRes import androidx.annotation.DrawableRes +import androidx.annotation.FontRes import androidx.annotation.IntDef import androidx.annotation.StringRes import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat import com.runnect.runnect.R import com.runnect.runnect.databinding.LayoutCommonToolbarBinding -import com.runnect.runnect.util.extension.dpToPx interface CommonToolbarLayout { @@ -24,28 +25,24 @@ interface CommonToolbarLayout { const val LEFT = 0 const val RIGHT = 1 + // Toolbar 관련 + private const val TOOLBAR_TITLE_TEXT_SIZE = 18 // Title 텍스트 기본 크기(sp) + private const val TOOLBAR_MENU_LIMIT_COUNT = 2 // 메뉴 최대 노출 갯수 + + // 메뉴 아이템 관련 const val MENU_ITEM_SIZE = 48 // 메뉴 아이템 뷰 기본 크기 const val MENU_ITEM_PADDING = 15 // 메뉴 아이템 뷰 기본 패딩(dp) const val MENU_ITEM_TEXT_SIZE = 18 // 텍스트 기본 크기 const val MENU_ITEM_RESOURCE_NONE = -1 // 메뉴 아이템 리소스(아이콘,텍스트) 없음 - // Toolbar 관련 - private const val TOOLBAR_HEIGHT = 56 // Toolbar 기본 높이 - private const val TOOLBAR_TITLE_TEXT_SIZE = 18 // Title 텍스트 기본 크기(sp) - private const val TOOLBAR_MENU_LIMIT_COUNT = 2 // 메뉴 최대 노출 갯수 - - // 뷰 관련 - private const val VIEW_SIZE_NONE = -1 // 뷰 사이즈 없음 + // 기본 리소스 + @DrawableRes val MENU_ICON_BACK_BUTTON: Int = R.drawable.all_back_arrow + @FontRes val TOOLBAR_TITLE_FONT_RES: Int = R.font.pretendard_bold + @ColorRes private val TOOLBAR_TITLE_TEXT_COLOR = R.color.G1 + @ColorRes private val TOOLBAR_BACKGROUND_COLOR = R.color.white } - /** 기본 리소스 */ - private val menuIconBack: Int // 메뉴 뒤로가기 기본 아이콘 - get() = R.drawable.all_back_arrow - private val toolbarTitleTextColor: Int // Title 텍스트 기본 색상 - get() = R.color.G1 - private val toolbarBackgroundColor: Int // Toolbar 기본 배경 색상 - get() = R.color.white val toolbarBinding: LayoutCommonToolbarBinding @@ -60,23 +57,22 @@ interface CommonToolbarLayout { * * @param bgColorResId 툴바의 배경색 리소스 id * @param textResId title 텍스트 리소스 id - * @param textSizeDip title 텍스트의 크기(sp) + * @param textSize title 텍스트의 크기(sp) * @param textColorResId title 텍스트 색상의 리소스 id + * @param fontResId title 폰트 리소스 id * @param backButtonResId back 버튼의 아이콘 리소스 id * @param backButtonEvent back 버튼의 클릭 이벤트 핸들러 */ fun setToolbar ( - @ColorRes bgColorResId: Int = toolbarBackgroundColor, + @ColorRes bgColorResId: Int = TOOLBAR_BACKGROUND_COLOR, @StringRes textResId: Int? = null, - @ColorRes textColorResId: Int = toolbarTitleTextColor, - @DrawableRes backButtonResId: Int = menuIconBack, + @ColorRes textColorResId: Int = TOOLBAR_TITLE_TEXT_COLOR, + @FontRes fontResId: Int = TOOLBAR_TITLE_FONT_RES, + @DrawableRes backButtonResId: Int = MENU_ICON_BACK_BUTTON, titleText: String? = null, - textSizeDip: Int = TOOLBAR_TITLE_TEXT_SIZE, + textSize: Int = TOOLBAR_TITLE_TEXT_SIZE, backButtonEvent: ((View) -> Unit)? = null ) { - // 백그라운드 영역 - setToolbarBackgroundColor(bgColorResId) - // title 영역 text if(textResId != null) { setToolBarTitleText(textResId) @@ -84,8 +80,10 @@ interface CommonToolbarLayout { setToolBarTitleText(titleText) } + setToolbarBackgroundColor(bgColorResId) setToolBarTitleTextColor(textColorResId) - setToolbarTitleTextSize(textSizeDip) + setToolbarTitleTextSize(textSize) + setToolBarTitleFont(fontResId) // 왼쪽 메뉴 추가 addMenuTo( @@ -97,18 +95,6 @@ interface CommonToolbarLayout { ) } - /** - * Toolbar의 배경 색상 지정 메소드 - * @param colorResId 배경 색상 Color Resource Id - */ - fun setToolbarBackgroundColor(@ColorRes colorResId: Int = toolbarBackgroundColor) { - toolbarBinding.toolbar.run { - context?.let { - setBackgroundColor(getColor(it, colorResId)) - } - } - } - /** * Toolbar의 Title Text 설정 * - Title Text는 Toolbar의 정가운데에 위치하는 TextView를 의미 @@ -129,11 +115,23 @@ interface CommonToolbarLayout { toolbarBinding.tvTitle.text = title } + /** + * Toolbar의 배경 색상 지정 메소드 + * @param colorResId 배경 색상 Color Resource Id + */ + fun setToolbarBackgroundColor(@ColorRes colorResId: Int = TOOLBAR_BACKGROUND_COLOR) { + toolbarBinding.toolbar.run { + context?.let { + setBackgroundColor(getColor(it, colorResId)) + } + } + } + /** * Toolbar의 Title Text Color 설정 * @param textColorResId 색상 리소스 id */ - fun setToolBarTitleTextColor(@ColorRes textColorResId: Int = toolbarTitleTextColor) { + fun setToolBarTitleTextColor(@ColorRes textColorResId: Int = TOOLBAR_TITLE_TEXT_COLOR) { toolbarBinding.tvTitle.run { context?.let { setTextColor(getColor(it, textColorResId)) @@ -153,6 +151,17 @@ interface CommonToolbarLayout { } } + /** + * Toolbar Title Font 설정 + * + * @param fontResId - title 폰트 리소스 id + */ + fun setToolBarTitleFont(@FontRes fontResId: Int) { + with(toolbarBinding){ + val context = toolbar.context ?: return + tvTitle.typeface = ResourcesCompat.getFont(context, fontResId) + } + } /** * Toolbar의 왼쪽 영역에 메뉴 아이템들을 추가합니다. diff --git a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt index 338c89e61..043d68c45 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt @@ -2,7 +2,9 @@ package com.runnect.runnect.util.custom.toolbar import android.content.Context import android.view.View +import androidx.annotation.ColorRes import androidx.annotation.DrawableRes +import androidx.annotation.FontRes import androidx.annotation.StringRes import com.runnect.runnect.util.custom.popup.PopupItem import com.runnect.runnect.util.custom.popup.RunnectPopupMenu @@ -36,12 +38,13 @@ sealed class ToolbarMenu( @StringRes override val resourceId: Int = CommonToolbarLayout.MENU_ITEM_RESOURCE_NONE, override val padding: Int = 0, val textSize: Int = CommonToolbarLayout.MENU_ITEM_TEXT_SIZE, + @FontRes val fontRes: Int = CommonToolbarLayout.TOOLBAR_TITLE_FONT_RES ) : ToolbarMenu(resourceId, padding) { override fun createMenu( context: Context ): View { - return createBaseTextView(context, resourceId, padding, textSize.toFloat()) + return createBaseTextView(context, resourceId, padding, textSize.toFloat(), fontRes) } } From 7c76406d819eec213f2e9e83e4db2f316b02c3f9 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Mon, 8 Jan 2024 00:45:14 +0900 Subject: [PATCH 14/16] =?UTF-8?q?[chore]=20#287=20=EB=AF=B8=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20import=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runnect/util/custom/toolbar/CommonToolbarLayout.kt | 8 ++++---- .../runnect/runnect/util/custom/toolbar/ToolbarMenu.kt | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt index 8978be2eb..dbf1f5701 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt @@ -63,7 +63,7 @@ interface CommonToolbarLayout { * @param backButtonResId back 버튼의 아이콘 리소스 id * @param backButtonEvent back 버튼의 클릭 이벤트 핸들러 */ - fun setToolbar ( + fun setToolbar( @ColorRes bgColorResId: Int = TOOLBAR_BACKGROUND_COLOR, @StringRes textResId: Int? = null, @ColorRes textColorResId: Int = TOOLBAR_TITLE_TEXT_COLOR, @@ -74,7 +74,7 @@ interface CommonToolbarLayout { backButtonEvent: ((View) -> Unit)? = null ) { // title 영역 text - if(textResId != null) { + if (textResId != null) { setToolBarTitleText(textResId) } else if (titleText != null) { setToolBarTitleText(titleText) @@ -157,7 +157,7 @@ interface CommonToolbarLayout { * @param fontResId - title 폰트 리소스 id */ fun setToolBarTitleFont(@FontRes fontResId: Int) { - with(toolbarBinding){ + with(toolbarBinding) { val context = toolbar.context ?: return tvTitle.typeface = ResourcesCompat.getFont(context, fontResId) } @@ -170,7 +170,7 @@ interface CommonToolbarLayout { * @param menu 추가할 메뉴 아이템 리스트(가변 인자) */ fun addMenuTo(@Direction direction: Int, vararg menu: ToolbarMenu) { - val menuLayout = when(direction) { + val menuLayout = when (direction) { LEFT -> toolbarBinding.llLeftMenu else -> toolbarBinding.llRightMenu } diff --git a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt index 043d68c45..82cf16477 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/ToolbarMenu.kt @@ -2,7 +2,6 @@ package com.runnect.runnect.util.custom.toolbar import android.content.Context import android.view.View -import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.FontRes import androidx.annotation.StringRes @@ -25,7 +24,7 @@ sealed class ToolbarMenu( val width: Int = CommonToolbarLayout.MENU_ITEM_SIZE, val height: Int = CommonToolbarLayout.MENU_ITEM_SIZE, val clickEvent: ((View) -> Unit)? = null, - ) : ToolbarMenu(resourceId, padding) { + ): ToolbarMenu(resourceId, padding) { override fun createMenu( context: Context, @@ -39,7 +38,7 @@ sealed class ToolbarMenu( override val padding: Int = 0, val textSize: Int = CommonToolbarLayout.MENU_ITEM_TEXT_SIZE, @FontRes val fontRes: Int = CommonToolbarLayout.TOOLBAR_TITLE_FONT_RES - ) : ToolbarMenu(resourceId, padding) { + ): ToolbarMenu(resourceId, padding) { override fun createMenu( context: Context @@ -55,7 +54,7 @@ sealed class ToolbarMenu( val height: Int = CommonToolbarLayout.MENU_ITEM_SIZE, private val popupItems: List, private val menuItemClickListener: (View, PopupItem, Int) -> Unit, - ) : ToolbarMenu(resourceId, padding) { + ): ToolbarMenu(resourceId, padding) { private var popupMenu: RunnectPopupMenu? = null From 9038febe4fb172f1818b33be74ea11952769fd02 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Sat, 20 Jan 2024 17:02:05 +0900 Subject: [PATCH 15/16] Merge branch 'develop' into feature/add_common_toolbar_layout --- app/src/main/AndroidManifest.xml | 3 + .../runnect/data/dto/ProfileCourseData.kt | 18 --- .../dto/response/ResponseGetCourseDetail.kt | 5 +- .../response/ResponseGetDiscoverRecommend.kt | 4 + .../dto/response/ResponseGetUserProfile.kt | 87 +++++++++++ .../data/dto/response/ResponsePostScrap.kt | 14 ++ .../data/repository/CourseRepositoryImpl.kt | 14 +- .../data/repository/UserRepositoryImpl.kt | 8 +- .../runnect/data/service/CourseService.kt | 6 +- .../runnect/data/service/UserService.kt | 6 + .../source/remote/RemoteCourseDataSource.kt | 11 +- .../source/remote/RemoteUserDataSource.kt | 19 ++- .../runnect/domain/entity/CourseDetail.kt | 1 + .../domain/entity/DiscoverMultiViewItem.kt | 12 +- .../entity/RecommendCoursePagingData.kt | 6 + .../runnect/domain/entity/UserProfile.kt | 26 ++++ .../domain/repository/CourseRepository.kt | 12 +- .../domain/repository/UserRepository.kt | 10 +- .../coursemain/CourseMainFragment.kt | 87 ++++++----- .../detail/CourseDetailActivity.kt | 113 ++++++++++----- .../detail/CourseDetailViewModel.kt | 29 ++-- .../presentation/discover/DiscoverFragment.kt | 73 ++++++++-- .../discover/DiscoverViewModel.kt | 98 ++++++++----- .../adapter/DiscoverMultiViewAdapter.kt | 136 ------------------ .../adapter/DiscoverMultiViewHolder.kt | 82 ----------- .../multiview/DiscoverMultiViewAdapter.kt | 90 ++++++++++++ .../multiview/DiscoverMultiViewHolder.kt | 121 ++++++++++++++++ .../DiscoverMultiViewHolderFactory.kt | 40 ++++++ .../multiview/DiscoverMultiViewType.kt | 6 + .../runnect/presentation/draw/DrawActivity.kt | 27 +++- .../presentation/login/LoginActivity.kt | 4 +- .../mydrawdetail/MyDrawDetailActivity.kt | 30 +++- .../presentation/profile/ProfileActivity.kt | 119 ++++++++++++++- .../profile/ProfileCourseAdapter.kt | 37 ++++- .../presentation/profile/ProfileViewModel.kt | 102 ++++++++----- .../presentation/search/SearchActivity.kt | 34 ++++- .../runnect/util/binding/BindingAdapter.kt | 16 +++ .../deco/DiscoverMarathonItemDecoration.kt | 10 +- .../deco/DiscoverRecommendItemDecoration.kt | 27 ++++ .../runnect/util/extension/ContextExt.kt | 14 +- .../runnect/util/extension/PermissionExt.kt | 68 +++++++++ .../runnect/runnect/util/extension/ViewExt.kt | 15 +- .../res/layout/activity_course_detail.xml | 25 ++-- app/src/main/res/layout/activity_profile.xml | 51 ++++--- .../res/layout/item_discover_marathon.xml | 15 +- .../item_discover_multiview_recommend.xml | 3 +- .../res/layout/item_discover_recommend.xml | 7 +- .../main/res/layout/item_profile_course.xml | 16 ++- app/src/main/res/values/strings.xml | 8 ++ 49 files changed, 1242 insertions(+), 523 deletions(-) delete mode 100644 app/src/main/java/com/runnect/runnect/data/dto/ProfileCourseData.kt create mode 100644 app/src/main/java/com/runnect/runnect/data/dto/response/ResponseGetUserProfile.kt create mode 100644 app/src/main/java/com/runnect/runnect/data/dto/response/ResponsePostScrap.kt create mode 100644 app/src/main/java/com/runnect/runnect/domain/entity/RecommendCoursePagingData.kt create mode 100644 app/src/main/java/com/runnect/runnect/domain/entity/UserProfile.kt delete mode 100644 app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverMultiViewAdapter.kt delete mode 100644 app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverMultiViewHolder.kt create mode 100644 app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewAdapter.kt create mode 100644 app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolder.kt create mode 100644 app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolderFactory.kt create mode 100644 app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewType.kt create mode 100644 app/src/main/java/com/runnect/runnect/util/custom/deco/DiscoverRecommendItemDecoration.kt create mode 100644 app/src/main/java/com/runnect/runnect/util/extension/PermissionExt.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0c0a6718e..337e58fb3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -134,6 +134,9 @@ + ) { diff --git a/app/src/main/java/com/runnect/runnect/data/dto/response/ResponseGetUserProfile.kt b/app/src/main/java/com/runnect/runnect/data/dto/response/ResponseGetUserProfile.kt new file mode 100644 index 000000000..c61fff21e --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/dto/response/ResponseGetUserProfile.kt @@ -0,0 +1,87 @@ +package com.runnect.runnect.data.dto.response + +import com.runnect.runnect.domain.entity.Departure +import com.runnect.runnect.domain.entity.UserCourse +import com.runnect.runnect.domain.entity.UserProfile +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseGetUserProfile( + @SerialName("user") + val user: User, + @SerialName("courses") + val courses: List +) { + @Serializable + data class User( + @SerialName("nickname") + val nickname: String, + @SerialName("level") + val level: Int, + @SerialName("levelPercent") + val levelPercent: Int, + @SerialName("latestStamp") + val latestStamp: String, + @SerialName("userId") + val userId: Int + ) + + @Serializable + data class CourseData( + @SerialName("publicCourseId") + val publicCourseId: Int, + @SerialName("courseId") + val courseId: Int, + @SerialName("title") + val title: String, + @SerialName("image") + val image: String, + @SerialName("departure") + val departure: Departure, + @SerialName("scrapTF") + val scrapTF: Boolean, + ) { + @Serializable + data class Departure( + @SerialName("region") + val region: String, + @SerialName("city") + val city: String, + @SerialName("town") + val town: String, + @SerialName("name") + val name: String?, + @SerialName("detail") + val detail: String? + ) + } + + fun toUserProfile(): UserProfile { + val userCourseLists: List = courses.map { course -> + UserCourse( + publicCourseId = course.publicCourseId, + courseId = course.courseId, + title = course.title, + image = course.image, + departure = Departure( + region = course.departure.region, + city = course.departure.city, + town = course.departure.town, + detail = course.departure.detail, + name = course.departure.name ?: "" + ), + scrapTF = course.scrapTF + ) + } + + return UserProfile( + nickname = user.nickname, + level = user.level, + levelPercent = user.levelPercent, + latestStamp = user.latestStamp, + courseData = userCourseLists + ) + } + +} diff --git a/app/src/main/java/com/runnect/runnect/data/dto/response/ResponsePostScrap.kt b/app/src/main/java/com/runnect/runnect/data/dto/response/ResponsePostScrap.kt new file mode 100644 index 000000000..cd5d3b0ae --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/dto/response/ResponsePostScrap.kt @@ -0,0 +1,14 @@ +package com.runnect.runnect.data.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponsePostScrap( + @SerialName("publicCourseId") + val publicCourseId: Long, + @SerialName("scrapCount") + val scrapCount: Long, + @SerialName("scrapTF") + val scrapTF: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt index cab91a52a..540a03cd1 100644 --- a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt +++ b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt @@ -12,10 +12,12 @@ import com.runnect.runnect.data.dto.response.ResponsePostMyDrawCourse import com.runnect.runnect.data.dto.response.ResponsePostMyHistory import com.runnect.runnect.data.dto.response.ResponsePutMyDrawCourse import com.runnect.runnect.data.dto.response.ResponsePostDiscoverUpload +import com.runnect.runnect.data.dto.response.ResponsePostScrap import com.runnect.runnect.data.source.remote.RemoteCourseDataSource import com.runnect.runnect.domain.entity.DiscoverSearchCourse import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.* import com.runnect.runnect.domain.entity.EditableCourseDetail +import com.runnect.runnect.domain.entity.RecommendCoursePagingData import com.runnect.runnect.domain.repository.CourseRepository import com.runnect.runnect.util.extension.toData import okhttp3.MultipartBody @@ -32,11 +34,15 @@ class CourseRepositoryImpl @Inject constructor(private val remoteCourseDataSourc override suspend fun getRecommendCourse( pageNo: String, ordering: String - ): Result?> = runCatching { - remoteCourseDataSource.getRecommendCourse( + ): Result = runCatching { + val response = remoteCourseDataSource.getRecommendCourse( pageNo = pageNo, ordering = ordering - ).data?.toRecommendCourses() + ).data + + response?.let { + RecommendCoursePagingData(response.isEnd, response.toRecommendCourses()) + } } override suspend fun getCourseSearch(keyword: String): Result?> = @@ -95,7 +101,7 @@ class CourseRepositoryImpl @Inject constructor(private val remoteCourseDataSourc override suspend fun postCourseScrap( requestPostCourseScrap: RequestPostCourseScrap - ): Result = runCatching { + ): Result = runCatching { remoteCourseDataSource.postCourseScrap(requestPostCourseScrap = requestPostCourseScrap).data } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/runnect/runnect/data/repository/UserRepositoryImpl.kt index 2b6350af5..8c0bf1081 100644 --- a/app/src/main/java/com/runnect/runnect/data/repository/UserRepositoryImpl.kt +++ b/app/src/main/java/com/runnect/runnect/data/repository/UserRepositoryImpl.kt @@ -1,6 +1,7 @@ package com.runnect.runnect.data.repository import com.runnect.runnect.data.dto.HistoryInfoDTO +import com.runnect.runnect.domain.entity.UserProfile import com.runnect.runnect.data.dto.UserUploadCourseDTO import com.runnect.runnect.data.dto.request.RequestDeleteHistory import com.runnect.runnect.data.dto.request.RequestDeleteUploadCourse @@ -9,9 +10,9 @@ import com.runnect.runnect.data.dto.request.RequestPatchNickName import com.runnect.runnect.data.dto.response.ResponseDeleteHistory import com.runnect.runnect.data.dto.response.ResponseDeleteUploadCourse import com.runnect.runnect.data.dto.response.ResponseDeleteUser +import com.runnect.runnect.data.dto.response.ResponseGetUser import com.runnect.runnect.data.dto.response.ResponsePatchHistoryTitle import com.runnect.runnect.data.dto.response.ResponsePatchUserNickName -import com.runnect.runnect.data.dto.response.ResponseGetUser import com.runnect.runnect.data.source.remote.RemoteUserDataSource import com.runnect.runnect.domain.repository.UserRepository import com.runnect.runnect.util.extension.toData @@ -36,6 +37,11 @@ class UserRepositoryImpl @Inject constructor(private val remoteUserDataSource: R .toMutableList() } + override suspend fun getUserProfile(userId: Int): Result = + runCatching { + remoteUserDataSource.getUserProfile(userId).data?.toUserProfile() + } + override suspend fun putDeleteUploadCourse( requestDeleteUploadCourse: RequestDeleteUploadCourse ): Result = runCatching { diff --git a/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt b/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt index 64a8225ad..aa579c61b 100644 --- a/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt +++ b/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt @@ -1,10 +1,10 @@ package com.runnect.runnect.data.service +import com.runnect.runnect.data.dto.request.RequestPatchPublicCourse import com.runnect.runnect.data.dto.request.RequestPostCourseScrap +import com.runnect.runnect.data.dto.request.RequestPostPublicCourse import com.runnect.runnect.data.dto.request.RequestPostRunningHistory import com.runnect.runnect.data.dto.request.RequestPutMyDrawCourse -import com.runnect.runnect.data.dto.request.RequestPatchPublicCourse -import com.runnect.runnect.data.dto.request.RequestPostPublicCourse import com.runnect.runnect.data.dto.response.* import com.runnect.runnect.data.dto.response.base.BaseResponse import okhttp3.MultipartBody @@ -25,7 +25,7 @@ interface CourseService { @POST("/api/scrap") suspend fun postCourseScrap( @Body requestPostCourseScrap: RequestPostCourseScrap, - ): BaseResponse + ): BaseResponse @GET("/api/public-course/search?") suspend fun getCourseSearch( diff --git a/app/src/main/java/com/runnect/runnect/data/service/UserService.kt b/app/src/main/java/com/runnect/runnect/data/service/UserService.kt index 62fb27610..780a22bf9 100644 --- a/app/src/main/java/com/runnect/runnect/data/service/UserService.kt +++ b/app/src/main/java/com/runnect/runnect/data/service/UserService.kt @@ -48,4 +48,10 @@ interface UserService { @DELETE("api/user") suspend fun deleteUser(): ResponseDeleteUser + + // 유저 프로필 조회 + @GET("/api/user/{profileUserId}") + suspend fun getUserProfile( + @Path("profileUserId") userId: Int, + ): BaseResponse } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt index fecd2ff75..ffb83942d 100644 --- a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt +++ b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt @@ -1,16 +1,17 @@ package com.runnect.runnect.data.source.remote -import com.runnect.runnect.data.service.CourseService +import com.runnect.runnect.data.dto.request.RequestPatchPublicCourse import com.runnect.runnect.data.dto.request.RequestPostCourseScrap +import com.runnect.runnect.data.dto.request.RequestPostPublicCourse import com.runnect.runnect.data.dto.request.RequestPostRunningHistory import com.runnect.runnect.data.dto.request.RequestPutMyDrawCourse -import com.runnect.runnect.data.dto.request.RequestPatchPublicCourse -import com.runnect.runnect.data.dto.request.RequestPostPublicCourse import com.runnect.runnect.data.dto.response.ResponseGetCourseDetail import com.runnect.runnect.data.dto.response.ResponseGetDiscoverMarathon -import com.runnect.runnect.data.dto.response.ResponsePatchPublicCourse import com.runnect.runnect.data.dto.response.ResponseGetDiscoverRecommend +import com.runnect.runnect.data.dto.response.ResponsePatchPublicCourse +import com.runnect.runnect.data.dto.response.ResponsePostScrap import com.runnect.runnect.data.dto.response.base.BaseResponse +import com.runnect.runnect.data.service.CourseService import okhttp3.MultipartBody import okhttp3.RequestBody import javax.inject.Inject @@ -27,7 +28,7 @@ class RemoteCourseDataSource @Inject constructor( ): BaseResponse = courseService.getRecommendCourse(pageNo = pageNo, ordering = ordering) - suspend fun postCourseScrap(requestPostCourseScrap: RequestPostCourseScrap): BaseResponse = + suspend fun postCourseScrap(requestPostCourseScrap: RequestPostCourseScrap): BaseResponse = courseService.postCourseScrap(requestPostCourseScrap) suspend fun getCourseSearch(keyword: String) = courseService.getCourseSearch(keyword) diff --git a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteUserDataSource.kt b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteUserDataSource.kt index 0964340c5..19d9b64e0 100644 --- a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteUserDataSource.kt +++ b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteUserDataSource.kt @@ -1,12 +1,21 @@ package com.runnect.runnect.data.source.remote -import com.runnect.runnect.data.service.UserService import com.runnect.runnect.data.dto.request.RequestDeleteHistory import com.runnect.runnect.data.dto.request.RequestDeleteUploadCourse import com.runnect.runnect.data.dto.request.RequestPatchHistoryTitle import com.runnect.runnect.data.dto.request.RequestPatchNickName -import com.runnect.runnect.data.dto.response.* +import com.runnect.runnect.data.dto.response.ResponseDeleteHistory +import com.runnect.runnect.data.dto.response.ResponseDeleteUploadCourse +import com.runnect.runnect.data.dto.response.ResponseDeleteUser +import com.runnect.runnect.data.dto.response.ResponseGetMyHistory +import com.runnect.runnect.data.dto.response.ResponseGetMyStamp +import com.runnect.runnect.data.dto.response.ResponseGetUser +import com.runnect.runnect.data.dto.response.ResponseGetUserProfile +import com.runnect.runnect.data.dto.response.ResponseGetUserUploadCourse +import com.runnect.runnect.data.dto.response.ResponsePatchHistoryTitle +import com.runnect.runnect.data.dto.response.ResponsePatchUserNickName import com.runnect.runnect.data.dto.response.base.BaseResponse +import com.runnect.runnect.data.service.UserService import javax.inject.Inject class RemoteUserDataSource @Inject constructor(private val userService: UserService) { @@ -16,7 +25,11 @@ class RemoteUserDataSource @Inject constructor(private val userService: UserServ suspend fun getMyStamp(): ResponseGetMyStamp = userService.getMyStamp() suspend fun getRecord(): ResponseGetMyHistory = userService.getRecord() - suspend fun getUserUploadCourse(): ResponseGetUserUploadCourse = userService.getUserUploadCourse() + suspend fun getUserUploadCourse(): ResponseGetUserUploadCourse = + userService.getUserUploadCourse() + + suspend fun getUserProfile(userId: Int): BaseResponse = + userService.getUserProfile(userId) suspend fun putDeleteUploadCourse( requestDeleteUploadCourse: RequestDeleteUploadCourse diff --git a/app/src/main/java/com/runnect/runnect/domain/entity/CourseDetail.kt b/app/src/main/java/com/runnect/runnect/domain/entity/CourseDetail.kt index ca0476e67..4fe5a6cb1 100644 --- a/app/src/main/java/com/runnect/runnect/domain/entity/CourseDetail.kt +++ b/app/src/main/java/com/runnect/runnect/domain/entity/CourseDetail.kt @@ -15,4 +15,5 @@ data class CourseDetail( val path: List>, val distance: String, val departure: String, + val userId: Int ) diff --git a/app/src/main/java/com/runnect/runnect/domain/entity/DiscoverMultiViewItem.kt b/app/src/main/java/com/runnect/runnect/domain/entity/DiscoverMultiViewItem.kt index 26279c775..9e1c9600c 100644 --- a/app/src/main/java/com/runnect/runnect/domain/entity/DiscoverMultiViewItem.kt +++ b/app/src/main/java/com/runnect/runnect/domain/entity/DiscoverMultiViewItem.kt @@ -1,21 +1,23 @@ package com.runnect.runnect.domain.entity -sealed class DiscoverMultiViewItem { +sealed class DiscoverMultiViewItem( + open val id: Int +) { data class MarathonCourse( - val id: Int, + override val id: Int, val courseId: Int, var title: String, val image: String, var scrap: Boolean, val departure: String, - ) : DiscoverMultiViewItem() + ) : DiscoverMultiViewItem(id) data class RecommendCourse( - val id: Int, + override val id: Int, val courseId: Int, var title: String, val image: String, var scrap: Boolean, val departure: String, - ) : DiscoverMultiViewItem() + ) : DiscoverMultiViewItem(id) } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/domain/entity/RecommendCoursePagingData.kt b/app/src/main/java/com/runnect/runnect/domain/entity/RecommendCoursePagingData.kt new file mode 100644 index 000000000..1e3fbe3f6 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/domain/entity/RecommendCoursePagingData.kt @@ -0,0 +1,6 @@ +package com.runnect.runnect.domain.entity + +data class RecommendCoursePagingData( + val isEnd: Boolean, + val recommendCourses: List +) \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/domain/entity/UserProfile.kt b/app/src/main/java/com/runnect/runnect/domain/entity/UserProfile.kt new file mode 100644 index 000000000..99276137e --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/domain/entity/UserProfile.kt @@ -0,0 +1,26 @@ +package com.runnect.runnect.domain.entity + +data class UserProfile( + val nickname: String, + val level: Int, + val levelPercent: Int, + val latestStamp: String, + val courseData: List +) + +data class UserCourse( + val publicCourseId: Int, + val courseId: Int, + val title: String, + val image: String, + val departure: Departure, + var scrapTF: Boolean, +) + +data class Departure( + val region: String, + val city: String, + val town: String, + val detail: String?, + val name: String +) diff --git a/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt b/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt index da3be14a7..ec56b0e9b 100644 --- a/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt +++ b/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt @@ -10,11 +10,13 @@ import com.runnect.runnect.data.dto.response.ResponseGetMyDrawDetail import com.runnect.runnect.data.dto.response.ResponsePostDiscoverUpload import com.runnect.runnect.data.dto.response.ResponsePostMyDrawCourse import com.runnect.runnect.data.dto.response.ResponsePostMyHistory +import com.runnect.runnect.data.dto.response.ResponsePostScrap import com.runnect.runnect.data.dto.response.ResponsePutMyDrawCourse import com.runnect.runnect.domain.entity.CourseDetail +import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.MarathonCourse import com.runnect.runnect.domain.entity.DiscoverSearchCourse -import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.* import com.runnect.runnect.domain.entity.EditableCourseDetail +import com.runnect.runnect.domain.entity.RecommendCoursePagingData import okhttp3.MultipartBody import okhttp3.RequestBody import retrofit2.Response @@ -22,7 +24,10 @@ import retrofit2.Response interface CourseRepository { suspend fun getMarathonCourse(): Result?> - suspend fun getRecommendCourse(pageNo: String, ordering: String): Result?> + suspend fun getRecommendCourse( + pageNo: String, + ordering: String + ): Result suspend fun getCourseSearch(keyword: String): Result?> @@ -39,6 +44,7 @@ interface CourseRepository { suspend fun uploadCourse( image: MultipartBody.Part, courseCreateRequestDto: RequestBody ): Response + suspend fun getCourseDetail(publicCourseId: Int): Result suspend fun patchPublicCourse( @@ -46,5 +52,5 @@ interface CourseRepository { requestPatchPublicCourse: RequestPatchPublicCourse ): Result - suspend fun postCourseScrap(requestPostCourseScrap: RequestPostCourseScrap): Result + suspend fun postCourseScrap(requestPostCourseScrap: RequestPostCourseScrap): Result } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/domain/repository/UserRepository.kt b/app/src/main/java/com/runnect/runnect/domain/repository/UserRepository.kt index 7f8f4db02..bb2713628 100644 --- a/app/src/main/java/com/runnect/runnect/domain/repository/UserRepository.kt +++ b/app/src/main/java/com/runnect/runnect/domain/repository/UserRepository.kt @@ -1,12 +1,18 @@ package com.runnect.runnect.domain.repository import com.runnect.runnect.data.dto.HistoryInfoDTO +import com.runnect.runnect.domain.entity.UserProfile import com.runnect.runnect.data.dto.UserUploadCourseDTO import com.runnect.runnect.data.dto.request.RequestDeleteHistory import com.runnect.runnect.data.dto.request.RequestDeleteUploadCourse import com.runnect.runnect.data.dto.request.RequestPatchHistoryTitle import com.runnect.runnect.data.dto.request.RequestPatchNickName -import com.runnect.runnect.data.dto.response.* +import com.runnect.runnect.data.dto.response.ResponseDeleteHistory +import com.runnect.runnect.data.dto.response.ResponseDeleteUploadCourse +import com.runnect.runnect.data.dto.response.ResponseDeleteUser +import com.runnect.runnect.data.dto.response.ResponseGetUser +import com.runnect.runnect.data.dto.response.ResponsePatchHistoryTitle +import com.runnect.runnect.data.dto.response.ResponsePatchUserNickName interface UserRepository { suspend fun getUserInfo(): ResponseGetUser @@ -19,6 +25,8 @@ interface UserRepository { suspend fun getUserUploadCourse(): MutableList + suspend fun getUserProfile(userId: Int): Result + suspend fun putDeleteUploadCourse( requestDeleteUploadCourse: RequestDeleteUploadCourse ): Result diff --git a/app/src/main/java/com/runnect/runnect/presentation/coursemain/CourseMainFragment.kt b/app/src/main/java/com/runnect/runnect/presentation/coursemain/CourseMainFragment.kt index e87a35111..12b398503 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/coursemain/CourseMainFragment.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/coursemain/CourseMainFragment.kt @@ -2,12 +2,12 @@ package com.runnect.runnect.presentation.coursemain import android.Manifest import android.content.Intent +import android.content.pm.PackageManager import android.os.Bundle import android.view.View +import androidx.core.content.ContextCompat import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationServices -import com.gun0912.tedpermission.PermissionListener -import com.gun0912.tedpermission.normal.TedPermission import com.naver.maps.geometry.LatLng import com.naver.maps.map.CameraAnimation import com.naver.maps.map.CameraUpdate @@ -21,6 +21,8 @@ import com.runnect.runnect.R import com.runnect.runnect.binding.BindingFragment import com.runnect.runnect.databinding.FragmentCourseMainBinding import com.runnect.runnect.presentation.search.SearchActivity +import com.runnect.runnect.util.extension.PermissionUtil +import com.runnect.runnect.util.extension.showToast class CourseMainFragment : @@ -35,18 +37,17 @@ class CourseMainFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) init() - getCurrentLocation() - drawCourseButton() + checkAndRequestLocationPermission() } private fun init() { fusedLocation = LocationServices.getFusedLocationProviderClient(requireActivity()) - requestPermission() + initView() + initCurrentLocationButtonClickListener() + initDrawCourseButtonClickListener() } private fun initView() { - - //MapFragment 추가 val fm = childFragmentManager val mapFragment = fm.findFragmentById(R.id.mapView) as MapFragment? ?: MapFragment.newInstance().also { @@ -57,13 +58,42 @@ class CourseMainFragment : } - private fun getCurrentLocation() { + private fun checkAndRequestLocationPermission() { + if (isLocationPermissionGranted()) { + if (!::naverMap.isInitialized) { + initView() + } + } else { + context?.let { + PermissionUtil.requestLocationPermission( + context = it, + onPermissionGranted = { updateCamera(currentLocation) }, + onPermissionDenied = { showPermissionDeniedToast() }, + permissionType = PermissionUtil.PermissionType.LOCATION + ) + } + } + } + + + private fun initCurrentLocationButtonClickListener() { binding.btnCurrentLocation.setOnClickListener { - cameraUpdate(currentLocation) + if (isLocationPermissionGranted()) { + updateCamera(currentLocation) + } else { + context?.let { + PermissionUtil.requestLocationPermission( + context = it, + onPermissionGranted = { updateCamera(currentLocation) }, + onPermissionDenied = { showPermissionDeniedToast() }, + permissionType = PermissionUtil.PermissionType.LOCATION + ) + } + } } } - private fun drawCourseButton() { + private fun initDrawCourseButtonClickListener() { binding.btnDraw.setOnClickListener { val intent = Intent(activity, SearchActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) @@ -77,8 +107,10 @@ class CourseMainFragment : naverMap.minZoom = 10.0 map.locationSource = locationSource - map.locationTrackingMode = LocationTrackingMode.Follow //위치추적 모드 Follow + if (isLocationPermissionGranted()) { + map.locationTrackingMode = LocationTrackingMode.Follow //위치추적 모드 Follow + } //네이버 맵 sdk에 위치 정보 제공 locationSource = FusedLocationSource( @@ -103,40 +135,27 @@ class CourseMainFragment : locationOverlay.icon = OverlayImage.fromResource(R.drawable.current_location) } - private fun requestPermission() { - TedPermission.create() - .setPermissionListener(object : PermissionListener { - override fun onPermissionGranted() { //요청 승인 시 - initView() //지도 뷰 표시 - } - - override fun onPermissionDenied(deniedPermissions: MutableList?) { //요청 거부 시 - naverMap.locationTrackingMode = LocationTrackingMode.None - onDestroy() //앱 종료 - } - }) - .setRationaleTitle(PERMISSION_TITLE) - .setRationaleMessage(PERMISSION_CONTENT) - .setDeniedMessage(PERMISSION_GUIDE) - .setPermissions( - Manifest.permission.POST_NOTIFICATIONS, // 러닝 시 notification icon 띄우기 - Manifest.permission.ACCESS_COARSE_LOCATION, + private fun isLocationPermissionGranted(): Boolean { + return context?.let { + ContextCompat.checkSelfPermission( + it, Manifest.permission.ACCESS_FINE_LOCATION ) - .check() + } == PackageManager.PERMISSION_GRANTED } - private fun cameraUpdate(location: LatLng) { + private fun updateCamera(location: LatLng) { val cameraUpdate = CameraUpdate.scrollTo(LatLng(location.latitude, location.longitude)) .animate(CameraAnimation.Easing) naverMap.moveCamera(cameraUpdate) } + private fun showPermissionDeniedToast() { + showToast(getString(R.string.location_permission_denied)) + } + companion object { private const val LOCATION_PERMISSION_REQUEST_CODE = 1000 - const val PERMISSION_TITLE = "위치권한 요청" - const val PERMISSION_CONTENT = "코스의 출발지 설정과 러닝 트래킹을 위해 현재 위치 정보를 사용하도록 허용합니다." - const val PERMISSION_GUIDE = "권한을 허용해주세요. [설정] > [앱 및 알림] > [고급] > [앱 권한]" } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt index e94112524..59c90fde6 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt @@ -29,21 +29,26 @@ import com.runnect.runnect.data.dto.CourseData import com.runnect.runnect.databinding.ActivityCourseDetailBinding import com.runnect.runnect.domain.entity.CourseDetail import com.runnect.runnect.domain.entity.EditableCourseDetail -import com.runnect.runnect.presentation.discover.model.EditableDiscoverCourse import com.runnect.runnect.presentation.MainActivity import com.runnect.runnect.presentation.countdown.CountDownActivity -import com.runnect.runnect.presentation.detail.CourseDetailRootScreen.* +import com.runnect.runnect.presentation.detail.CourseDetailRootScreen.COURSE_DISCOVER +import com.runnect.runnect.presentation.detail.CourseDetailRootScreen.COURSE_DISCOVER_SEARCH +import com.runnect.runnect.presentation.detail.CourseDetailRootScreen.COURSE_STORAGE_SCRAP +import com.runnect.runnect.presentation.detail.CourseDetailRootScreen.MY_PAGE_UPLOAD_COURSE import com.runnect.runnect.presentation.discover.DiscoverFragment.Companion.EXTRA_EDITABLE_DISCOVER_COURSE +import com.runnect.runnect.presentation.discover.model.EditableDiscoverCourse import com.runnect.runnect.presentation.discover.search.DiscoverSearchActivity import com.runnect.runnect.presentation.login.LoginActivity import com.runnect.runnect.presentation.mypage.upload.MyUploadActivity +import com.runnect.runnect.presentation.profile.ProfileActivity import com.runnect.runnect.presentation.state.UiStateV2 import com.runnect.runnect.util.custom.dialog.CommonDialogFragment import com.runnect.runnect.util.custom.dialog.CommonDialogText -import com.runnect.runnect.util.custom.popup.PopupItem import com.runnect.runnect.util.custom.dialog.RequireLoginDialogFragment +import com.runnect.runnect.util.custom.popup.PopupItem import com.runnect.runnect.util.custom.popup.RunnectPopupMenu import com.runnect.runnect.util.custom.toast.RunnectToast +import com.runnect.runnect.util.extension.applyScreenEnterAnimation import com.runnect.runnect.util.extension.applyScreenExitAnimation import com.runnect.runnect.util.extension.getCompatibleSerializableExtra import com.runnect.runnect.util.extension.getStampResId @@ -51,7 +56,8 @@ import com.runnect.runnect.util.extension.hideKeyboard import com.runnect.runnect.util.extension.showSnackbar import com.runnect.runnect.util.extension.showToast import com.runnect.runnect.util.extension.showWebBrowser -import com.runnect.runnect.util.mode.ScreenMode.* +import com.runnect.runnect.util.mode.ScreenMode.EditMode +import com.runnect.runnect.util.mode.ScreenMode.ReadOnlyMode import dagger.hilt.android.AndroidEntryPoint import timber.log.Timber @@ -129,6 +135,15 @@ class CourseDetailActivity : applyScreenExitAnimation() } + private fun navigateToUserProfileWithBundle() { + Intent(this@CourseDetailActivity, ProfileActivity::class.java).apply { + putExtra(EXTRA_COURSE_USER_ID, courseDetail.userId) + addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) + startActivity(this) + } + applyScreenEnterAnimation() + } + private fun handleBackButtonByCurrentScreenMode() { when (viewModel.currentScreenMode) { is ReadOnlyMode -> navigateToPreviousScreen() @@ -136,6 +151,16 @@ class CourseDetailActivity : } } + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + intent?.let { newIntent -> + newIntent.getCompatibleSerializableExtra(EXTRA_ROOT_SCREEN) + ?.let { rootScreen = it } + publicCourseId = newIntent.getIntExtra(EXTRA_PUBLIC_COURSE_ID, 0) + getCourseDetail() + } + } + private fun navigateToPreviousScreen() { if (isFromDeepLink) { navigateToMainScreenWithBundle() @@ -178,6 +203,7 @@ class CourseDetailActivity : initScrapButtonClickListener() initStartRunButtonClickListener() initEditFinishButtonClickListener() + initUserInfoClickListener() initShareButtonClickListener() initShowMoreButtonClickListener() @@ -215,6 +241,12 @@ class CourseDetailActivity : } } + private fun initUserInfoClickListener() { + binding.constCourseDetailUserInfo.setOnClickListener { + navigateToUserProfileWithBundle() + } + } + // todo: 함수를 더 작게 분리하는 게 좋을 거 같아요! @우남 private fun sendKakaoLink(title: String, desc: String, image: String) { // 메시지 템플릿 만들기 (피드형) @@ -287,24 +319,33 @@ class CourseDetailActivity : return@setOnClickListener } - Intent( - this@CourseDetailActivity, - CountDownActivity::class.java - ).apply { - putExtra( - EXTRA_COURSE_DATA, CourseData( - courseId = courseDetail.courseId, - publicCourseId = courseDetail.id, - touchList = connectedSpots, - startLatLng = departureLatLng, - departure = courseDetail.departure, - distance = courseDetail.distance.toFloat(), - image = courseDetail.image, - dataFrom = "detail" - ) - ) - startActivity(this) + if (!::departureLatLng.isInitialized || connectedSpots.isEmpty()) { + showSnackbar(binding.root, getString(R.string.course_detail_list_empty_error_msg)) + return@setOnClickListener } + + navigateToCountDownActivity() + } + } + + private fun navigateToCountDownActivity() { + Intent( + this@CourseDetailActivity, + CountDownActivity::class.java + ).apply { + putExtra( + EXTRA_COURSE_DATA, CourseData( + courseId = courseDetail.courseId, + publicCourseId = courseDetail.id, + touchList = connectedSpots, + startLatLng = departureLatLng, + departure = courseDetail.departure, + distance = courseDetail.distance.toFloat(), + image = courseDetail.image, + dataFrom = "detail" + ) + ) + startActivity(this) } } @@ -334,7 +375,7 @@ class CourseDetailActivity : onNegativeButtonClicked = {}, onPositiveButtonClicked = { // 편집 모드 -> 뒤로가기 버튼 -> 편집 중단 확인 -> 뷰에 원래 제목으로 보여줌. - viewModel.restoreOriginalContents() + viewModel.restoreOriginalCourseDetail() enterReadMode() } ) @@ -396,7 +437,7 @@ class CourseDetailActivity : private fun enterEditMode() { viewModel.apply { updateCurrentScreenMode(EditMode) - saveCurrentContents() + saveCurrentCourseDetail() } updateLayoutForEditMode() } @@ -448,7 +489,12 @@ class CourseDetailActivity : courseDetail = state.data ?: return@observe binding.courseDetail = courseDetail - viewModel.updateCourseDetailContents(courseDetail.toCourseDetailContents()) + val editableCourseDetail = EditableCourseDetail( + title = courseDetail.title, + description = courseDetail.description + ) + viewModel.updateCourseDetailEditText(editableCourseDetail) + updateUserProfileStamp() updateUserLevel() updateScrapState() @@ -466,16 +512,10 @@ class CourseDetailActivity : } } - private fun T.toCourseDetailContents(): EditableCourseDetail? { - return when (this) { - is CourseDetail -> EditableCourseDetail(title, description) - is EditableCourseDetail -> EditableCourseDetail(title, description) - else -> null - } - } - private fun initDepartureLatLng() { - departureLatLng = LatLng(courseDetail.path[0][0], courseDetail.path[0][1]) + if (courseDetail.path.isNotEmpty()) { + departureLatLng = LatLng(courseDetail.path[0][0], courseDetail.path[0][1]) + } } private fun initConnectedSpots() { @@ -494,8 +534,8 @@ class CourseDetailActivity : binding.indeterminateBar.isVisible = false state.data?.let { response -> - viewModel.updateCourseDetailContents(response.toCourseDetailContents()) - updateTextView(response) + viewModel.updateCourseDetailEditText(response) + updateCourseDetailTextView(response) } enterReadMode() @@ -512,7 +552,7 @@ class CourseDetailActivity : } } - private fun updateTextView(courseDetail: EditableCourseDetail) { + private fun updateCourseDetailTextView(courseDetail: EditableCourseDetail) { binding.apply { tvCourseDetailTitle.text = courseDetail.title tvCourseDetailDesc.text = courseDetail.description @@ -600,6 +640,7 @@ class CourseDetailActivity : private const val EXTRA_COURSE_DATA = "CourseData" private const val EXTRA_FRAGMENT_REPLACEMENT_DIRECTION = "fragmentReplacementDirection" private const val EXTRA_FROM_COURSE_DETAIL = "fromCourseDetail" + private const val EXTRA_COURSE_USER_ID = "courseUserId" private const val POPUP_MENU_X_OFFSET = 17 private const val POPUP_MENU_Y_OFFSET = -10 diff --git a/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailViewModel.kt b/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailViewModel.kt index 2ff85fe22..895cd711b 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailViewModel.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailViewModel.kt @@ -5,14 +5,15 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.map import androidx.lifecycle.viewModelScope -import com.runnect.runnect.data.dto.request.RequestPostCourseScrap import com.runnect.runnect.data.dto.request.RequestDeleteUploadCourse import com.runnect.runnect.data.dto.request.RequestPatchPublicCourse +import com.runnect.runnect.data.dto.request.RequestPostCourseScrap import com.runnect.runnect.data.dto.response.ResponseDeleteUploadCourse -import com.runnect.runnect.domain.repository.CourseRepository -import com.runnect.runnect.domain.repository.UserRepository +import com.runnect.runnect.data.dto.response.ResponsePostScrap import com.runnect.runnect.domain.entity.CourseDetail import com.runnect.runnect.domain.entity.EditableCourseDetail +import com.runnect.runnect.domain.repository.CourseRepository +import com.runnect.runnect.domain.repository.UserRepository import com.runnect.runnect.presentation.state.UiStateV2 import com.runnect.runnect.util.mode.ScreenMode import dagger.hilt.android.lifecycle.HiltViewModel @@ -37,8 +38,8 @@ class CourseDetailViewModel @Inject constructor( val courseDeleteState: LiveData> get() = _courseDeleteState - private var _courseScrapState = MutableLiveData>() - val courseScrapState: LiveData> + private var _courseScrapState = MutableLiveData>() + val courseScrapState: LiveData> get() = _courseScrapState // 플래그 변수 @@ -58,21 +59,19 @@ class CourseDetailViewModel @Inject constructor( private var _currentScreenMode: ScreenMode = ScreenMode.ReadOnlyMode val currentScreenMode get() = _currentScreenMode - private var savedCourseDetailContents = EditableCourseDetail("", "") + private var savedCourseDetail = EditableCourseDetail("", "") - fun updateCourseDetailContents(courseDetail: EditableCourseDetail?) { - courseDetail?.let { - _title.value = courseDetail.title - _description.value = courseDetail.description - } + fun updateCourseDetailEditText(course: EditableCourseDetail) { + _title.value = course.title + _description.value = course.description } - fun saveCurrentContents() { - savedCourseDetailContents = EditableCourseDetail(title, description) + fun saveCurrentCourseDetail() { + savedCourseDetail = EditableCourseDetail(title, description) } - fun restoreOriginalContents() { - updateCourseDetailContents(savedCourseDetailContents) + fun restoreOriginalCourseDetail() { + updateCourseDetailEditText(savedCourseDetail) } fun updateCurrentScreenMode(mode: ScreenMode) { diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverFragment.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverFragment.kt index 1ca755405..a3a49ba67 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverFragment.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverFragment.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.Gravity import android.view.View import androidx.activity.OnBackPressedCallback import androidx.activity.result.contract.ActivityResultContracts @@ -22,7 +23,7 @@ import com.runnect.runnect.presentation.MainActivity.Companion.isVisitorMode import com.runnect.runnect.presentation.detail.CourseDetailActivity import com.runnect.runnect.presentation.detail.CourseDetailRootScreen import com.runnect.runnect.presentation.discover.adapter.BannerAdapter -import com.runnect.runnect.presentation.discover.adapter.DiscoverMultiViewAdapter +import com.runnect.runnect.presentation.discover.adapter.multiview.DiscoverMultiViewAdapter import com.runnect.runnect.presentation.discover.pick.DiscoverPickActivity import com.runnect.runnect.presentation.discover.search.DiscoverSearchActivity import com.runnect.runnect.presentation.state.UiStateV2 @@ -55,11 +56,10 @@ class DiscoverFragment : BindingFragment(R.layout.fragm private val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == Activity.RESULT_OK) { + // 상세페이지 갔다가 이전으로 돌아오면 아이템 변경사항이 바로 반영되도록 (제목, 스크랩) val updatedCourse: EditableDiscoverCourse = result.data?.getCompatibleParcelableExtra(EXTRA_EDITABLE_DISCOVER_COURSE) ?: return@registerForActivityResult - - // todo: 상세페이지 갔다가 이전으로 돌아오면 제목, 스크랩 변경사항이 바로 표시되도록 multiViewAdapter.updateCourseItem( publicCourseId = viewModel.clickedCourseId, updatedCourse = updatedCourse @@ -145,12 +145,28 @@ class DiscoverFragment : BindingFragment(R.layout.fragm binding.rvDiscoverMultiView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) + val isScrollDown = dy > 0 if (isScrollDown) showCircleUploadButton() + + checkNextPageLoadingCondition(recyclerView) } }) } + private fun checkNextPageLoadingCondition(recyclerView: RecyclerView) { + if (!recyclerView.canScrollVertically(SCROLL_DIRECTION)) { + Timber.d("스크롤이 끝에 도달했어요!") + + if (viewModel.isNextPageLoading()) { + Timber.d("다음 페이지 로딩 중입니다.") + return + } + + viewModel.getRecommendCourseNextPage() + } + } + private fun showCircleUploadButton() { binding.fabDiscoverUploadText.isVisible = false binding.fabDiscoverUpload.isVisible = true @@ -215,6 +231,7 @@ class DiscoverFragment : BindingFragment(R.layout.fragm setupBannerGetStateObserver() setupMarathonCourseGetStateObserver() setupRecommendCourseGetStateObserver() + setupRecommendCourseNextPageStateObserver() setupCourseScrapStateObserver() } @@ -227,7 +244,11 @@ class DiscoverFragment : BindingFragment(R.layout.fragm } is UiStateV2.Failure -> { - context?.showSnackbar(binding.root, state.msg) + context?.showSnackbar( + anchorView = binding.root, + message = state.msg, + gravity = Gravity.TOP + ) } else -> {} @@ -277,7 +298,11 @@ class DiscoverFragment : BindingFragment(R.layout.fragm is UiStateV2.Failure -> { dismissLoadingProgressBar() - context?.showSnackbar(binding.root, state.msg) + context?.showSnackbar( + anchorView = binding.root, + message = state.msg, + gravity = Gravity.TOP + ) } else -> {} @@ -300,7 +325,11 @@ class DiscoverFragment : BindingFragment(R.layout.fragm is UiStateV2.Failure -> { dismissLoadingProgressBar() - context?.showSnackbar(binding.root, state.msg) + context?.showSnackbar( + anchorView = binding.root, + message = state.msg, + gravity = Gravity.TOP + ) } else -> {} @@ -343,11 +372,11 @@ class DiscoverFragment : BindingFragment(R.layout.fragm private fun navigateToDetailScreen(publicCourseId: Int) { val context = context ?: return - val intent = Intent(context, CourseDetailActivity::class.java).apply { + Intent(context, CourseDetailActivity::class.java).apply { putExtra(EXTRA_PUBLIC_COURSE_ID, publicCourseId) putExtra(EXTRA_ROOT_SCREEN, CourseDetailRootScreen.COURSE_DISCOVER) + resultLauncher.launch(this) } - resultLauncher.launch(intent) activity?.applyScreenEnterAnimation() } @@ -358,10 +387,35 @@ class DiscoverFragment : BindingFragment(R.layout.fragm ).show() } + private fun setupRecommendCourseNextPageStateObserver() { + viewModel.nextPageState.observe(viewLifecycleOwner) { state -> + when (state) { + is UiStateV2.Success -> { + val nextPageCourses = state.data + multiViewAdapter.addRecommendCourseNextPage(nextPageCourses) + } + + is UiStateV2.Failure -> { + context?.showSnackbar( + anchorView = binding.root, + message = state.msg, + gravity = Gravity.TOP + ) + } + + else -> {} + } + } + } + private fun setupCourseScrapStateObserver() { viewModel.courseScrapState.observe(viewLifecycleOwner) { state -> if (state is UiStateV2.Failure) { - context?.showSnackbar(binding.root, state.msg) + context?.showSnackbar( + anchorView = binding.root, + message = state.msg, + gravity = Gravity.TOP + ) } } } @@ -414,6 +468,7 @@ class DiscoverFragment : BindingFragment(R.layout.fragm companion object { private const val BANNER_SCROLL_DELAY_TIME = 5000L private const val CENTER_POS_OF_INFINITE_BANNERS = Int.MAX_VALUE / 2 + private const val SCROLL_DIRECTION = 1 private const val EXTRA_PUBLIC_COURSE_ID = "publicCourseId" private const val EXTRA_ROOT_SCREEN = "rootScreen" const val EXTRA_EDITABLE_DISCOVER_COURSE = "editable_discover_course" diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt index 2af1dda79..e6c5e129f 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt @@ -5,11 +5,13 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.runnect.runnect.data.dto.request.RequestPostCourseScrap +import com.runnect.runnect.data.dto.response.ResponsePostScrap import com.runnect.runnect.domain.entity.DiscoverMultiViewItem import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.* import com.runnect.runnect.domain.entity.DiscoverBanner import com.runnect.runnect.domain.repository.BannerRepository import com.runnect.runnect.domain.repository.CourseRepository +import com.runnect.runnect.presentation.discover.adapter.multiview.DiscoverMultiViewType import com.runnect.runnect.presentation.state.UiStateV2 import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.catch @@ -30,40 +32,40 @@ class DiscoverViewModel @Inject constructor( val marathonCourseState: LiveData?>> get() = _marathonCourseState - private val _recommendCourseState = MutableLiveData?>>() - val recommendCourseState: LiveData?>> + private val _recommendCourseState = MutableLiveData>>() + val recommendCourseState: LiveData>> get() = _recommendCourseState - private val _courseScrapState = MutableLiveData>() - val courseScrapState: LiveData> + private val _nextPageState = MutableLiveData>>() + val nextPageState: LiveData>> + get() = _nextPageState + + private val _courseScrapState = MutableLiveData>() + val courseScrapState: LiveData> get() = _courseScrapState - private val _multiViewItems: ArrayList> = arrayListOf() + private val _multiViewItems: MutableList> = mutableListOf() val multiViewItems: List> get() = _multiViewItems - private var _currentPageNumber = 1 - val currentPageNumber get() = _currentPageNumber - private var _clickedCourseId = -1 val clickedCourseId get() = _clickedCourseId + private var isRecommendCoursePageEnd = false + private var currentPageNo = 1 + init { getDiscoverBanners() getMarathonCourse() - getRecommendCourse(pageNo = currentPageNumber, ordering = "date") + getRecommendCourse(pageNo = 1, ordering = "date") } fun saveClickedCourseId(id: Int) { _clickedCourseId = id } - fun updateCurrentPageNumber(number: Int) { - _currentPageNumber = number - } - fun resetMultiViewItems() { _multiViewItems.clear() - _currentPageNumber = 1 + currentPageNo = 1 } fun refreshCurrentCourses() { @@ -91,16 +93,11 @@ class DiscoverViewModel @Inject constructor( courseRepository.getMarathonCourse() .onSuccess { courses -> - if (courses == null) { - _marathonCourseState.value = - UiStateV2.Failure("MARATHON COURSE DATA IS NULL") - Timber.e("MARATHON COURSE DATA IS NULL") - return@launch + courses?.let { + _multiViewItems.add(it) + _marathonCourseState.value = UiStateV2.Success(it) + Timber.d("MARATHON COURSE GET SUCCESS") } - - _multiViewItems.add(courses) - _marathonCourseState.value = UiStateV2.Success(courses) - Timber.e("MARATHON COURSE GET SUCCESS") } .onFailure { exception -> _marathonCourseState.value = UiStateV2.Failure(exception.message.toString()) @@ -114,18 +111,18 @@ class DiscoverViewModel @Inject constructor( _recommendCourseState.value = UiStateV2.Loading courseRepository.getRecommendCourse(pageNo = pageNo.toString(), ordering = ordering) - .onSuccess { courses -> - if (courses == null) { + .onSuccess { pagingData -> + if (pagingData == null) { _recommendCourseState.value = UiStateV2.Failure("RECOMMEND COURSE DATA IS NULL") - Timber.e("RECOMMEND COURSE DATA IS NULL") - return@launch + return@onSuccess } - _multiViewItems.add(courses) - _recommendCourseState.value = UiStateV2.Success(courses) - Timber.e("RECOMMEND COURSE GET SUCCESS") - Timber.e("ITEM SIZE: ${multiViewItems.size}") + isRecommendCoursePageEnd = pagingData.isEnd + _multiViewItems.add(pagingData.recommendCourses) + _recommendCourseState.value = UiStateV2.Success(pagingData.recommendCourses) + Timber.d("RECOMMEND COURSE GET SUCCESS") + Timber.d("ITEM SIZE: ${multiViewItems.size}") }.onFailure { exception -> _recommendCourseState.value = UiStateV2.Failure(exception.message.toString()) Timber.e("RECOMMEND COURSE GET FAIL") @@ -133,10 +130,43 @@ class DiscoverViewModel @Inject constructor( } } + fun isNextPageLoading() = nextPageState.value is UiStateV2.Loading + + fun getRecommendCourseNextPage() { + viewModelScope.launch { + if (isRecommendCoursePageEnd) return@launch + + Timber.d("다음 페이지를 요청했어요!") + _nextPageState.value = UiStateV2.Loading + currentPageNo++ + + courseRepository.getRecommendCourse( + pageNo = currentPageNo.toString(), + ordering = "date" + ) + .onSuccess { pagingData -> + if (pagingData == null) { + _nextPageState.value = + UiStateV2.Failure("RECOMMEND COURSE NEXT PAGE DATA IS NULL") + return@onSuccess + } + + isRecommendCoursePageEnd = pagingData.isEnd + _nextPageState.value = UiStateV2.Success(pagingData.recommendCourses) + Timber.d("RECOMMEND COURSE NEXT PAGE GET SUCCESS") + } + .onFailure { exception -> + _nextPageState.value = UiStateV2.Failure(exception.message.toString()) + Timber.e("RECOMMEND COURSE NEXT PAGE GET FAIL") + } + } + } + + // todo: 동기 처리 로직 수정 필요 (간헐적으로 무한 로딩 상태에 빠짐) fun checkCourseLoadState(): Boolean { return marathonCourseState.value is UiStateV2.Success && recommendCourseState.value is UiStateV2.Success && - multiViewItems.size >= MULTI_VIEW_TYPE_SIZE + multiViewItems.size >= DiscoverMultiViewType.values().size } fun postCourseScrap(id: Int, scrapTF: Boolean) { @@ -154,8 +184,4 @@ class DiscoverViewModel @Inject constructor( } } } - - companion object { - private const val MULTI_VIEW_TYPE_SIZE = 2 - } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverMultiViewAdapter.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverMultiViewAdapter.kt deleted file mode 100644 index c85b7a8f4..000000000 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverMultiViewAdapter.kt +++ /dev/null @@ -1,136 +0,0 @@ -package com.runnect.runnect.presentation.discover.adapter - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.runnect.runnect.databinding.ItemDiscoverMultiviewMarathonBinding -import com.runnect.runnect.databinding.ItemDiscoverMultiviewRecommendBinding -import com.runnect.runnect.domain.entity.DiscoverMultiViewItem -import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.MarathonCourse -import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.RecommendCourse -import com.runnect.runnect.presentation.discover.model.EditableDiscoverCourse -import timber.log.Timber - -class DiscoverMultiViewAdapter( - private val multiViewItems: List>, - private val onHeartButtonClick: (Int, Boolean) -> Unit, - private val onCourseItemClick: (Int) -> Unit, - private val handleVisitorMode: () -> Unit, -) : RecyclerView.Adapter() { - enum class MultiViewType { - MARATHON, - RECOMMEND - } - - override fun getItemViewType(position: Int): Int { - return when (multiViewItems[position].first()) { - is MarathonCourse -> MultiViewType.MARATHON.ordinal - is RecommendCourse -> MultiViewType.RECOMMEND.ordinal - } - } - - override fun getItemCount(): Int = multiViewItems.size - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DiscoverMultiViewHolder { - return when (viewType) { - MultiViewType.MARATHON.ordinal -> { - DiscoverMultiViewHolder.MarathonCourseViewHolder( - binding = ItemDiscoverMultiviewMarathonBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ), - onHeartButtonClick = onHeartButtonClick, - onCourseItemClick = onCourseItemClick, - handleVisitorMode = handleVisitorMode - ) - } - - else -> { - DiscoverMultiViewHolder.RecommendCourseViewHolder( - binding = ItemDiscoverMultiviewRecommendBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ), - onHeartButtonClick = onHeartButtonClick, - onCourseItemClick = onCourseItemClick, - handleVisitorMode = handleVisitorMode - ) - } - } - } - - override fun onBindViewHolder(holder: DiscoverMultiViewHolder, position: Int) { - when (holder) { - is DiscoverMultiViewHolder.MarathonCourseViewHolder -> { - (multiViewItems[position] as? List)?.let { - holder.bind(it) - } - } - - is DiscoverMultiViewHolder.RecommendCourseViewHolder -> { - (multiViewItems[position] as? List)?.let { - holder.bind(it) - } - } - } - } - - fun updateCourseItem( - publicCourseId: Int, - updatedCourse: EditableDiscoverCourse - ) { - for (courses in multiViewItems) { - for (course in courses) { - // todo: 해당하는 코스 id 발견하여 아이템 갱신하고 나면, 바로 함수 종료시키기 - if (findCourseItemByViewType(publicCourseId, course, updatedCourse)) { - Timber.e("SUCCESS COURSE ITEM UPDATE: ${publicCourseId}") - return - } - } - } - } - - private fun findCourseItemByViewType( - publicCourseId: Int, - course: DiscoverMultiViewItem, - updatedCourse: EditableDiscoverCourse - ): Boolean { - when (course) { - is MarathonCourse -> { - if (course.id == publicCourseId) { - updateMarathonCourseItem(course, updatedCourse) - return true - } - } - - is RecommendCourse -> { - if (course.id == publicCourseId) { - updateRecommendCourseItem(course, updatedCourse) - return true - } - } - } - - return false - } - - private fun updateMarathonCourseItem( - course: MarathonCourse, - updatedCourse: EditableDiscoverCourse - ) { - course.title = updatedCourse.title - course.scrap = updatedCourse.scrap - notifyItemChanged(MultiViewType.MARATHON.ordinal) - } - - private fun updateRecommendCourseItem( - course: RecommendCourse, - updatedCourse: EditableDiscoverCourse - ) { - course.title = updatedCourse.title - course.scrap = updatedCourse.scrap - notifyItemChanged(MultiViewType.RECOMMEND.ordinal) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverMultiViewHolder.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverMultiViewHolder.kt deleted file mode 100644 index 25a51387c..000000000 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverMultiViewHolder.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.runnect.runnect.presentation.discover.adapter - -import androidx.databinding.ViewDataBinding -import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.runnect.runnect.databinding.ItemDiscoverMultiviewMarathonBinding -import com.runnect.runnect.databinding.ItemDiscoverMultiviewRecommendBinding -import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.MarathonCourse -import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.RecommendCourse -import com.runnect.runnect.util.custom.deco.DiscoverMarathonItemDecoration -import com.runnect.runnect.util.custom.deco.GridSpacingItemDecoration -import timber.log.Timber - -sealed class DiscoverMultiViewHolder(binding: ViewDataBinding) : - RecyclerView.ViewHolder(binding.root) { - class MarathonCourseViewHolder( - private val binding: ItemDiscoverMultiviewMarathonBinding, - private val onHeartButtonClick: (Int, Boolean) -> Unit, - private val onCourseItemClick: (Int) -> Unit, - private val handleVisitorMode: () -> Unit - ) : DiscoverMultiViewHolder(binding) { - fun bind(marathonCourses: List) { - binding.rvDiscoverMarathon.apply { - setHasFixedSize(true) - adapter = DiscoverMarathonAdapter( - onHeartButtonClick, - onCourseItemClick, - handleVisitorMode - ).apply { - submitList(marathonCourses) - } - addItemDecoration( - DiscoverMarathonItemDecoration( - context = context, - spaceSize = 10, - itemCount = marathonCourses.size - ) - ) - } - } - } - - class RecommendCourseViewHolder( - private val binding: ItemDiscoverMultiviewRecommendBinding, - private val onHeartButtonClick: (Int, Boolean) -> Unit, - private val onCourseItemClick: (Int) -> Unit, - private val handleVisitorMode: () -> Unit, - ) : DiscoverMultiViewHolder(binding) { - fun bind(recommendCourses: List) { - binding.rvDiscoverRecommend.apply { - setHasFixedSize(true) - layoutManager = GridLayoutManager(context, 2) - - adapter = DiscoverRecommendAdapter( - onHeartButtonClick, - onCourseItemClick, - handleVisitorMode - ).apply { - submitList(recommendCourses) - } - - addItemDecoration( - GridSpacingItemDecoration( - context = context, - spanCount = 2, - horizontalSpaceSize = 6, - topSpaceSize = 20 - ) - ) - } - } - - private fun initScrollListener(currentPageNumber: Int, recyclerView: RecyclerView) { - recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - super.onScrolled(recyclerView, dx, dy) - // TODO: 스크롤이 최하단까지 내려간 경우, 다음 페이지 요청하기 (다음 페이지가 있는 경우에만) - } - }) - } - } -} diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewAdapter.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewAdapter.kt new file mode 100644 index 000000000..43326e011 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewAdapter.kt @@ -0,0 +1,90 @@ +package com.runnect.runnect.presentation.discover.adapter.multiview + +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.runnect.runnect.domain.entity.DiscoverMultiViewItem +import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.MarathonCourse +import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.RecommendCourse +import com.runnect.runnect.presentation.discover.model.EditableDiscoverCourse + +class DiscoverMultiViewAdapter( + multiViewItems: List>, + private val onHeartButtonClick: (Int, Boolean) -> Unit, + private val onCourseItemClick: (Int) -> Unit, + private val handleVisitorMode: () -> Unit, +) : RecyclerView.Adapter() { + private val multiViewHolderFactory by lazy { DiscoverMultiViewHolderFactory() } + private val currentList: List> = + multiViewItems.map { it.toMutableList() } + + override fun getItemViewType(position: Int): Int { + return when (currentList[position].first()) { + is MarathonCourse -> DiscoverMultiViewType.MARATHON.ordinal + is RecommendCourse -> DiscoverMultiViewType.RECOMMEND.ordinal + } + } + + override fun getItemCount(): Int = currentList.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DiscoverMultiViewHolder { + return multiViewHolderFactory.createMultiViewHolder( + parent = parent, + viewType = DiscoverMultiViewType.values()[viewType], + onHeartButtonClick = onHeartButtonClick, + onCourseItemClick = onCourseItemClick, + handleVisitorMode = handleVisitorMode + ) + } + + override fun onBindViewHolder(holder: DiscoverMultiViewHolder, position: Int) { + when (holder) { + is DiscoverMultiViewHolder.MarathonCourseViewHolder -> { + (currentList[position] as? List)?.let { + holder.bind(it) + } + } + + is DiscoverMultiViewHolder.RecommendCourseViewHolder -> { + (currentList[position] as? List)?.let { + holder.bind(it) + } + } + } + } + + fun addRecommendCourseNextPage(nextPageCourses: List) { + // 외부 리사이클러뷰의 추천 코스 리스트 갱신 -> 내부 리사이클러뷰 재바인딩 -> 새로운 데이터 submitList + val position = DiscoverMultiViewType.RECOMMEND.ordinal + currentList[position].addAll(nextPageCourses) + notifyItemChanged(position) + } + + fun updateCourseItem( + publicCourseId: Int, + updatedCourse: EditableDiscoverCourse + ) { + val targetItem = currentList.flatten().find { item -> + item.id == publicCourseId + } ?: return + + when (targetItem) { + is MarathonCourse -> { + val position = DiscoverMultiViewType.MARATHON.ordinal + val targetIndex = currentList[position].indexOf(targetItem) + multiViewHolderFactory.marathonViewHolder.updateMarathonCourseItem( + targetIndex = targetIndex, + updatedCourse = updatedCourse + ) + } + + is RecommendCourse -> { + val position = DiscoverMultiViewType.RECOMMEND.ordinal + val targetIndex = currentList[position].indexOf(targetItem) + multiViewHolderFactory.recommendViewHolder.updateRecommendCourseItem( + targetIndex = targetIndex, + updatedCourse = updatedCourse + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolder.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolder.kt new file mode 100644 index 000000000..c390ee67b --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolder.kt @@ -0,0 +1,121 @@ +package com.runnect.runnect.presentation.discover.adapter.multiview + +import androidx.databinding.ViewDataBinding +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.runnect.runnect.databinding.ItemDiscoverMultiviewMarathonBinding +import com.runnect.runnect.databinding.ItemDiscoverMultiviewRecommendBinding +import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.MarathonCourse +import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.RecommendCourse +import com.runnect.runnect.presentation.discover.adapter.DiscoverMarathonAdapter +import com.runnect.runnect.presentation.discover.adapter.DiscoverRecommendAdapter +import com.runnect.runnect.presentation.discover.model.EditableDiscoverCourse +import com.runnect.runnect.util.custom.deco.DiscoverMarathonItemDecoration +import com.runnect.runnect.util.custom.deco.DiscoverRecommendItemDecoration +import com.runnect.runnect.util.custom.deco.GridSpacingItemDecoration +import timber.log.Timber + +sealed class DiscoverMultiViewHolder(binding: ViewDataBinding) : + RecyclerView.ViewHolder(binding.root) { + + class MarathonCourseViewHolder( + private val binding: ItemDiscoverMultiviewMarathonBinding, + onHeartButtonClick: (Int, Boolean) -> Unit, + onCourseItemClick: (Int) -> Unit, + handleVisitorMode: () -> Unit + ) : DiscoverMultiViewHolder(binding) { + private val marathonAdapter by lazy { + DiscoverMarathonAdapter( + onHeartButtonClick, onCourseItemClick, handleVisitorMode + ) + } + + fun bind(courses: List) { + initMarathonRecyclerView(courses) + } + + private fun initMarathonRecyclerView(courses: List) { + binding.rvDiscoverMarathon.apply { + setHasFixedSize(true) + adapter = marathonAdapter.apply { + submitList(courses) + } + addItemDecoration( + DiscoverMarathonItemDecoration( + context = context, + spaceSize = 10, + itemCount = courses.size + ) + ) + } + } + + fun updateMarathonCourseItem( + targetIndex: Int, + updatedCourse: EditableDiscoverCourse + ) { + marathonAdapter.currentList[targetIndex].apply { + title = updatedCourse.title + scrap = updatedCourse.scrap + } + marathonAdapter.notifyItemChanged(targetIndex) + } + } + + class RecommendCourseViewHolder( + private val binding: ItemDiscoverMultiviewRecommendBinding, + onHeartButtonClick: (Int, Boolean) -> Unit, + onCourseItemClick: (Int) -> Unit, + handleVisitorMode: () -> Unit, + ) : DiscoverMultiViewHolder(binding) { + private val recommendAdapter by lazy { + DiscoverRecommendAdapter( + onHeartButtonClick, + onCourseItemClick, + handleVisitorMode + ) + } + + fun bind(courses: List) { + Timber.d("추천 코스 리스트 크기: ${courses.size}") + initRecommendRecyclerView(courses) + } + + private fun initRecommendRecyclerView(courses: List) { + binding.rvDiscoverRecommend.apply { + setHasFixedSize(true) + layoutManager = GridLayoutManager(context, 2) + adapter = recommendAdapter.apply { + submitList(courses) + } + addItemDecorationOnlyOnce(this) + } + } + + private fun addItemDecorationOnlyOnce(recyclerView: RecyclerView) { + with(recyclerView) { + if (itemDecorationCount > 0) { + removeItemDecorationAt(0) + } + addItemDecoration( + DiscoverRecommendItemDecoration( + context = context, + rightSpacing = 6, + bottomSpacing = 20 + ) + ) + } + } + + fun updateRecommendCourseItem( + targetIndex: Int, + updatedCourse: EditableDiscoverCourse + ) { + recommendAdapter.currentList[targetIndex].apply { + title = updatedCourse.title + scrap = updatedCourse.scrap + } + recommendAdapter.notifyItemChanged(targetIndex) + } + } +} diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolderFactory.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolderFactory.kt new file mode 100644 index 000000000..fac9d33b6 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolderFactory.kt @@ -0,0 +1,40 @@ +package com.runnect.runnect.presentation.discover.adapter.multiview + +import android.view.ViewGroup +import com.runnect.runnect.R +import com.runnect.runnect.util.extension.getViewDataBinding + +class DiscoverMultiViewHolderFactory { + lateinit var marathonViewHolder: DiscoverMultiViewHolder.MarathonCourseViewHolder + lateinit var recommendViewHolder: DiscoverMultiViewHolder.RecommendCourseViewHolder + + fun createMultiViewHolder( + parent: ViewGroup, + viewType: DiscoverMultiViewType, + onHeartButtonClick: (Int, Boolean) -> Unit, + onCourseItemClick: (Int) -> Unit, + handleVisitorMode: () -> Unit, + ): DiscoverMultiViewHolder { + when (viewType) { + DiscoverMultiViewType.MARATHON -> { + marathonViewHolder = DiscoverMultiViewHolder.MarathonCourseViewHolder( + binding = parent.getViewDataBinding(layoutRes = R.layout.item_discover_multiview_marathon), + onHeartButtonClick = onHeartButtonClick, + onCourseItemClick = onCourseItemClick, + handleVisitorMode = handleVisitorMode + ) + return marathonViewHolder + } + + DiscoverMultiViewType.RECOMMEND -> { + recommendViewHolder = DiscoverMultiViewHolder.RecommendCourseViewHolder( + binding = parent.getViewDataBinding(layoutRes = R.layout.item_discover_multiview_recommend), + onHeartButtonClick = onHeartButtonClick, + onCourseItemClick = onCourseItemClick, + handleVisitorMode = handleVisitorMode + ) + return recommendViewHolder + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewType.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewType.kt new file mode 100644 index 000000000..75a517a51 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewType.kt @@ -0,0 +1,6 @@ +package com.runnect.runnect.presentation.discover.adapter.multiview + +enum class DiscoverMultiViewType { + MARATHON, + RECOMMEND +} diff --git a/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt index 5af92a7c6..64742a410 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt @@ -20,7 +20,6 @@ import androidx.lifecycle.lifecycleScope import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationServices import com.google.android.material.bottomsheet.BottomSheetDialog -import com.google.android.material.internal.ViewUtils.hideKeyboard import com.naver.maps.geometry.LatLng import com.naver.maps.geometry.LatLngBounds import com.naver.maps.map.CameraAnimation @@ -47,8 +46,10 @@ import com.runnect.runnect.presentation.countdown.CountDownActivity import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.util.DepartureSetMode import com.runnect.runnect.util.custom.dialog.RequireLoginDialogFragment +import com.runnect.runnect.util.extension.PermissionUtil import com.runnect.runnect.util.extension.hideKeyboard import com.runnect.runnect.util.extension.setActivityDialog +import com.runnect.runnect.util.extension.showToast import com.runnect.runnect.util.multipart.ContentUriRequestBody import dagger.hilt.android.AndroidEntryPoint import kotlinx.android.synthetic.main.custom_dialog_make_course.view.btn_run @@ -186,7 +187,6 @@ class DrawActivity : BindingActivity(R.layout.activity_draw private fun initCustomLocationMode() { isCustomLocationMode = true - with(binding) { customDepartureMarker.isVisible = true customDepartureInfoWindow.isVisible = true @@ -197,7 +197,9 @@ class DrawActivity : BindingActivity(R.layout.activity_draw showDrawGuide() hideDeparture() showDrawCourse() - drawCourse(departureLatLng = getCenterPosition()) + getCenterPosition().apply { + departureLatLng = this + }.let(::drawCourse) hideFloatedDeparture() } } @@ -308,14 +310,27 @@ class DrawActivity : BindingActivity(R.layout.activity_draw bottomSheetDialog.setContentView(bottomSheetView) btnCreateCourse.setOnClickListener { - hideKeyboard(etCourseName) - bottomSheetDialog.dismiss() - createMBR() + this.let { + PermissionUtil.requestLocationPermission( + context = it, + onPermissionGranted = { + hideKeyboard(etCourseName) + bottomSheetDialog.dismiss() + createMBR() + }, + onPermissionDenied = { showPermissionDeniedToast() }, + permissionType = PermissionUtil.PermissionType.LOCATION + ) + } } return bottomSheetDialog } + private fun showPermissionDeniedToast() { + showToast(getString(R.string.location_permission_denied)) + } + private fun activateDrawCourse() { binding.btnPreStart.setOnClickListener { isMarkerAvailable = true diff --git a/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt index ca0541de4..1234ae32a 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt @@ -122,8 +122,8 @@ class LoginActivity : BindingActivity(com.runnect.runnect. key = TOKEN_KEY_REFRESH, value = viewModel.loginResult.value?.refreshToken ) - Timber.e("ACCESS TOKEN: ${viewModel.loginResult.value?.accessToken}") - Timber.e("REFRESH TOKEN: ${viewModel.loginResult.value?.refreshToken}") + Timber.d("ACCESS TOKEN: ${viewModel.loginResult.value?.accessToken}") + Timber.d("REFRESH TOKEN: ${viewModel.loginResult.value?.refreshToken}") } private fun moveToMain() { diff --git a/app/src/main/java/com/runnect/runnect/presentation/mydrawdetail/MyDrawDetailActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/mydrawdetail/MyDrawDetailActivity.kt index 24c2eac70..16344e6d2 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/mydrawdetail/MyDrawDetailActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/mydrawdetail/MyDrawDetailActivity.kt @@ -15,8 +15,10 @@ import com.runnect.runnect.data.dto.response.ResponseGetMyDrawDetail import com.runnect.runnect.databinding.ActivityMyDrawDetailBinding import com.runnect.runnect.presentation.MainActivity import com.runnect.runnect.presentation.countdown.CountDownActivity +import com.runnect.runnect.util.extension.PermissionUtil import com.runnect.runnect.util.extension.navigateToPreviousScreenWithAnimation import com.runnect.runnect.util.extension.setActivityDialog +import com.runnect.runnect.util.extension.showToast import dagger.hilt.android.AndroidEntryPoint import kotlinx.android.synthetic.main.custom_dialog_delete.view.btn_delete_no import kotlinx.android.synthetic.main.custom_dialog_delete.view.btn_delete_yes @@ -40,7 +42,7 @@ class MyDrawDetailActivity : getMyDrawDetail() backButton() addObserver() - toCountDownButton() + initDrawButtonClickListener() deleteButton() } @@ -88,14 +90,30 @@ class MyDrawDetailActivity : viewModel.getMyDrawDetail(courseId = courseId) } - fun toCountDownButton() { + + private fun initDrawButtonClickListener() { binding.btnMyDrawDetailRun.setOnClickListener { - startActivity(Intent(this, CountDownActivity::class.java).apply { - putExtra(EXTRA_COURSE_DATA, viewModel.myDrawToRunData.value) - }) + this.let { + PermissionUtil.requestLocationPermission( + context = it, + onPermissionGranted = { navigateToCountDown() }, + onPermissionDenied = { showPermissionDeniedToast() }, + permissionType = PermissionUtil.PermissionType.LOCATION + ) + } } } + private fun showPermissionDeniedToast() { + showToast(getString(R.string.location_permission_denied)) + } + + private fun navigateToCountDown() { + startActivity(Intent(this, CountDownActivity::class.java).apply { + putExtra(EXTRA_COURSE_DATA, viewModel.myDrawToRunData.value) + }) + } + fun addObserver() { observeGetResult() registerBackPressedCallback() @@ -158,7 +176,7 @@ class MyDrawDetailActivity : } } - fun deleteCourse() { + private fun deleteCourse() { viewModel.deleteMyDrawCourse(selectList) } diff --git a/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileActivity.kt index 361bf01dd..21d30c6d7 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileActivity.kt @@ -1,27 +1,142 @@ package com.runnect.runnect.presentation.profile +import android.content.Intent import android.os.Bundle import androidx.activity.viewModels +import androidx.core.view.isVisible import com.runnect.runnect.R import com.runnect.runnect.binding.BindingActivity import com.runnect.runnect.databinding.ActivityProfileBinding +import com.runnect.runnect.presentation.detail.CourseDetailActivity +import com.runnect.runnect.presentation.state.UiStateV2 +import com.runnect.runnect.util.extension.applyScreenEnterAnimation +import com.runnect.runnect.util.extension.showSnackbar import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class ProfileActivity : BindingActivity(R.layout.activity_profile) { private val viewModel: ProfileViewModel by viewModels() private lateinit var adapter: ProfileCourseAdapter + private var userId: Int = -1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding.vm = viewModel binding.lifecycleOwner = this initAdapter() + addListener() + addObserver() + getIntentExtra() + getUserProfile() + } + + private fun getIntentExtra() { + userId = intent.getIntExtra(EXTRA_COURSE_USER_ID, -1) } private fun initAdapter() { - adapter = ProfileCourseAdapter().also { adapter -> + adapter = ProfileCourseAdapter(onScrapButtonClick = { courseId, scrapTF -> + viewModel.postCourseScrap(courseId = courseId, scrapTF = scrapTF) + }, onCourseItemClick = { courseId -> + navigateToCourseDetail(courseId) + }).also { adapter -> binding.rvProfileUploadCourse.adapter = adapter - adapter.submitList(viewModel.courseList) } } + + private fun addListener() { + initBackButtonClickListener() + } + + private fun addObserver() { + setupUserProfileGetStateObserver() + setupCourseScrapPostStateObserver() + } + + private fun navigateToCourseDetail(courseId: Int) { + Intent(this@ProfileActivity, CourseDetailActivity::class.java).apply { + putExtra(EXTRA_PUBLIC_COURSE_ID, courseId) + addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) + startActivity(this) + } + applyScreenEnterAnimation() + } + + private fun initBackButtonClickListener() { + binding.ivProfileBack.setOnClickListener { + finish() + } + } + + private fun getUserProfile() { + viewModel.getUserProfile(userId = userId) + } + + private fun setupUserProfileGetStateObserver() { + viewModel.userProfileState.observe(this) { state -> + when (state) { + is UiStateV2.Loading -> { + activateLoadingProgressBar() + } + + is UiStateV2.Success -> { + deactivateLoadingProgressBar() + binding.data = state.data + adapter.submitList(state.data.courseData) + } + + is UiStateV2.Failure -> { + deactivateLoadingProgressBar() + this.showSnackbar(binding.root, state.msg) + } + + else -> { + + } + } + + } + } + + private fun setupCourseScrapPostStateObserver() { + viewModel.courseScrapState.observe(this) { state -> + when (state) { + is UiStateV2.Loading -> { + + } + + is UiStateV2.Success -> { + state.data?.let { it -> + adapter.updateCourseItem( + courseId = it.publicCourseId.toInt(), + scrapTF = it.scrapTF + ) + } + } + + is UiStateV2.Failure -> { + this.showSnackbar(binding.root, state.msg) + } + + else -> { + + } + } + + } + } + + private fun activateLoadingProgressBar() { + binding.clProfile.isVisible = false + binding.pbProfileIntermediate.isVisible = true + } + + private fun deactivateLoadingProgressBar() { + binding.clProfile.isVisible = true + binding.pbProfileIntermediate.isVisible = false + } + + companion object { + private const val EXTRA_COURSE_USER_ID = "courseUserId" + private const val EXTRA_PUBLIC_COURSE_ID = "publicCourseId" + } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileCourseAdapter.kt b/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileCourseAdapter.kt index 11ddf62e4..8d4966a37 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileCourseAdapter.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileCourseAdapter.kt @@ -4,12 +4,15 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import com.runnect.runnect.data.dto.ProfileCourseData +import com.runnect.runnect.domain.entity.UserCourse import com.runnect.runnect.databinding.ItemProfileCourseBinding import com.runnect.runnect.util.callback.diff.ItemDiffCallback +import com.runnect.runnect.util.extension.setOnSingleClickListener class ProfileCourseAdapter( -) : ListAdapter(diffUtil) { + private val onScrapButtonClick: (Int, Boolean) -> Unit, + private val onCourseItemClick: (Int) -> Unit +) : ListAdapter(diffUtil) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UploadedCourseViewHolder { return UploadedCourseViewHolder( @@ -17,7 +20,9 @@ class ProfileCourseAdapter( LayoutInflater.from(parent.context), parent, false - ) + ), + onScrapButtonClick = onScrapButtonClick, + onCourseItemClick = onCourseItemClick ) } @@ -26,17 +31,35 @@ class ProfileCourseAdapter( } class UploadedCourseViewHolder( - private val binding: ItemProfileCourseBinding + private val binding: ItemProfileCourseBinding, + private val onScrapButtonClick: (Int, Boolean) -> Unit, + private val onCourseItemClick: (Int) -> Unit ) : RecyclerView.ViewHolder(binding.root) { - fun bind(profileCourseData: ProfileCourseData) { + fun bind(userCourse: UserCourse) { with(binding) { - data = profileCourseData + data = userCourse + ivItemProfileCourseHeart.setOnSingleClickListener { + onScrapButtonClick(userCourse.publicCourseId, !userCourse.scrapTF) + } + + clItemProfileCourse.setOnSingleClickListener { + onCourseItemClick(userCourse.publicCourseId) + } + } + } + } + + fun updateCourseItem(courseId: Int, scrapTF: Boolean) { + currentList.forEachIndexed { index, userCourseData -> + if (userCourseData.publicCourseId == courseId) { + userCourseData.scrapTF = scrapTF + notifyItemChanged(index) } } } companion object { - private val diffUtil = ItemDiffCallback( + private val diffUtil = ItemDiffCallback( onItemsTheSame = { old, new -> old.courseId == new.courseId }, onContentsTheSame = { old, new -> old == new } ) diff --git a/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileViewModel.kt b/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileViewModel.kt index e5667de47..b518cc418 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileViewModel.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileViewModel.kt @@ -1,40 +1,72 @@ package com.runnect.runnect.presentation.profile +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import com.runnect.runnect.data.dto.DepartureData -import com.runnect.runnect.data.dto.ProfileCourseData - -class ProfileViewModel : ViewModel() { - val courseList: List = generateMockData() - - private fun generateMockData(): List { - val mockDataList = mutableListOf() - - val mockData1 = ProfileCourseData( - publicCourseId = 1, - courseId = 101, - title = "제목 1", - image = "이미지 1", - departure = DepartureData( - region = "지역 1", - city = "도시 1", - town = "동네 1", - detail = null, - name = "출발지 1" - ), - scrapTF = true - ) - mockDataList.add(mockData1) - mockDataList.add(mockData1) - mockDataList.add(mockData1) - mockDataList.add(mockData1) - mockDataList.add(mockData1) - mockDataList.add(mockData1) - mockDataList.add(mockData1) - mockDataList.add(mockData1) - mockDataList.add(mockData1) - mockDataList.add(mockData1) - mockDataList.add(mockData1) - return mockDataList +import androidx.lifecycle.viewModelScope +import com.runnect.runnect.data.dto.request.RequestPostCourseScrap +import com.runnect.runnect.data.dto.response.ResponsePostScrap +import com.runnect.runnect.domain.entity.UserProfile +import com.runnect.runnect.domain.repository.CourseRepository +import com.runnect.runnect.domain.repository.UserRepository +import com.runnect.runnect.presentation.state.UiStateV2 +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@HiltViewModel +class ProfileViewModel @Inject constructor( + private val userRepository: UserRepository, + private val courseRepository: CourseRepository +) : + ViewModel() { + + private val _courseScrapState = MutableLiveData>() + val courseScrapState: LiveData> + get() = _courseScrapState + + private val _userProfileState = MutableLiveData>() + val userProfileState: LiveData> + get() = _userProfileState + + + fun getUserProfile(userId: Int) { + viewModelScope.launch { + _userProfileState.value = UiStateV2.Loading + + userRepository.getUserProfile(userId = userId) + .onSuccess { profileData -> + if (profileData == null) { + _userProfileState.value = UiStateV2.Failure("PROFILE DATA IS NULL") + Timber.d("PROFILE DATA IS NULL") + return@launch + } + _userProfileState.value = UiStateV2.Success(profileData) + Timber.d("GET PROFILE DATA SUCCESS") + } + .onFailure { error -> + _userProfileState.value = UiStateV2.Failure(error.message.toString()) + Timber.e("GET PROFILE DATA FAILURE") + } + } + } + + fun postCourseScrap(courseId: Int, scrapTF: Boolean) { + viewModelScope.launch { + _courseScrapState.value = UiStateV2.Loading + courseRepository.postCourseScrap( + RequestPostCourseScrap( + publicCourseId = courseId, scrapTF = scrapTF.toString() + ) + ).onSuccess { response -> + Timber.d("POST COURSE SCRAP SUCCESS") + _courseScrapState.value = UiStateV2.Success(response) + }.onFailure { exception -> + Timber.e("POST COURSE SCRAP FAILURE") + _courseScrapState.value = UiStateV2.Failure(exception.message.toString()) + } + } } } + diff --git a/app/src/main/java/com/runnect/runnect/presentation/search/SearchActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/search/SearchActivity.kt index 0dea2a8ca..7b00996db 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/search/SearchActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/search/SearchActivity.kt @@ -7,7 +7,6 @@ import android.os.Bundle import android.view.KeyEvent import android.view.MotionEvent import android.view.inputmethod.EditorInfo.IME_ACTION_SEARCH -import android.view.inputmethod.InputMethodManager import android.widget.TextView import androidx.activity.viewModels import androidx.core.content.ContextCompat @@ -22,13 +21,15 @@ import com.runnect.runnect.presentation.draw.DrawActivity import com.runnect.runnect.presentation.search.adapter.SearchAdapter import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.util.callback.listener.OnSearchItemClick +import com.runnect.runnect.util.extension.PermissionUtil import com.runnect.runnect.util.extension.hideKeyboard import com.runnect.runnect.util.extension.showKeyboard +import com.runnect.runnect.util.extension.showToast import dagger.hilt.android.AndroidEntryPoint import timber.log.Timber @AndroidEntryPoint -class SearchActivity: BindingActivity(R.layout.activity_search), +class SearchActivity : BindingActivity(R.layout.activity_search), OnSearchItemClick { val viewModel: SearchViewModel by viewModels() private lateinit var searchAdapter: SearchAdapter @@ -159,7 +160,7 @@ class SearchActivity: BindingActivity(R.layout.activity_s override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean { if (actionId == IME_ACTION_SEARCH) { val keyword = binding.etSearch.text - if(!keyword.isNullOrBlank()){ + if (!keyword.isNullOrBlank()) { searchKeyword(keyword.toString()) hideKeyboard(binding.etSearch) } @@ -170,7 +171,14 @@ class SearchActivity: BindingActivity(R.layout.activity_s }) binding.cvStartCurrentLocation.setOnClickListener { - startCurrentLocation() + this.let { + PermissionUtil.requestLocationPermission( + context = it, + onPermissionGranted = { startCurrentLocation() }, + onPermissionDenied = { showPermissionDeniedToast() }, + permissionType = PermissionUtil.PermissionType.LOCATION + ) + } } binding.cvStartCustomLocation.setOnClickListener { @@ -178,13 +186,22 @@ class SearchActivity: BindingActivity(R.layout.activity_s } } + private fun showPermissionDeniedToast() { + showToast(getString(R.string.location_permission_denied)) + } + private fun startCurrentLocation() { startActivity( Intent(this, DrawActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) putExtra( EXTRA_SEARCH_RESULT, - SearchResultEntity(fullAddress = "", name = "", locationLatLng = null, mode = "currentLocation") + SearchResultEntity( + fullAddress = "", + name = "", + locationLatLng = null, + mode = "currentLocation" + ) ) } ) @@ -196,7 +213,12 @@ class SearchActivity: BindingActivity(R.layout.activity_s addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) putExtra( EXTRA_SEARCH_RESULT, - SearchResultEntity(fullAddress = "", name = "", locationLatLng = null, mode = "customLocation") + SearchResultEntity( + fullAddress = "", + name = "", + locationLatLng = null, + mode = "customLocation" + ) ) } ) diff --git a/app/src/main/java/com/runnect/runnect/util/binding/BindingAdapter.kt b/app/src/main/java/com/runnect/runnect/util/binding/BindingAdapter.kt index 6f60357be..782755995 100644 --- a/app/src/main/java/com/runnect/runnect/util/binding/BindingAdapter.kt +++ b/app/src/main/java/com/runnect/runnect/util/binding/BindingAdapter.kt @@ -12,6 +12,22 @@ fun ImageView.setLocalImageByResourceId(resId: Int) { load(resId) } +@BindingAdapter("setStampImageByResourceId") +fun ImageView.setStampImageByResourceId(stampId: String?) { + val resNameParam = "mypage_img_stamp_" + val resType = "drawable" + val packageName = context.packageName + + var resName = "" + resName = if (stampId == "CSPR0") { + "${resNameParam}basic" + } else { + "${resNameParam}$stampId" + } + val resId = context.resources.getIdentifier(resName, resType, packageName) + setImageResource(resId) +} + @BindingAdapter("setImageUrl") fun ImageView.setImageUrl(url: String?) { if (url == null) return diff --git a/app/src/main/java/com/runnect/runnect/util/custom/deco/DiscoverMarathonItemDecoration.kt b/app/src/main/java/com/runnect/runnect/util/custom/deco/DiscoverMarathonItemDecoration.kt index 2c4ca9e2c..5078773dd 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/deco/DiscoverMarathonItemDecoration.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/deco/DiscoverMarathonItemDecoration.kt @@ -13,20 +13,20 @@ class DiscoverMarathonItemDecoration( ) : RecyclerView.ItemDecoration() { private val spaceSizePx = spaceSize.dpToPx(context) - // 리사이클러뷰 아이템 주위의 여백을 설정하는 함수 override fun getItemOffsets( outRect: Rect, // 아이템의 사각형 영역 view: View, // 아이템 뷰 parent: RecyclerView, state: RecyclerView.State ) { - // 마지막 아이템에 대해서만 오른쪽 여백 제거 - if (!checkLastItem(view, parent)) { + // 마지막 아이템 제외하고는 오른쪽 마진 추가 + if (!isLastItem(view, parent)) { outRect.right = spaceSizePx } } - private fun checkLastItem(view: View, parent: RecyclerView): Boolean { - return parent.getChildAdapterPosition(view) == itemCount - 1 + private fun isLastItem(view: View, parent: RecyclerView): Boolean { + val currentItemPosition = parent.getChildAdapterPosition(view) + return currentItemPosition == itemCount - 1 } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/util/custom/deco/DiscoverRecommendItemDecoration.kt b/app/src/main/java/com/runnect/runnect/util/custom/deco/DiscoverRecommendItemDecoration.kt new file mode 100644 index 000000000..0e30106ad --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/util/custom/deco/DiscoverRecommendItemDecoration.kt @@ -0,0 +1,27 @@ +package com.runnect.runnect.util.custom.deco + +import android.content.Context +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.runnect.runnect.util.extension.dpToPx +import timber.log.Timber + +class DiscoverRecommendItemDecoration( + context: Context, + rightSpacing: Int, + bottomSpacing: Int +) : RecyclerView.ItemDecoration() { + private val rightSpacingPx = rightSpacing.dpToPx(context) + private val bottomSpacingPx = bottomSpacing.dpToPx(context) + + override fun getItemOffsets( + outRect: Rect, // 아이템의 사각형 영역 + view: View, // 아이템 뷰 + parent: RecyclerView, + state: RecyclerView.State + ) { + outRect.right = rightSpacingPx + outRect.bottom = bottomSpacingPx + } +} diff --git a/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt b/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt index 3cab9548f..e7f491a14 100644 --- a/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt +++ b/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt @@ -10,11 +10,9 @@ import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.inputmethod.InputMethodManager -import android.widget.FrameLayout import android.widget.Toast import androidx.annotation.ColorRes import androidx.annotation.DrawableRes -import androidx.annotation.StringRes import androidx.appcompat.widget.AppCompatButton import androidx.appcompat.widget.LinearLayoutCompat import androidx.coordinatorlayout.widget.CoordinatorLayout @@ -22,6 +20,7 @@ import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.snackbar.Snackbar +import com.google.android.material.transition.SlideDistanceProvider.GravityFlag import com.runnect.runnect.R import kotlinx.android.synthetic.main.custom_dialog_delete.btn_delete_no import kotlinx.android.synthetic.main.custom_dialog_delete.view.btn_delete_no @@ -30,7 +29,6 @@ import kotlinx.android.synthetic.main.custom_dialog_delete.view.tv_dialog import kotlinx.android.synthetic.main.custom_dialog_edit_mode.layout_delete_frame import kotlinx.android.synthetic.main.custom_dialog_edit_mode.layout_edit_frame import kotlinx.android.synthetic.main.fragment_bottom_sheet.btn_delete_yes -import kotlinx.android.synthetic.main.fragment_require_login_dialog.view.tv_require_login_dialog_desc fun Context.setActivityDialog( layoutInflater: LayoutInflater, @@ -171,12 +169,12 @@ fun Context.showToast(message: String) { Toast.makeText(this, message, Toast.LENGTH_SHORT).show() } -fun Context.showSnackbar(anchorView: View, message: String) { +fun Context.showSnackbar(anchorView: View, message: String, @GravityFlag gravity: Int = Gravity.BOTTOM) { val snackbar = Snackbar.make(anchorView, message, Snackbar.LENGTH_SHORT) - snackbar.view.apply { - val params = layoutParams as? CoordinatorLayout.LayoutParams - params?.gravity = Gravity.TOP - layoutParams = params + val layoutParams = snackbar.view.layoutParams as CoordinatorLayout.LayoutParams + layoutParams.apply { + this.gravity = gravity + snackbar.view.layoutParams = this } snackbar.show() } diff --git a/app/src/main/java/com/runnect/runnect/util/extension/PermissionExt.kt b/app/src/main/java/com/runnect/runnect/util/extension/PermissionExt.kt new file mode 100644 index 000000000..069465d03 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/util/extension/PermissionExt.kt @@ -0,0 +1,68 @@ +package com.runnect.runnect.util.extension + +import android.Manifest +import android.content.Context +import android.widget.Toast +import com.gun0912.tedpermission.PermissionListener +import com.gun0912.tedpermission.normal.TedPermission +import com.runnect.runnect.R + +object PermissionUtil { + fun requestLocationPermission( + context: Context, + onPermissionGranted: () -> Unit, + onPermissionDenied: () -> Unit = { + Toast.makeText( + context, + R.string.common_permission_denied, + Toast.LENGTH_SHORT + ).show() + }, + permissionType: PermissionType + ) { + val permission = setUpPermissionByType(permissionType) + TedPermission.create() + .setPermissionListener(object : PermissionListener { + override fun onPermissionGranted() { + onPermissionGranted() + } + + override fun onPermissionDenied(deniedPermissions: MutableList?) { + onPermissionDenied() + } + }) + .setRationaleTitle(permission.title) + .setRationaleMessage(permission.content) + .setDeniedMessage(permission.guide) + .setPermissions(*permission.permissions.toTypedArray()) + .check() + } + + enum class PermissionType { + LOCATION, + // 필요한 권한 있을 시 추가 + } + + data class PermissionInfo( + val permissions: List, + val title: Int, + val content: Int, + val guide: Int + ) + + private fun setUpPermissionByType(permissionType: PermissionType): PermissionInfo { + return when (permissionType) { + PermissionType.LOCATION -> { + PermissionInfo( + listOf( + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION + ), + R.string.location_permission_title, + R.string.location_permission_content, + R.string.location_permission_guide + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/util/extension/ViewExt.kt b/app/src/main/java/com/runnect/runnect/util/extension/ViewExt.kt index da66e976c..9adcea375 100644 --- a/app/src/main/java/com/runnect/runnect/util/extension/ViewExt.kt +++ b/app/src/main/java/com/runnect/runnect/util/extension/ViewExt.kt @@ -1,7 +1,12 @@ package com.runnect.runnect.util.extension +import android.view.LayoutInflater import android.view.View import androidx.annotation.Px +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding inline fun View.setOnSingleClickListener( delay: Long = 500L, @@ -19,4 +24,12 @@ inline fun View.setOnSingleClickListener( fun View.setPadding(@Px size: Int) { setPadding(size, size, size, size) -} \ No newline at end of file +} +fun ViewGroup.getViewDataBinding( + @LayoutRes layoutRes: Int +): T = DataBindingUtil.inflate( + LayoutInflater.from(this.context), + layoutRes, + this, + false +) \ No newline at end of file diff --git a/app/src/main/res/layout/activity_course_detail.xml b/app/src/main/res/layout/activity_course_detail.xml index 98a48a8c1..32b5f29bb 100644 --- a/app/src/main/res/layout/activity_course_detail.xml +++ b/app/src/main/res/layout/activity_course_detail.xml @@ -178,7 +178,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="15dp" - android:layout_marginTop="14dp" + android:layout_marginTop="18dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_course_detail_title" app:srcCompat="@drawable/all_star" /> @@ -200,7 +200,7 @@ android:id="@+id/tv_course_detail_distance" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="34dp" + android:layout_marginStart="36dp" android:fontFamily="@font/pretendard_regular" android:text="@{courseDetail.distance}" android:textColor="@color/G1" @@ -227,7 +227,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="15dp" - android:layout_marginTop="8dp" + android:layout_marginTop="9dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/iv_course_detail_distance_indicator" app:srcCompat="@drawable/all_star" /> @@ -249,7 +249,7 @@ android:id="@+id/tv_course_detail_departure" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="23dp" + android:layout_marginStart="25dp" android:ellipsize="end" android:fontFamily="@font/pretendard_regular" android:maxLength="25" @@ -299,7 +299,7 @@ android:id="@+id/cl_course_detail_bottom_container" android:layout_width="0dp" android:layout_height="wrap_content" - android:paddingBottom="34dp" + android:layout_marginBottom="18dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> @@ -308,8 +308,8 @@ android:id="@+id/iv_course_detail_scrap" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="26dp" - android:layout_marginTop="13dp" + android:layout_marginStart="16dp" + android:layout_marginTop="17dp" android:layout_marginEnd="17dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -319,11 +319,11 @@ android:id="@+id/tv_course_detail_scrap_count" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="10dp" + android:layout_marginTop="2dp" android:fontFamily="@font/pretendard_semibold" android:text="@{courseDetail.scrapCount}" android:textColor="@color/G2" - android:textSize="11sp" + android:textSize="10sp" app:layout_constraintEnd_toEndOf="@id/iv_course_detail_scrap" app:layout_constraintStart_toStartOf="@id/iv_course_detail_scrap" app:layout_constraintTop_toBottomOf="@id/iv_course_detail_scrap" @@ -332,9 +332,10 @@ - + @@ -13,6 +15,7 @@ @@ -27,16 +30,20 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/M4" + app:contentInsetStart="0dp" app:layout_constraintTop_toTopOf="@id/cl_profile_toolbar"> + + setStampImageByResourceId="@{data.latestStamp}" /> + android:text="@{data.nickname}" /> - + android:text="@{Integer.toString(data.level)}"/> + android:progress="@{data.levelPercent}" /> + android:text="@{Integer.toString(data.levelPercent)}" /> + app:scale_base_height="160" + app:scale_base_width="156"> + app:scale_height="111" + app:scale_width="156"> + type="com.runnect.runnect.domain.entity.UserCourse" /> @@ -19,15 +20,15 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="3dp" android:layout_marginBottom="20dp" - app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:scale_base_height="154" app:scale_base_width="162"> 수정이 완료되었습니다 + Error: 코스 데이터가 비어있습니다. 2023 마라톤 코스 @@ -165,4 +166,11 @@ 나에게 최적화된 코스를 찾아보세요 최신순 스크랩순 + + 권한이 거부되었습니다. + + 위치 권한이 거부되었습니다. + 위치권한 요청 + 코스의 출발지 설정과 러닝 트래킹을 위해 현재 위치 정보를 사용하도록 허용합니다. + 권한을 허용해주세요. [설정] > [앱 및 알림] > [고급] > [앱 권한] \ No newline at end of file From f1cc4b20d3a8ffef361c5ddda6c2b8f8ce4a04f2 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 14 Feb 2024 22:20:24 +0900 Subject: [PATCH 16/16] =?UTF-8?q?[MOD]=20#287=20Companion=20Object=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/toolbar/CommonToolbarLayout.kt | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt index dbf1f5701..33a4c7409 100644 --- a/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt +++ b/app/src/main/java/com/runnect/runnect/util/custom/toolbar/CommonToolbarLayout.kt @@ -20,30 +20,6 @@ interface CommonToolbarLayout { @Retention(AnnotationRetention.SOURCE) annotation class Direction - companion object { - // 메뉴 추가 위치 - const val LEFT = 0 - const val RIGHT = 1 - - // Toolbar 관련 - private const val TOOLBAR_TITLE_TEXT_SIZE = 18 // Title 텍스트 기본 크기(sp) - private const val TOOLBAR_MENU_LIMIT_COUNT = 2 // 메뉴 최대 노출 갯수 - - - // 메뉴 아이템 관련 - const val MENU_ITEM_SIZE = 48 // 메뉴 아이템 뷰 기본 크기 - const val MENU_ITEM_PADDING = 15 // 메뉴 아이템 뷰 기본 패딩(dp) - const val MENU_ITEM_TEXT_SIZE = 18 // 텍스트 기본 크기 - const val MENU_ITEM_RESOURCE_NONE = -1 // 메뉴 아이템 리소스(아이콘,텍스트) 없음 - - // 기본 리소스 - @DrawableRes val MENU_ICON_BACK_BUTTON: Int = R.drawable.all_back_arrow - @FontRes val TOOLBAR_TITLE_FONT_RES: Int = R.font.pretendard_bold - @ColorRes private val TOOLBAR_TITLE_TEXT_COLOR = R.color.G1 - @ColorRes private val TOOLBAR_BACKGROUND_COLOR = R.color.white - } - - val toolbarBinding: LayoutCommonToolbarBinding /** @@ -202,4 +178,27 @@ interface CommonToolbarLayout { private fun getColor(context: Context, @ColorRes colorResId: Int): Int { return ContextCompat.getColor(context, colorResId) } + + companion object { + // 메뉴 추가 위치 + const val LEFT = 0 + const val RIGHT = 1 + + // Toolbar 관련 + private const val TOOLBAR_TITLE_TEXT_SIZE = 18 // Title 텍스트 기본 크기(sp) + private const val TOOLBAR_MENU_LIMIT_COUNT = 2 // 메뉴 최대 노출 갯수 + + + // 메뉴 아이템 관련 + const val MENU_ITEM_SIZE = 48 // 메뉴 아이템 뷰 기본 크기 + const val MENU_ITEM_PADDING = 15 // 메뉴 아이템 뷰 기본 패딩(dp) + const val MENU_ITEM_TEXT_SIZE = 18 // 텍스트 기본 크기 + const val MENU_ITEM_RESOURCE_NONE = -1 // 메뉴 아이템 리소스(아이콘,텍스트) 없음 + + // 기본 리소스 + @DrawableRes val MENU_ICON_BACK_BUTTON: Int = R.drawable.all_back_arrow + @FontRes val TOOLBAR_TITLE_FONT_RES: Int = R.font.pretendard_bold + @ColorRes private val TOOLBAR_TITLE_TEXT_COLOR = R.color.G1 + @ColorRes private val TOOLBAR_BACKGROUND_COLOR = R.color.white + } } \ No newline at end of file