Skip to content

Commit

Permalink
Merge pull request #76 from HRHN-HaruHana/feature/today-screen
Browse files Browse the repository at this point in the history
오늘의 챌린지 컴포즈 적용
  • Loading branch information
oreocube authored Jan 20, 2025
2 parents e4f12dd + 1993bbd commit 6116174
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 190 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import com.hrhn.databinding.FragmentTodayBinding
import com.hrhn.presentation.ui.screen.addchallenge.AddChallengeActivity
import com.hrhn.presentation.ui.screen.edit.EditChallengeActivity
import com.hrhn.presentation.util.observeEvent
import com.hrhn.presentation.util.showToast
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

@AndroidEntryPoint
class TodayFragment : Fragment() {
Expand All @@ -27,29 +24,19 @@ class TodayFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentTodayBinding.inflate(inflater)
_binding = FragmentTodayBinding.inflate(inflater).apply {
composeView.setContent {
TodayScreen(viewModel)
}
}
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViews()
observeData()
}

override fun onResume() {
super.onResume()
viewModel.fetchData()
}

private fun initViews() {
with(binding) {
vm = viewModel
lifecycleOwner = viewLifecycleOwner
srlToday.setOnRefreshListener { viewModel.fetchData() }
}
}

private fun observeData() {
with(viewModel) {
addEvent.observeEvent(viewLifecycleOwner) {
Expand All @@ -61,14 +48,11 @@ class TodayFragment : Fragment() {
editEvent.observeEvent(viewLifecycleOwner) {
startActivity(EditChallengeActivity.newIntent(requireContext(), it))
}
isRefreshing.onEach {
binding.srlToday.isRefreshing = it
}.launchIn(viewLifecycleOwner.lifecycleScope)
}
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
super.onDestroyView()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package com.hrhn.presentation.ui.screen.main.today

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.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.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.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.hrhn.R
import com.hrhn.domain.model.Challenge
import com.hrhn.presentation.ui.theme.CellFill
import com.hrhn.presentation.ui.theme.CellLabel
import com.hrhn.presentation.ui.theme.Gray54
import com.hrhn.presentation.ui.theme.Red02
import com.hrhn.presentation.ui.theme.SecondaryLabel
import com.hrhn.presentation.ui.theme.Typography
import com.hrhn.presentation.ui.theme.White
import com.hrhn.presentation.util.formatDateWithYearString

@Composable
fun TodayScreen(viewModel: TodayViewModel) {
val today by viewModel.today.collectAsStateWithLifecycle()
val todayString by remember(today) {
derivedStateOf { today.formatDateWithYearString() }
}
val todayChallenge by viewModel.todayChallenge.collectAsStateWithLifecycle()

Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
TodayHeader(today = todayString)
TodayCard(
modifier = Modifier
.weight(1f)
.padding(start = 40.dp, end = 40.dp, top = 16.dp, bottom = 40.dp),
challenge = todayChallenge,
onCardClick = viewModel::editTodayChallenge,
onAddClick = viewModel::addTodayChallenge,
)
}
}

@Composable
private fun TodayHeader(today: String) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResource(id = R.string.title_today),
style = Typography.titleLarge,
color = Red02,
)
Spacer(modifier = Modifier.height(5.dp))
Text(
text = today,
style = Typography.labelSmall,
color = SecondaryLabel,
)
}
}

@Composable
private fun TodayCard(
modifier: Modifier = Modifier,
challenge: Challenge?,
onCardClick: () -> Unit,
onAddClick: () -> Unit,
) {
Column(
modifier = modifier
.fillMaxSize()
.clip(RoundedCornerShape(size = 16.dp))
.background(color = CellFill)
.clickable(
enabled = challenge != null,
onClick = onCardClick,
),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (challenge == null) {
Text(
text = stringResource(id = R.string.message_no_challenge),
style = Typography.labelSmall,
color = Gray54,
)
Spacer(modifier = Modifier.height(16.dp))
Text(
modifier = Modifier
.clip(RoundedCornerShape(50.dp))
.background(Red02)
.clickable(onClick = onAddClick)
.padding(horizontal = 24.dp, vertical = 12.dp),
text = stringResource(id = R.string.button_add_challenge),
style = Typography.labelSmall,
color = White,
)
} else {
Text(
text = challenge.content,
style = Typography.titleLarge,
color = CellLabel,
)
}
}
}

@Preview(showBackground = true)
@Composable
private fun TodayHeaderPreview() {
TodayHeader(today = "2025.01.15")
}

@Preview(showBackground = true)
@Composable
private fun TodayCardPreview() {
TodayCard(
challenge = Challenge(content = "빨래널기"),
onCardClick = {},
onAddClick = {},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,34 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.hrhn.domain.model.Challenge
import com.hrhn.domain.repository.ChallengeRepository
import com.hrhn.domain.usecase.GetTodayChallengeUseCase
import com.hrhn.presentation.util.Event
import com.hrhn.presentation.util.emit
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import java.time.LocalDate
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import java.time.LocalDateTime
import javax.inject.Inject

@HiltViewModel
class TodayViewModel @Inject constructor(
private val repository: ChallengeRepository
getTodayChallengeUseCase: GetTodayChallengeUseCase,
) : ViewModel() {

private val _isRefreshing = MutableSharedFlow<Boolean>()
val isRefreshing = _isRefreshing.asSharedFlow()

private val _today = MutableSharedFlow<LocalDateTime>()
private val _today = MutableStateFlow(LocalDateTime.now())
val today = _today.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(500),
initialValue = LocalDateTime.now()
)

val todayChallengeFlow: StateFlow<Challenge?> = _today.map { today ->
repository.getChallengesWithPeriod(today, today.plusDays(1)).map {
if (it.isNotEmpty()) it.getOrNull(0) else null
}.first()
}.catch { e ->
e.message?.let { _message.emit(it) }
}.onEach {
_isRefreshing.emit(false)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(500),
initialValue = null
)

val isEmpty: StateFlow<Boolean> = todayChallengeFlow.map { it == null }.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(500),
initialValue = true
)
val todayChallenge: StateFlow<Challenge?> = getTodayChallengeUseCase()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(500),
initialValue = null
)

private val _message = MutableLiveData<Event<String>>()
val message: LiveData<Event<String>> get() = _message
Expand All @@ -59,16 +43,11 @@ class TodayViewModel @Inject constructor(
private val _editEvent = MutableLiveData<Event<Challenge>>()
val editEvent: LiveData<Event<Challenge>> get() = _editEvent

fun fetchData() {
viewModelScope.launch {
_isRefreshing.emit(true)
_today.emit(LocalDate.now().atStartOfDay())
}
}

fun addTodayChallenge() = _addEvent.emit()

fun editTodayChallenge() {
_editEvent.emit(todayChallengeFlow.value!!)
todayChallenge.value?.let { challenge ->
_editEvent.emit(challenge)
}
}
}
}
45 changes: 45 additions & 0 deletions app/src/main/java/com/hrhn/presentation/ui/theme/Color.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,48 @@ val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

val Black = Color(0xFF000000)
val White = Color(0xFFFFFFFF)
val WhiteAlpha80 = Color(0x80FFFFFF)
val Clear = Color(0x00000000)
val GrayFB = Color(0xFFFBFBFB)
val GrayEF = Color(0xFFEFEFEF)
val GrayF6 = Color(0xFFF6F6F6)
val GrayD4 = Color(0xFFD4D4D4)
val GrayD0 = Color(0xFFD0D0D0)
val GrayC5 = Color(0xFFC5C5C5)
val Gray81 = Color(0xFF818181)
val GrayB9 = Color(0xFFB9B9B9)
val Gray59 = Color(0xFF595959)
val Gray54 = Color(0xFF545454)
val Gray4A = Color(0xFF4A4A4A)
val Gray22 = Color(0xFF222222)
val Gray1D = Color(0xFF1D1D1D)
val Gray14 = Color(0xFF141414)
val Red01 = Color(0xFFFFBBAC)
val Red02 = Color(0xFFDC6046)
val Yellow01 = Color(0xFFFED977)
val Green01 = Color(0xFFACDE80)
val SkyBlue01 = Color(0xFFB0D9FF)
val Blue01 = Color(0xFF97B4FE)
val Purple01 = Color(0xFFE1BAFF)
val LightBlue50 = Color(0xFFE1F5FE)
val LightBlue200 = Color(0xFF81D4FA)
val LightBlue600 = Color(0xFF039BE5)
val LightBlue900 = Color(0xFF01579B)
val None = Color(0xFFCCCCCC)

val Label = Black
val SecondaryLabel = Color(0x993C3C43)
val TertiaryLabel = Color(0x4D3C3C43)
val QuaternaryLabel = Color(0x2E3C3C43)
val HeaderLabel = Black

//val disabled = GrayEF
//val disabledLabel = GrayB9
//val dim = Gray54
//val background = White
//val setting_icon_fill = GrayC5
val CellFill = GrayFB
val CellLabel = Gray4A
29 changes: 11 additions & 18 deletions app/src/main/java/com/hrhn/presentation/ui/theme/Type.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,22 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontWeight = FontWeight.Bold,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
color = Black,
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
color = Gray54
),
labelMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 20.sp,
),
)
Loading

0 comments on commit 6116174

Please sign in to comment.