diff --git a/app/build.gradle b/app/build.gradle
index 58d84760..4709fad5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -29,8 +29,8 @@ android {
applicationId "com.mashup"
minSdkVersion minVersion
targetSdkVersion targetVersion
- versionCode 31
- versionName "1.6.2"
+ versionCode 34
+ versionName "1.7.2"
testInstrumentationRunner "com.mashup.core.testing.MashUpTestRunner"
vectorDrawables {
@@ -107,6 +107,8 @@ dependencies {
implementation project(":feature:setting")
implementation project(":feature:danggn")
implementation project(":feature:myPage")
+ implementation project(":feature:moreMenu")
+ implementation project(':feature:moreMenu:notice')
// ml Kit
implementation "com.google.mlkit:barcode-scanning:$barcodeSacnnerVersion"
@@ -164,4 +166,7 @@ dependencies {
// Naver Map
implementation "io.github.fornewid:naver-map-compose:1.4.0"
+
+ // Date Time
+ implementation 'com.jakewharton.threetenabp:threetenabp:1.3.1'
}
\ No newline at end of file
diff --git a/app/src/debug/java/com/mashup/data/network/NetworkConstanst.kt b/app/src/debug/java/com/mashup/data/network/NetworkConstanst.kt
index c941150a..ce43f3f0 100644
--- a/app/src/debug/java/com/mashup/data/network/NetworkConstanst.kt
+++ b/app/src/debug/java/com/mashup/data/network/NetworkConstanst.kt
@@ -1,3 +1,4 @@
package com.mashup.data.network
const val API_HOST = "https://api.dev-member.mash-up.kr/"
+const val WEB_HOST = "https://dev-app.mash-up.kr/"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b4c7f6b2..16a58fa4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,7 +6,8 @@
-
+
+
@@ -15,19 +16,21 @@
-
-
+
+
@@ -62,8 +65,9 @@
android:name=".ui.login.LoginActivity"
android:exported="true"
android:screenOrientation="portrait"
- tools:ignore="LockedOrientationActivity"/>
-
+
+ tools:ignore="LockedOrientationActivity" />
+ tools:ignore="LockedOrientationActivity" />
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/mashup/constant/ExtraConstant.kt b/app/src/main/java/com/mashup/constant/ExtraConstant.kt
index 092e1e24..1494c71f 100644
--- a/app/src/main/java/com/mashup/constant/ExtraConstant.kt
+++ b/app/src/main/java/com/mashup/constant/ExtraConstant.kt
@@ -6,6 +6,7 @@ const val EXTRA_LOGIN_TYPE = "EXTRA_MAIN_TYPE"
const val EXTRA_TITLE_KEY = "EXTRA_TITLE_KEY"
const val EXTRA_URL_KEY = "EXTRA_URL_KEY"
const val EXTRA_SCHEDULE_ID = "EXTRA_SCHEDULE_ID"
+const val EXTRA_SCHEDULE_TYPE = "EXTRA_SCHEDULE_TYPE"
const val EXTRA_LOGOUT = "EXTRA_LOGOUT"
const val EXTRA_WITH_DRAWL = "EXTRA_WITH_DRAWL"
diff --git a/app/src/main/java/com/mashup/constant/log/UserActionLogs.kt b/app/src/main/java/com/mashup/constant/log/UserActionLogs.kt
index 61194d3f..0388170d 100644
--- a/app/src/main/java/com/mashup/constant/log/UserActionLogs.kt
+++ b/app/src/main/java/com/mashup/constant/log/UserActionLogs.kt
@@ -1,42 +1,96 @@
package com.mashup.constant.log
-const val LOG_BACK = "back"
-const val LOG_CLOSE = "close"
+/**
+ * Category Common
+ */
+const val LOG_COMMON_BACK = "back"
+const val LOG_COMMON_CLOSE = "close"
+const val LOG_COMMON_POPUP_CONFIRM = "popup_new_confirm"
+const val LOG_COMMON_POPUP_CANCEL = "popup_new_cancel"
+/**
+ * Category SignUp
+ */
const val LOG_LOGIN = "login"
const val LOG_SIGN_UP = "signup"
-
-const val LOG_LOGOUT = "logout"
-const val LOG_DELETE_USER = "delete_user"
-const val LOG_SNS_FACEBOOK = "facebook"
-const val LOG_SNS_INSTAGRAM = "instagram"
-const val LOG_SNS_TISTORY = "tistory"
-const val LOG_SNS_YOUTUBE = "youtube"
-const val LOG_SNS_MASHUP_HOME = "mashup_home"
-const val LOG_SNS_MASHUP_RECRUIT = "mashup_recruit"
-const val LOG_PLACE_SIGN_CODE = "signup_code"
-const val LOG_PLACE_SIGN_MEMBER_INFO = "signup_info"
-const val LOG_PLACE_SIGN_PLATFORM = "signup_platform"
const val LOG_POPUP_SIGNUP_CONFIRM = "popup_signup_confirm"
const val LOG_POPUP_SIGNUP_CANCEL = "popup_signup_cancel"
-
const val LOG_PLACE_CHANGE_PASSWORD = "change_password"
-const val LOG_PLACE_ENTER_ID = "enter_id"
-const val LOG_SCHEDULE_LIST_REFRESH = "refresh"
-const val LOG_SCHEDULE_STATUS_CONFIRM = "status_confirm"
-const val LOG_SCHEDULE_EVENT_DETAIL = "event_detail"
+/**
+ * Category Event List
+ */
+const val LOG_EVENT_LIST_REFRESH = "refresh"
+const val LOG_EVENT_LIST_STATUS_CONFIRM = "status_confirm"
+const val LOG_EVENT_LIST_EVENT_DETAIL = "event_detail"
+const val LOG_EVENT_LIST_WEEK_MASHONG = "week_mashong"
+const val LOG_EVENT_LIST_WEEK = "week"
+const val LOG_EVENT_LIST_ALL = "all"
+const val LOG_EVENT_LIST_DETAIL_COPY = "detail_copy"
+
+/**
+ * Category More
+ */
+const val LOG_MORE_BIRTH = "more_birth"
+const val LOG_MORE_MASHONG = "more_mashong"
+const val LOG_MORE_CARROT = "more_carrot"
+const val LOG_MORE_ALARM = "more_alarm"
+const val LOG_MORE_SETTING = "more_setting"
+
+/**
+ * Category Alarm
+ */
+const val LOG_ALARM_LIST = "alarm_list"
+/**
+ * Category Logout
+ */
+const val LOG_POPUP_LOGOUT_CONFIRM = "popup_logout_confirm"
+const val LOG_POPUP_LOGOUT_CANCEL = "popup_logout_cancel"
+
+/**
+ * Category QR
+ */
const val LOG_QR = "qr"
const val LOG_QR_SUCCESS = "qr_success"
const val LOG_QR_DONE = "qr_done"
const val LOG_QR_TIME_FAIL = "qr_time_fail"
const val LOG_QR_WRONG = "qr_wrong"
+const val LOG_QR_LOCATION = "qr_location"
+const val LOG_QR_DISTANCE_OUT_OF_RANGE = "qr_distance_out_of_range"
-const val LOG_DELETE_SUCCESS_USER = "delete_user_success"
+/**
+ * Category MyPage
+ */
+const val LOG_MYPAGE_HELP = "help"
-const val LOG_COMMON_POPUP_CONFIRM = "popup_new_confirm"
-const val LOG_COMMON_POPUP_CANCEL = "popup_new_cancel"
+/**
+ * Category Setting
+ */
+const val LOG_SETTING_LOGOUT = "logout"
+const val LOG_SETTING_DELETE_USER = "delete_user"
+const val LOG_SETTING_SNS_FACEBOOK = "facebook"
+const val LOG_SETTING_SNS_INSTAGRAM = "instagram"
+const val LOG_SETTING_SNS_TISTORY = "tistory"
+const val LOG_SETTING_SNS_YOUTUBE = "youtube"
+const val LOG_SETTING_SNS_MASHUP_HOME = "mashup_home"
+const val LOG_SETTING_SNS_MASHUP_RECRUIT = "mashup_recruit"
+/**
+ * Category Delete User
+ */
+const val LOG_DELETE_USER_SUCCESS = "delete_user_success"
+
+/**
+ * Category Danggn
+ */
const val LOG_DANGGN = "danggn"
const val LOG_DANGGN_HELP = "danggn_help"
+
+/**
+ * UnCategorization
+ */
+const val LOG_PLACE_SIGN_CODE = "signup_code"
+const val LOG_PLACE_SIGN_MEMBER_INFO = "signup_info"
+const val LOG_PLACE_SIGN_PLATFORM = "signup_platform"
+const val LOG_PLACE_ENTER_ID = "enter_id"
diff --git a/app/src/main/java/com/mashup/data/dto/ScheduleResponse.kt b/app/src/main/java/com/mashup/data/dto/ScheduleResponse.kt
index 8d1a97e5..80003897 100644
--- a/app/src/main/java/com/mashup/data/dto/ScheduleResponse.kt
+++ b/app/src/main/java/com/mashup/data/dto/ScheduleResponse.kt
@@ -9,6 +9,10 @@ import java.util.Locale
@JsonClass(generateAdapter = true)
data class ScheduleResponse(
+ @field:Json(name = "scheduleType")
+ val scheduleType: String,
+ @field:Json(name = "notice")
+ val notice: String?,
@field:Json(name = "scheduleId")
val scheduleId: Int,
@field:Json(name = "dateCount")
@@ -54,9 +58,11 @@ data class ScheduleResponse(
dateCount == 0 -> {
"D-Day"
}
+
dateCount > 0 -> {
"D-$dateCount"
}
+
else -> {
"D+${-dateCount}"
}
diff --git a/app/src/main/java/com/mashup/di/NetworkModule.kt b/app/src/main/java/com/mashup/di/NetworkModule.kt
index bda8c4a2..047d8941 100644
--- a/app/src/main/java/com/mashup/di/NetworkModule.kt
+++ b/app/src/main/java/com/mashup/di/NetworkModule.kt
@@ -5,7 +5,9 @@ import com.facebook.flipper.plugins.network.NetworkFlipperPlugin
import com.mashup.BuildConfig.DEBUG_MODE
import com.mashup.core.model.Platform
import com.mashup.core.network.adapter.PlatformJsonAdapter
+import com.mashup.core.network.dao.MetaDao
import com.mashup.core.network.dao.PopupDao
+import com.mashup.core.network.dao.PushHistoryDao
import com.mashup.core.network.dao.StorageDao
import com.mashup.data.network.API_HOST
import com.mashup.network.CustomDateAdapter
@@ -145,4 +147,20 @@ class NetworkModule {
): MemberProfileDao {
return retrofit.create()
}
+
+ @Provides
+ @Singleton
+ fun provideMetaDao(
+ retrofit: Retrofit
+ ): MetaDao {
+ return retrofit.create()
+ }
+
+ @Provides
+ @Singleton
+ fun providePushHistoryDao(
+ retrofit: Retrofit
+ ): PushHistoryDao {
+ return retrofit.create()
+ }
}
diff --git a/app/src/main/java/com/mashup/service/MashUpFirebaseMessagingService.kt b/app/src/main/java/com/mashup/service/MashUpFirebaseMessagingService.kt
index 90587a55..3210b1b8 100644
--- a/app/src/main/java/com/mashup/service/MashUpFirebaseMessagingService.kt
+++ b/app/src/main/java/com/mashup/service/MashUpFirebaseMessagingService.kt
@@ -13,12 +13,15 @@ import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
+import androidx.core.os.bundleOf
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.mashup.BuildConfig
import com.mashup.R
import com.mashup.constant.EXTRA_LINK
+import com.mashup.constant.log.LOG_ALARM_LIST
import com.mashup.ui.splash.SplashActivity
+import com.mashup.util.AnalyticsManager
import dagger.hilt.android.AndroidEntryPoint
import java.net.URL
@@ -58,6 +61,13 @@ class MashUpFirebaseMessagingService : FirebaseMessagingService() {
imageUrl: Uri?,
data: Map
) {
+ AnalyticsManager.addEvent(
+ eventName = LOG_ALARM_LIST,
+ params = bundleOf(
+ "place" to "PUSH",
+ "type" to PushLinkType.getPushLinkType(data[EXTRA_LINK].orEmpty()).name
+ )
+ )
val splashIntent = Intent(this, SplashActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
putExtra(EXTRA_LINK, data[EXTRA_LINK])
diff --git a/app/src/main/java/com/mashup/service/PushLinkType.kt b/app/src/main/java/com/mashup/service/PushLinkType.kt
index ae3e1982..b015cd70 100644
--- a/app/src/main/java/com/mashup/service/PushLinkType.kt
+++ b/app/src/main/java/com/mashup/service/PushLinkType.kt
@@ -5,6 +5,8 @@ enum class PushLinkType {
QR, // QR 페이지
DANGGN, // 당근 페이지
DANGGN_REWARD,
+ BIRTHDAY, // 생일 축하
+ MASHONG, // 매숑이 키우기
MYPAGE,
UNKNOWN
;
diff --git a/app/src/main/java/com/mashup/ui/login/LoginActivity.kt b/app/src/main/java/com/mashup/ui/login/LoginActivity.kt
index 97ae5ad4..a0fd12d3 100644
--- a/app/src/main/java/com/mashup/ui/login/LoginActivity.kt
+++ b/app/src/main/java/com/mashup/ui/login/LoginActivity.kt
@@ -26,6 +26,8 @@ import com.mashup.ui.main.model.MainTab
import com.mashup.ui.password.PasswordActivity
import com.mashup.ui.qrscan.QRScanActivity
import com.mashup.ui.signup.SignUpActivity
+import com.mashup.ui.webview.birthday.BirthdayActivity
+import com.mashup.ui.webview.mashong.MashongActivity
import com.mashup.util.AnalyticsManager
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
@@ -160,6 +162,14 @@ class LoginActivity : BaseActivity() {
)
}
+ PushLinkType.BIRTHDAY -> {
+ buildTaskStack(baseIntent, BirthdayActivity.newIntent(this))
+ }
+
+ PushLinkType.MASHONG -> {
+ buildTaskStack(baseIntent, MashongActivity.newIntent(this))
+ }
+
PushLinkType.QR -> {
buildTaskStack(baseIntent, QRScanActivity.newIntent(this))
}
diff --git a/app/src/main/java/com/mashup/ui/main/MainActivity.kt b/app/src/main/java/com/mashup/ui/main/MainActivity.kt
index 4c230395..231c8ef0 100644
--- a/app/src/main/java/com/mashup/ui/main/MainActivity.kt
+++ b/app/src/main/java/com/mashup/ui/main/MainActivity.kt
@@ -5,11 +5,14 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Build
+import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.navOptions
+import com.jakewharton.threetenabp.AndroidThreeTen
import com.mashup.R
import com.mashup.base.BaseActivity
import com.mashup.constant.EXTRA_ANIMATION
@@ -30,6 +33,7 @@ import com.mashup.ui.main.model.MainTab
import com.mashup.ui.main.popup.MainBottomPopup
import com.mashup.ui.qrscan.CongratsAttendanceScreen
import com.mashup.ui.qrscan.QRScanActivity
+import com.mashup.ui.webview.birthday.BirthdayActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@@ -58,12 +62,18 @@ class MainActivity : BaseActivity() {
viewModel.confirmAttendance()
viewModel.successAttendance()
}
+
QRScanActivity.RESULT_CONFIRM_QR -> {
viewModel.confirmAttendance()
}
}
}
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ AndroidThreeTen.init(this)
+ }
+
override fun initViews() {
super.initViews()
@@ -131,6 +141,17 @@ class MainActivity : BaseActivity() {
)
)
}
+
+ MainPopupType.BIRTHDAY_CELEBRATION -> {
+ viewModel.disablePopup(popupType)
+ startActivity(
+ BirthdayActivity.newIntent(
+ context = this@MainActivity,
+ urlKey = "birthday/event"
+ )
+ )
+ }
+
else -> {
}
}
@@ -140,18 +161,22 @@ class MainActivity : BaseActivity() {
}
private fun navigationTab(toDestination: MainTab) {
- val currentNavigationId = navController.currentDestination?.id
val newNavigationId = when (toDestination) {
MainTab.EVENT -> {
R.id.eventFragment
}
+
MainTab.MY_PAGE -> {
R.id.myPageFragment
}
}
- if (currentNavigationId != newNavigationId) {
- navController.navigate(newNavigationId)
+ val navOptions = navOptions {
+ popUpTo(newNavigationId) {
+ saveState = true
+ }
+ launchSingleTop = true
}
+ navController.navigate(newNavigationId, null, navOptions)
}
private fun setUIOfTab(tab: MainTab) = with(viewBinding.layoutMainTab) {
@@ -176,6 +201,7 @@ class MainActivity : BaseActivity() {
tvMyPage.setTextColor(unSelectedColor)
imgMyPage.imageTintList = unSelectedColorList
}
+
MainTab.MY_PAGE -> {
tvEvent.setTextColor(unSelectedColor)
imgEvent.imageTintList = unSelectedColorList
diff --git a/app/src/main/java/com/mashup/ui/main/model/MainPopupType.kt b/app/src/main/java/com/mashup/ui/main/model/MainPopupType.kt
index 8c0c21cd..41d142a2 100644
--- a/app/src/main/java/com/mashup/ui/main/model/MainPopupType.kt
+++ b/app/src/main/java/com/mashup/ui/main/model/MainPopupType.kt
@@ -1,7 +1,7 @@
package com.mashup.ui.main.model
enum class MainPopupType {
- DANGGN, DANGGN_UPDATE, UNKNOWN;
+ DANGGN, DANGGN_UPDATE, BIRTHDAY_CELEBRATION, UNKNOWN;
companion object {
fun getMainPopupType(type: String): MainPopupType {
diff --git a/app/src/main/java/com/mashup/ui/main/popup/MainBottomPopup.kt b/app/src/main/java/com/mashup/ui/main/popup/MainBottomPopup.kt
index 3e2b3d14..45868f87 100644
--- a/app/src/main/java/com/mashup/ui/main/popup/MainBottomPopup.kt
+++ b/app/src/main/java/com/mashup/ui/main/popup/MainBottomPopup.kt
@@ -96,12 +96,18 @@ class MainBottomPopup : BottomSheetDialogFragment() {
MainBottomPopupScreen(
viewModel = viewModel,
onClickLeftButton = {
- AnalyticsManager.addEvent(LOG_COMMON_POPUP_CANCEL, bundleOf("key" to viewModel.popupKey))
+ AnalyticsManager.addEvent(
+ LOG_COMMON_POPUP_CANCEL,
+ bundleOf("key" to viewModel.popupKey)
+ )
dismiss()
},
onClickRightButton = {
mainViewModel.onClickPopup(viewModel.popupKey.orEmpty())
- AnalyticsManager.addEvent(LOG_COMMON_POPUP_CONFIRM, bundleOf("key" to viewModel.popupKey))
+ AnalyticsManager.addEvent(
+ LOG_COMMON_POPUP_CONFIRM,
+ bundleOf("key" to viewModel.popupKey)
+ )
dismiss()
}
)
@@ -215,19 +221,23 @@ fun MainBottomPopupContent(
.padding(horizontal = 20.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
- MashUpButton(
- modifier = Modifier.wrapContentWidth(),
- text = mainPopupEntity.leftButtonText,
- buttonStyle = ButtonStyle.INVERSE,
- onClick = onClickLeftButton
- )
+ if (mainPopupEntity.leftButtonText.isNotEmpty()) {
+ MashUpButton(
+ modifier = Modifier.wrapContentWidth(),
+ text = mainPopupEntity.leftButtonText,
+ buttonStyle = ButtonStyle.INVERSE,
+ onClick = onClickLeftButton
+ )
+ }
- MashUpButton(
- modifier = Modifier.weight(1f),
- text = mainPopupEntity.rightButtonText,
- buttonStyle = ButtonStyle.PRIMARY,
- onClick = onClickRightButton
- )
+ if (mainPopupEntity.rightButtonText.isNotEmpty()) {
+ MashUpButton(
+ modifier = Modifier.weight(1f),
+ text = mainPopupEntity.rightButtonText,
+ buttonStyle = ButtonStyle.PRIMARY,
+ onClick = onClickRightButton
+ )
+ }
}
Spacer(modifier = Modifier.height(24.dp))
diff --git a/app/src/main/java/com/mashup/ui/main/popup/MainBottomPopupViewModel.kt b/app/src/main/java/com/mashup/ui/main/popup/MainBottomPopupViewModel.kt
index fce4e18e..6248ed89 100644
--- a/app/src/main/java/com/mashup/ui/main/popup/MainBottomPopupViewModel.kt
+++ b/app/src/main/java/com/mashup/ui/main/popup/MainBottomPopupViewModel.kt
@@ -7,14 +7,17 @@ import com.mashup.constant.EXTRA_POPUP_KEY
import com.mashup.core.common.base.BaseViewModel
import com.mashup.core.data.repository.PopUpRepository
import com.mashup.core.data.repository.StorageRepository
+import com.mashup.datastore.data.repository.UserPreferenceRepository
import com.mashup.ui.main.model.MainPopupEntity
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.first
import javax.inject.Inject
@HiltViewModel
class MainBottomPopupViewModel @Inject constructor(
private val storageRepository: StorageRepository,
private val popUpRepository: PopUpRepository,
+ private val userPreferenceRepository: UserPreferenceRepository,
savedStateHandle: SavedStateHandle
) : BaseViewModel() {
val popupKey = savedStateHandle.get(EXTRA_POPUP_KEY)
@@ -30,10 +33,13 @@ class MainBottomPopupViewModel @Inject constructor(
if (popupKey.isNullOrBlank()) return@mashUpScope
val result = storageRepository.getStorage(popupKey)
+ val userName = userPreferenceRepository.getUserPreference().first().name
+ val title = result.data?.valueMap?.get("title").orEmpty().replace("\${name}", userName)
+
if (result.isSuccess()) {
_uiState.value = MainBottomPopupUiState.Success(
MainPopupEntity(
- title = result.data?.valueMap?.get("title").orEmpty(),
+ title = title,
description = result.data?.valueMap?.get("subtitle").orEmpty(),
imageResName = result.data?.valueMap?.get("imageName").orEmpty(),
leftButtonText = result.data?.valueMap?.get("leftButtonTitle").orEmpty(),
diff --git a/app/src/main/java/com/mashup/ui/moremenu/MoreMenuActivity.kt b/app/src/main/java/com/mashup/ui/moremenu/MoreMenuActivity.kt
new file mode 100644
index 00000000..c949fdc0
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/moremenu/MoreMenuActivity.kt
@@ -0,0 +1,79 @@
+package com.mashup.ui.moremenu
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.viewModels
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.example.moremenu.MoreMenuRoute
+import com.example.moremenu.model.Menu
+import com.example.moremenu.model.MenuType
+import com.example.moremenu.model.MoreMenuSideEffect
+import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.ui.danggn.ShakeDanggnActivity
+import com.mashup.ui.notice.NoticeActivity
+import com.mashup.ui.setting.SettingActivity
+import com.mashup.ui.webview.birthday.BirthdayActivity
+import com.mashup.ui.webview.mashong.MashongActivity
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+
+@AndroidEntryPoint
+class MoreMenuActivity : ComponentActivity() {
+
+ private val moreMenuViewModel: MoreMenuViewModel by viewModels()
+
+ override fun onResume() {
+ super.onResume()
+ moreMenuViewModel.getMoreMenuState()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ moreMenuViewModel.moreMenuEvent.collect { sideEffect ->
+ when (sideEffect) {
+ is MoreMenuSideEffect.NavigateMenu -> onNavigateMenu(sideEffect.menu)
+ is MoreMenuSideEffect.NavigateBackStack -> finish()
+ }
+ }
+ }
+ }
+ setContent {
+ MashUpTheme {
+ val moreMenuState by moreMenuViewModel.moreMenuState.collectAsState()
+ Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
+ MoreMenuRoute(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(innerPadding),
+ moreMenuState = moreMenuState,
+ onBackPressed = moreMenuViewModel::onClickBackButton,
+ onClickMenu = moreMenuViewModel::onClickMenuButton
+ )
+ }
+ }
+ }
+ }
+
+ private fun onNavigateMenu(menu: Menu) {
+ val intent = when (menu.type) {
+ MenuType.NOTI -> NoticeActivity.newIntent(this)
+ MenuType.DANGGN -> ShakeDanggnActivity.newIntent(this)
+ MenuType.MASHONG -> MashongActivity.newIntent(this)
+ MenuType.SETTING -> SettingActivity.newIntent(this)
+ MenuType.BIRTHDAY -> BirthdayActivity.newIntent(this)
+ }
+ startActivity(intent)
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/moremenu/MoreMenuViewModel.kt b/app/src/main/java/com/mashup/ui/moremenu/MoreMenuViewModel.kt
new file mode 100644
index 00000000..2bce389e
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/moremenu/MoreMenuViewModel.kt
@@ -0,0 +1,134 @@
+package com.mashup.ui.moremenu
+
+import androidx.core.os.bundleOf
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.example.moremenu.model.Menu
+import com.example.moremenu.model.Menu.Companion.toMenu
+import com.example.moremenu.model.MoreMenuSideEffect
+import com.example.moremenu.model.MoreMenuState
+import com.mashup.constant.log.LOG_MORE_ALARM
+import com.mashup.constant.log.LOG_MORE_BIRTH
+import com.mashup.constant.log.LOG_MORE_CARROT
+import com.mashup.constant.log.LOG_MORE_MASHONG
+import com.mashup.constant.log.LOG_MORE_SETTING
+import com.mashup.core.data.repository.MetaRepository
+import com.mashup.core.data.repository.PushHistoryRepository
+import com.mashup.core.network.Response
+import com.mashup.core.network.dto.PushHistoryResponse
+import com.mashup.core.network.dto.RnbResponse
+import com.mashup.util.AnalyticsManager
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class MoreMenuViewModel @Inject constructor(
+ private val metaRepository: MetaRepository,
+ private val pushHistoryRepository: PushHistoryRepository
+) : ViewModel() {
+
+ private val _moreMenuState = MutableStateFlow(MoreMenuState())
+ val moreMenuState = _moreMenuState.asStateFlow()
+
+ private val _moreMenuEvent = MutableSharedFlow()
+ val moreMenuEvent = _moreMenuEvent.asSharedFlow()
+
+ fun getMoreMenuState() {
+ viewModelScope.launch {
+ val rnb: Flow> = flow { emit(metaRepository.getRnb()) }
+ val notice: Flow> =
+ flow { emit(pushHistoryRepository.getPushHistory(page = 0, size = 1)) }
+
+ combine(rnb, notice) { rnbFlow, noticeFlow ->
+ rnbFlow to noticeFlow
+ }.collect { (rnb, notice) ->
+
+ when {
+ rnb.isSuccess() && notice.isSuccess() -> {
+ val rnbResponse = rnb.data?.toMenu() ?: emptyList()
+ val isHasNewNotice = notice.data?.unread?.isNotEmpty() ?: false
+ _moreMenuState.value = MoreMenuState(
+ menus = rnbResponse,
+ isShowNewIcon = isHasNewNotice
+ )
+ }
+
+ rnb.isSuccess() && notice.isSuccess().not() -> {
+ val rnbResponse = rnb.data?.toMenu() ?: emptyList()
+ _moreMenuState.value = MoreMenuState(
+ menus = rnbResponse,
+ isShowNewIcon = false
+ )
+ }
+
+ rnb.isSuccess().not() -> {
+ _moreMenuState.value = MoreMenuState(
+ menus = emptyList(),
+ isShowNewIcon = false
+ )
+ }
+ }
+ }
+ }
+ }
+
+ fun onClickBackButton() {
+ viewModelScope.launch {
+ _moreMenuEvent.emit(MoreMenuSideEffect.NavigateBackStack)
+ }
+ }
+
+ fun onClickMenuButton(menu: Menu) {
+ val bundle = bundleOf(
+ "place" to "LIST",
+ "type" to menu.menuName
+ )
+ when (menu) {
+ is Menu.Noti -> {
+ AnalyticsManager.addEvent(
+ eventName = LOG_MORE_ALARM,
+ params = bundle
+ )
+ }
+
+ is Menu.Setting -> {
+ AnalyticsManager.addEvent(
+ eventName = LOG_MORE_SETTING,
+ params = bundle
+ )
+ }
+
+ is Menu.Mashong -> {
+ AnalyticsManager.addEvent(
+ eventName = LOG_MORE_MASHONG,
+ params = bundle
+ )
+ }
+
+ is Menu.Danggn -> {
+ AnalyticsManager.addEvent(
+ eventName = LOG_MORE_CARROT,
+ params = bundle
+ )
+ }
+
+ is Menu.BirthDay -> {
+ AnalyticsManager.addEvent(
+ eventName = LOG_MORE_BIRTH,
+ params = bundle
+ )
+ }
+ }
+ viewModelScope.launch {
+ _moreMenuEvent.emit(MoreMenuSideEffect.NavigateMenu(menu))
+ }
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/mypage/AttendanceType.kt b/app/src/main/java/com/mashup/ui/mypage/AttendanceType.kt
index e70239e9..1fdd755d 100644
--- a/app/src/main/java/com/mashup/ui/mypage/AttendanceType.kt
+++ b/app/src/main/java/com/mashup/ui/mypage/AttendanceType.kt
@@ -19,6 +19,8 @@ enum class AttendanceType(@DrawableRes val resourceId: Int) {
MASHUP_SUBLEADER(R.drawable.img_mashupsubleader),
TECH_BLOG_WRITE(R.drawable.img_techblogwrite),
MASHUP_CONTENTS_WRITE(R.drawable.img_mashupcontentswrite),
+ ADD_SCORE_DURING_SEMINAR_ACTIVITY_0_5(R.drawable.img_presentation),
+ ADD_SCORE_DURING_SEMINAR_ACTIVITY_1(R.drawable.img_presentation),
DEFAULT(R.drawable.img_default_score);
companion object {
diff --git a/app/src/main/java/com/mashup/ui/mypage/MyPageFragment.kt b/app/src/main/java/com/mashup/ui/mypage/MyPageFragment.kt
index a4310bba..5cb2878a 100644
--- a/app/src/main/java/com/mashup/ui/mypage/MyPageFragment.kt
+++ b/app/src/main/java/com/mashup/ui/mypage/MyPageFragment.kt
@@ -9,11 +9,13 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.viewModels
import com.mashup.R
import com.mashup.base.BaseFragment
+import com.mashup.core.common.extensions.setStatusBarColorRes
import com.mashup.databinding.FragmentMyPageBinding
import com.mashup.feature.mypage.profile.model.ProfileCardData
import com.mashup.ui.setting.SettingActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
+import com.mashup.core.common.R as CR
@AndroidEntryPoint
class MyPageFragment : BaseFragment() {
@@ -62,10 +64,15 @@ class MyPageFragment : BaseFragment() {
override fun initViews() {
super.initViews()
+ initStatusBar()
initSwipeRefresh()
initRecyclerView()
}
+ private fun initStatusBar() {
+ requireActivity().setStatusBarColorRes(CR.color.gray50)
+ }
+
private fun initSwipeRefresh() {
viewBinding.layoutSwipe.apply {
setOnRefreshListener { viewModel.getMyPageData() }
diff --git a/app/src/main/java/com/mashup/ui/notice/NoticeActivity.kt b/app/src/main/java/com/mashup/ui/notice/NoticeActivity.kt
new file mode 100644
index 00000000..a68d2440
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/notice/NoticeActivity.kt
@@ -0,0 +1,90 @@
+package com.mashup.ui.notice
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.viewModels
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import com.example.notice.NoticeRoute
+import com.example.notice.model.NoticeSideEffect
+import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.ui.danggn.ShakeDanggnActivity
+import com.mashup.ui.login.LoginType
+import com.mashup.ui.main.MainActivity
+import com.mashup.ui.qrscan.QRScanActivity
+import com.mashup.ui.webview.birthday.BirthdayActivity
+import com.mashup.ui.webview.mashong.MashongActivity
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class NoticeActivity : ComponentActivity() {
+ private val noticeViewModel: NoticeViewModel by viewModels()
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ MashUpTheme {
+ val noticeState by noticeViewModel.noticeState.collectAsState()
+
+ LaunchedEffect(Unit) {
+ noticeViewModel.noticeEvent.collect {
+ when (it) {
+ is NoticeSideEffect.OnBackPressed -> finish()
+ is NoticeSideEffect.OnNavigateMenu -> {
+ val intent = when (it.notice.linkType) {
+ "QR" -> {
+ QRScanActivity.newIntent(this@NoticeActivity)
+ }
+
+ "DANGGN" -> {
+ ShakeDanggnActivity.newIntent(this@NoticeActivity)
+ }
+
+ "BIRTHDAY" -> {
+ BirthdayActivity.newIntent(this@NoticeActivity)
+ }
+
+ "MASHONG" -> {
+ MashongActivity.newIntent(this@NoticeActivity)
+ }
+
+ "SEMINAR" -> {
+ MainActivity.newIntent(
+ this@NoticeActivity,
+ loginType = LoginType.AUTO
+ )
+ }
+
+ else -> {
+ MainActivity.newIntent(
+ this@NoticeActivity,
+ loginType = LoginType.AUTO
+ )
+ }
+ }
+ startActivity(intent)
+ }
+ }
+ }
+ }
+
+ NoticeRoute(
+ modifier = Modifier.fillMaxSize(),
+ noticeState = noticeState,
+ onBackPressed = noticeViewModel::onBackPressed,
+ onClickNoticeItem = noticeViewModel::onClickNoticeItem,
+ onLoadNextNotice = noticeViewModel::onLoadNextNotice
+ )
+ }
+ }
+ }
+
+ companion object {
+ fun newIntent(context: Context) = Intent(context, NoticeActivity::class.java)
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/notice/NoticeViewModel.kt b/app/src/main/java/com/mashup/ui/notice/NoticeViewModel.kt
new file mode 100644
index 00000000..f6f7539e
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/notice/NoticeViewModel.kt
@@ -0,0 +1,109 @@
+package com.mashup.ui.notice
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.example.notice.model.NoticeSideEffect
+import com.example.notice.model.NoticeState
+import com.mashup.core.data.repository.PushHistoryRepository
+import com.mashup.core.network.Response
+import com.mashup.core.network.dto.PushHistoryResponse
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class NoticeViewModel @Inject constructor(
+ private val pushHistoryRepository: PushHistoryRepository
+) : ViewModel() {
+
+ private val _noticeState = MutableStateFlow(NoticeState())
+ val noticeState = _noticeState.asStateFlow()
+
+ private val _noticeEvent = MutableSharedFlow()
+ val noticeEvent = _noticeEvent.asSharedFlow()
+
+ private var _currentPage = MutableStateFlow(0)
+
+ init {
+ viewModelScope.launch {
+ val noticeResponse: Response =
+ pushHistoryRepository.getPushHistory(
+ page = _currentPage.value,
+ size = PAGE_SIZE,
+ sort = DESC
+ )
+
+ if (noticeResponse.isSuccess()) {
+ val pushHistoryResponse = noticeResponse.data
+ onReadNewNoticeList()
+ _noticeState.value = NoticeState(
+ oldNoticeList = pushHistoryResponse?.read ?: emptyList(),
+ newNoticeList = pushHistoryResponse?.unread ?: emptyList(),
+ isError = false
+ )
+ } else {
+ _noticeState.value = NoticeState(
+ isError = true
+ )
+ }
+ }
+ }
+
+ fun onLoadNextNotice() {
+ viewModelScope.launch {
+ _currentPage.value += 1
+ val noticeResponse: Response =
+ pushHistoryRepository.getPushHistory(
+ page = _currentPage.value,
+ size = PAGE_SIZE,
+ sort = DESC
+ )
+
+ if (noticeResponse.isSuccess()) {
+ val pushHistoryResponse = noticeResponse.data
+ onReadNewNoticeList()
+ _noticeState.value = NoticeState(
+ oldNoticeList = _noticeState.value.oldNoticeList + pushHistoryResponse?.read.orEmpty(),
+ newNoticeList = _noticeState.value.newNoticeList + pushHistoryResponse?.unread.orEmpty(),
+ isError = false
+ )
+ } else {
+ _noticeState.value = NoticeState(
+ isError = true
+ )
+ }
+ }
+ }
+
+ fun onBackPressed() {
+ viewModelScope.launch {
+ _noticeEvent.emit(NoticeSideEffect.OnBackPressed)
+ }
+ }
+
+ fun onClickNoticeItem(notice: PushHistoryResponse.Notice) {
+ viewModelScope.launch {
+ _noticeEvent.emit(NoticeSideEffect.OnNavigateMenu(notice))
+ }
+ }
+
+ private fun onReadNewNoticeList() {
+ viewModelScope.launch {
+ val currentPage = _currentPage.value
+ pushHistoryRepository.postPushHistoryCheck(
+ page = currentPage,
+ size = PAGE_SIZE,
+ sort = DESC
+ )
+ }
+ }
+
+ companion object {
+ const val PAGE_SIZE = 100
+ const val DESC = "createdAt,desc"
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/password/PasswordActivity.kt b/app/src/main/java/com/mashup/ui/password/PasswordActivity.kt
index fc2df900..4c4bc5ab 100644
--- a/app/src/main/java/com/mashup/ui/password/PasswordActivity.kt
+++ b/app/src/main/java/com/mashup/ui/password/PasswordActivity.kt
@@ -10,7 +10,7 @@ import com.mashup.R
import com.mashup.base.BaseActivity
import com.mashup.constant.EXTRA_ANIMATION
import com.mashup.constant.log.KEY_PLACE
-import com.mashup.constant.log.LOG_BACK
+import com.mashup.constant.log.LOG_COMMON_BACK
import com.mashup.constant.log.LOG_PLACE_CHANGE_PASSWORD
import com.mashup.constant.log.LOG_PLACE_ENTER_ID
import com.mashup.core.common.model.NavigationAnimationType
@@ -41,7 +41,7 @@ class PasswordActivity : BaseActivity() {
override fun onBackPressed() {
getPlaceGALog()?.run {
AnalyticsManager.addEvent(
- LOG_BACK,
+ LOG_COMMON_BACK,
bundleOf(KEY_PLACE to this)
)
}
diff --git a/app/src/main/java/com/mashup/ui/schedule/ScheduleFragment.kt b/app/src/main/java/com/mashup/ui/schedule/ScheduleFragment.kt
index 1af862c7..fc4e15cf 100644
--- a/app/src/main/java/com/mashup/ui/schedule/ScheduleFragment.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/ScheduleFragment.kt
@@ -1,5 +1,6 @@
package com.mashup.ui.schedule
+import android.content.Intent
import android.os.Bundle
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
@@ -7,10 +8,14 @@ import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import com.mashup.R
import com.mashup.base.BaseFragment
+import com.mashup.core.common.extensions.setStatusBarColorRes
+import com.mashup.core.common.utils.ToastUtil
import com.mashup.core.ui.theme.MashUpTheme
import com.mashup.databinding.FragmentScheduleBinding
import com.mashup.ui.main.MainViewModel
+import com.mashup.ui.moremenu.MoreMenuActivity
import dagger.hilt.android.AndroidEntryPoint
+import com.mashup.core.common.R as CR
@AndroidEntryPoint
class ScheduleFragment : BaseFragment() {
@@ -27,12 +32,20 @@ class ScheduleFragment : BaseFragment() {
override fun initViews() {
super.initViews()
+ requireActivity().setStatusBarColorRes(CR.color.white)
viewBinding.cvSchedule.setContent {
MashUpTheme {
ScheduleRoute(
modifier = Modifier.fillMaxSize(),
mainViewModel = mainViewModel,
- viewModel = viewModel
+ viewModel = viewModel,
+ onClickMoreMenuIcon = {
+ val intent = Intent(requireActivity(), MoreMenuActivity::class.java)
+ requireActivity().startActivity(intent)
+ },
+ makeToast = { message ->
+ ToastUtil.showToast(requireContext(), message)
+ }
)
}
}
diff --git a/app/src/main/java/com/mashup/ui/schedule/ScheduleRoute.kt b/app/src/main/java/com/mashup/ui/schedule/ScheduleRoute.kt
new file mode 100644
index 00000000..67494bb6
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/schedule/ScheduleRoute.kt
@@ -0,0 +1,267 @@
+package com.mashup.ui.schedule
+
+import android.content.Context
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.LocalOverscrollConfiguration
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.pullrefresh.PullRefreshIndicator
+import androidx.compose.material.pullrefresh.pullRefresh
+import androidx.compose.material.pullrefresh.rememberPullRefreshState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.mashup.R
+import com.mashup.constant.log.LOG_EVENT_LIST_ALL
+import com.mashup.constant.log.LOG_EVENT_LIST_STATUS_CONFIRM
+import com.mashup.constant.log.LOG_EVENT_LIST_WEEK
+import com.mashup.constant.log.LOG_EVENT_LIST_WEEK_MASHONG
+import com.mashup.core.common.extensions.fromHtml
+import com.mashup.core.ui.colors.Brand500
+import com.mashup.core.ui.colors.Gray50
+import com.mashup.core.ui.colors.White
+import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.core.ui.widget.MashUpHtmlText
+import com.mashup.ui.attendance.platform.PlatformAttendanceActivity
+import com.mashup.ui.main.MainViewModel
+import com.mashup.ui.schedule.component.ScheduleTabRow
+import com.mashup.ui.schedule.detail.ScheduleDetailActivity
+import com.mashup.ui.schedule.model.ScheduleType
+import com.mashup.ui.webview.mashong.MashongActivity
+import com.mashup.util.AnalyticsManager
+import com.mashup.core.common.R as CR
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun ScheduleRoute(
+ mainViewModel: MainViewModel,
+ viewModel: ScheduleViewModel,
+ modifier: Modifier = Modifier,
+ onClickMoreMenuIcon: () -> Unit = {},
+ makeToast: (String) -> Unit = {}
+) {
+ val context = LocalContext.current
+
+ var title by remember { mutableStateOf("") }
+
+ var isRefreshing by remember { mutableStateOf(false) }
+ val pullRefreshState = rememberPullRefreshState(
+ refreshing = isRefreshing,
+ onRefresh = {
+ isRefreshing = true
+ viewModel.getScheduleList() // refresh api
+ }
+ )
+
+ val lifecycle = LocalLifecycleOwner.current
+ LaunchedEffect(Unit) {
+ lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ mainViewModel.onAttendance.collect {
+ viewModel.getScheduleList()
+ }
+ }
+ }
+
+ val scheduleState by viewModel.scheduleState.collectAsState()
+ LaunchedEffect(scheduleState) {
+ when (val state = scheduleState) {
+ is ScheduleState.Loading -> {}
+ is ScheduleState.Init -> {
+ isRefreshing = false
+ }
+
+ is ScheduleState.Success -> {
+ isRefreshing = false
+ title = context.setUiOfScheduleTitle(state.scheduleTitleState)
+ }
+
+ is ScheduleState.Error -> {
+ isRefreshing = false
+ }
+ }
+ }
+
+ val weeklyListState = rememberLazyListState()
+ val dailyListState = rememberLazyListState()
+
+ var selectedTabIndex by remember { mutableIntStateOf(0) }
+ CompositionLocalProvider(
+ LocalOverscrollConfiguration provides null
+ ) {
+ Box(
+ modifier = modifier
+ .pullRefresh(pullRefreshState)
+ .background(color = if (ScheduleType.values()[selectedTabIndex] == ScheduleType.WEEK) Color.White else Gray50)
+ ) {
+ LazyColumn(
+ modifier = modifier,
+ state = if (selectedTabIndex == 0) weeklyListState else dailyListState
+ ) {
+ item {
+ ScheduleTopbar(
+ title,
+ onClickMoreMenuIcon = onClickMoreMenuIcon
+ )
+ Spacer(
+ modifier = Modifier
+ .height(26.dp)
+ .fillMaxWidth()
+ .background(Color.White)
+ )
+ }
+
+ stickyHeader {
+ ScheduleTabRow(
+ modifier = Modifier.background(White),
+ selectedTabIndex = selectedTabIndex,
+ updateSelectedTabIndex = { index ->
+ if (index == 0) {
+ AnalyticsManager.addEvent(eventName = LOG_EVENT_LIST_WEEK)
+ } else {
+ AnalyticsManager.addEvent(eventName = LOG_EVENT_LIST_ALL)
+ }
+ selectedTabIndex = index
+ }
+ )
+ }
+
+ item {
+ when (scheduleState) {
+ is ScheduleState.Error -> {}
+ is ScheduleState.Init -> {}
+ else -> {
+ ScheduleScreen(
+ modifier = Modifier.fillMaxSize(),
+ scheduleState = scheduleState,
+ dailyListState = dailyListState,
+ onClickScheduleInformation = { id, type -> context.moveToScheduleInformation(id, type) },
+ onClickMashongButton = {
+ AnalyticsManager.addEvent(eventName = LOG_EVENT_LIST_WEEK_MASHONG)
+ context.moveToMashong()
+ },
+ makeToast = makeToast,
+ refreshState = isRefreshing,
+ scheduleType = ScheduleType.values()[selectedTabIndex]
+ )
+ }
+ }
+ }
+ }
+
+ PullRefreshIndicator(
+ modifier = Modifier.align(Alignment.TopCenter),
+ scale = true,
+ contentColor = Brand500,
+ refreshing = isRefreshing,
+ state = pullRefreshState
+ )
+ }
+ }
+}
+
+fun Context.setUiOfScheduleTitle(scheduleTitleState: ScheduleTitleState): String {
+ return when (scheduleTitleState) {
+ ScheduleTitleState.Empty -> {
+ getString(R.string.empty_schedule)
+ }
+
+ is ScheduleTitleState.End -> {
+ getString(R.string.end_schedule, scheduleTitleState.generatedNumber)
+ }
+
+ is ScheduleTitleState.DateCount -> {
+ getString(R.string.event_list_title, scheduleTitleState.dataCount)
+ }
+
+ is ScheduleTitleState.SchedulePreparing -> {
+ getString(R.string.preparing_attendance)
+ }
+ }
+}
+
+fun Context.moveToScheduleInformation(scheduleId: Int, scheduleType: String) {
+ startActivity(
+ ScheduleDetailActivity.newIntent(this, scheduleId, scheduleType)
+ )
+}
+
+fun Context.moveToAttendance(scheduleId: Int) {
+ AnalyticsManager.addEvent(eventName = LOG_EVENT_LIST_STATUS_CONFIRM)
+ startActivity(
+ PlatformAttendanceActivity.newIntent(this, scheduleId)
+ )
+}
+
+fun Context.moveToMashong() {
+ startActivity(
+ MashongActivity.newIntent(this)
+ )
+}
+
+@Composable
+fun ScheduleTopbar(
+ title: String,
+ onClickMoreMenuIcon: () -> Unit = {}
+) {
+ Row(
+ modifier = Modifier
+ .background(White)
+ .padding(horizontal = 20.dp)
+ .padding(top = 24.dp)
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ MashUpHtmlText(
+ content = title.fromHtml(),
+ modifier = Modifier.weight(1f, false),
+ textAppearance = CR.style.TextAppearance_Mashup_Header1_24_B
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Image(
+ painter = painterResource(id = CR.drawable.ic_more),
+ contentDescription = null,
+ modifier = Modifier
+ .size(20.dp)
+ .clickable {
+ onClickMoreMenuIcon()
+ }
+ )
+ }
+}
+
+@Preview
+@Composable
+fun PreviewScheduleTopbar() {
+ MashUpTheme {
+ ScheduleTopbar(title = "다음 세미나 준비 중이에요.\n조금만 기다려주세요.")
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/schedule/ScheduleScreen.kt b/app/src/main/java/com/mashup/ui/schedule/ScheduleScreen.kt
index 8aa9b5e2..faeb21c2 100644
--- a/app/src/main/java/com/mashup/ui/schedule/ScheduleScreen.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/ScheduleScreen.kt
@@ -1,308 +1,65 @@
package com.mashup.ui.schedule
-import android.content.Context
-import androidx.appcompat.widget.AppCompatTextView
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.pager.HorizontalPager
-import androidx.compose.foundation.pager.PageSize
-import androidx.compose.foundation.pager.rememberPagerState
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.pullrefresh.PullRefreshIndicator
-import androidx.compose.material.pullrefresh.pullRefresh
-import androidx.compose.material.pullrefresh.rememberPullRefreshState
+import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.mashup.R
-import com.mashup.constant.log.LOG_SCHEDULE_EVENT_DETAIL
-import com.mashup.constant.log.LOG_SCHEDULE_STATUS_CONFIRM
-import com.mashup.core.common.extensions.fromHtml
-import com.mashup.core.ui.colors.Brand500
-import com.mashup.ui.attendance.platform.PlatformAttendanceActivity
-import com.mashup.ui.danggn.ShakeDanggnActivity
-import com.mashup.ui.main.MainViewModel
-import com.mashup.ui.main.model.MainPopupType
-import com.mashup.ui.schedule.detail.ScheduleDetailActivity
-import com.mashup.ui.schedule.item.ScheduleViewPagerEmptyItem
-import com.mashup.ui.schedule.item.ScheduleViewPagerInProgressItem
-import com.mashup.ui.schedule.item.ScheduleViewPagerSuccessItem
-import com.mashup.ui.schedule.model.ScheduleCard
+import androidx.core.os.bundleOf
+import com.mashup.constant.log.LOG_EVENT_LIST_EVENT_DETAIL
+import com.mashup.ui.schedule.component.DailySchedule
+import com.mashup.ui.schedule.component.WeeklySchedule
+import com.mashup.ui.schedule.model.ScheduleType
import com.mashup.util.AnalyticsManager
-import com.mashup.util.debounce
-import kotlin.math.abs
-import kotlin.math.absoluteValue
-
-@OptIn(ExperimentalMaterialApi::class)
-@Composable
-fun ScheduleRoute(
- mainViewModel: MainViewModel,
- viewModel: ScheduleViewModel,
- modifier: Modifier = Modifier
-) {
- var isRefreshing by remember { mutableStateOf(false) }
-
- val pullRefreshState = rememberPullRefreshState(
- refreshing = isRefreshing,
- onRefresh = {
- isRefreshing = true
- // refresh api
- viewModel.getScheduleList()
- }
- )
- val context = LocalContext.current
-
- val state by viewModel.scheduleState.collectAsState()
-
- var title by remember { mutableStateOf("") }
-
- val lifecycle = LocalLifecycleOwner.current
-
- LaunchedEffect(Unit) {
- lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
- mainViewModel.onAttendance.collect {
- viewModel.getScheduleList()
- }
- }
- }
-
- LaunchedEffect(state) {
- when (state) {
- is ScheduleState.Loading -> {}
- is ScheduleState.Empty -> { isRefreshing = false }
- is ScheduleState.Success -> {
- isRefreshing = false
- title = context.setUiOfScheduleTitle(
- (state as ScheduleState.Success).scheduleTitleState
- )
- }
- is ScheduleState.Error -> { isRefreshing = false }
- }
- }
-
- val scrollState = rememberScrollState()
-
- Box(
- modifier = modifier.pullRefresh(pullRefreshState)
- ) {
- Column(
- modifier = modifier
- ) {
- Row(
- modifier = Modifier
- .padding(horizontal = 20.dp)
- .padding(top = 24.dp)
- .fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceBetween
- ) {
- AndroidView(
- factory = { context ->
- AppCompatTextView(
- context
- ).apply {
- setTextAppearance(
- com.mashup.core.common.R.style.TextAppearance_Mashup_Header1_24_B
- )
- text = title
- }
- },
- update = { view ->
- view.text = title
- }
- )
- if (title.isNotEmpty()) {
- val coroutineScope = rememberCoroutineScope()
- Image(
- modifier = Modifier.clickable {
- debounce(500L, scope = coroutineScope, destinationFunction = {
- mainViewModel.disablePopup(MainPopupType.DANGGN)
- })
- context.startActivity(
- ShakeDanggnActivity.newIntent(context)
- )
- },
- painter = painterResource(
- id = com.mashup.core.common.R.drawable.img_carrot_button
- ),
- contentDescription = null
- )
- }
- }
- when (state) {
- is ScheduleState.Error -> {}
- is ScheduleState.Empty -> {}
- else -> {
- ScheduleScreen(
- modifier = Modifier.fillMaxSize().verticalScroll(scrollState),
- scheduleState = state,
- onClickScheduleInformation = { scheduleId: Int ->
- AnalyticsManager.addEvent(eventName = LOG_SCHEDULE_EVENT_DETAIL)
- context.startActivity(
- ScheduleDetailActivity.newIntent(context, scheduleId)
- )
- },
- onClickAttendance = { scheduleId: Int ->
- AnalyticsManager.addEvent(eventName = LOG_SCHEDULE_STATUS_CONFIRM)
- context.startActivity(
- PlatformAttendanceActivity.newIntent(context, scheduleId)
- )
- },
- refreshState = isRefreshing
-
- )
- }
- }
- }
-
- PullRefreshIndicator(
- modifier = Modifier.align(Alignment.TopCenter),
- scale = true,
- contentColor = Brand500,
- refreshing = isRefreshing,
- state = pullRefreshState
- )
- }
-}
@Composable
fun ScheduleScreen(
scheduleState: ScheduleState,
+ dailyListState: LazyListState,
modifier: Modifier = Modifier,
- onClickScheduleInformation: (Int) -> Unit = {},
- onClickAttendance: (Int) -> Unit = {},
+ scheduleType: ScheduleType = ScheduleType.WEEK,
+ onClickScheduleInformation: (Int, String) -> Unit = { _, _ -> },
+ onClickMashongButton: () -> Unit = {},
+ makeToast: (String) -> Unit = {},
refreshState: Boolean = false
) {
- var cacheScheduleState by remember {
- mutableStateOf(scheduleState)
- }
-
LaunchedEffect(scheduleState) {
if (scheduleState is ScheduleState.Success) {
- cacheScheduleState = scheduleState
+ if (scheduleState.schedulePosition < scheduleState.scheduleList.size) {
+ dailyListState.animateScrollToItem(scheduleState.schedulePosition)
+ }
}
}
- when (cacheScheduleState) {
- is ScheduleState.Success -> {
- val castingState = cacheScheduleState as ScheduleState.Success
- val horizontalPagerState = rememberPagerState(
- initialPage = if (castingState.scheduleList.size < 6) 1 else castingState.scheduleList.size - 4,
- pageCount = { castingState.scheduleList.size }
+ when (scheduleType) {
+ ScheduleType.WEEK -> {
+ WeeklySchedule(
+ scheduleState = scheduleState,
+ modifier = modifier,
+ onClickScheduleInformation = { scheduleId: Int, type: String ->
+ AnalyticsManager.addEvent(
+ eventName = LOG_EVENT_LIST_EVENT_DETAIL,
+ params = bundleOf("place" to "이번주일정")
+ )
+ onClickScheduleInformation(scheduleId, type)
+ },
+ onClickMashongButton = onClickMashongButton,
+ makeToast = makeToast,
+ refreshState = refreshState
)
- LaunchedEffect(refreshState) {
- if (refreshState.not()) { // refresh 가 끝났을 경우
- horizontalPagerState.animateScrollToPage(castingState.schedulePosition)
- }
- }
+ }
- HorizontalPager(
+ ScheduleType.TOTAL -> {
+ DailySchedule(
+ scheduleState = scheduleState,
modifier = modifier,
- state = horizontalPagerState,
- pageSpacing = 12.dp,
- pageSize = PageSize.Fill,
- contentPadding = PaddingValues(33.dp),
- verticalAlignment = Alignment.Top
- ) { index ->
- when (val data = castingState.scheduleList[index]) {
- is ScheduleCard.EmptySchedule -> {
- Box(
- modifier = Modifier.fillMaxSize()
- ) {
- ScheduleViewPagerEmptyItem(
- modifier = Modifier
- .graphicsLayer {
- val pageOffset = (
- (horizontalPagerState.currentPage - index) + horizontalPagerState
- .currentPageOffsetFraction
- ).absoluteValue
- scaleY = 1 - 0.1f * abs(pageOffset)
- },
- data = data
- )
- }
- }
- is ScheduleCard.EndSchedule -> {
- Box(
- modifier = Modifier.fillMaxSize()
- ) {
- ScheduleViewPagerSuccessItem(
- modifier = Modifier
- .graphicsLayer {
- val pageOffset = (
- (horizontalPagerState.currentPage - index) + horizontalPagerState
- .currentPageOffsetFraction
- ).absoluteValue
- scaleY = 1 - 0.1f * abs(pageOffset)
- },
- data = data,
- onClickScheduleInformation = onClickScheduleInformation,
- onClickAttendance = onClickAttendance
- )
- }
- }
- is ScheduleCard.InProgressSchedule -> {
- Box(
- modifier = Modifier.fillMaxSize()
- ) {
- ScheduleViewPagerInProgressItem(
- modifier = Modifier
- .graphicsLayer {
- val pageOffset = (
- (horizontalPagerState.currentPage - index) + horizontalPagerState
- .currentPageOffsetFraction
- ).absoluteValue
- scaleY = 1 - 0.1f * abs(pageOffset)
- },
- data = data,
- onClickScheduleInformation = onClickScheduleInformation,
- onClickAttendance = onClickAttendance
- )
- }
- }
+ onClickScheduleInformation = { scheduleId: Int, type: String ->
+ AnalyticsManager.addEvent(
+ eventName = LOG_EVENT_LIST_EVENT_DETAIL,
+ params = bundleOf("place" to "전체일정")
+ )
+ onClickScheduleInformation(scheduleId, type)
}
- }
- }
- else -> {}
- }
-}
-fun Context.setUiOfScheduleTitle(scheduleTitleState: ScheduleTitleState): String {
- return when (scheduleTitleState) {
- ScheduleTitleState.Empty -> {
- getString(R.string.empty_schedule)
- }
- is ScheduleTitleState.End -> {
- getString(R.string.end_schedule, scheduleTitleState.generatedNumber)
- }
- is ScheduleTitleState.DateCount -> {
- getString(R.string.event_list_title, scheduleTitleState.dataCount).fromHtml().toString()
- }
- is ScheduleTitleState.SchedulePreparing -> {
- getString(R.string.preparing_attendance)
+ )
}
}
}
diff --git a/app/src/main/java/com/mashup/ui/schedule/ScheduleViewModel.kt b/app/src/main/java/com/mashup/ui/schedule/ScheduleViewModel.kt
index 71fcb289..cc5bea37 100644
--- a/app/src/main/java/com/mashup/ui/schedule/ScheduleViewModel.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/ScheduleViewModel.kt
@@ -1,6 +1,9 @@
package com.mashup.ui.schedule
import com.mashup.core.common.base.BaseViewModel
+import com.mashup.core.common.extensions.month
+import com.mashup.core.common.extensions.year
+import com.mashup.core.ui.widget.PlatformType
import com.mashup.data.dto.ScheduleResponse
import com.mashup.data.dto.SchedulesProgress
import com.mashup.data.repository.AttendanceRepository
@@ -8,12 +11,19 @@ import com.mashup.data.repository.ScheduleRepository
import com.mashup.datastore.data.repository.AppPreferenceRepository
import com.mashup.datastore.data.repository.UserPreferenceRepository
import com.mashup.ui.schedule.model.ScheduleCard
+import com.mashup.ui.schedule.util.convertCamelCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
+import org.threeten.bp.DayOfWeek
+import org.threeten.bp.Instant
+import org.threeten.bp.LocalDateTime
+import org.threeten.bp.ZoneId
+import org.threeten.bp.temporal.TemporalAdjusters
+import java.util.Date
import javax.inject.Inject
@HiltViewModel
@@ -23,7 +33,7 @@ class ScheduleViewModel @Inject constructor(
private val scheduleRepository: ScheduleRepository,
private val attendanceRepository: AttendanceRepository
) : BaseViewModel() {
- private val _scheduleState = MutableStateFlow(ScheduleState.Empty)
+ private val _scheduleState = MutableStateFlow(ScheduleState.Init)
val scheduleState: StateFlow = _scheduleState
private val _showCoachMark = MutableSharedFlow()
@@ -37,19 +47,27 @@ class ScheduleViewModel @Inject constructor(
scheduleRepository.getScheduleList(generateNumber)
.onSuccess { response ->
+
+ val weeklySchedule = if (response.scheduleList.isEmpty()) {
+ listOf()
+ } else {
+ response.scheduleList.filterSchedulesForCurrentWeek()
+ }
_scheduleState.emit(
ScheduleState.Success(
scheduleTitleState = when {
response.progress == SchedulesProgress.DONE -> {
ScheduleTitleState.End(generateNumber)
}
+
response.progress == SchedulesProgress.NOT_REGISTERED -> {
ScheduleTitleState.Empty
}
- response.progress == SchedulesProgress.ON_GOING &&
- response.dateCount != null -> {
+
+ response.progress == SchedulesProgress.ON_GOING && response.dateCount != null -> {
ScheduleTitleState.DateCount(response.dateCount)
}
+
else -> {
ScheduleTitleState.SchedulePreparing
}
@@ -59,7 +77,16 @@ class ScheduleViewModel @Inject constructor(
} else {
response.scheduleList.map { mapperToScheduleCard(it) }
},
- schedulePosition = getSchedulePosition(response.scheduleList)
+ monthlyScheduleList = getMonthlyScheduleList(response.scheduleList),
+ schedulePosition = getSchedulePosition(response.scheduleList),
+ weeklySchedule = weeklySchedule.map { mapperToScheduleCard(it) },
+ weeklySchedulePosition = if (weeklySchedule.isEmpty()) {
+ 0
+ } else {
+ getSchedulePosition(
+ weeklySchedule
+ )
+ }
)
)
showCoachMark(response.scheduleList)
@@ -69,13 +96,33 @@ class ScheduleViewModel @Inject constructor(
ScheduleState.Success(
scheduleTitleState = ScheduleTitleState.Empty,
scheduleList = listOf(ScheduleCard.EmptySchedule()),
- schedulePosition = 0
+ monthlyScheduleList = emptyList(),
+ schedulePosition = 0,
+ weeklySchedule = listOf(ScheduleCard.EmptySchedule()),
+ weeklySchedulePosition = 0
)
)
}
}
}
+ private fun List.filterSchedulesForCurrentWeek(): List {
+ val koreaZone = ZoneId.of("Asia/Seoul")
+ val now = LocalDateTime.now(koreaZone)
+ val startOfWeek = now.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).toLocalDate().atTime(0, 0, 0, 0).atZone(koreaZone).toLocalDateTime()
+ val endOfWeek = now.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)).toLocalDate().atTime(23, 59, 59, 999999999).atZone(koreaZone).toLocalDateTime()
+ val result = this.filter {
+ val scheduleStart = it.startedAt.toLocalDateTime(koreaZone)
+ scheduleStart.isAfter(startOfWeek) && scheduleStart.isBefore(endOfWeek)
+ }
+ return result
+ }
+
+ private fun Date.toLocalDateTime(zone: ZoneId = ZoneId.systemDefault()): LocalDateTime {
+ val instant = Instant.ofEpochMilli(this.time)
+ return LocalDateTime.ofInstant(instant, zone)
+ }
+
override fun handleErrorCode(code: String) {
mashUpScope {
_scheduleState.emit(ScheduleState.Error(code))
@@ -89,7 +136,7 @@ class ScheduleViewModel @Inject constructor(
attendanceRepository.getScheduleAttendanceInfo(scheduleResponse.scheduleId)
.onSuccess { response ->
- return if (response.attendanceInfos.isEmpty()) {
+ return if (response.attendanceInfos.isEmpty() && scheduleResponse.scheduleType.convertCamelCase() == PlatformType.Seminar) {
ScheduleCard.InProgressSchedule(
scheduleResponse = scheduleResponse,
attendanceInfo = response
@@ -108,6 +155,14 @@ class ScheduleViewModel @Inject constructor(
return ScheduleCard.EmptySchedule(scheduleResponse)
}
+ private fun getMonthlyScheduleList(scheduleList: List): List>> {
+ return scheduleList.groupBy {
+ val year = it.startedAt.year()
+ val month = it.startedAt.month()
+ "${year}년 ${month}월"
+ }.toList()
+ }
+
private fun getSchedulePosition(schedules: List): Int {
return schedules.size - schedules.filter { it.dateCount >= 0 }.size
}
@@ -133,12 +188,15 @@ sealed interface ScheduleTitleState {
}
sealed interface ScheduleState {
- object Empty : ScheduleState
+ object Init : ScheduleState
object Loading : ScheduleState
data class Success(
val scheduleTitleState: ScheduleTitleState,
val scheduleList: List,
- val schedulePosition: Int
+ val monthlyScheduleList: List>>,
+ val weeklySchedule: List,
+ val schedulePosition: Int,
+ val weeklySchedulePosition: Int
) : ScheduleState
data class Error(val code: String) : ScheduleState
diff --git a/app/src/main/java/com/mashup/ui/schedule/ViewEventTimeline.kt b/app/src/main/java/com/mashup/ui/schedule/ViewEventTimeline.kt
index 0632363e..40d2c435 100644
--- a/app/src/main/java/com/mashup/ui/schedule/ViewEventTimeline.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/ViewEventTimeline.kt
@@ -3,10 +3,13 @@ package com.mashup.ui.schedule
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Divider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -21,6 +24,7 @@ import com.mashup.core.ui.colors.Gray500
import com.mashup.core.ui.colors.Gray600
import com.mashup.core.ui.colors.Gray800
import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.core.ui.typography.Body5
import com.mashup.core.ui.typography.Caption2
import com.mashup.core.ui.typography.SubTitle3
@@ -39,29 +43,33 @@ fun ViewEventTimeline(
val (divider, attendanceImage, attendanceCaption, attendanceTime, attendanceStatus) = createRefs()
Image(
- modifier = Modifier.size(20.dp).constrainAs(attendanceImage) {
- start.linkTo(parent.start)
- top.linkTo(parent.top)
- bottom.linkTo(attendanceStatus.bottom)
- },
+ modifier = Modifier
+ .size(20.dp)
+ .constrainAs(attendanceImage) {
+ start.linkTo(parent.start)
+ top.linkTo(parent.top)
+ bottom.linkTo(attendanceStatus.bottom)
+ },
painter = painterResource(id = image),
contentDescription = null
)
Text(
modifier = Modifier.constrainAs(attendanceCaption) {
- top.linkTo(parent.top)
+ top.linkTo(attendanceImage.top)
+ bottom.linkTo(attendanceImage.bottom, margin = 3.dp)
start.linkTo(attendanceImage.end, 8.dp)
},
text = caption,
- style = Caption2,
+ style = Body5,
color = Gray600
)
Text(
modifier = Modifier.constrainAs(attendanceStatus) {
- top.linkTo(attendanceCaption.bottom, 2.dp)
- start.linkTo(attendanceCaption.start)
+ top.linkTo(attendanceCaption.top)
+ bottom.linkTo(attendanceCaption.bottom)
+ start.linkTo(attendanceCaption.end, 7.dp)
},
text = stringResource(status),
style = SubTitle3.copy(
@@ -79,18 +87,22 @@ fun ViewEventTimeline(
color = Gray500
)
if (isFinal.not()) {
- Divider(
+ Row(
modifier = Modifier.constrainAs(divider) {
start.linkTo(attendanceImage.start)
end.linkTo(attendanceImage.end)
- top.linkTo(attendanceStatus.bottom, 2.dp)
+ top.linkTo(attendanceStatus.bottom, 6.dp)
height = Dimension.value(16.dp)
width = Dimension.value(1.dp)
},
- thickness = 1.dp,
- color = Gray200
-
- )
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Divider(
+ modifier = Modifier.height(12.dp),
+ thickness = 1.dp,
+ color = Gray200
+ )
+ }
}
}
}
diff --git a/app/src/main/java/com/mashup/ui/schedule/component/DailySchedule.kt b/app/src/main/java/com/mashup/ui/schedule/component/DailySchedule.kt
new file mode 100644
index 00000000..464104ff
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/schedule/component/DailySchedule.kt
@@ -0,0 +1,83 @@
+package com.mashup.ui.schedule.component
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import com.mashup.core.ui.colors.Gray50
+import com.mashup.core.ui.colors.Gray600
+import com.mashup.core.ui.typography.Body5
+import com.mashup.ui.schedule.ScheduleState
+import com.mashup.ui.schedule.daily.DailyScheduleByMonth
+import com.mashup.core.common.R as CR
+
+@Composable
+fun DailySchedule(
+ scheduleState: ScheduleState,
+ modifier: Modifier = Modifier,
+ onClickScheduleInformation: (Int, String) -> Unit = { _, _ -> }
+) {
+ var cacheScheduleState by remember {
+ mutableStateOf(scheduleState)
+ }
+
+ LaunchedEffect(scheduleState) {
+ if (scheduleState is ScheduleState.Success) {
+ cacheScheduleState = scheduleState
+ }
+ }
+
+ (cacheScheduleState as? ScheduleState.Success)?.let { state ->
+ if (state.monthlyScheduleList.isEmpty()) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Spacer(modifier = Modifier.height(200.dp))
+ Image(
+ painter = painterResource(id = CR.drawable.img_placeholder_sleeping),
+ contentDescription = null,
+ modifier = Modifier.size(88.dp)
+ )
+ Spacer(modifier = Modifier.height(12.dp))
+ Text(
+ text = "열심히 일정을 준비하고 있어요\n조금만 기다려 주세요!",
+ style = Body5.copy(color = Gray600),
+ textAlign = TextAlign.Center
+ )
+ }
+ } else {
+ Spacer(modifier = Modifier.height(22.dp))
+ state.monthlyScheduleList.forEach { (title, scheduleList) ->
+ DailyScheduleByMonth(
+ title = title,
+ scheduleList = scheduleList,
+ modifier = Modifier
+ .background(Gray50)
+ .padding(horizontal = 20.dp),
+ isWeeklySchedule = { scheduleId ->
+ state.weeklySchedule.any { scheduleId == it.getScheduleId() }
+ },
+ onClickScheduleInformation = onClickScheduleInformation
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/schedule/component/ScheduleTabRow.kt b/app/src/main/java/com/mashup/ui/schedule/component/ScheduleTabRow.kt
new file mode 100644
index 00000000..195774f9
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/schedule/component/ScheduleTabRow.kt
@@ -0,0 +1,165 @@
+package com.mashup.ui.schedule.component
+
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Tab
+import androidx.compose.material3.TabPosition
+import androidx.compose.material3.TabRow
+import androidx.compose.material3.TabRowDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.mashup.core.ui.colors.Gray950
+import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.core.ui.typography.SubTitle1
+
+/**
+ * 일정 탭을 표시하는 컴포저블입니다.
+ * @param modifier Modifier
+ * @param tabMenu List 탭 메뉴 리스트
+ * @param selectedTabIndex Int 선택된 탭 인덱스
+ * @param updateSelectedTabIndex (Int) -> Unit 선택된 탭 인덱스 업데이트 함수
+ *
+ * 사용 예시
+ * ```
+ * val selectedTabIndex by remember { mutableIntStateOf(0) }
+ *
+ * ScheduleTabRow(
+ * modifier = Modifier.fillMaxWidth(),
+ * selectedTabIndex = selectedTabIndex,
+ * updateSelectedTabIndex = { selectedTabIndex = it }
+ * )
+ *
+ * when(selectedTabIndex) {
+ * 0 -> { // 이번 주 일정 Contents }
+ * 1 -> { // 전체 일정 Contents }
+ * }
+ * ```
+ */
+@Composable
+fun ScheduleTabRow(
+ modifier: Modifier = Modifier,
+ tabMenu: List = listOf("이번 주 일정", "전체 일정"),
+ selectedTabIndex: Int = 0,
+ updateSelectedTabIndex: (Int) -> Unit = {}
+) {
+ Column(
+ modifier = modifier
+ ) {
+ TabRow(
+ modifier = Modifier.fillMaxWidth(),
+ selectedTabIndex = selectedTabIndex,
+ containerColor = Color.Transparent,
+ indicator = { tabPositions ->
+ TabRowDefaults.Indicator(
+ color = Gray950,
+ modifier = Modifier
+ .customTabIndicatorOffset(
+ tabPositions[selectedTabIndex],
+ tabWidth = 150.dp
+ )
+ .clip(
+ RoundedCornerShape(20.dp)
+ )
+ )
+ },
+ tabs = {
+ tabMenu.forEachIndexed { index, a ->
+ Tab(
+ selected = index == selectedTabIndex,
+ onClick = {
+ updateSelectedTabIndex(index)
+ }
+ ) {
+ Spacer(
+ modifier = Modifier.height(2.5.dp)
+ )
+ Text(
+ text = a,
+ style = SubTitle1.copy(
+ fontWeight = if (index == selectedTabIndex) {
+ FontWeight.W700
+ } else {
+ FontWeight.W400
+ }
+
+ ),
+ color = Gray950
+ )
+ Spacer(
+ modifier = Modifier.height(14.5.dp)
+ )
+ }
+ }
+ }
+ )
+ }
+}
+
+fun Modifier.customTabIndicatorOffset(
+ currentTabPosition: TabPosition,
+ tabWidth: Dp
+): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "customTabIndicatorOffset"
+ value = currentTabPosition
+ }
+) {
+ val currentTabWidth by animateDpAsState(
+ targetValue = tabWidth,
+ animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing),
+ label = ""
+ )
+
+ val indicatorOffset by animateDpAsState(
+ targetValue = ((currentTabPosition.left + currentTabPosition.right - tabWidth) / 2),
+ animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing),
+ label = ""
+ )
+
+ fillMaxWidth()
+ .wrapContentSize(Alignment.BottomStart) // indicator 표시 위치
+ .offset(x = indicatorOffset)
+ .width(currentTabWidth)
+}
+
+@Preview
+@Composable
+private fun PreviewTabRow() {
+ MashUpTheme {
+ var selectedTabIndex by remember { mutableIntStateOf(0) }
+
+ ScheduleTabRow(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(color = Color.White),
+ selectedTabIndex = selectedTabIndex,
+ updateSelectedTabIndex = {
+ selectedTabIndex = it
+ }
+ )
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/schedule/component/WeeklySchedule.kt b/app/src/main/java/com/mashup/ui/schedule/component/WeeklySchedule.kt
new file mode 100644
index 00000000..6535d2e7
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/schedule/component/WeeklySchedule.kt
@@ -0,0 +1,133 @@
+package com.mashup.ui.schedule.component
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.PageSize
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.unit.dp
+import com.mashup.ui.schedule.ScheduleState
+import com.mashup.ui.schedule.item.EmptyScheduleItem
+import com.mashup.ui.schedule.item.ScheduleViewPagerEmptyItem
+import com.mashup.ui.schedule.item.ScheduleViewPagerInProgressItem
+import com.mashup.ui.schedule.item.ScheduleViewPagerSuccessItem
+import com.mashup.ui.schedule.model.ScheduleCard
+import kotlin.math.abs
+import kotlin.math.absoluteValue
+
+@Composable
+fun WeeklySchedule(
+ scheduleState: ScheduleState,
+ modifier: Modifier = Modifier,
+ onClickScheduleInformation: (Int, String) -> Unit = { _, _ -> },
+ onClickMashongButton: () -> Unit = {},
+ makeToast: (String) -> Unit = {},
+ refreshState: Boolean = false
+) {
+ var cacheScheduleState by remember {
+ mutableStateOf(scheduleState)
+ }
+
+ LaunchedEffect(scheduleState) {
+ if (scheduleState is ScheduleState.Success) {
+ cacheScheduleState = scheduleState
+ }
+ }
+
+ when (cacheScheduleState) {
+ is ScheduleState.Success -> {
+ val castingState = cacheScheduleState as ScheduleState.Success
+ val horizontalPagerState = rememberPagerState(
+ initialPage = castingState.weeklySchedulePosition,
+ pageCount = { castingState.weeklySchedule.size }
+ )
+ LaunchedEffect(refreshState) {
+ if (refreshState.not()) { // refresh 가 끝났을 경우
+ horizontalPagerState.animateScrollToPage(
+ castingState.weeklySchedulePosition
+ )
+ }
+ }
+
+ if (castingState.weeklySchedule.isEmpty()) {
+ EmptyScheduleItem(
+ modifier = modifier,
+ onClickMashongButton = onClickMashongButton
+ )
+ } else {
+ HorizontalPager(
+ modifier = modifier,
+ state = horizontalPagerState,
+ pageSpacing = 12.dp,
+ pageSize = PageSize.Fill,
+ contentPadding = PaddingValues(33.dp),
+ verticalAlignment = Alignment.Top
+ ) { index ->
+ when (val data = castingState.weeklySchedule[index]) {
+ is ScheduleCard.EmptySchedule -> {
+ Box(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ ScheduleViewPagerEmptyItem(
+ modifier = Modifier
+ .fillMaxSize()
+ .graphicsLayer {
+ val pageOffset =
+ ((horizontalPagerState.currentPage - index) + horizontalPagerState.currentPageOffsetFraction).absoluteValue
+ scaleY = 1 - 0.1f * abs(pageOffset)
+ },
+ data = data
+ )
+ }
+ }
+
+ is ScheduleCard.EndSchedule -> {
+ Box(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ ScheduleViewPagerSuccessItem(
+ modifier = Modifier.graphicsLayer {
+ val pageOffset =
+ ((horizontalPagerState.currentPage - index) + horizontalPagerState.currentPageOffsetFraction).absoluteValue
+ scaleY = 1 - 0.1f * abs(pageOffset)
+ },
+ data = data,
+ onClickScheduleInformation = onClickScheduleInformation,
+ makeToast = makeToast
+ )
+ }
+ }
+
+ is ScheduleCard.InProgressSchedule -> {
+ Box(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ ScheduleViewPagerInProgressItem(
+ modifier = Modifier.graphicsLayer {
+ val pageOffset =
+ ((horizontalPagerState.currentPage - index) + horizontalPagerState.currentPageOffsetFraction).absoluteValue
+ scaleY = 1 - 0.1f * abs(pageOffset)
+ },
+ data = data,
+ onClickScheduleInformation = onClickScheduleInformation
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+
+ else -> {}
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/schedule/daily/DailyScheduleByMonth.kt b/app/src/main/java/com/mashup/ui/schedule/daily/DailyScheduleByMonth.kt
new file mode 100644
index 00000000..dd795089
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/schedule/daily/DailyScheduleByMonth.kt
@@ -0,0 +1,86 @@
+package com.mashup.ui.schedule.daily
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.mashup.core.common.extensions.day
+import com.mashup.core.common.extensions.week
+import com.mashup.core.ui.colors.Gray500
+import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.core.ui.typography.Body3
+import com.mashup.core.ui.typography.Caption2
+import com.mashup.core.ui.typography.Title3
+import com.mashup.data.dto.ScheduleResponse
+
+/**
+ * DailyScheduleByMonth.kt
+ *
+ * Created by Minji Jeong on 2024/06/29
+ * Copyright © 2024 MashUp All rights reserved.
+ */
+
+@Composable
+fun DailyScheduleByMonth(
+ title: String,
+ scheduleList: List,
+ modifier: Modifier = Modifier,
+ isWeeklySchedule: (Int) -> Boolean = { false },
+ onClickScheduleInformation: (Int, String) -> Unit = { _, _ -> }
+) {
+ Column(modifier = modifier) {
+ Text(title, style = Title3)
+ Spacer(modifier = Modifier.height(18.dp))
+ scheduleList
+ .groupBy { it.startedAt.day() }
+ .forEach { (_, dailySchedule) ->
+ DailyScheduleByDay(dailySchedule, isWeeklySchedule, onClickScheduleInformation)
+ }
+ }
+}
+
+@Composable
+fun DailyScheduleByDay(
+ schedule: List,
+ isWeeklySchedule: (Int) -> Boolean,
+ onClickScheduleInformation: (Int, String) -> Unit
+) {
+ Row(modifier = Modifier.padding(bottom = 32.dp)) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.padding(top = 14.dp, end = 14.dp)
+ ) {
+ Text(text = "${schedule[0].startedAt.day()}", style = Body3)
+ Text(text = schedule[0].startedAt.week(), style = Caption2.copy(color = Gray500))
+ }
+
+ Column {
+ schedule.forEach {
+ val highlight = isWeeklySchedule.invoke(it.scheduleId)
+ DailyScheduleItem(
+ title = it.name,
+ time = it.getTimeLine(),
+ place = it.location?.detailAddress ?: "-",
+ highlight = highlight,
+ modifier = Modifier.padding(bottom = 12.dp),
+ onClickScheduleInformation = { onClickScheduleInformation.invoke(it.scheduleId, it.scheduleType) }
+ )
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun PreviewDailySchedule() {
+ MashUpTheme {
+ DailyScheduleByMonth("2024년 8월", emptyList())
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/schedule/daily/DailyScheduleItem.kt b/app/src/main/java/com/mashup/ui/schedule/daily/DailyScheduleItem.kt
new file mode 100644
index 00000000..7345b01f
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/schedule/daily/DailyScheduleItem.kt
@@ -0,0 +1,95 @@
+package com.mashup.ui.schedule.daily
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.mashup.core.ui.colors.Brand500
+import com.mashup.core.ui.colors.Gray100
+import com.mashup.core.ui.colors.White
+import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.core.ui.typography.SubTitle1
+import com.mashup.ui.schedule.detail.composable.ScheduleInfoText
+import com.mashup.core.common.R as CR
+
+/**
+ * TotalScheduleItem.kt
+ *
+ * Created by Minji Jeong on 2024/06/29
+ * Copyright © 2024 MashUp All rights reserved.
+ */
+
+@Composable
+fun DailyScheduleItem(
+ title: String,
+ time: String,
+ place: String,
+ highlight: Boolean,
+ modifier: Modifier = Modifier,
+ onClickScheduleInformation: () -> Unit = {}
+) {
+ val borderColor = if (highlight) {
+ listOf(Brand500, Color(0xFF31C1FF))
+ } else {
+ listOf(Gray100, Gray100)
+ }
+
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(16.dp))
+ .clickable { onClickScheduleInformation() }
+ .background(White)
+ .border(
+ width = if (highlight) (1.5).dp else 1.dp,
+ brush = Brush.horizontalGradient(borderColor),
+ shape = RoundedCornerShape(16.dp)
+ )
+ .padding(horizontal = 20.dp, vertical = 16.dp)
+ ) {
+ Text(text = title, style = SubTitle1)
+ Spacer(modifier = Modifier.height(4.dp))
+ ScheduleInfoText(iconRes = CR.drawable.ic_clock, info = time)
+ Spacer(modifier = Modifier.height(4.dp))
+ ScheduleInfoText(iconRes = CR.drawable.ic_mappin, info = place)
+ }
+}
+
+@Preview
+@Composable
+fun PreviewDailyScheduleItemHighlight() {
+ MashUpTheme {
+ DailyScheduleItem(
+ "안드로이드 팀 세미나",
+ "오후 3:00 - 오전 7:00",
+ "디스코드",
+ true
+ )
+ }
+}
+
+@Preview
+@Composable
+fun PreviewDailyScheduleItem() {
+ MashUpTheme {
+ DailyScheduleItem(
+ "안드로이드 팀 세미나",
+ "오후 3:00 - 오전 7:00",
+ "디스코드",
+ false
+ )
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/schedule/detail/ScheduleDetailActivity.kt b/app/src/main/java/com/mashup/ui/schedule/detail/ScheduleDetailActivity.kt
index 11ffa21b..26f1427a 100644
--- a/app/src/main/java/com/mashup/ui/schedule/detail/ScheduleDetailActivity.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/detail/ScheduleDetailActivity.kt
@@ -5,13 +5,22 @@ import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import androidx.activity.viewModels
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import com.mashup.R
import com.mashup.base.BaseActivity
import com.mashup.constant.EXTRA_SCHEDULE_ID
+import com.mashup.constant.EXTRA_SCHEDULE_TYPE
+import com.mashup.constant.log.LOG_EVENT_LIST_DETAIL_COPY
import com.mashup.core.common.constant.SCHEDULE_NOT_FOUND
+import com.mashup.core.common.extensions.setStatusBarColorRes
+import com.mashup.core.ui.theme.MashUpTheme
import com.mashup.databinding.ActivityScheduleDetailBinding
+import com.mashup.ui.attendance.platform.PlatformAttendanceActivity
+import com.mashup.util.AnalyticsManager
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
+import com.mashup.core.common.R as CR
@AndroidEntryPoint
class ScheduleDetailActivity : BaseActivity() {
@@ -19,14 +28,22 @@ class ScheduleDetailActivity : BaseActivity() {
private val viewModel: ScheduleDetailViewModel by viewModels()
- private val eventDetailAdapter by lazy {
- EventDetailAdapter(copyToClipboard = ::copyToClipboard)
- }
-
override fun initViews() {
- initButton()
- viewBinding.rvEvent.apply {
- adapter = eventDetailAdapter
+ super.initViews()
+
+ setStatusBarColorRes(CR.color.white)
+ viewBinding.composeView.setContent {
+ val state by viewModel.scheduleState.collectAsState()
+
+ MashUpTheme {
+ ScheduleDetailScreen(
+ state = state,
+ isPlatformSeminar = viewModel.scheduleType != "ALL",
+ copyToClipboard = ::copyToClipboard,
+ moveToPlatformAttendance = ::moveToPlatformAttendance,
+ onBackPressed = { finish() }
+ )
+ }
}
}
@@ -38,15 +55,17 @@ class ScheduleDetailActivity : BaseActivity() {
ScheduleState.Loading -> {
showLoading()
}
+
is ScheduleState.Success -> {
hideLoading()
- eventDetailAdapter.submitList(state.eventDetailList)
}
+
is ScheduleState.Error -> {
hideLoading()
handleCommonError(state.code)
handleScheduleDetailErrorCode(state)
}
+
else -> {
hideLoading()
}
@@ -67,23 +86,24 @@ class ScheduleDetailActivity : BaseActivity() {
codeMessage?.run { showToast(this) }
}
- private fun initButton() {
- viewBinding.btnReturn.setOnClickListener {
- onBackPressed()
- }
- }
-
private fun copyToClipboard(text: String) {
+ AnalyticsManager.addEvent(eventName = LOG_EVENT_LIST_DETAIL_COPY)
(getSystemService(CLIPBOARD_SERVICE) as? ClipboardManager)?.let { clipboardManager ->
val clip = ClipData.newPlainText("location", text)
clipboardManager.setPrimaryClip(clip)
}
}
+ private fun moveToPlatformAttendance() {
+ val intent = PlatformAttendanceActivity.newIntent(this, viewModel.scheduleId)
+ startActivity(intent)
+ }
+
companion object {
- fun newIntent(context: Context, scheduleId: Int) =
+ fun newIntent(context: Context, scheduleId: Int, scheduleType: String) =
Intent(context, ScheduleDetailActivity::class.java).apply {
putExtra(EXTRA_SCHEDULE_ID, scheduleId)
+ putExtra(EXTRA_SCHEDULE_TYPE, scheduleType)
}
}
}
diff --git a/app/src/main/java/com/mashup/ui/schedule/detail/ScheduleDetailAdapter.kt b/app/src/main/java/com/mashup/ui/schedule/detail/ScheduleDetailAdapter.kt
deleted file mode 100644
index 2bdf73f5..00000000
--- a/app/src/main/java/com/mashup/ui/schedule/detail/ScheduleDetailAdapter.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-package com.mashup.ui.schedule.detail
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.layout.padding
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.platform.ViewCompositionStrategy
-import androidx.compose.ui.unit.dp
-import androidx.databinding.DataBindingUtil
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
-import com.mashup.core.ui.theme.MashUpTheme
-import com.mashup.databinding.ItemEventTimelineContentBinding
-import com.mashup.databinding.ItemEventTimelineHeaderBinding
-import com.mashup.ui.schedule.detail.composable.ScheduleDetailInfoContent
-import com.mashup.ui.schedule.detail.composable.ScheduleDetailLocationContent
-import com.mashup.ui.schedule.model.EventDetail
-import com.mashup.ui.schedule.model.EventDetailType
-
-class EventDetailAdapter(
- private val copyToClipboard: (String) -> Unit
-) :
- ListAdapter(EventComparator) {
-
- override fun getItemViewType(position: Int): Int {
- return getItem(position).type.num
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
- return when (viewType) {
- EventDetailType.HEADER.num -> {
- HeaderViewHolder(parent)
- }
- EventDetailType.CONTENT.num -> {
- ContentViewHolder(parent)
- }
- EventDetailType.LOCATION.num -> {
- LocationViewHolder(ComposeView(parent.context))
- }
- EventDetailType.INFO.num -> {
- InfoViewHolder(ComposeView(parent.context))
- }
- else -> {
- InfoViewHolder(ComposeView(parent.context))
- }
- }
- }
-
- override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
- when (holder) {
- is HeaderViewHolder -> {
- holder.bind(getItem(position))
- }
- is ContentViewHolder -> {
- holder.bind(getItem(position))
- }
- is LocationViewHolder -> {
- holder.bind(getItem(position), copyToClipboard)
- }
- is InfoViewHolder -> {
- holder.bind(getItem(position))
- }
- }
- }
-
- class HeaderViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(
- ItemEventTimelineHeaderBinding.inflate(
- LayoutInflater.from(parent.context),
- parent,
- false
- ).root
- ) {
- private val binding: ItemEventTimelineHeaderBinding? = DataBindingUtil.bind(itemView)
-
- fun bind(item: EventDetail) {
- binding?.model = item
- if (item.header?.eventId == 1) {
- binding?.line?.visibility = View.GONE
- }
- }
- }
-
- class ContentViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(
- ItemEventTimelineContentBinding.inflate(
- LayoutInflater.from(parent.context),
- parent,
- false
- ).root
- ) {
- private val binding: ItemEventTimelineContentBinding? =
- DataBindingUtil.bind(itemView)
-
- fun bind(item: EventDetail) {
- binding?.model = item
- }
- }
-
- class LocationViewHolder(private val composeView: ComposeView) :
- RecyclerView.ViewHolder(composeView) {
- init {
- composeView.setViewCompositionStrategy(
- ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool // (Default)
- )
- }
-
- fun bind(item: EventDetail, copyToClipboard: (String) -> Unit) {
- item.location?.let { location ->
- composeView.setContent {
- MashUpTheme {
- ScheduleDetailLocationContent(
- detailAddress = location.detailAddress.orEmpty(),
- roadAddress = location.roadAddress.orEmpty(),
- latitude = location.latitude,
- longitude = location.longitude,
- copyToClipboard = copyToClipboard
- )
- }
- }
- }
- }
- }
-
- class InfoViewHolder(private val composeView: ComposeView) :
- RecyclerView.ViewHolder(composeView) {
- init {
- composeView.setViewCompositionStrategy(
- ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool // (Default)
- )
- }
-
- fun bind(item: EventDetail) {
- item.info?.let { info ->
- composeView.setContent {
- MashUpTheme {
- ScheduleDetailInfoContent(
- title = info.title,
- date = info.date,
- time = info.time,
- modifier = Modifier.padding(top = 24.dp)
- )
- }
- }
- }
- }
- }
-
- interface OnItemEventListener {
- fun onExitEventClick()
- }
-}
-
-object EventComparator : DiffUtil.ItemCallback() {
- override fun areItemsTheSame(
- oldItem: EventDetail,
- newItem: EventDetail
- ): Boolean {
- return oldItem.id == newItem.id
- }
-
- override fun areContentsTheSame(
- oldItem: EventDetail,
- newItem: EventDetail
- ): Boolean {
- return oldItem == newItem
- }
-}
diff --git a/app/src/main/java/com/mashup/ui/schedule/detail/ScheduleDetailScreen.kt b/app/src/main/java/com/mashup/ui/schedule/detail/ScheduleDetailScreen.kt
new file mode 100644
index 00000000..2d8e58d4
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/schedule/detail/ScheduleDetailScreen.kt
@@ -0,0 +1,155 @@
+package com.mashup.ui.schedule.detail
+
+import androidx.compose.foundation.LocalOverscrollConfiguration
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.core.ui.widget.ButtonStyle
+import com.mashup.core.ui.widget.MashUpButton
+import com.mashup.core.ui.widget.MashUpToolbar
+import com.mashup.ui.schedule.detail.composable.ScheduleDetailContentItem
+import com.mashup.ui.schedule.detail.composable.ScheduleDetailHeaderItem
+import com.mashup.ui.schedule.detail.composable.ScheduleDetailInfoItem
+import com.mashup.ui.schedule.detail.composable.ScheduleDetailLocationItem
+import com.mashup.ui.schedule.model.EventDetail
+
+@Composable
+fun ScheduleDetailScreen(
+ state: ScheduleState,
+ isPlatformSeminar: Boolean,
+ copyToClipboard: (String) -> Unit,
+ moveToPlatformAttendance: () -> Unit,
+ onBackPressed: () -> Unit
+) {
+ Box(modifier = Modifier.fillMaxSize()) {
+ Column {
+ MashUpToolbar(
+ title = "상세 스케쥴",
+ showBackButton = true,
+ onClickBackButton = onBackPressed
+ )
+
+ when (state) {
+ is ScheduleState.Empty -> {}
+ is ScheduleState.Success -> EventDetailList(state.eventDetailList, copyToClipboard)
+ else -> {}
+ }
+ }
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(140.dp)
+ .background(
+ brush = Brush.verticalGradient(
+ colors = listOf(
+ Color.Transparent,
+ Color.White
+ )
+ )
+ )
+ .align(Alignment.BottomCenter)
+ )
+
+ if (isPlatformSeminar.not()) {
+ MashUpButton(
+ text = "플랫폼 출석현황 보러가기",
+ onClick = moveToPlatformAttendance,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(start = 20.dp, bottom = 28.dp, end = 20.dp)
+ .align(Alignment.BottomCenter),
+ buttonStyle = ButtonStyle.INVERSE
+ )
+ }
+ }
+}
+
+@Composable
+fun EventDetailList(eventDetailList: List, copyToClipboard: (String) -> Unit) {
+ CompositionLocalProvider(
+ LocalOverscrollConfiguration provides null
+ ) {
+ LazyColumn(
+ modifier = Modifier
+ .padding(start = 20.dp, bottom = 60.dp, end = 20.dp)
+ ) {
+ itemsIndexed(eventDetailList) { _, item ->
+ when (item) {
+ is EventDetail.Info -> {
+ ScheduleDetailInfoItem(
+ title = item.title,
+ date = item.date,
+ time = item.formattedTime
+ )
+ }
+
+ is EventDetail.Location -> {
+ ScheduleDetailLocationItem(
+ detailAddress = item.detailAddress,
+ roadAddress = item.roadAddress,
+ latitude = item.latitude,
+ longitude = item.longitude,
+ copyToClipboard = copyToClipboard
+ )
+ }
+
+ is EventDetail.Notice -> {
+ ScheduleDetailLocationItem(item.content)
+ }
+
+ is EventDetail.Header -> {
+ ScheduleDetailHeaderItem(
+ isFirstEvent = item.eventId == 1,
+ title = item.title,
+ time = item.formattedTime
+ )
+ }
+
+ is EventDetail.Content -> {
+ ScheduleDetailContentItem(
+ contentId = item.contentId,
+ title = item.title,
+ content = item.content,
+ time = item.formattedTime
+ )
+ }
+ }
+ }
+
+ item {
+ Spacer(modifier = Modifier.height(28.dp))
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun PreviewScheduleDetailScreen() {
+ MashUpTheme {
+ ScheduleDetailScreen(
+ state = ScheduleState.Empty,
+ isPlatformSeminar = false,
+ copyToClipboard = {},
+ moveToPlatformAttendance = {},
+ onBackPressed = {}
+ )
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/schedule/detail/ScheduleDetailViewModel.kt b/app/src/main/java/com/mashup/ui/schedule/detail/ScheduleDetailViewModel.kt
index 91927b61..5d3dae40 100644
--- a/app/src/main/java/com/mashup/ui/schedule/detail/ScheduleDetailViewModel.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/detail/ScheduleDetailViewModel.kt
@@ -2,18 +2,11 @@ package com.mashup.ui.schedule.detail
import androidx.lifecycle.SavedStateHandle
import com.mashup.constant.EXTRA_SCHEDULE_ID
+import com.mashup.constant.EXTRA_SCHEDULE_TYPE
import com.mashup.core.common.base.BaseViewModel
-import com.mashup.core.common.extensions.getTimeFormat
-import com.mashup.data.dto.ContentResponse
-import com.mashup.data.dto.EventResponse
-import com.mashup.data.dto.ScheduleResponse
import com.mashup.data.repository.ScheduleRepository
-import com.mashup.ui.schedule.model.Body
import com.mashup.ui.schedule.model.EventDetail
-import com.mashup.ui.schedule.model.EventDetailType
-import com.mashup.ui.schedule.model.Header
-import com.mashup.ui.schedule.model.Info
-import com.mashup.ui.schedule.model.Location
+import com.mashup.ui.schedule.model.EventDetailMapper
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -22,12 +15,11 @@ import javax.inject.Inject
@HiltViewModel
class ScheduleDetailViewModel @Inject constructor(
private val scheduleRepository: ScheduleRepository,
+ private val eventMapper: EventDetailMapper,
savedStateHandle: SavedStateHandle
) : BaseViewModel() {
- private val scheduleId =
- checkNotNull(
- savedStateHandle.get(EXTRA_SCHEDULE_ID)
- )
+ val scheduleId = checkNotNull(savedStateHandle.get(EXTRA_SCHEDULE_ID))
+ val scheduleType = checkNotNull(savedStateHandle.get(EXTRA_SCHEDULE_TYPE))
private val _scheduleState = MutableStateFlow(ScheduleState.Empty)
val scheduleState: StateFlow = _scheduleState
@@ -41,16 +33,15 @@ class ScheduleDetailViewModel @Inject constructor(
_scheduleState.emit(ScheduleState.Loading)
scheduleRepository.getSchedule(scheduleId)
.onSuccess { response ->
- _scheduleState.emit(
- ScheduleState.Success(
- getEventDetailList(
- title = response.name,
- date = response.getDate(),
- eventList = response.eventList,
- location = response.location
- )
- )
+ val eventList = eventMapper.getEventDetailList(
+ title = response.name,
+ date = response.getDate(),
+ eventList = response.eventList,
+ location = response.location,
+ notice = response.notice
)
+
+ _scheduleState.emit(ScheduleState.Success(eventList))
}
.onFailure { code ->
handleErrorCode(code)
@@ -63,93 +54,6 @@ class ScheduleDetailViewModel @Inject constructor(
_scheduleState.emit(ScheduleState.Error(code))
}
}
-
- private fun getEventDetailList(
- title: String,
- date: String,
- eventList: List,
- location: ScheduleResponse.Location?
- ): List {
- var itemId = 0
- val eventDetailList = mutableListOf()
-
- val startAt = eventList.first().startedAt.getTimeFormat()
- val endedAt = eventList.last().endedAt.getTimeFormat()
- eventDetailList.add(mapToInfoModel(itemId++, title, date, "$startAt - $endedAt"))
-
- if (location?.detailAddress != null) { // 위치 정보가 있는 경우(온라인이면 placeName이 Zoom으로 내려옴)
- eventDetailList.add(mapToLocationModel(itemId++, location))
- }
-
- eventList.forEachIndexed { eventIndex, event ->
- eventDetailList.add(mapToHeaderModel(itemId++, eventIndex, event))
-
- event.contentList.forEachIndexed { contentIndex, content ->
- eventDetailList.add(mapToContentModel(itemId++, contentIndex, content))
- }
- }
-
- return eventDetailList
- }
-
- private fun mapToHeaderModel(itemId: Int, eventIndex: Int, event: EventResponse): EventDetail {
- return EventDetail(
- id = itemId,
- type = EventDetailType.HEADER,
- header = Header(
- eventId = eventIndex + 1,
- startedAt = event.startedAt,
- endedAt = event.endedAt
- )
- )
- }
-
- private fun mapToContentModel(
- itemId: Int,
- contentIndex: Int,
- content: ContentResponse
- ): EventDetail {
- return EventDetail(
- id = itemId,
- type = EventDetailType.CONTENT,
- body = Body(
- contentId = "${contentIndex + 1}",
- title = content.title,
- content = content.content.orEmpty(),
- startedAt = content.startedAt
- )
- )
- }
-
- private fun mapToLocationModel(itemId: Int, location: ScheduleResponse.Location): EventDetail {
- return EventDetail(
- id = itemId,
- type = EventDetailType.LOCATION,
- location = Location(
- detailAddress = location.detailAddress.orEmpty(),
- roadAddress = location.roadAddress.orEmpty(),
- latitude = location.latitude,
- longitude = location.longitude
- )
- )
- }
-
- private fun mapToInfoModel(
- itemId: Int,
- title: String,
- date: String,
- time: String
- ): EventDetail {
- return EventDetail(
- id = itemId,
- type = EventDetailType.INFO,
- info = Info(
- title = title,
- date = date,
- time = time
- )
- )
- }
}
sealed interface ScheduleState {
diff --git a/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailContentItem.kt b/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailContentItem.kt
new file mode 100644
index 00000000..99223626
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailContentItem.kt
@@ -0,0 +1,89 @@
+package com.mashup.ui.schedule.detail.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.mashup.core.ui.colors.Gray100
+import com.mashup.core.ui.colors.Gray400
+import com.mashup.core.ui.colors.Gray600
+import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.core.ui.typography.Body4
+import com.mashup.core.ui.typography.Caption1
+import com.mashup.core.ui.typography.Caption2
+import com.mashup.core.ui.typography.SubTitle2
+
+@Composable
+fun ScheduleDetailContentItem(
+ contentId: String,
+ title: String,
+ content: String,
+ time: String
+) {
+ Column(modifier = Modifier.padding(vertical = 8.dp)) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Box(
+ modifier = Modifier
+ .size(20.dp)
+ .clip(CircleShape)
+ .background(Gray100),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = contentId,
+ style = Caption2,
+ color = Gray600
+ )
+ }
+
+ Text(
+ text = title,
+ style = SubTitle2,
+ modifier = Modifier
+ .weight(1f)
+ .padding(horizontal = 8.dp)
+ )
+
+ Text(
+ text = time,
+ style = Caption1,
+ color = Gray400
+ )
+ }
+
+ if (content.isNotEmpty()) {
+ Spacer(modifier = Modifier.height(4.dp))
+ Text(
+ text = content,
+ style = Body4,
+ color = Gray600,
+ modifier = Modifier.padding(start = 28.dp)
+ )
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun PreviewScheduleDetailContentItem() {
+ MashUpTheme {
+ ScheduleDetailContentItem(
+ contentId = "1",
+ title = "안드로이드 팀 세미나",
+ content = "Android Crew",
+ time = "AM 11:00"
+ )
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailHeaderItem.kt b/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailHeaderItem.kt
new file mode 100644
index 00000000..503dcd45
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailHeaderItem.kt
@@ -0,0 +1,87 @@
+package com.mashup.ui.schedule.detail.composable
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.Divider
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.mashup.core.common.R
+import com.mashup.core.ui.colors.Brand100
+import com.mashup.core.ui.colors.Brand500
+import com.mashup.core.ui.colors.Gray100
+import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.core.ui.typography.Caption1
+import com.mashup.core.ui.typography.Header2
+
+@Composable
+fun ScheduleDetailHeaderItem(
+ isFirstEvent: Boolean,
+ title: String,
+ time: String
+) {
+ Column {
+ if (isFirstEvent.not()) {
+ Spacer(modifier = Modifier.height(20.dp))
+ Divider(color = Gray100, thickness = 1.dp)
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 20.dp, bottom = 12.dp),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(text = title, style = Header2)
+
+ Row(
+ modifier = Modifier
+ .clip(CircleShape)
+ .background(Brand100)
+ .padding(horizontal = 8.dp, vertical = 4.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.ic_clock),
+ contentDescription = null,
+ modifier = Modifier.size(16.dp)
+ )
+
+ Spacer(modifier = Modifier.width(4.dp))
+
+ Text(
+ text = time,
+ style = Caption1,
+ color = Brand500
+ )
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun PreviewScheduleDetailHeaderItem() {
+ MashUpTheme {
+ ScheduleDetailHeaderItem(
+ isFirstEvent = false,
+ title = "1부",
+ time = "10:00 - 11:00"
+ )
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailInfoContent.kt b/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailInfoItem.kt
similarity index 91%
rename from app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailInfoContent.kt
rename to app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailInfoItem.kt
index 728f8df8..7e7ddaa1 100644
--- a/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailInfoContent.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailInfoItem.kt
@@ -13,7 +13,7 @@ import com.mashup.core.ui.typography.Header1
import com.mashup.core.common.R as CR
@Composable
-fun ScheduleDetailInfoContent(
+fun ScheduleDetailInfoItem(
title: String,
date: String,
time: String,
@@ -33,9 +33,9 @@ fun ScheduleDetailInfoContent(
@Preview
@Composable
-fun PreviewScheduleDetailInfoContent() {
+fun PreviewScheduleDetailInfoItem() {
MashUpTheme {
- ScheduleDetailInfoContent(
+ ScheduleDetailInfoItem(
title = "1차 정기 세미나",
date = "3월 27일",
time = "오후 3:00 - 오전 7:00"
diff --git a/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailLocationContent.kt b/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailLocationItem.kt
similarity index 97%
rename from app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailLocationContent.kt
rename to app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailLocationItem.kt
index 84b38ebf..dedb249c 100644
--- a/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailLocationContent.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailLocationItem.kt
@@ -41,7 +41,7 @@ import com.mashup.core.common.R as CR
@OptIn(ExperimentalNaverMapApi::class)
@Composable
-fun ScheduleDetailLocationContent(
+fun ScheduleDetailLocationItem(
detailAddress: String,
roadAddress: String,
latitude: Double?,
@@ -140,9 +140,9 @@ fun ScheduleDetailLocationContent(
@Preview
@Composable
-fun PreviewScheduleDetailLocationContent() {
+fun PreviewScheduleDetailLocationItem() {
MashUpTheme {
- ScheduleDetailLocationContent(
+ ScheduleDetailLocationItem(
detailAddress = "알파돔타워",
roadAddress = "경기도 성남시 분당구 판교역로 152",
latitude = 37.532600,
diff --git a/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailNoticeItem.kt b/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailNoticeItem.kt
new file mode 100644
index 00000000..0596b592
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleDetailNoticeItem.kt
@@ -0,0 +1,23 @@
+package com.mashup.ui.schedule.detail.composable
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.mashup.core.ui.colors.Gray600
+import com.mashup.core.ui.colors.Gray700
+import com.mashup.core.ui.typography.Body5
+import com.mashup.core.ui.typography.SubTitle2
+
+@Composable
+fun ScheduleDetailLocationItem(content: String) {
+ Column {
+ Spacer(modifier = Modifier.height(16.dp))
+ Text(text = "공지", style = SubTitle2, color = Gray700)
+ Spacer(modifier = Modifier.height(4.dp))
+ Text(text = content, style = Body5, color = Gray600)
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleInfoText.kt b/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleInfoText.kt
index 033bb9e2..f5dda445 100644
--- a/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleInfoText.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/detail/composable/ScheduleInfoText.kt
@@ -17,7 +17,7 @@ import androidx.compose.ui.unit.dp
import com.mashup.core.ui.colors.Gray300
import com.mashup.core.ui.colors.Gray700
import com.mashup.core.ui.theme.MashUpTheme
-import com.mashup.core.ui.typography.Body3
+import com.mashup.core.ui.typography.Body4
import com.mashup.core.common.R as CR
@Composable
@@ -39,7 +39,7 @@ fun ScheduleInfoText(
Spacer(modifier = Modifier.width(4.dp))
- Text(text = info, style = Body3, color = Gray700)
+ Text(text = info, style = Body4, color = Gray700)
}
}
diff --git a/app/src/main/java/com/mashup/ui/schedule/item/CardInfoItem.kt b/app/src/main/java/com/mashup/ui/schedule/item/CardInfoItem.kt
index d0d0eb4e..8b4ebfe7 100644
--- a/app/src/main/java/com/mashup/ui/schedule/item/CardInfoItem.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/item/CardInfoItem.kt
@@ -3,14 +3,11 @@ package com.mashup.ui.schedule.item
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -22,19 +19,18 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.mashup.core.common.R
-import com.mashup.core.ui.colors.Gray100
import com.mashup.core.ui.colors.Gray300
import com.mashup.core.ui.colors.Gray400
-import com.mashup.core.ui.colors.Gray600
-import com.mashup.core.ui.colors.Gray700
+import com.mashup.core.ui.colors.Gray800
import com.mashup.core.ui.colors.Gray900
import com.mashup.core.ui.theme.MashUpTheme
-import com.mashup.core.ui.typography.Body3
+import com.mashup.core.ui.typography.Body4
import com.mashup.core.ui.typography.Header1
+import com.mashup.core.ui.widget.MashupPlatformBadge
@Composable
fun CardInfoItem(
- dDay: String,
+ platform: String,
title: String,
calendar: String,
timeLine: String,
@@ -42,19 +38,7 @@ fun CardInfoItem(
modifier: Modifier = Modifier
) {
Column(modifier = modifier) {
- Box(
- modifier = Modifier.background(
- color = Gray100,
- shape = RoundedCornerShape(100.dp)
- ).padding(horizontal = 10.dp, vertical = 3.5.dp),
- contentAlignment = Alignment.Center
- ) {
- Text(
- style = Body3,
- text = dDay,
- color = Gray600
- )
- }
+ MashupPlatformBadge(platform = platform)
Spacer(
modifier = Modifier.height(10.dp)
)
@@ -82,9 +66,9 @@ fun CardInfoItem(
)
Text(
- style = Body3,
- text = calendar,
- color = Gray700
+ style = Body4,
+ text = calendar.ifEmpty { "-" },
+ color = Gray800
)
}
@@ -100,32 +84,30 @@ fun CardInfoItem(
colorFilter = ColorFilter.tint(color = Gray300)
)
Text(
- text = timeLine,
- style = Body3,
- color = Gray700
+ text = timeLine.ifEmpty { "-" },
+ style = Body4,
+ color = Gray800
)
}
- if (location.isNotEmpty()) {
- Row(
- horizontalArrangement = Arrangement.spacedBy(4.dp),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Image(
- modifier = Modifier.size(20.dp),
- painter = painterResource(id = R.drawable.ic_location),
- contentDescription = null,
- contentScale = ContentScale.Fit,
- colorFilter = ColorFilter.tint(color = Gray300)
- )
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ modifier = Modifier.size(20.dp),
+ painter = painterResource(id = R.drawable.ic_location),
+ contentDescription = null,
+ contentScale = ContentScale.Fit,
+ colorFilter = ColorFilter.tint(color = Gray300)
+ )
- Text(
- style = Body3,
- text = location,
- color = Gray700
- )
- }
+ Text(
+ style = Body4,
+ text = location.ifEmpty { "-" },
+ color = Gray800
+ )
}
}
}
@@ -137,7 +119,7 @@ private fun PreviewCardInfoItem() {
MashUpTheme {
CardInfoItem(
modifier = Modifier.background(color = Color.White),
- dDay = "D+13",
+ platform = "ALL",
title = "스케쥴 테스트",
calendar = "02월 05일",
timeLine = "오후 01:00 - 오후 01:10",
diff --git a/app/src/main/java/com/mashup/ui/schedule/item/EmptyScheduleItem.kt b/app/src/main/java/com/mashup/ui/schedule/item/EmptyScheduleItem.kt
new file mode 100644
index 00000000..efe58e7e
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/schedule/item/EmptyScheduleItem.kt
@@ -0,0 +1,79 @@
+package com.mashup.ui.schedule.item
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.core.ui.typography.Header2
+import com.mashup.core.ui.widget.MashUpGradientButton
+import com.mashup.core.common.R as CommonR
+
+@Composable
+fun EmptyScheduleItem(
+ modifier: Modifier = Modifier,
+ onClickMashongButton: () -> Unit = {}
+) {
+ Column(
+ modifier = modifier,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Spacer(
+ modifier = Modifier.height(34.dp)
+ )
+ Image(
+ painter = painterResource(id = CommonR.drawable.img_empty_schdule),
+ contentDescription = null,
+ modifier = Modifier
+ .width(284.dp)
+ .height(256.dp)
+ )
+ Spacer(
+ modifier = Modifier.height(17.dp)
+ )
+ Text(
+ text = "이번주는 자유다..!",
+ style = Header2,
+ color = Color(0xFF412491)
+ )
+ Spacer(
+ modifier = Modifier.height(17.dp)
+ )
+ MashUpGradientButton(
+ modifier = Modifier
+ .width(256.dp)
+ .height(48.dp),
+ text = "매숑이 밥주러 가기",
+ onClick = onClickMashongButton,
+ gradientColors = listOf(
+ Color(0xFFB398FE),
+ Color(0xFF47BBF1)
+ )
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun PreviewEmptyScheduleItem() {
+ MashUpTheme {
+ Box(
+ modifier = Modifier.background(color = Color.White)
+ ) {
+ EmptyScheduleItem()
+ }
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/schedule/item/SchedeuleViewPagerInProgressItem.kt b/app/src/main/java/com/mashup/ui/schedule/item/SchedeuleViewPagerInProgressItem.kt
index cd5bbcae..279c4580 100644
--- a/app/src/main/java/com/mashup/ui/schedule/item/SchedeuleViewPagerInProgressItem.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/item/SchedeuleViewPagerInProgressItem.kt
@@ -1,8 +1,6 @@
package com.mashup.ui.schedule.item
-import android.text.SpannableStringBuilder
import android.view.Gravity
-import android.view.View
import androidx.appcompat.widget.AppCompatTextView
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -15,73 +13,89 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.ParagraphStyle
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
-import androidx.compose.ui.viewinterop.AndroidViewBinding
import androidx.constraintlayout.compose.ConstraintLayout
-import androidx.core.content.res.ResourcesCompat
-import androidx.core.text.HtmlCompat
import com.mashup.R
-import com.mashup.core.ui.colors.Brand100
-import com.mashup.core.ui.colors.Gray100
-import com.mashup.core.ui.colors.Gray50
-import com.mashup.core.ui.colors.Gray700
import com.mashup.core.ui.theme.MashUpTheme
-import com.mashup.core.ui.typography.Body1
import com.mashup.data.dto.ScheduleResponse
-import com.mashup.databinding.LayoutAttendanceCoachMarkBindingImpl
import com.mashup.ui.schedule.model.ScheduleCard
+import com.mashup.ui.schedule.util.getBackgroundColor
+import com.mashup.ui.schedule.util.getBorderColor
+import com.mashup.ui.schedule.util.getButtonTextColor
import java.util.Date
@Composable
fun ScheduleViewPagerInProgressItem(
data: ScheduleCard.InProgressSchedule,
modifier: Modifier = Modifier,
- onClickScheduleInformation: (Int) -> Unit = {},
- onClickAttendance: (Int) -> Unit = {}
+ onClickScheduleInformation: (Int, String) -> Unit = { _, _ -> }
) {
+ val textColor by remember { mutableStateOf(data.scheduleResponse.scheduleType.getButtonTextColor()) }
Column(
- modifier = modifier.fillMaxWidth().wrapContentHeight().background(
- color = Color.White,
- shape = RoundedCornerShape(20.dp)
- ).border(
- width = 1.dp,
- color = Gray100,
- shape = RoundedCornerShape(20.dp)
- ).clip(RoundedCornerShape(20.dp))
+ modifier = modifier
+ .fillMaxWidth()
+ .wrapContentHeight()
+ .background(
+ color = data.scheduleResponse.scheduleType.getBackgroundColor(),
+ shape = RoundedCornerShape(20.dp)
+ )
+ .border(
+ width = 1.dp,
+ color = data.scheduleResponse.scheduleType.getBorderColor(),
+ shape = RoundedCornerShape(20.dp)
+ )
+ .clip(RoundedCornerShape(20.dp))
.clickable {
- onClickScheduleInformation(data.scheduleResponse.scheduleId)
- }.padding(20.dp),
+ onClickScheduleInformation(data.scheduleResponse.scheduleId, data.scheduleResponse.scheduleType)
+ }
+ .padding(20.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
CardInfoItem(
- modifier = Modifier.fillMaxWidth().wrapContentHeight(),
- dDay = data.scheduleResponse.getDDay(),
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ platform = data.scheduleResponse.scheduleType,
title = data.scheduleResponse.name,
calendar = data.scheduleResponse.getDate(),
timeLine = data.scheduleResponse.getTimeLine(),
- location = ""
+ location = data.scheduleResponse.location?.detailAddress ?: ""
)
Spacer(modifier = Modifier.height(16.dp))
ConstraintLayout {
- val (coachMark, button, schedule) = createRefs()
+ val (button, schedule) = createRefs()
Column(
- modifier = Modifier.background(color = Gray50, shape = RoundedCornerShape(16.dp))
- .height(220.dp).padding(top = 16.dp, bottom = 20.dp, start = 20.dp, end = 20.dp).fillMaxWidth()
+ modifier = Modifier
+ .background(color = Color(0xFFE1F2FA), shape = RoundedCornerShape(16.dp))
+ .height(176.dp)
+ .padding(top = 12.dp, bottom = 12.dp, start = 20.dp, end = 20.dp)
+ .fillMaxWidth()
.constrainAs(schedule) {
top.linkTo(parent.top)
start.linkTo(parent.start)
@@ -91,50 +105,57 @@ fun ScheduleViewPagerInProgressItem(
verticalArrangement = Arrangement.Center
) {
Image(
- painterResource(id = com.mashup.core.common.R.drawable.img_standby),
+ modifier = Modifier.size(100.dp),
+ painter = painterResource(id = com.mashup.core.common.R.drawable.img_standby),
contentDescription = null
)
Spacer(
modifier = Modifier.height(10.dp)
)
- val text = stringResource(id = R.string.description_standby_schedule)
- val spannableString = SpannableStringBuilder(
- String.format(
- text,
- data.attendanceInfo?.memberName ?: "알 수 없음"
- )
- ).toString()
+
Text(
- text = HtmlCompat.fromHtml(
- spannableString,
- HtmlCompat.FROM_HTML_MODE_COMPACT
- ).toAnnotatedString(),
- color = Gray700,
- style = Body1
+ text = buildAnnotatedString {
+ withStyle(
+ ParagraphStyle(lineHeight = 19.09.sp)
+ ) {
+ withStyle(
+ SpanStyle(
+ color = Color(0xFF4D535E),
+ fontSize = 16.sp,
+ fontWeight = FontWeight.W500
+ )
+ ) {
+ append(data.attendanceInfo?.memberName ?: "알 수 없음")
+ }
+ withStyle(
+ SpanStyle(
+ color = Color(0xFFABB2C1),
+ fontSize = 16.sp,
+ fontWeight = FontWeight.W400
+ )
+ ) {
+ append("님의\n참석을 기다리고 있어요.")
+ }
+ }
+ },
+ textAlign = TextAlign.Center
)
}
- AndroidViewBinding(
- modifier = Modifier.constrainAs(coachMark) {
- bottom.linkTo(button.top, 4.dp)
- start.linkTo(button.start)
- end.linkTo(button.end)
- },
- factory = LayoutAttendanceCoachMarkBindingImpl::inflate,
- update = {
- this.root.visibility = View.VISIBLE
- }
- )
AndroidView(
- modifier = Modifier.fillMaxWidth().height(48.dp).background(
- color = Brand100,
- shape = RoundedCornerShape(16.dp)
- ).constrainAs(button) {
- bottom.linkTo(parent.bottom)
- start.linkTo(parent.start)
- end.linkTo(parent.end)
- top.linkTo(schedule.bottom, 12.dp)
- },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(48.dp)
+ .background(
+ color = Color(0xFFC2EBFF),
+ shape = RoundedCornerShape(16.dp)
+ )
+ .constrainAs(button) {
+ bottom.linkTo(parent.bottom)
+ start.linkTo(parent.start)
+ end.linkTo(parent.end)
+ top.linkTo(schedule.bottom, 14.dp)
+ },
factory = { context ->
AppCompatTextView(context).apply {
text = context.getString(R.string.click_attendance_list)
@@ -143,15 +164,11 @@ fun ScheduleViewPagerInProgressItem(
)
gravity = Gravity.CENTER
setTextColor(
- ResourcesCompat.getColor(
- resources,
- com.mashup.core.common.R.color.brand500,
- null
- )
+ textColor.toArgb()
)
setPadding(12, 0, 0, 0)
setOnClickListener {
- onClickAttendance(data.scheduleResponse.scheduleId)
+ onClickScheduleInformation(data.scheduleResponse.scheduleId, data.scheduleResponse.scheduleType)
}
}
}
@@ -165,7 +182,9 @@ fun ScheduleViewPagerInProgressItem(
private fun PreviewScheduleViewPagerEmptySchedule() {
MashUpTheme {
Box(
- modifier = Modifier.width(294.dp).height(479.dp)
+ modifier = Modifier
+ .width(294.dp)
+ .height(479.dp)
) {
ScheduleViewPagerInProgressItem(
data = ScheduleCard.InProgressSchedule(
@@ -182,7 +201,9 @@ private fun PreviewScheduleViewPagerEmptySchedule() {
longitude = 0.0,
roadAddress = null,
detailAddress = null
- )
+ ),
+ scheduleType = "ALL",
+ notice = null
),
attendanceInfo = null
)
diff --git a/app/src/main/java/com/mashup/ui/schedule/item/ScheduleViewPagerEmptySchedule.kt b/app/src/main/java/com/mashup/ui/schedule/item/ScheduleViewPagerEmptySchedule.kt
index a4b32ce6..5c8560c6 100644
--- a/app/src/main/java/com/mashup/ui/schedule/item/ScheduleViewPagerEmptySchedule.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/item/ScheduleViewPagerEmptySchedule.kt
@@ -22,12 +22,12 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.mashup.R
-import com.mashup.core.ui.colors.Gray100
import com.mashup.core.ui.colors.Gray400
-import com.mashup.core.ui.colors.Gray50
import com.mashup.core.ui.theme.MashUpTheme
import com.mashup.core.ui.typography.Body1
import com.mashup.ui.schedule.model.ScheduleCard
+import com.mashup.ui.schedule.util.getBackgroundColor
+import com.mashup.ui.schedule.util.getBorderColor
@Composable
fun ScheduleViewPagerEmptyItem(
@@ -36,28 +36,29 @@ fun ScheduleViewPagerEmptyItem(
) {
Column(
modifier = modifier.fillMaxWidth().wrapContentHeight().background(
- color = Color.White,
+ color = data.scheduleResponse?.scheduleType?.getBackgroundColor() ?: Color(0xFFECF9FF),
shape = RoundedCornerShape(20.dp)
- ).border(
- width = 1.dp,
- color = Gray100,
- shape = RoundedCornerShape(20.dp)
- ).padding(20.dp),
+ )
+ .border(
+ width = 1.dp,
+ color = data.scheduleResponse?.scheduleType?.getBorderColor() ?: Color(0xFFE1F2FA),
+ shape = RoundedCornerShape(20.dp)
+ ).padding(20.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
CardInfoItem(
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
- dDay = data.scheduleResponse?.getDDay() ?: "?",
+ platform = data.scheduleResponse?.scheduleType ?: "ALL",
title = data.scheduleResponse?.name ?: "",
calendar = data.scheduleResponse?.getDate() ?: "-",
timeLine = data.scheduleResponse?.getTimeLine() ?: "-",
- location = ""
+ location = "-"
)
Spacer(modifier = Modifier.height(16.dp))
Box(
modifier = Modifier.fillMaxWidth().background(
- color = Gray50,
+ color = Color(0xFFE1F2FA),
shape = RoundedCornerShape(16.dp)
).padding(
vertical = 22.dp,
diff --git a/app/src/main/java/com/mashup/ui/schedule/item/ScheduleViewPagerSuccessItem.kt b/app/src/main/java/com/mashup/ui/schedule/item/ScheduleViewPagerSuccessItem.kt
index 07c783a2..281329df 100644
--- a/app/src/main/java/com/mashup/ui/schedule/item/ScheduleViewPagerSuccessItem.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/item/ScheduleViewPagerSuccessItem.kt
@@ -8,46 +8,68 @@ import android.text.style.StyleSpan
import android.text.style.UnderlineSpan
import android.view.Gravity
import androidx.appcompat.widget.AppCompatTextView
+import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Divider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
-import androidx.core.content.res.ResourcesCompat
import androidx.core.text.HtmlCompat
import com.mashup.R
-import com.mashup.core.ui.colors.Brand100
-import com.mashup.core.ui.colors.Gray100
-import com.mashup.core.ui.colors.Gray50
+import com.mashup.core.ui.colors.Brand200
+import com.mashup.core.ui.colors.Gray600
import com.mashup.core.ui.colors.Gray700
+import com.mashup.core.ui.colors.Gray900
import com.mashup.core.ui.typography.Body1
+import com.mashup.core.ui.typography.Body5
+import com.mashup.core.ui.typography.Caption1
+import com.mashup.core.ui.typography.SubTitle2
+import com.mashup.core.ui.widget.PlatformType
import com.mashup.data.dto.EventResponse
import com.mashup.ui.schedule.ViewEventTimeline
import com.mashup.ui.schedule.model.ScheduleCard
+import com.mashup.ui.schedule.util.convertCamelCase
+import com.mashup.ui.schedule.util.getBackgroundColor
+import com.mashup.ui.schedule.util.getBorderColor
+import com.mashup.ui.schedule.util.getButtonBackgroundColor
+import com.mashup.ui.schedule.util.getButtonTextColor
+import com.mashup.ui.schedule.util.getEventTimelineBackgroundColor
import com.mashup.ui.schedule.util.onBindAttendanceImage
import com.mashup.ui.schedule.util.onBindAttendanceStatus
import com.mashup.ui.schedule.util.onBindAttendanceTime
@@ -56,27 +78,36 @@ import com.mashup.ui.schedule.util.onBindAttendanceTime
fun ScheduleViewPagerSuccessItem(
data: ScheduleCard.EndSchedule,
modifier: Modifier = Modifier,
- onClickScheduleInformation: (Int) -> Unit = {},
- onClickAttendance: (Int) -> Unit = {}
+ onClickScheduleInformation: (Int, String) -> Unit = { _, _ -> },
+ makeToast: (String) -> Unit = {}
) {
val context = LocalContext.current
val listState = rememberLazyListState()
+ val textColor by remember { mutableStateOf(data.scheduleResponse.scheduleType.getButtonTextColor()) }
Column(
modifier = modifier
.fillMaxWidth()
.wrapContentHeight()
.background(
- color = Color.White,
+ color = data.scheduleResponse.scheduleType.getBackgroundColor(),
shape = RoundedCornerShape(20.dp)
)
.border(
width = 1.dp,
- color = Gray100,
+ color = data.scheduleResponse.scheduleType.getBorderColor(),
shape = RoundedCornerShape(20.dp)
- ).clip(RoundedCornerShape(20.dp))
+ )
+ .clip(RoundedCornerShape(20.dp))
.clickable {
- onClickScheduleInformation(data.scheduleResponse.scheduleId)
+ if (data.scheduleResponse.notice.isNullOrEmpty() && data.scheduleResponse.eventList.isEmpty()) {
+ makeToast("볼 수 있는 일정이 없어요..!")
+ } else {
+ onClickScheduleInformation(
+ data.scheduleResponse.scheduleId,
+ data.scheduleResponse.scheduleType
+ )
+ }
}
.padding(20.dp),
horizontalAlignment = Alignment.CenterHorizontally
@@ -85,111 +116,215 @@ fun ScheduleViewPagerSuccessItem(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
- dDay = data.scheduleResponse.getDDay(),
+ platform = data.scheduleResponse.scheduleType,
title = data.scheduleResponse.name,
calendar = data.scheduleResponse.getDate(),
timeLine = data.scheduleResponse.getTimeLine(),
location = data.scheduleResponse.location?.detailAddress ?: ""
)
- Spacer(modifier = Modifier.height(16.dp))
- LazyColumn(
- state = listState,
- modifier = Modifier
- .background(color = Gray50, shape = RoundedCornerShape(16.dp))
- .height(220.dp)
- .padding(top = 16.dp, start = 20.dp, end = 20.dp)
- ) {
- itemsIndexed(data.scheduleResponse.eventList, key = { _: Int, item: EventResponse ->
- item.eventId
- }) { index: Int, _: EventResponse ->
- Column {
- if (index == 0) {
- val spannableString = SpannableStringBuilder(
- String.format(
- context.resources.getString(
- R.string.event_list_card_title
- ),
- data.attendanceInfo.memberName
- )
- ).toString()
+ if (data.scheduleResponse.scheduleType.convertCamelCase() == PlatformType.Seminar) {
+ Spacer(modifier = Modifier.height(12.dp))
+ LazyColumn(
+ state = listState,
+ modifier = Modifier
+ .background(
+ color = data.scheduleResponse.scheduleType.getEventTimelineBackgroundColor(),
+ shape = RoundedCornerShape(16.dp)
+ )
+ .height(176.dp)
+ .padding(top = 16.dp, start = 20.dp, end = 20.dp)
+ ) {
+ itemsIndexed(data.scheduleResponse.eventList, key = { _: Int, item: EventResponse ->
+ item.eventId
+ }) { index: Int, _: EventResponse ->
+ Column {
+ if (index == 0) {
+ val spannableString = SpannableStringBuilder(
+ String.format(
+ context.resources.getString(
+ R.string.event_list_card_title
+ ),
+ data.attendanceInfo.memberName
+ )
+ ).toString()
- Text(
- text = HtmlCompat.fromHtml(
- spannableString,
- HtmlCompat.FROM_HTML_MODE_COMPACT
- ).toAnnotatedString(),
- color = Gray700,
- style = Body1
- )
+ Text(
+ text = HtmlCompat.fromHtml(
+ spannableString,
+ HtmlCompat.FROM_HTML_MODE_COMPACT
+ ).toAnnotatedString(),
+ color = Gray700,
+ style = Body1
+ )
- Spacer(
- modifier = Modifier.height(16.dp)
+ Spacer(
+ modifier = Modifier.height(16.dp)
+ )
+ }
+ ViewEventTimeline(
+ modifier = Modifier.fillMaxWidth(),
+ caption = stringResource(id = R.string.attendance_caption, index + 1),
+ time = onBindAttendanceTime(data.attendanceInfo.getAttendanceAt(index)),
+ status = onBindAttendanceStatus(
+ data.attendanceInfo.getAttendanceStatus(index)
+ ),
+ image = onBindAttendanceImage(
+ data.attendanceInfo.getAttendanceStatus(index)
+ )
)
}
+ Spacer(
+ modifier = Modifier.height(6.dp)
+ )
+ }
+ item {
+ val status = data.attendanceInfo.getFinalAttendance()
ViewEventTimeline(
modifier = Modifier.fillMaxWidth(),
- caption = stringResource(id = R.string.attendance_caption, index + 1),
- time = onBindAttendanceTime(data.attendanceInfo.getAttendanceAt(index)),
- status = onBindAttendanceStatus(
- data.attendanceInfo.getAttendanceStatus(index)
- ),
- image = onBindAttendanceImage(
- data.attendanceInfo.getAttendanceStatus(index)
- )
+ caption = stringResource(id = R.string.attendance_final),
+ status = onBindAttendanceStatus(status, isFinal = true),
+ image = onBindAttendanceImage(status, isFinal = true),
+ isFinal = true
)
}
- }
- item {
- val status = data.attendanceInfo.getFinalAttendance()
- ViewEventTimeline(
- modifier = Modifier.fillMaxWidth(),
- caption = stringResource(id = R.string.attendance_final),
- status = onBindAttendanceStatus(status, isFinal = true),
- image = onBindAttendanceImage(status, isFinal = true),
- isFinal = true
- )
- }
- item {
- Spacer(modifier = Modifier.height(20.dp))
+ item {
+ Spacer(modifier = Modifier.height(20.dp))
+ }
}
- }
-
- Spacer(
- modifier = Modifier.height(12.dp)
- )
- AndroidView(
- modifier = Modifier
- .fillMaxWidth()
- .height(48.dp)
- .background(
- color = Brand100,
- shape = RoundedCornerShape(16.dp)
+ Spacer(
+ modifier = Modifier.height(12.dp)
+ )
+ AndroidView(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(48.dp)
+ .background(
+ color = data.scheduleResponse.scheduleType.getButtonBackgroundColor(),
+ shape = RoundedCornerShape(16.dp)
+ ),
+ factory = { context ->
+ AppCompatTextView(context).apply {
+ text = context.getString(R.string.click_attendance_list)
+ setTextAppearance(
+ com.mashup.core.common.R.style.TextAppearance_Mashup_Body3_14_M
+ )
+ gravity = Gravity.CENTER
+ setTextColor(
+ textColor.toArgb()
+ )
+ setPadding(12, 0, 0, 0)
+ setOnClickListener {
+ onClickScheduleInformation(
+ data.scheduleResponse.scheduleId,
+ data.scheduleResponse.scheduleType
+ )
+ }
+ }
+ }
+ )
+ } else {
+ Spacer(modifier = Modifier.height(18.dp))
+ Divider(
+ modifier = Modifier.fillMaxWidth(),
+ color = Brand200
+ )
+ Spacer(
+ modifier = Modifier.height(18.dp)
+ )
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ text = "공지",
+ textAlign = TextAlign.Left,
+ style = SubTitle2.copy(
+ lineHeight = 19.09.sp
),
- factory = { context ->
- AppCompatTextView(context).apply {
- text = context.getString(R.string.click_attendance_list)
- setTextAppearance(
- com.mashup.core.common.R.style.TextAppearance_Mashup_Body3_14_M
- )
- gravity = Gravity.CENTER
- setTextColor(
- ResourcesCompat.getColor(
- resources,
- com.mashup.core.common.R.color.brand500,
- null
+ color = Gray900
+ )
+ Spacer(
+ modifier = Modifier.height(6.dp)
+ )
+
+ if (data.scheduleResponse.notice.isNullOrEmpty()) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(
+ color = Color(0xFFEFE9F8),
+ shape = RoundedCornerShape(16.dp)
+ )
+ .padding(
+ vertical = 22.dp,
+ horizontal = 20.dp
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ modifier = Modifier.size(88.dp),
+ painter = painterResource(id = com.mashup.core.common.R.drawable.img_placeholder_sleeping),
+ contentDescription = null
+ )
+ Text(
+ text = "공지가 없어요!",
+ style = Caption1,
+ color = Gray600
)
- )
- setPadding(12, 0, 0, 0)
- setOnClickListener {
- onClickAttendance(data.scheduleResponse.scheduleId)
}
}
+ } else {
+ Text(
+ text = data.scheduleResponse.notice,
+ maxLines = 5,
+ style = Body5.copy(
+ lineHeight = 20.sp
+ ),
+ color = Gray700,
+ overflow = TextOverflow.Ellipsis,
+ textAlign = TextAlign.Left,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = 20.dp)
+ )
+
+ AndroidView(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(48.dp)
+ .background(
+ color = data.scheduleResponse.scheduleType.getButtonBackgroundColor(),
+ shape = RoundedCornerShape(16.dp)
+ ),
+ factory = { context ->
+ AppCompatTextView(context).apply {
+ text = context.getString(R.string.click_attendance_list)
+ setTextAppearance(
+ com.mashup.core.common.R.style.TextAppearance_Mashup_Body3_14_M
+ )
+ gravity = Gravity.CENTER
+ setTextColor(
+ textColor.toArgb()
+ )
+ setPadding(12, 0, 0, 0)
+ setOnClickListener {
+ onClickScheduleInformation(
+ data.scheduleResponse.scheduleId,
+ data.scheduleResponse.scheduleType
+ )
+ }
+ }
+ }
+ )
}
- )
+ }
}
}
+
fun Spanned.toAnnotatedString(): AnnotatedString = buildAnnotatedString {
val spanned = this@toAnnotatedString
append(spanned.toString())
@@ -206,11 +341,13 @@ fun Spanned.toAnnotatedString(): AnnotatedString = buildAnnotatedString {
end
)
}
+
is UnderlineSpan -> addStyle(
SpanStyle(textDecoration = TextDecoration.Underline),
start,
end
)
+
is ForegroundColorSpan -> addStyle(
SpanStyle(color = Color(span.foregroundColor)),
start,
diff --git a/app/src/main/java/com/mashup/ui/schedule/model/EventDetail.kt b/app/src/main/java/com/mashup/ui/schedule/model/EventDetail.kt
index a5a29344..c4243580 100644
--- a/app/src/main/java/com/mashup/ui/schedule/model/EventDetail.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/model/EventDetail.kt
@@ -1,59 +1,52 @@
package com.mashup.ui.schedule.model
-import java.text.SimpleDateFormat
+import com.mashup.core.common.extensions.getTimeFormat
import java.util.Date
-import java.util.Locale
-data class EventDetail(
- val id: Int,
- val type: EventDetailType,
- val header: Header? = null,
- val body: Body? = null,
- val location: Location? = null,
- val info: Info? = null
-)
-
-data class Header(
- val eventId: Int,
- val startedAt: Date,
- val endedAt: Date
+sealed class EventDetail(
+ open val index: Int,
+ val type: EventDetailType
) {
- fun getHeader() = "${eventId}부"
- fun getTimeStampStr(): String {
- return try {
- val timeLineFormat = SimpleDateFormat("a hh:mm", Locale.ENGLISH)
- "${timeLineFormat.format(startedAt)} - ${timeLineFormat.format(endedAt)}"
- } catch (ignore: Exception) {
- "??:?? - ??:??"
- }
+ data class Header(
+ override val index: Int,
+ val eventId: Int,
+ val startedAt: Date,
+ val endedAt: Date
+ ) : EventDetail(index, EventDetailType.HEADER) {
+ val title = "${eventId}부"
+ val formattedTime = "${startedAt.getTimeFormat()} - ${endedAt.getTimeFormat()}"
}
-}
-data class Body(
- val contentId: String,
- val title: String,
- val content: String,
- val startedAt: Date
-) {
- fun getTimeStampStr(): String {
- return try {
- val timeLineFormat = SimpleDateFormat("a hh:mm", Locale.ENGLISH)
- timeLineFormat.format(startedAt)
- } catch (ignore: Exception) {
- "??:??"
- }
+ data class Content(
+ override val index: Int,
+ val contentId: String,
+ val title: String,
+ val content: String,
+ val startedAt: Date
+ ) : EventDetail(index, EventDetailType.CONTENT) {
+ val formattedTime = startedAt.getTimeFormat()
}
-}
-data class Location(
- val detailAddress: String?,
- val roadAddress: String?,
- val latitude: Double?,
- val longitude: Double?
-)
+ data class Location(
+ override val index: Int,
+ val detailAddress: String,
+ val roadAddress: String,
+ val latitude: Double?,
+ val longitude: Double?
+ ) : EventDetail(index, EventDetailType.LOCATION)
-data class Info(
- val title: String,
- val date: String,
- val time: String
-)
+ data class Info(
+ override val index: Int,
+ val title: String,
+ val date: String,
+ val startedAt: Date,
+ val endedAt: Date
+ ) : EventDetail(index, EventDetailType.INFO) {
+ val formattedTime = "${startedAt.getTimeFormat()} - ${endedAt.getTimeFormat()}"
+ }
+
+ data class Notice(
+ override val index: Int,
+ val content: String
+ ) : EventDetail(index, EventDetailType.NOTICE)
+}
diff --git a/app/src/main/java/com/mashup/ui/schedule/model/EventDetailMapper.kt b/app/src/main/java/com/mashup/ui/schedule/model/EventDetailMapper.kt
new file mode 100644
index 00000000..3a2bf34a
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/schedule/model/EventDetailMapper.kt
@@ -0,0 +1,107 @@
+package com.mashup.ui.schedule.model
+
+import com.mashup.data.dto.ContentResponse
+import com.mashup.data.dto.EventResponse
+import com.mashup.data.dto.ScheduleResponse
+import java.util.Date
+import javax.inject.Inject
+
+class EventDetailMapper @Inject constructor() {
+ fun getEventDetailList(
+ title: String,
+ date: String,
+ eventList: List,
+ location: ScheduleResponse.Location?,
+ notice: String?
+ ): List {
+ val eventDetailList = mutableListOf()
+ var index = 0
+
+ val infoModel = mapToInfoModel(
+ index++,
+ title,
+ date,
+ eventList.first().startedAt,
+ eventList.last().endedAt
+ )
+ eventDetailList.add(infoModel)
+
+ if (location?.detailAddress != null) { // 위치 정보가 있는 경우(온라인이면 placeName이 Zoom으로 내려옴)
+ val locationModel = mapToLocationModel(index++, location)
+ eventDetailList.add(locationModel)
+ }
+
+ if (!notice.isNullOrEmpty()) {
+ val noticeModel = mapToNoticeModel(index++, notice)
+ eventDetailList.add(noticeModel)
+ }
+
+ eventList.forEachIndexed { eventIndex, event ->
+ val headerModel = mapToHeaderModel(index++, eventIndex, event)
+ eventDetailList.add(headerModel)
+
+ event.contentList.forEachIndexed { contentIndex, content ->
+ val contentModel = mapToContentModel(index++, contentIndex, content)
+ eventDetailList.add(contentModel)
+ }
+ }
+
+ return eventDetailList
+ }
+
+ private fun mapToHeaderModel(index: Int, eventIndex: Int, event: EventResponse): EventDetail {
+ return EventDetail.Header(
+ index = index,
+ eventId = eventIndex + 1,
+ startedAt = event.startedAt,
+ endedAt = event.endedAt
+ )
+ }
+
+ private fun mapToContentModel(
+ index: Int,
+ contentIndex: Int,
+ content: ContentResponse
+ ): EventDetail {
+ return EventDetail.Content(
+ index = index,
+ contentId = "${contentIndex + 1}",
+ title = content.title,
+ content = content.content.orEmpty(),
+ startedAt = content.startedAt
+ )
+ }
+
+ private fun mapToLocationModel(index: Int, location: ScheduleResponse.Location): EventDetail {
+ return EventDetail.Location(
+ index = index,
+ detailAddress = location.detailAddress.orEmpty(),
+ roadAddress = location.roadAddress.orEmpty(),
+ latitude = location.latitude,
+ longitude = location.longitude
+ )
+ }
+
+ private fun mapToNoticeModel(index: Int, content: String): EventDetail {
+ return EventDetail.Notice(
+ index = index,
+ content = content
+ )
+ }
+
+ private fun mapToInfoModel(
+ index: Int,
+ title: String,
+ date: String,
+ startedAt: Date,
+ endedAt: Date
+ ): EventDetail {
+ return EventDetail.Info(
+ index = index,
+ title = title,
+ date = date,
+ startedAt = startedAt,
+ endedAt = endedAt
+ )
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/schedule/model/EventDetailType.kt b/app/src/main/java/com/mashup/ui/schedule/model/EventDetailType.kt
index 8d052a3f..e9baf2bb 100644
--- a/app/src/main/java/com/mashup/ui/schedule/model/EventDetailType.kt
+++ b/app/src/main/java/com/mashup/ui/schedule/model/EventDetailType.kt
@@ -1,3 +1,3 @@
package com.mashup.ui.schedule.model
-enum class EventDetailType(val num: Int) { HEADER(1), CONTENT(2), LOCATION(3), INFO(4) }
+enum class EventDetailType(val num: Int) { HEADER(1), CONTENT(2), LOCATION(3), INFO(4), NOTICE(5) }
diff --git a/app/src/main/java/com/mashup/ui/schedule/model/ScheduleType.kt b/app/src/main/java/com/mashup/ui/schedule/model/ScheduleType.kt
new file mode 100644
index 00000000..16d17a31
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/schedule/model/ScheduleType.kt
@@ -0,0 +1,5 @@
+package com.mashup.ui.schedule.model
+
+enum class ScheduleType {
+ WEEK, TOTAL
+}
diff --git a/app/src/main/java/com/mashup/ui/schedule/util/ScheduleColor.kt b/app/src/main/java/com/mashup/ui/schedule/util/ScheduleColor.kt
new file mode 100644
index 00000000..eae3d3b0
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/schedule/util/ScheduleColor.kt
@@ -0,0 +1,54 @@
+package com.mashup.ui.schedule.util
+
+import androidx.compose.ui.graphics.Color
+import com.mashup.core.ui.widget.PlatformType
+
+fun String.getEventTimelineBackgroundColor(): Color {
+ return when (this) {
+ "ALL" -> Color(0xFFE1F2FA)
+ else -> Color(0xFFF5F1FF)
+ }
+}
+
+fun String.getButtonBackgroundColor(): Color {
+ return when (this) {
+ "ALL" -> Color(0xFFC2EBFF)
+ else -> Color(0xFFE7DEFF)
+ }
+}
+
+fun String.getBackgroundColor(): Color {
+ return when (this) {
+ "ALL" -> Color(0xFFECF9FF)
+ else -> Color(0xFFF5F1FF)
+ }
+}
+
+fun String.getBorderColor(): Color {
+ return when (this) {
+ "ALL" -> Color(0xFFE1F2FA)
+ else -> Color(0xFFE7DEFF).copy(
+ alpha = 0.3f
+ )
+ }
+}
+
+fun String.getButtonTextColor(): Color {
+ return when (this) {
+ "ALL" -> Color(0xFF358CB6)
+ else -> Color(0xFF6A36FF)
+ }
+}
+
+fun String.convertCamelCase(): PlatformType {
+ return when (this) {
+ "ALL" -> PlatformType.Seminar
+ "DESIGN" -> PlatformType.Design
+ "SPRING" -> PlatformType.Spring
+ "IOS" -> PlatformType.Ios
+ "ANDROID" -> PlatformType.Android
+ "WEB" -> PlatformType.Web
+ "NODE" -> PlatformType.Node
+ else -> PlatformType.Seminar
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/setting/SettingActivity.kt b/app/src/main/java/com/mashup/ui/setting/SettingActivity.kt
index def650cf..08896b99 100644
--- a/app/src/main/java/com/mashup/ui/setting/SettingActivity.kt
+++ b/app/src/main/java/com/mashup/ui/setting/SettingActivity.kt
@@ -10,14 +10,14 @@ import com.mashup.R
import com.mashup.URL
import com.mashup.base.BaseActivity
import com.mashup.constant.EXTRA_ANIMATION
-import com.mashup.constant.log.LOG_DELETE_USER
-import com.mashup.constant.log.LOG_LOGOUT
-import com.mashup.constant.log.LOG_SNS_FACEBOOK
-import com.mashup.constant.log.LOG_SNS_INSTAGRAM
-import com.mashup.constant.log.LOG_SNS_MASHUP_HOME
-import com.mashup.constant.log.LOG_SNS_MASHUP_RECRUIT
-import com.mashup.constant.log.LOG_SNS_TISTORY
-import com.mashup.constant.log.LOG_SNS_YOUTUBE
+import com.mashup.constant.log.LOG_SETTING_DELETE_USER
+import com.mashup.constant.log.LOG_SETTING_LOGOUT
+import com.mashup.constant.log.LOG_SETTING_SNS_FACEBOOK
+import com.mashup.constant.log.LOG_SETTING_SNS_INSTAGRAM
+import com.mashup.constant.log.LOG_SETTING_SNS_MASHUP_HOME
+import com.mashup.constant.log.LOG_SETTING_SNS_MASHUP_RECRUIT
+import com.mashup.constant.log.LOG_SETTING_SNS_TISTORY
+import com.mashup.constant.log.LOG_SETTING_SNS_YOUTUBE
import com.mashup.core.common.model.NavigationAnimationType
import com.mashup.core.common.widget.CommonDialog
import com.mashup.core.ui.theme.MashUpTheme
@@ -61,7 +61,7 @@ class SettingActivity : BaseActivity() {
}
private fun onClickLogoutButton() {
- AnalyticsManager.addEvent(LOG_LOGOUT)
+ AnalyticsManager.addEvent(LOG_SETTING_LOGOUT)
showLogoutDialog()
}
@@ -95,7 +95,7 @@ class SettingActivity : BaseActivity() {
}
private fun moveToDeleteAccount() {
- AnalyticsManager.addEvent(LOG_DELETE_USER)
+ AnalyticsManager.addEvent(LOG_SETTING_DELETE_USER)
startActivity(
WithdrawalActivity.newInstance(this)
)
@@ -103,12 +103,12 @@ class SettingActivity : BaseActivity() {
private fun onClickSNS(link: String) {
val eventLog = when (link) {
- URL.FACEBOOK -> LOG_SNS_FACEBOOK
- URL.INSTAGRAM -> LOG_SNS_INSTAGRAM
- URL.TISTORY -> LOG_SNS_TISTORY
- URL.YOUTUBE -> LOG_SNS_YOUTUBE
- URL.MASHUP_UP_HOME -> LOG_SNS_MASHUP_HOME
- URL.MASHUP_UP_RECRUIT -> LOG_SNS_MASHUP_RECRUIT
+ URL.FACEBOOK -> LOG_SETTING_SNS_FACEBOOK
+ URL.INSTAGRAM -> LOG_SETTING_SNS_INSTAGRAM
+ URL.TISTORY -> LOG_SETTING_SNS_TISTORY
+ URL.YOUTUBE -> LOG_SETTING_SNS_YOUTUBE
+ URL.MASHUP_UP_HOME -> LOG_SETTING_SNS_MASHUP_HOME
+ URL.MASHUP_UP_RECRUIT -> LOG_SETTING_SNS_MASHUP_RECRUIT
else -> null
}
eventLog?.run { AnalyticsManager.addEvent(this) }
diff --git a/app/src/main/java/com/mashup/ui/signup/SignUpActivity.kt b/app/src/main/java/com/mashup/ui/signup/SignUpActivity.kt
index dc3658f0..4f1cd520 100644
--- a/app/src/main/java/com/mashup/ui/signup/SignUpActivity.kt
+++ b/app/src/main/java/com/mashup/ui/signup/SignUpActivity.kt
@@ -9,8 +9,8 @@ import com.mashup.R
import com.mashup.base.BaseActivity
import com.mashup.constant.EXTRA_ANIMATION
import com.mashup.constant.log.KEY_PLACE
-import com.mashup.constant.log.LOG_BACK
-import com.mashup.constant.log.LOG_CLOSE
+import com.mashup.constant.log.LOG_COMMON_BACK
+import com.mashup.constant.log.LOG_COMMON_CLOSE
import com.mashup.constant.log.LOG_PLACE_SIGN_CODE
import com.mashup.constant.log.LOG_PLACE_SIGN_MEMBER_INFO
import com.mashup.constant.log.LOG_PLACE_SIGN_PLATFORM
@@ -48,7 +48,7 @@ class SignUpActivity : BaseActivity() {
viewBinding.toolbar.setOnCloseButtonClickListener {
getPlaceGALog()?.run {
AnalyticsManager.addEvent(
- LOG_CLOSE,
+ LOG_COMMON_CLOSE,
bundleOf(KEY_PLACE to this)
)
}
@@ -85,7 +85,7 @@ class SignUpActivity : BaseActivity() {
override fun onBackPressed() {
getPlaceGALog()?.run {
AnalyticsManager.addEvent(
- LOG_BACK,
+ LOG_COMMON_BACK,
bundleOf(KEY_PLACE to this)
)
}
diff --git a/app/src/main/java/com/mashup/ui/webview/WebViewActivity.kt b/app/src/main/java/com/mashup/ui/webview/WebViewActivity.kt
index f01be4a4..45ddab37 100644
--- a/app/src/main/java/com/mashup/ui/webview/WebViewActivity.kt
+++ b/app/src/main/java/com/mashup/ui/webview/WebViewActivity.kt
@@ -7,11 +7,13 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import com.mashup.R
import com.mashup.base.BaseActivity
import com.mashup.constant.EXTRA_ANIMATION
import com.mashup.constant.EXTRA_TITLE_KEY
import com.mashup.constant.EXTRA_URL_KEY
+import com.mashup.core.common.bridge.MashupBridge
import com.mashup.core.common.extensions.setStatusBarColorRes
import com.mashup.core.common.model.NavigationAnimationType
import com.mashup.databinding.ActivityWebViewBinding
@@ -26,13 +28,13 @@ class WebViewActivity : BaseActivity() {
super.initViews()
setStatusBarColorRes(com.mashup.core.common.R.color.white)
initWindowInset()
-
initCompose()
}
private fun initCompose() {
viewBinding.webView.setContent {
val webViewUiState by viewModel.webViewUiState.collectAsState(WebViewUiState.Loading)
+ val context = LocalContext.current
WebViewScreen(
modifier = Modifier.fillMaxSize(),
@@ -40,7 +42,8 @@ class WebViewActivity : BaseActivity() {
onBackPressed = { finish() },
isScrollTop = {
viewModel.onWebViewScroll(it)
- }
+ },
+ mashupBridge = MashupBridge(context)
)
}
}
diff --git a/app/src/main/java/com/mashup/ui/webview/WebViewScreen.kt b/app/src/main/java/com/mashup/ui/webview/WebViewScreen.kt
index 17698811..a38aae98 100644
--- a/app/src/main/java/com/mashup/ui/webview/WebViewScreen.kt
+++ b/app/src/main/java/com/mashup/ui/webview/WebViewScreen.kt
@@ -8,29 +8,36 @@ import androidx.compose.ui.Modifier
import com.google.accompanist.web.WebView
import com.google.accompanist.web.rememberWebViewNavigator
import com.google.accompanist.web.rememberWebViewState
+import com.mashup.core.common.bridge.MashupBridge
import com.mashup.core.ui.widget.MashUpToolbar
@Composable
fun WebViewScreen(
+ mashupBridge: MashupBridge,
modifier: Modifier = Modifier,
webViewUiState: WebViewUiState,
isScrollTop: (Boolean) -> Unit = {},
- onBackPressed: () -> Unit
+ onBackPressed: () -> Unit = {},
+ isShowMashUpToolbar: Boolean = true
) {
Column(modifier = modifier) {
- MashUpToolbar(
- modifier = Modifier.fillMaxWidth(),
- title = (webViewUiState as? WebViewUiState.Success)?.title.orEmpty(),
- showBackButton = true,
- showBottomDivider = (webViewUiState as? WebViewUiState.Success)?.showToolbarDivider
- ?: false,
- onClickBackButton = onBackPressed
- )
+ if (isShowMashUpToolbar) {
+ MashUpToolbar(
+ modifier = Modifier.fillMaxWidth(),
+ title = (webViewUiState as? WebViewUiState.Success)?.title.orEmpty(),
+ showBackButton = true,
+ showBottomDivider = (webViewUiState as? WebViewUiState.Success)?.showToolbarDivider
+ ?: false,
+ onClickBackButton = onBackPressed
+ )
+ }
if (webViewUiState is WebViewUiState.Success) {
MashUpWebView(
webViewUrl = webViewUiState.webViewUrl,
isScrollTop = isScrollTop,
- onBackPressed = onBackPressed
+ onBackPressed = onBackPressed,
+ mashupBridge = mashupBridge,
+ additionalHttpHeaders = webViewUiState.additionalHttpHeaders
)
}
}
@@ -39,10 +46,15 @@ fun WebViewScreen(
@Composable
private fun MashUpWebView(
webViewUrl: String?,
+ mashupBridge: MashupBridge,
isScrollTop: (Boolean) -> Unit = {},
+ additionalHttpHeaders: Map = emptyMap(),
onBackPressed: () -> Unit
) {
- val webViewState = rememberWebViewState(url = webViewUrl.orEmpty())
+ val webViewState = rememberWebViewState(
+ url = webViewUrl.orEmpty(),
+ additionalHttpHeaders = additionalHttpHeaders
+ )
val webViewNavigator = rememberWebViewNavigator()
WebView(
@@ -51,9 +63,11 @@ private fun MashUpWebView(
onCreated = { webView ->
with(webView) {
settings.run {
+ javaScriptEnabled = true
domStorageEnabled = true
loadWithOverviewMode = true
defaultTextEncodingName = "UTF-8"
+ addJavascriptInterface(mashupBridge, MashupBridge.name)
}
setOnScrollChangeListener { view, _, _, _, _ ->
isScrollTop(!view.canScrollVertically(-1))
diff --git a/app/src/main/java/com/mashup/ui/webview/WebViewViewModel.kt b/app/src/main/java/com/mashup/ui/webview/WebViewViewModel.kt
index a782098d..830945aa 100644
--- a/app/src/main/java/com/mashup/ui/webview/WebViewViewModel.kt
+++ b/app/src/main/java/com/mashup/ui/webview/WebViewViewModel.kt
@@ -5,6 +5,8 @@ import androidx.lifecycle.viewModelScope
import com.mashup.constant.EXTRA_TITLE_KEY
import com.mashup.constant.EXTRA_URL_KEY
import com.mashup.core.common.base.BaseViewModel
+import com.mashup.data.network.WEB_HOST
+import com.mashup.datastore.data.repository.UserPreferenceRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -14,7 +16,8 @@ import javax.inject.Inject
@HiltViewModel
class WebViewViewModel @Inject constructor(
- savedStateHandle: SavedStateHandle
+ savedStateHandle: SavedStateHandle,
+ userPreferenceRepository: UserPreferenceRepository
) : BaseViewModel() {
private val showDividerFlow = MutableStateFlow(false)
@@ -22,12 +25,18 @@ class WebViewViewModel @Inject constructor(
val webViewUiState = combine(
savedStateHandle.getStateFlow(EXTRA_TITLE_KEY, ""),
savedStateHandle.getStateFlow(EXTRA_URL_KEY, ""),
- showDividerFlow
- ) { title, webViewUrl, showDivider ->
+ showDividerFlow,
+ userPreferenceRepository.getUserPreference()
+ ) { title, webViewUrl, showDivider, prefs ->
+ var convertWebViewUrl = WEB_HOST + webViewUrl
+ if (title == "mashong") {
+ convertWebViewUrl += prefs.platform
+ }
WebViewUiState.Success(
title = title,
- webViewUrl = webViewUrl,
- showToolbarDivider = showDivider
+ webViewUrl = convertWebViewUrl,
+ showToolbarDivider = showDivider,
+ additionalHttpHeaders = mapOf(Pair("authorization", prefs.token))
)
}.stateIn(
viewModelScope,
@@ -49,6 +58,7 @@ sealed interface WebViewUiState {
data class Success(
val title: String,
val webViewUrl: String,
- val showToolbarDivider: Boolean
+ val showToolbarDivider: Boolean,
+ val additionalHttpHeaders: Map
) : WebViewUiState
}
diff --git a/app/src/main/java/com/mashup/ui/webview/birthday/BirthdayActivity.kt b/app/src/main/java/com/mashup/ui/webview/birthday/BirthdayActivity.kt
new file mode 100644
index 00000000..d893ed2d
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/webview/birthday/BirthdayActivity.kt
@@ -0,0 +1,66 @@
+package com.mashup.ui.webview.birthday
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.KeyEvent
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.imePadding
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import com.mashup.constant.EXTRA_TITLE_KEY
+import com.mashup.constant.EXTRA_URL_KEY
+import com.mashup.core.common.bridge.MashupBridge
+import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.ui.webview.WebViewScreen
+import com.mashup.ui.webview.WebViewUiState
+import com.mashup.ui.webview.WebViewViewModel
+import com.mashup.util.setFullScreen
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class BirthdayActivity : ComponentActivity() {
+
+ private val webViewViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContent {
+ MashUpTheme {
+ val webViewUiState by webViewViewModel.webViewUiState.collectAsState(WebViewUiState.Loading)
+ WebViewScreen(
+ modifier = Modifier.fillMaxSize().imePadding(),
+ webViewUiState = webViewUiState,
+ mashupBridge = MashupBridge(
+ this,
+ onBackPressed = ::finish
+ ),
+ isShowMashUpToolbar = false
+ )
+ }
+ }
+ setFullScreen()
+ }
+
+ override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ finish()
+ return true
+ }
+ return super.onKeyDown(keyCode, event)
+ }
+
+ companion object {
+ fun newIntent(context: Context, urlKey: String = "birthday/crew-list"): Intent =
+ Intent(context, BirthdayActivity::class.java).apply {
+ putExtra(EXTRA_TITLE_KEY, "birthday")
+ putExtra(EXTRA_URL_KEY, urlKey)
+ }
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/webview/mashong/MashongActivity.kt b/app/src/main/java/com/mashup/ui/webview/mashong/MashongActivity.kt
new file mode 100644
index 00000000..a45c0ee6
--- /dev/null
+++ b/app/src/main/java/com/mashup/ui/webview/mashong/MashongActivity.kt
@@ -0,0 +1,69 @@
+package com.mashup.ui.webview.mashong
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.KeyEvent
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import com.mashup.constant.EXTRA_TITLE_KEY
+import com.mashup.constant.EXTRA_URL_KEY
+import com.mashup.core.common.bridge.MashupBridge
+import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.ui.danggn.ShakeDanggnActivity
+import com.mashup.ui.webview.WebViewScreen
+import com.mashup.ui.webview.WebViewUiState
+import com.mashup.ui.webview.WebViewViewModel
+import com.mashup.util.setFullScreen
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class MashongActivity : ComponentActivity() {
+
+ private val webViewViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContent {
+ MashUpTheme {
+ val webViewUiState by webViewViewModel.webViewUiState.collectAsState(WebViewUiState.Loading)
+ WebViewScreen(
+ modifier = Modifier.fillMaxSize(),
+ webViewUiState = webViewUiState,
+ mashupBridge = MashupBridge(
+ this,
+ onBackPressed = ::finish,
+ onNavigateDanggn = {
+ startActivity(ShakeDanggnActivity.newIntent(this))
+ }
+ ),
+ isShowMashUpToolbar = false
+ )
+ }
+ }
+ setFullScreen()
+ }
+
+ override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ finish()
+ return true
+ }
+ return super.onKeyDown(keyCode, event)
+ }
+
+ companion object {
+ fun newIntent(context: Context): Intent =
+ Intent(context, MashongActivity::class.java).apply {
+ putExtra(EXTRA_TITLE_KEY, "mashong")
+ putExtra(EXTRA_URL_KEY, "mashong/")
+ }
+ }
+}
diff --git a/app/src/main/java/com/mashup/ui/withdrawl/WithdrawalActivity.kt b/app/src/main/java/com/mashup/ui/withdrawl/WithdrawalActivity.kt
index 8dcb38aa..d119889b 100644
--- a/app/src/main/java/com/mashup/ui/withdrawl/WithdrawalActivity.kt
+++ b/app/src/main/java/com/mashup/ui/withdrawl/WithdrawalActivity.kt
@@ -8,7 +8,7 @@ import androidx.core.view.WindowInsetsCompat
import com.mashup.R
import com.mashup.base.BaseActivity
import com.mashup.constant.EXTRA_ANIMATION
-import com.mashup.constant.log.LOG_DELETE_SUCCESS_USER
+import com.mashup.constant.log.LOG_DELETE_USER_SUCCESS
import com.mashup.core.common.extensions.setEmptyUIOfTextField
import com.mashup.core.common.extensions.setFailedUiOfTextField
import com.mashup.core.common.extensions.setSuccessUiOfTextField
@@ -86,7 +86,7 @@ class WithdrawalActivity : BaseActivity() {
}
is WithdrawalState.Success -> {
hideLoading()
- AnalyticsManager.addEvent(LOG_DELETE_SUCCESS_USER)
+ AnalyticsManager.addEvent(LOG_DELETE_USER_SUCCESS)
finish()
startActivity(
LoginActivity.newIntent(
diff --git a/app/src/main/java/com/mashup/util/FullScreen.kt b/app/src/main/java/com/mashup/util/FullScreen.kt
new file mode 100644
index 00000000..8567a3a3
--- /dev/null
+++ b/app/src/main/java/com/mashup/util/FullScreen.kt
@@ -0,0 +1,19 @@
+package com.mashup.util
+
+import android.os.Build
+import android.view.View
+import android.view.WindowInsets
+import android.view.WindowInsetsController
+import androidx.activity.ComponentActivity
+
+fun ComponentActivity.setFullScreen() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ window.insetsController?.apply {
+ hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
+ systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ }
+ } else {
+ window.decorView.systemUiVisibility =
+ (View.SYSTEM_UI_FLAG_IMMERSIVE or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN)
+ }
+}
diff --git a/app/src/main/res/layout/activity_schedule_detail.xml b/app/src/main/res/layout/activity_schedule_detail.xml
index a72893a3..3ae97818 100644
--- a/app/src/main/res/layout/activity_schedule_detail.xml
+++ b/app/src/main/res/layout/activity_schedule_detail.xml
@@ -1,35 +1,9 @@
-
+
-
-
-
-
-
-
+ android:layout_height="match_parent"
+ android:background="@color/white" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_event_timeline_content.xml b/app/src/main/res/layout/item_event_timeline_content.xml
deleted file mode 100644
index 7d936230..00000000
--- a/app/src/main/res/layout/item_event_timeline_content.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/item_event_timeline_header.xml b/app/src/main/res/layout/item_event_timeline_header.xml
deleted file mode 100644
index b10aabfc..00000000
--- a/app/src/main/res/layout/item_event_timeline_header.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cc2787a2..a96eaa1f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -30,7 +30,7 @@
출석
결석
지각
- 플랫폼별 출석현황 보러가기
+ 상세 스케줄 보러가기
로그아웃
회원탈퇴
매시업을 잊지말아죠...
@@ -44,10 +44,10 @@
다시시도
돌아가기
- 일정 준비 중이에요.\n조금만 기다려주세요!
- %d기 모든 활동이\n종료되었어요.
- %s님의\n출석을 기다리고 있어요!]]>
- 열심히 일정을 준비하고 있어요\n조금만 기다려 주세요!
+ 조금만 기다려주세요!]]>
+ 종료되었어요.]]>
+ %s님의\n출석을 기다리고 있어요!]]>
+
D-?
등록된 일정이 없어요
매시업 크루들의 출석현황이 궁금하다면?
diff --git a/app/src/release/java/com/mashup/data/network/NetworkConstanst.kt b/app/src/release/java/com/mashup/data/network/NetworkConstanst.kt
index 782df926..0157244a 100644
--- a/app/src/release/java/com/mashup/data/network/NetworkConstanst.kt
+++ b/app/src/release/java/com/mashup/data/network/NetworkConstanst.kt
@@ -1,3 +1,4 @@
package com.mashup.data.network
-const val API_HOST = "https://api.member.mash-up.kr"
+const val API_HOST = "https://api.member.mash-up.kr/"
+const val WEB_HOST = "https://app.mash-up.kr/"
diff --git a/core/common/src/main/java/com/mashup/core/common/bridge/MashupBridge.kt b/core/common/src/main/java/com/mashup/core/common/bridge/MashupBridge.kt
new file mode 100644
index 00000000..d5fafd36
--- /dev/null
+++ b/core/common/src/main/java/com/mashup/core/common/bridge/MashupBridge.kt
@@ -0,0 +1,38 @@
+package com.mashup.core.common.bridge
+
+import android.content.Context
+import android.webkit.JavascriptInterface
+import android.widget.Toast
+
+class MashupBridge(
+ private val context: Context,
+ private val onBackPressed: () -> Unit = {},
+ private val onNavigateDanggn: () -> Unit = {}
+) : MashupBridgeInterface() {
+ @JavascriptInterface
+ override fun showToast(toast: String) {
+ Toast.makeText(context, toast, Toast.LENGTH_SHORT).show()
+ }
+
+ @JavascriptInterface
+ override fun step(type: String) {
+ when (Type.values().find { it.name == type.uppercase() }) {
+ Type.BACK -> onBackPressed()
+ Type.DANGGN -> onNavigateDanggn()
+ else -> {}
+ }
+ }
+
+ companion object {
+ const val name = "MashupBridge"
+ }
+}
+
+abstract class MashupBridgeInterface {
+ open fun showToast(toast: String) {}
+ open fun step(type: String) {}
+}
+
+enum class Type {
+ BACK, DANGGN
+}
diff --git a/core/common/src/main/java/com/mashup/core/common/extensions/DateExt.kt b/core/common/src/main/java/com/mashup/core/common/extensions/DateExt.kt
index c0c8fdbe..d67df070 100644
--- a/core/common/src/main/java/com/mashup/core/common/extensions/DateExt.kt
+++ b/core/common/src/main/java/com/mashup/core/common/extensions/DateExt.kt
@@ -1,6 +1,7 @@
package com.mashup.core.common.extensions
import java.text.SimpleDateFormat
+import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone
@@ -19,3 +20,36 @@ fun Date.getTimeFormat(): String {
"??:??"
}
}
+
+fun Date.year(): Int {
+ val calendar = Calendar.getInstance()
+ calendar.time = this
+ return calendar.get(Calendar.YEAR)
+}
+
+fun Date.month(): Int {
+ val calendar = Calendar.getInstance()
+ calendar.time = this
+ return calendar.get(Calendar.MONTH) + 1
+}
+
+fun Date.day(): Int {
+ val calendar = Calendar.getInstance()
+ calendar.time = this
+ return calendar.get(Calendar.DATE)
+}
+
+fun Date.week(): String {
+ val calendar = Calendar.getInstance()
+ calendar.time = this
+ return when (calendar.get(Calendar.DAY_OF_WEEK)) {
+ Calendar.MONDAY -> "월"
+ Calendar.TUESDAY -> "화"
+ Calendar.WEDNESDAY -> "수"
+ Calendar.THURSDAY -> "목"
+ Calendar.FRIDAY -> "금"
+ Calendar.SATURDAY -> "토"
+ Calendar.SUNDAY -> "일"
+ else -> ""
+ }
+}
diff --git a/core/common/src/main/res/drawable/ic_clock.xml b/core/common/src/main/res/drawable/ic_clock.xml
index cf58d00a..1e15c2e2 100644
--- a/core/common/src/main/res/drawable/ic_clock.xml
+++ b/core/common/src/main/res/drawable/ic_clock.xml
@@ -1,14 +1,17 @@
-
-
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+
+
diff --git a/core/common/src/main/res/drawable/ic_mappin.xml b/core/common/src/main/res/drawable/ic_mappin.xml
index e4a6a5cb..a2852d3a 100644
--- a/core/common/src/main/res/drawable/ic_mappin.xml
+++ b/core/common/src/main/res/drawable/ic_mappin.xml
@@ -1,14 +1,14 @@
+ android:width="21dp"
+ android:height="21dp"
+ android:viewportWidth="21"
+ android:viewportHeight="21">
diff --git a/core/common/src/main/res/drawable/ic_more.xml b/core/common/src/main/res/drawable/ic_more.xml
new file mode 100644
index 00000000..bc35f209
--- /dev/null
+++ b/core/common/src/main/res/drawable/ic_more.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/core/common/src/main/res/drawable/img_empty_schdule.png b/core/common/src/main/res/drawable/img_empty_schdule.png
new file mode 100644
index 00000000..5e70c6c8
Binary files /dev/null and b/core/common/src/main/res/drawable/img_empty_schdule.png differ
diff --git a/core/common/src/main/res/drawable/img_placeholder_sleeping.png b/core/common/src/main/res/drawable/img_placeholder_sleeping.png
index aea507e8..8a2ca245 100644
Binary files a/core/common/src/main/res/drawable/img_placeholder_sleeping.png and b/core/common/src/main/res/drawable/img_placeholder_sleeping.png differ
diff --git a/core/data/src/main/java/com/mashup/core/data/repository/MetaRepository.kt b/core/data/src/main/java/com/mashup/core/data/repository/MetaRepository.kt
new file mode 100644
index 00000000..56487fc9
--- /dev/null
+++ b/core/data/src/main/java/com/mashup/core/data/repository/MetaRepository.kt
@@ -0,0 +1,14 @@
+package com.mashup.core.data.repository
+
+import com.mashup.core.network.Response
+import com.mashup.core.network.dao.MetaDao
+import com.mashup.core.network.dto.RnbResponse
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class MetaRepository @Inject constructor(
+ private val metaDao: MetaDao
+) {
+ suspend fun getRnb(): Response = metaDao.getRnb()
+}
diff --git a/core/data/src/main/java/com/mashup/core/data/repository/PushHistoryRepository.kt b/core/data/src/main/java/com/mashup/core/data/repository/PushHistoryRepository.kt
new file mode 100644
index 00000000..cfa9def5
--- /dev/null
+++ b/core/data/src/main/java/com/mashup/core/data/repository/PushHistoryRepository.kt
@@ -0,0 +1,26 @@
+package com.mashup.core.data.repository
+
+import com.mashup.core.network.Response
+import com.mashup.core.network.dao.PushHistoryDao
+import com.mashup.core.network.dto.PushHistoryResponse
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class PushHistoryRepository @Inject constructor(
+ private val pushHistoryDao: PushHistoryDao
+) {
+ suspend fun getPushHistory(
+ page: Int,
+ size: Int,
+ sort: String? = null
+ ): Response =
+ pushHistoryDao.getPushHistory(page, size, sort)
+
+ suspend fun postPushHistoryCheck(
+ page: Int,
+ size: Int,
+ sort: String? = null
+ ): Response =
+ pushHistoryDao.postPushHistoryCheck(page, size, sort)
+}
diff --git a/core/network/build.gradle b/core/network/build.gradle
index 7676640f..0563dada 100644
--- a/core/network/build.gradle
+++ b/core/network/build.gradle
@@ -10,7 +10,6 @@ android {
compileSdk compileVersion
defaultConfig {
- minSdk minVersion
targetSdk targetVersion
}
compileOptions {
diff --git a/core/network/src/main/java/com/mashup/core/network/dao/MetaDao.kt b/core/network/src/main/java/com/mashup/core/network/dao/MetaDao.kt
new file mode 100644
index 00000000..6b38ca48
--- /dev/null
+++ b/core/network/src/main/java/com/mashup/core/network/dao/MetaDao.kt
@@ -0,0 +1,10 @@
+package com.mashup.core.network.dao
+
+import com.mashup.core.network.Response
+import com.mashup.core.network.dto.RnbResponse
+import retrofit2.http.GET
+
+interface MetaDao {
+ @GET("/api/v1/meta/rnb")
+ suspend fun getRnb(): Response
+}
diff --git a/core/network/src/main/java/com/mashup/core/network/dao/PushHistoryDao.kt b/core/network/src/main/java/com/mashup/core/network/dao/PushHistoryDao.kt
new file mode 100644
index 00000000..ca0bd32f
--- /dev/null
+++ b/core/network/src/main/java/com/mashup/core/network/dao/PushHistoryDao.kt
@@ -0,0 +1,23 @@
+package com.mashup.core.network.dao
+
+import com.mashup.core.network.Response
+import com.mashup.core.network.dto.PushHistoryResponse
+import retrofit2.http.GET
+import retrofit2.http.POST
+import retrofit2.http.Query
+
+interface PushHistoryDao {
+ @GET("/api/v1/push-histories")
+ suspend fun getPushHistory(
+ @Query("page") page: Int,
+ @Query("size") size: Int,
+ @Query("sort") sort: String?
+ ): Response
+
+ @POST("/api/v1/push-histories")
+ suspend fun postPushHistoryCheck(
+ @Query("page") page: Int,
+ @Query("size") size: Int,
+ @Query("sort") sort: String?
+ ): Response
+}
diff --git a/core/network/src/main/java/com/mashup/core/network/dto/PushHistoryResponse.kt b/core/network/src/main/java/com/mashup/core/network/dto/PushHistoryResponse.kt
new file mode 100644
index 00000000..5ad782d3
--- /dev/null
+++ b/core/network/src/main/java/com/mashup/core/network/dto/PushHistoryResponse.kt
@@ -0,0 +1,18 @@
+package com.mashup.core.network.dto
+
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class PushHistoryResponse(
+ val read: List,
+ val unread: List
+) {
+ @JsonClass(generateAdapter = true)
+ data class Notice(
+ val pushType: String,
+ val title: String,
+ val body: String,
+ val linkType: String,
+ val sendTime: String
+ )
+}
diff --git a/core/network/src/main/java/com/mashup/core/network/dto/RnbResponse.kt b/core/network/src/main/java/com/mashup/core/network/dto/RnbResponse.kt
new file mode 100644
index 00000000..cae2bbc8
--- /dev/null
+++ b/core/network/src/main/java/com/mashup/core/network/dto/RnbResponse.kt
@@ -0,0 +1,8 @@
+package com.mashup.core.network.dto
+
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class RnbResponse(
+ val menus: List
+)
diff --git a/core/ui/src/main/java/com/mashup/core/ui/widget/MashUpButton.kt b/core/ui/src/main/java/com/mashup/core/ui/widget/MashUpButton.kt
index 1851f688..ba7213b7 100644
--- a/core/ui/src/main/java/com/mashup/core/ui/widget/MashUpButton.kt
+++ b/core/ui/src/main/java/com/mashup/core/ui/widget/MashUpButton.kt
@@ -46,7 +46,10 @@ import com.mashup.core.ui.colors.White
import com.mashup.core.ui.theme.MashUpTheme
import com.mashup.core.ui.typography.Body1
-enum class ButtonStyle(val backgroundColor: Color, val textColor: Color) {
+enum class ButtonStyle(
+ val backgroundColor: Color,
+ val textColor: Color
+) {
PRIMARY(backgroundColor = Brand500, textColor = White),
INVERSE(backgroundColor = Brand100, textColor = Brand500),
DISABLE(backgroundColor = Brand300, textColor = White),
@@ -116,7 +119,8 @@ fun ButtonCircularProgressbar(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
- animation = keyframes {
+ animation =
+ keyframes {
durationMillis = progressDuration
}
)
diff --git a/core/ui/src/main/java/com/mashup/core/ui/widget/MashUpGradientButton.kt b/core/ui/src/main/java/com/mashup/core/ui/widget/MashUpGradientButton.kt
new file mode 100644
index 00000000..5c4edf04
--- /dev/null
+++ b/core/ui/src/main/java/com/mashup/core/ui/widget/MashUpGradientButton.kt
@@ -0,0 +1,59 @@
+package com.mashup.core.ui.widget
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import com.mashup.core.ui.typography.Body3
+
+@Composable
+fun MashUpGradientButton(
+ modifier: Modifier = Modifier,
+ text: String = "",
+ onClick: () -> Unit = {},
+ isEnabled: Boolean = true,
+ gradientColors: List = listOf()
+) {
+ Box(
+ modifier = modifier
+ .clip(RoundedCornerShape(12.dp))
+ .background(
+ brush = Brush.linearGradient(
+ colors = gradientColors
+ )
+ )
+ .padding(horizontal = 20.dp)
+ .clickable(
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() },
+ enabled = isEnabled,
+ onClick = onClick
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = text,
+ style = Body3.copy(
+ color = Color.White
+ )
+ )
+ }
+ }
+}
diff --git a/core/ui/src/main/java/com/mashup/core/ui/widget/MashUpHtmlText.kt b/core/ui/src/main/java/com/mashup/core/ui/widget/MashUpHtmlText.kt
new file mode 100644
index 00000000..65d7992e
--- /dev/null
+++ b/core/ui/src/main/java/com/mashup/core/ui/widget/MashUpHtmlText.kt
@@ -0,0 +1,40 @@
+package com.mashup.core.ui.widget
+
+import android.text.Spanned
+import androidx.annotation.StyleRes
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+
+/**
+ * MashUpHtmlText.kt
+ *
+ * Created by Minji Jeong on 2024/06/29
+ * Copyright © 2024 MashUp All rights reserved.
+ */
+
+@Composable
+fun MashUpHtmlText(
+ content: Spanned,
+ modifier: Modifier = Modifier,
+ @StyleRes textAppearance: Int? = null
+) {
+ AndroidView(
+ factory = { context ->
+ AppCompatTextView(
+ context
+ ).apply {
+ if (textAppearance != null) {
+ setTextAppearance(textAppearance)
+ }
+ text = content
+ includeFontPadding = false
+ }
+ },
+ modifier = modifier,
+ update = { view ->
+ view.text = content
+ }
+ )
+}
diff --git a/core/ui/src/main/java/com/mashup/core/ui/widget/MashupPlatformBadge.kt b/core/ui/src/main/java/com/mashup/core/ui/widget/MashupPlatformBadge.kt
new file mode 100644
index 00000000..195a2596
--- /dev/null
+++ b/core/ui/src/main/java/com/mashup/core/ui/widget/MashupPlatformBadge.kt
@@ -0,0 +1,82 @@
+package com.mashup.core.ui.widget
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import com.mashup.core.ui.R
+import com.mashup.core.ui.typography.Body3
+
+@Composable
+fun MashupPlatformBadge(
+ platform: String,
+ modifier: Modifier = Modifier
+) {
+ Row(
+ modifier = modifier
+ .background(
+ color = platform.getColor(),
+ shape = RoundedCornerShape(100.dp)
+ )
+ .padding(horizontal = 10.dp, vertical = 6.5.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Image(
+ modifier = Modifier.size(12.dp),
+ painter = painterResource(id = platform.getIcon()),
+ contentDescription = null
+ )
+ Spacer(
+ modifier = Modifier.width(4.dp)
+ )
+ Text(
+ text = platform.convertCamelCase().name,
+ style = Body3,
+ color = Color.White
+ )
+ }
+}
+
+private fun String.getIcon(): Int {
+ return when (this) {
+ "ALL" -> R.drawable.ic_semina
+ else -> R.drawable.ic_platform
+ }
+}
+
+enum class PlatformType {
+ Seminar, Design, Spring, Ios, Android, Web, Node
+}
+
+private fun String.getColor(): Color {
+ return when (this) {
+ "ALL" -> Color(0xFF7CD5FF)
+ else -> Color(0xFF8A61FF)
+ }
+}
+
+private fun String.convertCamelCase(): PlatformType {
+ return when (this) {
+ "ALL" -> PlatformType.Seminar
+ "DESIGN" -> PlatformType.Design
+ "SPRING" -> PlatformType.Spring
+ "IOS" -> PlatformType.Ios
+ "ANDROID" -> PlatformType.Android
+ "WEB" -> PlatformType.Web
+ "NODE" -> PlatformType.Node
+ else -> PlatformType.Seminar
+ }
+}
diff --git a/core/ui/src/main/res/drawable-hdpi/img_birthday_mashong.png b/core/ui/src/main/res/drawable-hdpi/img_birthday_mashong.png
new file mode 100644
index 00000000..062c0492
Binary files /dev/null and b/core/ui/src/main/res/drawable-hdpi/img_birthday_mashong.png differ
diff --git a/core/ui/src/main/res/drawable-hdpi/img_error.png b/core/ui/src/main/res/drawable-hdpi/img_error.png
new file mode 100644
index 00000000..51d27e13
Binary files /dev/null and b/core/ui/src/main/res/drawable-hdpi/img_error.png differ
diff --git a/core/ui/src/main/res/drawable-hdpi/img_noalert.png b/core/ui/src/main/res/drawable-hdpi/img_noalert.png
new file mode 100644
index 00000000..cb58e362
Binary files /dev/null and b/core/ui/src/main/res/drawable-hdpi/img_noalert.png differ
diff --git a/core/ui/src/main/res/drawable-mdpi/img_birthday_mashong.png b/core/ui/src/main/res/drawable-mdpi/img_birthday_mashong.png
new file mode 100644
index 00000000..d38c1d3a
Binary files /dev/null and b/core/ui/src/main/res/drawable-mdpi/img_birthday_mashong.png differ
diff --git a/core/ui/src/main/res/drawable-mdpi/img_error.png b/core/ui/src/main/res/drawable-mdpi/img_error.png
new file mode 100644
index 00000000..831894fb
Binary files /dev/null and b/core/ui/src/main/res/drawable-mdpi/img_error.png differ
diff --git a/core/ui/src/main/res/drawable-mdpi/img_noalert.png b/core/ui/src/main/res/drawable-mdpi/img_noalert.png
new file mode 100644
index 00000000..7621f56b
Binary files /dev/null and b/core/ui/src/main/res/drawable-mdpi/img_noalert.png differ
diff --git a/core/ui/src/main/res/drawable-xhdpi/img_birthday_mashong.png b/core/ui/src/main/res/drawable-xhdpi/img_birthday_mashong.png
new file mode 100644
index 00000000..c131eee5
Binary files /dev/null and b/core/ui/src/main/res/drawable-xhdpi/img_birthday_mashong.png differ
diff --git a/core/ui/src/main/res/drawable-xhdpi/img_error.png b/core/ui/src/main/res/drawable-xhdpi/img_error.png
new file mode 100644
index 00000000..4f397ac4
Binary files /dev/null and b/core/ui/src/main/res/drawable-xhdpi/img_error.png differ
diff --git a/core/ui/src/main/res/drawable-xhdpi/img_noalert.png b/core/ui/src/main/res/drawable-xhdpi/img_noalert.png
new file mode 100644
index 00000000..2d1740b2
Binary files /dev/null and b/core/ui/src/main/res/drawable-xhdpi/img_noalert.png differ
diff --git a/core/ui/src/main/res/drawable-xxhdpi/img_birthday_mashong.png b/core/ui/src/main/res/drawable-xxhdpi/img_birthday_mashong.png
new file mode 100644
index 00000000..7cc7744f
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxhdpi/img_birthday_mashong.png differ
diff --git a/core/ui/src/main/res/drawable-xxhdpi/img_error.png b/core/ui/src/main/res/drawable-xxhdpi/img_error.png
new file mode 100644
index 00000000..52155799
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxhdpi/img_error.png differ
diff --git a/core/ui/src/main/res/drawable-xxhdpi/img_noalert.png b/core/ui/src/main/res/drawable-xxhdpi/img_noalert.png
new file mode 100644
index 00000000..960e34ca
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxhdpi/img_noalert.png differ
diff --git a/core/ui/src/main/res/drawable-xxxhdpi/img_birthday_mashong.png b/core/ui/src/main/res/drawable-xxxhdpi/img_birthday_mashong.png
new file mode 100644
index 00000000..e1981506
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxxhdpi/img_birthday_mashong.png differ
diff --git a/core/ui/src/main/res/drawable-xxxhdpi/img_error.png b/core/ui/src/main/res/drawable-xxxhdpi/img_error.png
new file mode 100644
index 00000000..9e7ac234
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxxhdpi/img_error.png differ
diff --git a/core/ui/src/main/res/drawable-xxxhdpi/img_noalert.png b/core/ui/src/main/res/drawable-xxxhdpi/img_noalert.png
new file mode 100644
index 00000000..50e66c91
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxxhdpi/img_noalert.png differ
diff --git a/core/ui/src/main/res/drawable/ic_alarm.xml b/core/ui/src/main/res/drawable/ic_alarm.xml
new file mode 100644
index 00000000..7a744b8c
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_alarm.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_birthday.xml b/core/ui/src/main/res/drawable/ic_birthday.xml
new file mode 100644
index 00000000..1e05e756
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_birthday.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_carrot.xml b/core/ui/src/main/res/drawable/ic_carrot.xml
new file mode 100644
index 00000000..58441ee5
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_carrot.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_etc.xml b/core/ui/src/main/res/drawable/ic_etc.xml
new file mode 100644
index 00000000..9f12ac8b
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_etc.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_mashong.xml b/core/ui/src/main/res/drawable/ic_mashong.xml
new file mode 100644
index 00000000..9d4dccc1
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_mashong.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_new.xml b/core/ui/src/main/res/drawable/ic_new.xml
new file mode 100644
index 00000000..61f78adb
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_new.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_platform.xml b/core/ui/src/main/res/drawable/ic_platform.xml
new file mode 100644
index 00000000..cf524524
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_platform.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_semina.xml b/core/ui/src/main/res/drawable/ic_semina.xml
new file mode 100644
index 00000000..e45ae499
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_semina.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_setting.xml b/core/ui/src/main/res/drawable/ic_setting.xml
new file mode 100644
index 00000000..95fe7fe3
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_setting.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/feature/moreMenu/.gitignore b/feature/moreMenu/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/feature/moreMenu/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/moreMenu/build.gradle b/feature/moreMenu/build.gradle
new file mode 100644
index 00000000..dba523e5
--- /dev/null
+++ b/feature/moreMenu/build.gradle
@@ -0,0 +1,65 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'com.mashup.feature.moreMenu'
+ compileSdk compileVersion
+
+ defaultConfig {
+ minSdk minVersion
+ targetSdk targetVersion
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion composeCompiler
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+ kapt {
+ correctErrorTypes = true
+ }
+}
+
+dependencies {
+
+ implementation project(":core:common")
+ implementation project(':core:ui')
+ implementation project(":core:datastore")
+ implementation project(":core:model")
+ implementation project(':core:network')
+ implementation project(':core:data')
+ implementation 'androidx.activity:activity-compose:1.9.0'
+ implementation platform('androidx.compose:compose-bom:2023.08.00')
+ implementation 'androidx.compose.ui:ui'
+ implementation 'androidx.compose.ui:ui-graphics'
+ implementation 'androidx.compose.ui:ui-tooling-preview'
+ implementation 'androidx.compose.material3:material3'
+ implementation project(':feature:moreMenu:notice')
+ androidTestImplementation platform('androidx.compose:compose-bom:2023.08.00')
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
+ debugImplementation project(':core:testing')
+
+ implementation "com.google.dagger:hilt-android:$hiltVersion"
+ debugImplementation 'androidx.compose.ui:ui-tooling'
+ debugImplementation 'androidx.compose.ui:ui-test-manifest'
+ kapt "com.google.dagger:hilt-compiler:$hiltVersion"
+
+ implementation "com.squareup.moshi:moshi-kotlin:$moshiVersion"
+ implementation "com.squareup.moshi:moshi-adapters:$moshiVersion"
+ kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion"
+}
\ No newline at end of file
diff --git a/feature/moreMenu/consumer-rules.pro b/feature/moreMenu/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/feature/moreMenu/notice/.gitignore b/feature/moreMenu/notice/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/feature/moreMenu/notice/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/moreMenu/notice/build.gradle b/feature/moreMenu/notice/build.gradle
new file mode 100644
index 00000000..893a572a
--- /dev/null
+++ b/feature/moreMenu/notice/build.gradle
@@ -0,0 +1,63 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'com.mashup.feature.moreMenu.notice'
+ compileSdk compileVersion
+
+ defaultConfig {
+ minSdk minVersion
+ targetSdk targetVersion
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion composeCompiler
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+ kapt {
+ correctErrorTypes = true
+ }
+}
+
+dependencies {
+ implementation project(":core:common")
+ implementation project(':core:ui')
+ implementation project(":core:datastore")
+ implementation project(":core:model")
+ implementation project(':core:network')
+ implementation project(':core:data')
+ implementation 'androidx.activity:activity-compose:1.9.0'
+ implementation platform('androidx.compose:compose-bom:2023.08.00')
+ implementation 'androidx.compose.ui:ui'
+ implementation 'androidx.compose.ui:ui-graphics'
+ implementation 'androidx.compose.ui:ui-tooling-preview'
+ implementation 'androidx.compose.material3:material3'
+ androidTestImplementation platform('androidx.compose:compose-bom:2023.08.00')
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
+ debugImplementation project(':core:testing')
+
+ implementation "com.google.dagger:hilt-android:$hiltVersion"
+ debugImplementation 'androidx.compose.ui:ui-tooling'
+ debugImplementation 'androidx.compose.ui:ui-test-manifest'
+ kapt "com.google.dagger:hilt-compiler:$hiltVersion"
+
+ implementation "com.squareup.moshi:moshi-kotlin:$moshiVersion"
+ implementation "com.squareup.moshi:moshi-adapters:$moshiVersion"
+ kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion"
+}
\ No newline at end of file
diff --git a/feature/moreMenu/notice/consumer-rules.pro b/feature/moreMenu/notice/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/feature/moreMenu/notice/proguard-rules.pro b/feature/moreMenu/notice/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/feature/moreMenu/notice/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/moreMenu/notice/src/androidTest/java/com/example/notice/ExampleInstrumentedTest.kt b/feature/moreMenu/notice/src/androidTest/java/com/example/notice/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..7d67c1cf
--- /dev/null
+++ b/feature/moreMenu/notice/src/androidTest/java/com/example/notice/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.example.notice
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.example.alarm.test", appContext.packageName)
+ }
+}
diff --git a/feature/moreMenu/notice/src/main/AndroidManifest.xml b/feature/moreMenu/notice/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..d0a21e08
--- /dev/null
+++ b/feature/moreMenu/notice/src/main/AndroidManifest.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/moreMenu/notice/src/main/java/com/example/notice/NoticeScreen.kt b/feature/moreMenu/notice/src/main/java/com/example/notice/NoticeScreen.kt
new file mode 100644
index 00000000..ac1f8d3d
--- /dev/null
+++ b/feature/moreMenu/notice/src/main/java/com/example/notice/NoticeScreen.kt
@@ -0,0 +1,219 @@
+package com.example.notice
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.example.notice.components.NoticeItem
+import com.example.notice.model.NoticeState
+import com.example.notice.model.NoticeState.Companion.isNoticeEmpty
+import com.mashup.core.network.dto.PushHistoryResponse
+import com.mashup.core.ui.R
+import com.mashup.core.ui.colors.Gray600
+import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.core.ui.typography.Body5
+import com.mashup.core.ui.typography.Title3
+import com.mashup.core.ui.widget.MashUpToolbar
+
+@Composable
+fun NoticeRoute(
+ modifier: Modifier = Modifier,
+ noticeState: NoticeState = NoticeState(),
+ onBackPressed: () -> Unit = {},
+ onLoadNextNotice: () -> Unit = {},
+ onClickNoticeItem: (PushHistoryResponse.Notice) -> Unit = {}
+) {
+ NoticeScreen(
+ modifier = modifier,
+ noticeState = noticeState,
+ onBackPressed = onBackPressed,
+ onLoadNextNotice = onLoadNextNotice,
+ onClickNoticeItem = onClickNoticeItem
+ )
+}
+
+@Composable
+fun NoticeScreen(
+ modifier: Modifier = Modifier,
+ noticeState: NoticeState = NoticeState(),
+ onBackPressed: () -> Unit = {},
+ onLoadNextNotice: () -> Unit = {},
+ onClickNoticeItem: (PushHistoryResponse.Notice) -> Unit = {}
+) {
+ Column(modifier = modifier) {
+ MashUpToolbar(
+ modifier = Modifier.fillMaxWidth(),
+ title = "알림",
+ showBackButton = true,
+ onClickBackButton = onBackPressed
+ )
+
+ val scrollState = rememberLazyListState()
+ val lastItemReached by remember {
+ derivedStateOf {
+ val lastVisibleItem = scrollState.layoutInfo.visibleItemsInfo.lastOrNull()
+ val lastItemIndex = scrollState.layoutInfo.totalItemsCount - 1
+ lastVisibleItem?.index == lastItemIndex
+ }
+ }
+
+ LaunchedEffect(lastItemReached) {
+ if (lastItemReached) {
+ onLoadNextNotice()
+ }
+ }
+
+ if (noticeState.isError) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_error),
+ contentDescription = null
+ )
+ Text(
+ text = "오류가 발생했어요...",
+ color = Gray600,
+ style = Body5
+ )
+ return
+ }
+ }
+
+ if (noticeState.isNoticeEmpty()) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_noalert),
+ contentDescription = null
+ )
+ Text(
+ text = "아직 도착한 알림이 없어요",
+ color = Gray600,
+ style = Body5
+ )
+ return
+ }
+ }
+
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(
+ horizontal = 20.dp
+ ),
+ state = scrollState
+ ) {
+ if (noticeState.newNoticeList.isNotEmpty()) {
+ item {
+ Text(
+ text = "새로운 알림",
+ style = Title3,
+ fontWeight = FontWeight.Bold,
+ color = Color(0xFF2C3037)
+ )
+ Spacer(
+ modifier = Modifier.height(16.dp)
+ )
+ }
+ items(noticeState.newNoticeList) {
+ NoticeItem(
+ notice = it,
+ modifier = Modifier
+ .fillMaxWidth().clickable {
+ onClickNoticeItem(it)
+ }
+ )
+ Spacer(
+ modifier = Modifier.height(12.dp)
+ )
+ }
+ }
+
+ if (noticeState.oldNoticeList.isNotEmpty()) {
+ item {
+ Spacer(
+ modifier = Modifier.height(12.dp)
+ )
+ }
+ item {
+ Text(
+ text = "지난 알림",
+ style = Title3,
+ fontWeight = FontWeight.Bold,
+ color = Color(0xFF2C3037)
+ )
+ Spacer(
+ modifier = Modifier.height(16.dp)
+ )
+ }
+ items(noticeState.oldNoticeList) {
+ NoticeItem(
+ notice = it,
+ modifier = Modifier
+ .fillMaxWidth().clickable {
+ onClickNoticeItem(it)
+ }
+ )
+ Spacer(
+ modifier = Modifier.height(12.dp)
+ )
+ }
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun PreviewNoticeScreen() {
+ MashUpTheme {
+ NoticeScreen(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(color = Color(0xFFF8F7FC))
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun PreviewNoticeScreenError() {
+ MashUpTheme {
+ NoticeScreen(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(color = Color(0xFFF8F7FC)),
+ noticeState = NoticeState().copy(
+ isError = true
+ )
+ )
+ }
+}
diff --git a/feature/moreMenu/notice/src/main/java/com/example/notice/components/NoticeItem.kt b/feature/moreMenu/notice/src/main/java/com/example/notice/components/NoticeItem.kt
new file mode 100644
index 00000000..6f012bae
--- /dev/null
+++ b/feature/moreMenu/notice/src/main/java/com/example/notice/components/NoticeItem.kt
@@ -0,0 +1,102 @@
+package com.example.notice.components
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.example.notice.util.getNoticeTime
+import com.example.notice.util.getPushImage
+import com.mashup.core.network.dto.PushHistoryResponse
+import com.mashup.core.ui.colors.Gray400
+import com.mashup.core.ui.colors.Gray500
+import com.mashup.core.ui.colors.Gray950
+import com.mashup.core.ui.typography.Body5
+import com.mashup.core.ui.typography.Caption1
+import com.mashup.core.ui.typography.Caption2
+
+@Composable
+fun NoticeItem(
+ notice: PushHistoryResponse.Notice,
+ modifier: Modifier = Modifier
+) {
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .background(
+ color = Color.White,
+ shape = RoundedCornerShape(8.dp)
+ )
+ .clip(RoundedCornerShape(8.dp))
+ .padding(horizontal = 14.dp, vertical = 12.dp),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Image(
+ modifier = Modifier
+ .size(26.dp)
+ .align(Alignment.Top),
+ painter = painterResource(notice.getPushImage()),
+ contentDescription = null
+ )
+ Column(
+ modifier = Modifier
+ .width(254.dp)
+ ) {
+ Text(
+ text = notice.title,
+ style = Caption1,
+ color = Gray500
+ )
+ Spacer(
+ modifier = Modifier.height(4.dp)
+ )
+ Text(
+ text = notice.body,
+ style = Body5.copy(
+ fontWeight = FontWeight.W600
+ ),
+ color = Gray950
+ )
+ Spacer(
+ modifier = Modifier.height(8.dp)
+ )
+ Text(
+ text = notice.sendTime.getNoticeTime(),
+ style = Caption2,
+ color = Gray400
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun PreviewNoticeItem() {
+ NoticeItem(
+ modifier = Modifier.background(color = Color.White),
+ notice = PushHistoryResponse.Notice(
+ title = "",
+ body = "",
+ sendTime = "",
+ pushType = "",
+ linkType = ""
+ )
+ )
+}
diff --git a/feature/moreMenu/notice/src/main/java/com/example/notice/model/NoticeSideEffect.kt b/feature/moreMenu/notice/src/main/java/com/example/notice/model/NoticeSideEffect.kt
new file mode 100644
index 00000000..926501e3
--- /dev/null
+++ b/feature/moreMenu/notice/src/main/java/com/example/notice/model/NoticeSideEffect.kt
@@ -0,0 +1,8 @@
+package com.example.notice.model
+
+import com.mashup.core.network.dto.PushHistoryResponse
+
+sealed interface NoticeSideEffect {
+ object OnBackPressed : NoticeSideEffect
+ data class OnNavigateMenu(val notice: PushHistoryResponse.Notice) : NoticeSideEffect
+}
diff --git a/feature/moreMenu/notice/src/main/java/com/example/notice/model/NoticeState.kt b/feature/moreMenu/notice/src/main/java/com/example/notice/model/NoticeState.kt
new file mode 100644
index 00000000..f18c82d1
--- /dev/null
+++ b/feature/moreMenu/notice/src/main/java/com/example/notice/model/NoticeState.kt
@@ -0,0 +1,15 @@
+package com.example.notice.model
+
+import com.mashup.core.network.dto.PushHistoryResponse
+
+data class NoticeState(
+ val newNoticeList: List = emptyList(),
+ val oldNoticeList: List = emptyList(),
+ val isError: Boolean = false
+) {
+ companion object {
+ fun NoticeState.isNoticeEmpty(): Boolean {
+ return this.oldNoticeList.isEmpty() && this.newNoticeList.isEmpty()
+ }
+ }
+}
diff --git a/feature/moreMenu/notice/src/main/java/com/example/notice/util/GetNoticeTime.kt b/feature/moreMenu/notice/src/main/java/com/example/notice/util/GetNoticeTime.kt
new file mode 100644
index 00000000..7531c6cd
--- /dev/null
+++ b/feature/moreMenu/notice/src/main/java/com/example/notice/util/GetNoticeTime.kt
@@ -0,0 +1,25 @@
+package com.example.notice.util
+
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+import java.util.concurrent.TimeUnit
+
+fun String.getNoticeTime(): String {
+ val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault())
+ val parsedTime = formatter.parse(this) ?: return ""
+ val currentTime = Date()
+ val diff = currentTime.time - parsedTime.time
+
+ val hours = TimeUnit.MILLISECONDS.toHours(diff)
+ val minutes = TimeUnit.MILLISECONDS.toMinutes(diff) % 60
+
+ return when {
+ hours < 1 -> "${minutes}분 전"
+ hours < 24 -> "${hours}시간 ${minutes}분 전"
+ else -> {
+ val monthDayFormatter = SimpleDateFormat("MM월 dd일", Locale.getDefault())
+ monthDayFormatter.format(parsedTime)
+ }
+ }
+}
diff --git a/feature/moreMenu/notice/src/main/java/com/example/notice/util/GetNoticeTitle.kt b/feature/moreMenu/notice/src/main/java/com/example/notice/util/GetNoticeTitle.kt
new file mode 100644
index 00000000..7296386c
--- /dev/null
+++ b/feature/moreMenu/notice/src/main/java/com/example/notice/util/GetNoticeTitle.kt
@@ -0,0 +1,23 @@
+package com.example.notice.util
+
+import com.mashup.core.network.dto.PushHistoryResponse
+
+fun PushHistoryResponse.Notice.getNoticeTitle(): String {
+ return when (pushType) {
+ "BIRTHDAY" -> {
+ "생일 축하"
+ }
+
+ "MASHONG" -> {
+ "매숑이 키우기"
+ }
+
+ "DANGGN" -> {
+ "당근 흔들기"
+ }
+
+ else -> {
+ "세미나 알림"
+ }
+ }
+}
diff --git a/feature/moreMenu/notice/src/main/java/com/example/notice/util/GetPushImage.kt b/feature/moreMenu/notice/src/main/java/com/example/notice/util/GetPushImage.kt
new file mode 100644
index 00000000..96d11e22
--- /dev/null
+++ b/feature/moreMenu/notice/src/main/java/com/example/notice/util/GetPushImage.kt
@@ -0,0 +1,33 @@
+package com.example.notice.util
+
+import androidx.annotation.DrawableRes
+import com.mashup.core.network.dto.PushHistoryResponse
+
+@DrawableRes
+fun PushHistoryResponse.Notice.getPushImage(): Int {
+ return when (pushType) {
+ "BIRTHDAY" -> {
+ com.mashup.core.ui.R.drawable.ic_birthday
+ }
+
+ "MASHONG" -> {
+ com.mashup.core.ui.R.drawable.ic_mashong
+ }
+
+ "DANGGN" -> {
+ com.mashup.core.ui.R.drawable.ic_carrot
+ }
+
+ "NOTI" -> {
+ com.mashup.core.ui.R.drawable.ic_alarm
+ }
+
+ "SETTING" -> {
+ com.mashup.core.ui.R.drawable.ic_setting
+ }
+
+ else -> {
+ com.mashup.core.ui.R.drawable.ic_etc
+ }
+ }
+}
diff --git a/feature/moreMenu/notice/src/main/res/values/strings.xml b/feature/moreMenu/notice/src/main/res/values/strings.xml
new file mode 100644
index 00000000..f599d6c4
--- /dev/null
+++ b/feature/moreMenu/notice/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ NoticeActivity
+
\ No newline at end of file
diff --git a/feature/moreMenu/notice/src/main/res/values/themes.xml b/feature/moreMenu/notice/src/main/res/values/themes.xml
new file mode 100644
index 00000000..d267c0f5
--- /dev/null
+++ b/feature/moreMenu/notice/src/main/res/values/themes.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/moreMenu/notice/src/test/java/com/example/notice/ExampleUnitTest.kt b/feature/moreMenu/notice/src/test/java/com/example/notice/ExampleUnitTest.kt
new file mode 100644
index 00000000..4c50e610
--- /dev/null
+++ b/feature/moreMenu/notice/src/test/java/com/example/notice/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.example.notice
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/feature/moreMenu/proguard-rules.pro b/feature/moreMenu/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/feature/moreMenu/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/moreMenu/src/androidTest/java/com/example/moremenu/ExampleInstrumentedTest.kt b/feature/moreMenu/src/androidTest/java/com/example/moremenu/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..a1625238
--- /dev/null
+++ b/feature/moreMenu/src/androidTest/java/com/example/moremenu/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.example.moremenu
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.example.moremenu.test", appContext.packageName)
+ }
+}
diff --git a/feature/moreMenu/src/main/AndroidManifest.xml b/feature/moreMenu/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..df296b23
--- /dev/null
+++ b/feature/moreMenu/src/main/AndroidManifest.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/moreMenu/src/main/java/com/example/moremenu/MoreMenuScreen.kt b/feature/moreMenu/src/main/java/com/example/moremenu/MoreMenuScreen.kt
new file mode 100644
index 00000000..1552ffba
--- /dev/null
+++ b/feature/moreMenu/src/main/java/com/example/moremenu/MoreMenuScreen.kt
@@ -0,0 +1,82 @@
+package com.example.moremenu
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.Divider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.example.moremenu.components.MoreMenuItem
+import com.example.moremenu.model.Menu
+import com.example.moremenu.model.MoreMenuState
+import com.mashup.core.ui.colors.Gray100
+import com.mashup.core.ui.theme.MashUpTheme
+import com.mashup.core.ui.widget.MashUpToolbar
+
+@Composable
+fun MoreMenuRoute(
+ moreMenuState: MoreMenuState,
+ modifier: Modifier = Modifier,
+ onBackPressed: () -> Unit = {},
+ onClickMenu: (Menu) -> Unit = {}
+) {
+ MoreMenuScreen(
+ modifier = modifier,
+ moreMenuState = moreMenuState,
+ onClickMenu = onClickMenu,
+ onBackPressed = onBackPressed
+ )
+}
+
+@Composable
+fun MoreMenuScreen(
+ modifier: Modifier = Modifier,
+ moreMenuState: MoreMenuState = MoreMenuState(),
+ onClickMenu: (Menu) -> Unit = {},
+ onBackPressed: () -> Unit = {}
+) {
+ LazyColumn(modifier = modifier) {
+ item {
+ MashUpToolbar(
+ modifier = Modifier.fillMaxWidth(),
+ title = "더 보기",
+ showBackButton = true,
+ onClickBackButton = onBackPressed
+ )
+ }
+ items(moreMenuState.menus) { menu ->
+ MoreMenuItem(
+ menu = menu,
+ onClickMenu = onClickMenu,
+ isShowNewIcon = moreMenuState.isShowNewIcon,
+ additionalIcon = moreMenuState.additionalIcon
+ )
+ Divider(
+ modifier = Modifier.fillMaxWidth(),
+ color = Gray100,
+ thickness = 1.dp
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun PreviewMoreMenuScreen() {
+ MashUpTheme {
+ MoreMenuScreen(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(color = Color.White),
+ moreMenuState = MoreMenuState(
+ menus = Menu.menuList
+ )
+ )
+ }
+}
diff --git a/feature/moreMenu/src/main/java/com/example/moremenu/components/MoreMenuItem.kt b/feature/moreMenu/src/main/java/com/example/moremenu/components/MoreMenuItem.kt
new file mode 100644
index 00000000..058a4d47
--- /dev/null
+++ b/feature/moreMenu/src/main/java/com/example/moremenu/components/MoreMenuItem.kt
@@ -0,0 +1,65 @@
+package com.example.moremenu.components
+
+import androidx.annotation.DrawableRes
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.example.moremenu.model.Menu
+import com.example.moremenu.model.MenuType
+import com.mashup.core.ui.colors.Gray900
+import com.mashup.core.ui.typography.SubTitle2
+
+@Composable
+fun MoreMenuItem(
+ menu: Menu,
+ modifier: Modifier = Modifier,
+ isShowNewIcon: Boolean = false,
+ @DrawableRes additionalIcon: Int = 0,
+ onClickMenu: (Menu) -> Unit = {}
+) {
+ Row(
+ modifier = modifier.fillMaxWidth().clickable {
+ onClickMenu(menu)
+ }.padding(24.dp),
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ painter = painterResource(id = menu.type.icon),
+ contentDescription = null
+ )
+ Text(
+ text = stringResource(id = menu.type.title),
+ style = SubTitle2,
+ color = Gray900
+ )
+ if (menu.type == MenuType.NOTI && isShowNewIcon) {
+ Image(
+ painter = painterResource(id = additionalIcon),
+ contentDescription = null
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun PreviewMoreMenuItem() {
+ Column {
+ Menu.menuList.forEach {
+ MoreMenuItem(menu = it)
+ }
+ }
+}
diff --git a/feature/moreMenu/src/main/java/com/example/moremenu/model/Menu.kt b/feature/moreMenu/src/main/java/com/example/moremenu/model/Menu.kt
new file mode 100644
index 00000000..446ca1e0
--- /dev/null
+++ b/feature/moreMenu/src/main/java/com/example/moremenu/model/Menu.kt
@@ -0,0 +1,78 @@
+package com.example.moremenu.model
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import com.mashup.core.network.dto.RnbResponse
+import com.mashup.feature.moreMenu.R
+import com.mashup.core.ui.R as CoreR
+
+sealed interface Menu {
+ val menuName: String
+ val type: MenuType
+
+ data class BirthDay(
+ override val menuName: String = MenuType.BIRTHDAY.name,
+ override val type: MenuType = MenuType.BIRTHDAY
+ ) : Menu
+
+ data class Mashong(
+ override val menuName: String = MenuType.MASHONG.name,
+ override val type: MenuType = MenuType.MASHONG
+ ) : Menu
+
+ data class Danggn(
+ override val menuName: String = MenuType.DANGGN.name,
+ override val type: MenuType = MenuType.DANGGN
+ ) : Menu
+
+ data class Noti(
+ override val menuName: String = MenuType.NOTI.name,
+ override val type: MenuType = MenuType.NOTI
+
+ ) : Menu
+
+ data class Setting(
+ override val menuName: String = MenuType.SETTING.name,
+ override val type: MenuType = MenuType.SETTING
+ ) : Menu
+
+ companion object {
+ val menuList = listOf(
+ BirthDay(),
+ Mashong(),
+ Danggn(),
+ Noti(),
+ Setting()
+ )
+
+ fun RnbResponse.toMenu(): List