Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/implement board #33

Merged
merged 17 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/designsystem/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,6 @@ dependencies {
androidTestImplementation(platform(libs.androidx.compose.bom))
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)

implementation(libs.lottie.compose)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.goalpanzi.mission_mate.core.designsystem.component

import androidx.annotation.RawRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition

@Composable
fun LottieImage(
@RawRes lottieRes : Int,
modifier: Modifier = Modifier
) {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(lottieRes))
val progress by animateLottieCompositionAsState(composition)
LottieAnimation(
modifier = modifier,
composition = composition,
progress = { progress },
)
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable
data class MissionBoardResponse(
val number : Int,
val reward : BoardReward = BoardReward.NONE,
val isMyPosition : Boolean = false,
val missionBoardMembers : List<MissionBoardMembersResponse>
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,16 @@ import kotlinx.serialization.Serializable

@Serializable
data class MissionBoardsResponse(
val missionBoards : List<MissionBoardResponse>
)
val missionBoards : List<MissionBoardResponse>,
val progressCount : Int,
val rank : Int
){
val boardRewardList : List<MissionBoardResponse> by lazy {
missionBoards.filter { it.reward != BoardReward.NONE }
}

val passedCountByMe : Int
get() {
return missionBoards.find { it.isMyPosition }?.number ?: 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ data class MissionDetailResponse(
LocalDate.parse(missionStartDate, formatter)
}

val missionEndLocalDateTime : LocalDateTime by lazy {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")
LocalDateTime.parse(missionEndDate, formatter)
}

val missionPeriod : String by lazy {
try {
val inputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")
Expand All @@ -39,7 +44,7 @@ data class MissionDetailResponse(
}
}

val missionDaysOfWeek : List<String> by lazy {
val missionDaysOfWeekTextLocale : List<String> by lazy {
try {
missionDays.map {
DayOfWeek.valueOf(it).getDisplayName(TextStyle.SHORT, Locale.getDefault())
Expand All @@ -49,6 +54,16 @@ data class MissionDetailResponse(
}
}

val missionDaysOfWeek : List<DayOfWeek> by lazy {
try {
missionDays.map {
DayOfWeek.valueOf(it)
}
}catch (e: Exception){
emptyList()
}
}

fun isStartedMission() : Boolean {
val currentDate = LocalDate.now()
return currentDate.isEqual(missionStartLocalDate) || currentDate.isAfter(missionStartLocalDate)
Expand Down
2 changes: 2 additions & 0 deletions feature/board/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ dependencies {
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)

implementation(libs.balloon)

implementation(project(":core:designsystem"))
implementation(project(":core:navigation"))
implementation(project(":core:domain"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import com.goalpanzi.mission_mate.feature.onboarding.component.StableImage
fun Block(
index: Int,
type: BlockType,
eventType: BlockEventType,
eventType: BlockEventType?,
numberOfColumns: Int,
modifier: Modifier = Modifier,
isPassed: Boolean = false,
Expand All @@ -41,7 +41,7 @@ fun Block(
if (type == BlockType.START) R.drawable.img_board_start
else if (isStartedMission && isPassed) {
if (eventType is BlockEventType.Item) {
eventType.boardEventItem.eventType.imageId
eventType.boardEventItem.eventType?.imageId ?: 0
} else {
when (type) {
BlockType.CENTER -> R.drawable.img_board_center_jeju
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package com.goalpanzi.mission_mate.feature.board.component

import android.annotation.SuppressLint
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.drawscope.clipRect
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.goalpanzi.mission_mate.core.designsystem.theme.ColorGray1_FF404249
import com.goalpanzi.mission_mate.core.designsystem.theme.ColorGray2_FF4F505C
import com.goalpanzi.mission_mate.core.designsystem.theme.MissionMateTypography
import com.goalpanzi.mission_mate.feature.board.R
import com.goalpanzi.mission_mate.feature.board.model.BoardEventItem
import com.goalpanzi.mission_mate.feature.board.model.MissionState
import com.goalpanzi.mission_mate.feature.board.model.toCharacter
import com.goalpanzi.mission_mate.feature.board.model.toEventType
import com.goalpanzi.mission_mate.feature.board.util.BoardGenerator
import com.luckyoct.core.model.response.MissionBoardsResponse
import com.luckyoct.core.model.response.MissionVerificationResponse
import kotlin.math.absoluteValue


@Composable
fun Board(
scrollState: ScrollState,
boardCount: Int,
passedCount: Int,
startDateText: String,
missionBoards: MissionBoardsResponse,
numberOfColumns: Int,
profile: MissionVerificationResponse,
missionState: MissionState,
modifier: Modifier = Modifier,
) {
val statusBar = WindowInsets.statusBars
val navigationBar = WindowInsets.navigationBars
val localDensity = LocalDensity.current
val statusBarHeight = remember { (statusBar.getTop(localDensity) - statusBar.getBottom(localDensity)).absoluteValue }
val navigationBarHeight = remember { (navigationBar.getTop(localDensity) - navigationBar.getBottom(localDensity)).absoluteValue }
Box(
modifier = modifier
.fillMaxSize()
) {
Column(
modifier = modifier.modifierWithClipRect(
scrollState = scrollState,
missionState = missionState,
innerModifier = Modifier
.drawWithContent {
clipRect(bottom = statusBarHeight + 178.dp.toPx()) {
[email protected]()
}
}
.blur(10.dp, 10.dp),
)
) {
BoardContent(
boardCount,
passedCount,
startDateText,
missionBoards,
numberOfColumns,
profile,
missionState,
modifier
)
}
Column(
modifier = modifier.modifierWithClipRect(
scrollState = scrollState,
missionState = missionState,
innerModifier = Modifier
.drawWithContent {
clipRect(
top = statusBarHeight + 178.dp.toPx() - 1,
bottom = size.height + navigationBarHeight - (if (missionState.isVisiblePiece()) 188.dp else 46.dp).toPx()
) {
[email protected]()
}
}
)
) {
BoardContent(
boardCount,
passedCount,
startDateText,
missionBoards,
numberOfColumns,
profile,
missionState,
modifier
)
}

Column(
modifier = modifier.modifierWithClipRect(
scrollState = scrollState,
missionState = missionState,
innerModifier = Modifier
.drawWithContent {
clipRect(top = (size.height + navigationBarHeight - (if (missionState.isVisiblePiece()) 188.dp else 46.dp).toPx())) {
[email protected]()
}
}.blur(10.dp,10.dp)
)
) {
BoardContent(
boardCount,
passedCount,
startDateText,
missionBoards,
numberOfColumns,
profile,
missionState,
modifier
)
}


}

}

@Composable
fun ColumnScope.BoardContent(
boardCount: Int,
passedCount: Int,
startDateText: String,
missionBoards: MissionBoardsResponse,
numberOfColumns: Int,
profile: MissionVerificationResponse,
missionState: MissionState,
modifier: Modifier = Modifier,
) {
Text(
modifier = Modifier.padding(top = 28.dp),
text = startDateText,
style = MissionMateTypography.heading_md_bold,
color = ColorGray1_FF404249
)
Text(
modifier = Modifier.padding(top = 2.dp, bottom = 20.dp),
text = stringResource(id = R.string.board_before_start_description),
style = MissionMateTypography.body_lg_bold,
color = ColorGray2_FF4F505C
)
BoxWithConstraints {
val width = maxWidth
Column {
BoardGenerator.getBlockListByBoardCount(
boardCount,
numberOfColumns,
passedCount,
missionBoards.boardRewardList.map {
BoardEventItem(
index = it.number,
eventType = it.reward.toEventType()
)
}
).chunked(numberOfColumns).forEach {
Row() {
it.forEach {
Block(
index = it.index,
eventType = it.blockEventType,
type = it.blockType, modifier = Modifier
.weight(1f)
.aspectRatio(1f),
numberOfColumns = numberOfColumns,
isPassed = it.isPassed,
isStartedMission = missionState.isVisiblePiece()
)
}
}
}
}
if (missionState.isVisiblePiece()) {
missionBoards.missionBoards.forEach { block ->
if (block.missionBoardMembers.isNotEmpty()) {
Piece(
index = block.number,
count = block.missionBoardMembers.size,
nickname = if (block.isMyPosition) profile.nickname else block.missionBoardMembers.first().nickname,
sizePerBlock = width / numberOfColumns,
numberOfColumn = numberOfColumns,
isMe = block.isMyPosition,
imageId = if (block.isMyPosition) profile.characterType.toCharacter().imageId else block.missionBoardMembers.first().characterType.toCharacter().imageId,
imageIdForCount = if (block.isMyPosition) profile.characterType.toCharacter().imageId else block.missionBoardMembers.first().characterType.toCharacter().imageId
)
}
}
}
}
}

@SuppressLint("ModifierFactoryUnreferencedReceiver")
fun Modifier.modifierWithClipRect(
scrollState: ScrollState,
missionState: MissionState,
innerModifier: Modifier,
modifier: Modifier = Modifier,
): Modifier {
return modifier
.fillMaxSize()
.navigationBarsPadding()
.then(innerModifier)
.verticalScroll(scrollState)
.statusBarsPadding()
.padding(
top = 180.dp,
start = 24.dp,
end = 24.dp,
bottom = if (missionState.isVisiblePiece()) 188.dp else 46.dp
)
}
Loading
Loading