Skip to content

Commit

Permalink
feat: pull to refresh 추가 (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
easyhz committed Aug 22, 2024
1 parent 8559334 commit 5f8f8c3
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ParagraphStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
Expand All @@ -20,6 +21,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.easyhz.noffice.core.design_system.R
import com.easyhz.noffice.core.design_system.extension.screenHorizonPadding
import com.easyhz.noffice.core.design_system.extension.skeletonEffect
import com.easyhz.noffice.core.design_system.theme.Green100
import com.easyhz.noffice.core.design_system.theme.Title1
import com.easyhz.noffice.core.design_system.theme.Title2
Expand All @@ -34,7 +36,8 @@ fun Banner(
val bannerIntro = stringResource(id = R.string.banner_intro)
val bannerOutro = stringResource(id = R.string.banner_outro)
val annotatedString = remember(userName, date, bannerIntro, bannerOutro) {
buildAnnotatedString {
if (userName.isBlank()) AnnotatedString("")
else buildAnnotatedString {
withStyle(style = ParagraphStyle(lineHeight = 32.sp)) {
withStyle(style = Title1.toSpanStyle()) {
append(userName)
Expand All @@ -59,7 +62,8 @@ fun Banner(
brush = Brush.verticalGradient(listOf(White, Green100)),
)
) {
Text(text = annotatedString,
Text(
text = annotatedString,
modifier = Modifier
.align(Alignment.BottomStart)
.screenHorizonPadding()
Expand All @@ -68,6 +72,21 @@ fun Banner(
}
}

@Composable
fun SkeletonBanner(
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
.fillMaxWidth()
.heightIn(min = 108.dp)
.background(
brush = Brush.verticalGradient(listOf(White, Green100)),
)
.skeletonEffect()
)
}

@Preview
@Composable
private fun BannerPrev() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import com.easyhz.noffice.core.design_system.R
import com.easyhz.noffice.core.design_system.component.banner.Banner
import com.easyhz.noffice.core.design_system.component.banner.SkeletonBanner
import com.easyhz.noffice.core.design_system.component.card.ItemCard
import com.easyhz.noffice.core.design_system.component.skeleton.SkeletonProvider
import com.easyhz.noffice.core.design_system.extension.screenHorizonPadding
import com.easyhz.noffice.core.design_system.util.card.CardDetailInfo
import com.easyhz.noffice.core.design_system.util.card.CardExceptionType
Expand All @@ -33,19 +35,24 @@ fun NoticeView(
name: String,
dayOfWeek: String,
organizationList: LazyPagingItems<Organization>,
isLoading: Boolean,
isRefreshing: Boolean,
navigateToAnnouncementDetail: (Int, Int, String) -> Unit,
) {
LazyColumn(
modifier = modifier,
contentPadding = PaddingValues(bottom = 48.dp)
) {
item {
Banner(userName = name, date = dayOfWeek)
SkeletonProvider(isLoading = isLoading, skeletonContent = { SkeletonBanner() }) {
Banner(userName = name, date = dayOfWeek)
}
}
items(organizationList.itemCount) { index ->
organizationList[index]?.let {
OrganizationSection(
organization = it,
isRefreshing = isRefreshing,
navigateToAnnouncementDetail = {id, title -> navigateToAnnouncementDetail(it.id, id, title) }
)
}
Expand All @@ -58,12 +65,19 @@ private fun OrganizationSection(
modifier: Modifier = Modifier,
noticeViewModel: NoticeViewModel = hiltViewModel(),
organization: Organization,
isRefreshing: Boolean,
navigateToAnnouncementDetail: (Int, String) -> Unit,
) {
val announcementList = noticeViewModel.getAnnouncementStateByOrganization(organizationId = organization.id).collectAsLazyPagingItems()
LaunchedEffect(organization.id) {
noticeViewModel.fetchAnnouncementByOrganization(organization.id)
}

LaunchedEffect(key1 = isRefreshing) {
if(!isRefreshing) return@LaunchedEffect
noticeViewModel.refreshAnnouncementByOrganization(organization.id)
}

Column(modifier) {
OrganizationHeader(
modifier = Modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,10 @@ class NoticeViewModel @Inject constructor(
}
}

fun refreshAnnouncementByOrganization(id: Int) {
if (_isDataLoaded[id] == false) return
_isDataLoaded[id] = false
fetchAnnouncementByOrganization(id)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ sealed class HomeIntent: UiIntent() {
data class ChangeTopBarMenu(val topBarMenu: HomeTopBarMenu): HomeIntent()
data class ClickTopBarIconMenu(val iconMenu: TopBarIconMenu): HomeIntent()
data class JoinToOrganization(val organizationId: Int): HomeIntent()
data object Refresh: HomeIntent()
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ sealed class HomeSideEffect: UiSideEffect() {
data object NavigateToMyPage: HomeSideEffect()
data class NavigateToOrganizationJoin(val organizationSignUpInformation: OrganizationSignUpInformation): HomeSideEffect()
data class ShowSnackBar(@StringRes val stringId: Int): HomeSideEffect()
data object Refresh: HomeSideEffect()
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ data class HomeState(
val userInfo: UserInfo,
val name: String,
val dayOfWeek: String,
val isLoading: Boolean
val isJoinLoading: Boolean,
val isInitLoading: Boolean,
): UiState() {
companion object {
fun init() = HomeState(
Expand All @@ -23,7 +24,8 @@ data class HomeState(
),
name = "",
dayOfWeek = "",
isLoading = false
isJoinLoading = false,
isInitLoading = true
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
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.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.paging.LoadState
Expand All @@ -24,6 +31,7 @@ import com.easyhz.noffice.core.design_system.component.loading.LoadingScreenProv
import com.easyhz.noffice.core.design_system.component.scaffold.NofficeScaffold
import com.easyhz.noffice.core.design_system.component.topBar.HomeTopBar
import com.easyhz.noffice.core.design_system.extension.screenHorizonPadding
import com.easyhz.noffice.core.design_system.theme.Green500
import com.easyhz.noffice.core.design_system.util.exception.ExceptionType
import com.easyhz.noffice.core.model.organization.OrganizationSignUpInformation
import com.easyhz.noffice.feature.home.component.notice.NoticeView
Expand All @@ -33,6 +41,7 @@ import com.easyhz.noffice.feature.home.contract.home.HomeSideEffect
import com.easyhz.noffice.feature.home.permission.checkNotificationPermission
import com.easyhz.noffice.feature.home.util.HomeTopBarMenu

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun HomeScreen(
modifier: Modifier = Modifier,
Expand All @@ -46,6 +55,13 @@ fun HomeScreen(
val organizationList = viewModel.organizationState.collectAsLazyPagingItems()
val organizationIdToJoin = remember { DeepLinkManager.organizationIdToJoin }
val context = LocalContext.current
val isRefreshing = remember(organizationList.loadState.refresh) {
organizationList.loadState.refresh == LoadState.Loading
}
val pullRefreshState = rememberPullRefreshState(
refreshing = !uiState.isInitLoading && isRefreshing,
onRefresh = { viewModel.postIntent(HomeIntent.Refresh) }
)
val requestPermissionLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
Expand All @@ -61,12 +77,11 @@ fun HomeScreen(
launcher = requestPermissionLauncher
) { }
}

LaunchedEffect(key1 = organizationIdToJoin) {
viewModel.postIntent(HomeIntent.JoinToOrganization(organizationIdToJoin))
}
LoadingScreenProvider(
isLoading = uiState.isLoading
isLoading = uiState.isJoinLoading
) {
NofficeScaffold(
modifier = modifier,
Expand All @@ -81,34 +96,46 @@ fun HomeScreen(
}
}
) { paddingValues ->
if(organizationList.itemCount == 0 && organizationList.loadState.refresh != LoadState.Loading) {
ExceptionView(
modifier = Modifier.fillMaxSize(),
type = ExceptionType.NO_ORGANIZATION
)
}
Crossfade(
targetState = uiState.topBarMenu,
animationSpec = tween(500),
label = "TopBarMenu"
) { screen ->
when (screen) {
HomeTopBarMenu.NOTICE -> {
NoticeView(
modifier = Modifier.padding(top = paddingValues.calculateTopPadding()),
dayOfWeek = uiState.dayOfWeek,
name = uiState.name,
organizationList = organizationList,
navigateToAnnouncementDetail = navigateToAnnouncementDetail
)
}
Box(modifier = Modifier
.pullRefresh(pullRefreshState)
.padding(top = paddingValues.calculateTopPadding())) {
if(organizationList.itemCount == 0 && !isRefreshing) {
ExceptionView(
modifier = Modifier.fillMaxSize(),
type = ExceptionType.NO_ORGANIZATION
)
}
Crossfade(
targetState = uiState.topBarMenu,
animationSpec = tween(500),
label = "TopBarMenu"
) { screen ->
when (screen) {
HomeTopBarMenu.NOTICE -> {
NoticeView(
dayOfWeek = uiState.dayOfWeek,
name = uiState.name,
organizationList = organizationList,
isLoading = uiState.isInitLoading,
isRefreshing = isRefreshing,
navigateToAnnouncementDetail = navigateToAnnouncementDetail
)
}

HomeTopBarMenu.TASK -> {
TaskView(modifier = Modifier
.padding(top = paddingValues.calculateTopPadding())
.screenHorizonPadding())
HomeTopBarMenu.TASK -> {
TaskView(modifier = Modifier
.screenHorizonPadding())
}
}
}
PullRefreshIndicator(
refreshing = isRefreshing,
contentColor = Green500,
state = pullRefreshState,
modifier = Modifier
.align(Alignment.TopCenter)
.padding(top = 20.dp)
)
}
}
}
Expand All @@ -125,6 +152,9 @@ fun HomeScreen(
withDismissAction = true
)
}
is HomeSideEffect.Refresh -> {
organizationList.refresh()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.easyhz.noffice.core.common.error.HttpError
import com.easyhz.noffice.core.common.error.handleError
import com.easyhz.noffice.core.common.manager.DeepLinkManager
import com.easyhz.noffice.core.common.util.DateFormat
import com.easyhz.noffice.core.common.util.errorLogging
import com.easyhz.noffice.core.design_system.util.topBar.TopBarIconMenu
import com.easyhz.noffice.core.model.organization.Organization
import com.easyhz.noffice.domain.home.usecase.member.FetchUserInfoUseCase
Expand Down Expand Up @@ -45,6 +46,7 @@ class HomeViewModel @Inject constructor(
is HomeIntent.ChangeTopBarMenu -> { onChangeTopBarMenu(intent.topBarMenu) }
is HomeIntent.ClickTopBarIconMenu -> { onClickTopBarIconMenu(intent.iconMenu) }
is HomeIntent.JoinToOrganization -> { joinToOrganization(intent.organizationId) }
is HomeIntent.Refresh -> { refresh() }
}
}

Expand All @@ -58,7 +60,9 @@ class HomeViewModel @Inject constructor(
fetchUserInfoUseCase.invoke(Unit).onSuccess {
reduce { copy(userInfo = it, name = it.alias) }
}.onFailure {
// TODO FAIL 처리
errorLogging(this.javaClass.name, "fetchUserInfo", it)
}.also {
reduce { copy(isInitLoading = false)}
}
}

Expand Down Expand Up @@ -95,7 +99,7 @@ class HomeViewModel @Inject constructor(
/* 조직 가입 */
private fun joinToOrganization(id: Int) = viewModelScope.launch {
if (id == -1) return@launch
reduce { copy(isLoading = true) }
reduce { copy(isJoinLoading = true) }
fetchOrganizationSignUpInfoUseCase.invoke(id).onSuccess {
delay(500)
postSideEffect { HomeSideEffect.NavigateToOrganizationJoin(it) }
Expand All @@ -107,10 +111,14 @@ class HomeViewModel @Inject constructor(
showSnackBar(messageResId)
}.also {
DeepLinkManager.setOrganizationIdToJoin(-1)
reduce { copy(isLoading = false) }
reduce { copy(isJoinLoading = false) }
}
}

private fun refresh() {
postSideEffect { HomeSideEffect.Refresh }
}

private fun showSnackBar(@StringRes stringId: Int) {
postSideEffect {
HomeSideEffect.ShowSnackBar(stringId)
Expand Down

0 comments on commit 5f8f8c3

Please sign in to comment.