From 305837cdd92cf5b646af2de8b2ac6092713af239 Mon Sep 17 00:00:00 2001 From: jinukeu Date: Sun, 11 Feb 2024 19:22:30 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=EB=B3=B4=EB=82=B4=EC=9A=94=20?= =?UTF-8?q?=EB=8B=B9=EA=B2=A8=EC=84=9C=20=EB=A6=AC=ED=94=84=EB=A0=88?= =?UTF-8?q?=EC=8B=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/susu/feature/sent/SentScreen.kt | 30 ++++++++++++++++++- .../com/susu/feature/sent/SentViewModel.kt | 4 ++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/feature/sent/src/main/java/com/susu/feature/sent/SentScreen.kt b/feature/sent/src/main/java/com/susu/feature/sent/SentScreen.kt index 0e99d3b4..17f9883d 100644 --- a/feature/sent/src/main/java/com/susu/feature/sent/SentScreen.kt +++ b/feature/sent/src/main/java/com/susu/feature/sent/SentScreen.kt @@ -20,11 +20,15 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshContainer +import androidx.compose.material3.pulltorefresh.PullToRefreshState +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -42,6 +46,7 @@ import com.susu.core.designsystem.component.button.SelectedFilterButton import com.susu.core.designsystem.component.button.SmallButtonStyle import com.susu.core.designsystem.component.button.SusuFloatingButton import com.susu.core.designsystem.component.button.SusuGhostButton +import com.susu.core.designsystem.theme.Gray10 import com.susu.core.designsystem.theme.Gray15 import com.susu.core.designsystem.theme.Gray50 import com.susu.core.designsystem.theme.SusuTheme @@ -57,6 +62,7 @@ import me.onebone.toolbar.CollapsingToolbarScaffold import me.onebone.toolbar.ScrollStrategy import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState +@OptIn(ExperimentalMaterial3Api::class) @Composable fun SentRoute( viewModel: SentViewModel = hiltViewModel(), @@ -74,6 +80,10 @@ fun SentRoute( val envelopesListState = rememberLazyListState() val scope = rememberCoroutineScope() + val refreshState = rememberPullToRefreshState( + positionalThreshold = 100.dp + ) + viewModel.sideEffect.collectWithLifecycle { sideEffect -> when (sideEffect) { SentEffect.NavigateEnvelopeAdd -> navigateSentEnvelopeAdd() @@ -87,6 +97,14 @@ fun SentRoute( } } + LaunchedEffect(key1 = refreshState.isRefreshing) { + if (refreshState.isRefreshing.not()) return@LaunchedEffect + + viewModel.getEnvelopesList(refresh = true) { + refreshState.endRefresh() + } + } + LaunchedEffect(key1 = Unit) { if (deletedFriendId != null) { viewModel.deleteEmptyFriendStatistics(deletedFriendId) @@ -105,6 +123,7 @@ fun SentRoute( SentScreen( uiState = uiState, envelopesListState = envelopesListState, + refreshState = refreshState, padding = padding, onClickHistoryShowAll = viewModel::navigateSentEnvelope, onClickAddEnvelope = viewModel::navigateSentAdd, @@ -130,6 +149,7 @@ fun SentRoute( fun SentScreen( uiState: SentState = SentState(), envelopesListState: LazyListState = rememberLazyListState(), + refreshState: PullToRefreshState = rememberPullToRefreshState(), padding: PaddingValues, onClickSearchIcon: () -> Unit = {}, onClickHistory: (Boolean, Long) -> Unit = { _, _ -> }, @@ -147,7 +167,8 @@ fun SentScreen( modifier = Modifier .background(SusuTheme.colorScheme.background15) .padding(padding) - .fillMaxSize(), + .fillMaxSize() + .nestedScroll(refreshState.nestedScrollConnection), ) { Column { Spacer(modifier = Modifier.size(44.dp)) @@ -212,6 +233,12 @@ fun SentScreen( }, ) + PullToRefreshContainer( + modifier = Modifier.align(Alignment.TopCenter), + state = refreshState, + containerColor = Gray10, + ) + if (uiState.showAlignBottomSheet) { SusuSelectionBottomSheet( onDismissRequest = onDismissAlignBottomSheet, @@ -314,6 +341,7 @@ fun EmptyView( } } +@OptIn(ExperimentalMaterial3Api::class) @Preview @Composable fun SentScreenPreview() { diff --git a/feature/sent/src/main/java/com/susu/feature/sent/SentViewModel.kt b/feature/sent/src/main/java/com/susu/feature/sent/SentViewModel.kt index 6a73df83..d3697c8a 100644 --- a/feature/sent/src/main/java/com/susu/feature/sent/SentViewModel.kt +++ b/feature/sent/src/main/java/com/susu/feature/sent/SentViewModel.kt @@ -28,7 +28,7 @@ class SentViewModel @Inject constructor( private var filter: EnvelopeFilterArgument = EnvelopeFilterArgument() private var filterUri: String? = null - fun getEnvelopesList(refresh: Boolean?) = viewModelScope.launch { + fun getEnvelopesList(refresh: Boolean?, onFinish: () -> Unit = {}) = viewModelScope.launch { mutex.withLock { val currentList = if (refresh == true) { page = 0 @@ -63,6 +63,8 @@ class SentViewModel @Inject constructor( } if (refresh == true) postSideEffect(SentEffect.ScrollToTop) + + onFinish() } } From 27f2c2479d708ce6d96f7177cfc23d2273cc19cd Mon Sep 17 00:00:00 2001 From: jinukeu Date: Sun, 11 Feb 2024 19:27:30 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=EB=B0=9B=EC=9D=80=20=EB=B4=89?= =?UTF-8?q?=ED=88=AC=20=EB=8B=B9=EA=B2=A8=EC=84=9C=20=EB=A6=AC=ED=94=84?= =?UTF-8?q?=EB=A0=88=EC=8B=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/envelope/SentEnvelopeScreen.kt | 33 ++++++++++++++++++- .../feature/envelope/SentEnvelopeViewModel.kt | 3 +- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/feature/sent/src/main/java/com/susu/feature/envelope/SentEnvelopeScreen.kt b/feature/sent/src/main/java/com/susu/feature/envelope/SentEnvelopeScreen.kt index fbd790ea..6a826351 100644 --- a/feature/sent/src/main/java/com/susu/feature/envelope/SentEnvelopeScreen.kt +++ b/feature/sent/src/main/java/com/susu/feature/envelope/SentEnvelopeScreen.kt @@ -16,13 +16,19 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshContainer +import androidx.compose.material3.pulltorefresh.PullToRefreshState +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -33,6 +39,7 @@ import com.susu.core.designsystem.component.appbar.icon.BackIcon import com.susu.core.designsystem.component.badge.BadgeColor import com.susu.core.designsystem.component.badge.BadgeStyle import com.susu.core.designsystem.component.badge.SusuBadge +import com.susu.core.designsystem.theme.Gray10 import com.susu.core.designsystem.theme.Gray100 import com.susu.core.designsystem.theme.Gray20 import com.susu.core.designsystem.theme.Gray60 @@ -46,6 +53,7 @@ import com.susu.feature.envelope.component.EnvelopeHistoryItem import com.susu.feature.sent.R import kotlinx.datetime.toJavaLocalDateTime +@OptIn(ExperimentalMaterial3Api::class) @Composable fun SentEnvelopeRoute( viewModel: SentEnvelopeViewModel = hiltViewModel(), @@ -58,6 +66,10 @@ fun SentEnvelopeRoute( val uiState = viewModel.uiState.collectAsStateWithLifecycle().value val historyListState = rememberLazyListState() + val refreshState = rememberPullToRefreshState( + positionalThreshold = 100.dp + ) + viewModel.sideEffect.collectWithLifecycle { sideEffect -> when (sideEffect) { SentEnvelopeSideEffect.PopBackStack -> { @@ -77,20 +89,31 @@ fun SentEnvelopeRoute( viewModel.getEnvelopeHistoryList() } + LaunchedEffect(key1 = refreshState.isRefreshing) { + if (refreshState.isRefreshing.not()) return@LaunchedEffect + + viewModel.initData { + refreshState.endRefresh() + } + } + BackHandler { editedFriendId?.let { popBackStackWithEditedFriendId(it) } ?: popBackStack() } SentEnvelopeScreen( uiState = uiState, + refreshState = refreshState, onClickBackIcon = viewModel::popBackStack, onClickEnvelopeDetail = viewModel::navigateSentEnvelopeDetail, ) } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun SentEnvelopeScreen( modifier: Modifier = Modifier, + refreshState: PullToRefreshState = rememberPullToRefreshState(), uiState: SentEnvelopeState = SentEnvelopeState(), historyListState: LazyListState = rememberLazyListState(), onClickBackIcon: () -> Unit = {}, @@ -99,7 +122,8 @@ fun SentEnvelopeScreen( Box( modifier = modifier .fillMaxSize() - .background(SusuTheme.colorScheme.background10), + .background(SusuTheme.colorScheme.background10) + .nestedScroll(refreshState.nestedScrollConnection), ) { Column { SusuDefaultAppBar( @@ -194,9 +218,16 @@ fun SentEnvelopeScreen( } } } + + PullToRefreshContainer( + modifier = Modifier.align(Alignment.TopCenter), + state = refreshState, + containerColor = Gray10, + ) } } +@OptIn(ExperimentalMaterial3Api::class) @Preview @Composable fun SentEnvelopeScreenPreview() { diff --git a/feature/sent/src/main/java/com/susu/feature/envelope/SentEnvelopeViewModel.kt b/feature/sent/src/main/java/com/susu/feature/envelope/SentEnvelopeViewModel.kt index 292e8e0a..22e11f9f 100644 --- a/feature/sent/src/main/java/com/susu/feature/envelope/SentEnvelopeViewModel.kt +++ b/feature/sent/src/main/java/com/susu/feature/envelope/SentEnvelopeViewModel.kt @@ -21,9 +21,10 @@ class SentEnvelopeViewModel @Inject constructor( ) { private val friendId = savedStateHandle.get(SentRoute.FRIEND_ID_ARGUMENT_NAME)!! - fun initData() { + fun initData(onFinish: () -> Unit = {}) { getEnvelopeInfo() getEnvelopeHistoryList() + onFinish() } private fun getEnvelopeInfo(id: Long = friendId) = viewModelScope.launch { From add8270f4fc91b7cfd688f340c9b5cfa8cef8f7c Mon Sep 17 00:00:00 2001 From: jinukeu Date: Sun, 11 Feb 2024 19:36:42 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=EB=B0=9B=EC=95=84=EC=9A=94,=20?= =?UTF-8?q?=EC=9E=A5=EB=B6=80=20=EC=83=81=EC=84=B8=20=EB=8B=B9=EA=B2=A8?= =?UTF-8?q?=EC=84=9C=20=EB=A6=AC=ED=94=84=EB=A0=88=EC=8B=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ledgerdetail/LedgerDetailScreen.kt | 30 ++++++++++++++++++- .../ledgerdetail/LedgerDetailViewModel.kt | 6 ++++ .../received/received/ReceivedScreen.kt | 29 +++++++++++++++++- .../received/received/ReceivedViewModel.kt | 3 +- 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/feature/received/src/main/java/com/susu/feature/received/ledgerdetail/LedgerDetailScreen.kt b/feature/received/src/main/java/com/susu/feature/received/ledgerdetail/LedgerDetailScreen.kt index 1d3915a0..07586706 100644 --- a/feature/received/src/main/java/com/susu/feature/received/ledgerdetail/LedgerDetailScreen.kt +++ b/feature/received/src/main/java/com/susu/feature/received/ledgerdetail/LedgerDetailScreen.kt @@ -22,10 +22,14 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshContainer +import androidx.compose.material3.pulltorefresh.PullToRefreshState +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -45,6 +49,7 @@ import com.susu.core.designsystem.component.button.SelectedFilterButton import com.susu.core.designsystem.component.button.SmallButtonStyle import com.susu.core.designsystem.component.button.SusuFloatingButton import com.susu.core.designsystem.component.button.SusuGhostButton +import com.susu.core.designsystem.theme.Gray10 import com.susu.core.designsystem.theme.Gray25 import com.susu.core.designsystem.theme.Gray50 import com.susu.core.designsystem.theme.SusuTheme @@ -61,6 +66,7 @@ import com.susu.feature.received.ledgerdetail.component.LedgerDetailEnvelopeCont import com.susu.feature.received.ledgerdetail.component.LedgerDetailOverviewColumn import kotlinx.collections.immutable.toPersistentList +@OptIn(ExperimentalMaterial3Api::class) @Composable fun LedgerDetailRoute( viewModel: LedgerDetailViewModel = hiltViewModel(), @@ -80,6 +86,9 @@ fun LedgerDetailRoute( val uiState = viewModel.uiState.collectAsStateWithLifecycle().value val listState = rememberLazyListState() val context = LocalContext.current + val refreshState = rememberPullToRefreshState( + positionalThreshold = 100.dp + ) viewModel.sideEffect.collectWithLifecycle { sideEffect -> when (sideEffect) { is LedgerDetailSideEffect.NavigateLedgerEdit -> navigateLedgerEdit(sideEffect.ledger) @@ -113,6 +122,14 @@ fun LedgerDetailRoute( } } + LaunchedEffect(key1 = refreshState.isRefreshing) { + if (refreshState.isRefreshing.not()) return@LaunchedEffect + + viewModel.refreshData { + refreshState.endRefresh() + } + } + LaunchedEffect(key1 = Unit) { viewModel.getLedger() viewModel.initReceivedEnvelopeList() @@ -134,6 +151,8 @@ fun LedgerDetailRoute( LedgerDetailScreen( uiState = uiState, + listState = listState, + refreshState = refreshState, onClickEdit = viewModel::navigateLedgerEdit, onClickDelete = viewModel::showDeleteDialog, onClickBack = viewModel::popBackStackWithLedger, @@ -154,6 +173,7 @@ fun LedgerDetailRoute( fun LedgerDetailScreen( uiState: LedgerDetailState = LedgerDetailState(), listState: LazyListState = rememberLazyListState(), + refreshState: PullToRefreshState = rememberPullToRefreshState(), onClickBack: () -> Unit = {}, onClickEdit: () -> Unit = {}, onClickDelete: () -> Unit = {}, @@ -170,7 +190,8 @@ fun LedgerDetailScreen( Box( modifier = Modifier .background(SusuTheme.colorScheme.background15) - .fillMaxSize(), + .fillMaxSize() + .nestedScroll(refreshState.nestedScrollConnection), ) { Column { SusuDefaultAppBar( @@ -294,6 +315,12 @@ fun LedgerDetailScreen( } } + PullToRefreshContainer( + modifier = Modifier.align(Alignment.TopCenter), + state = refreshState, + containerColor = Gray10, + ) + if (uiState.showAlignBottomSheet) { SusuSelectionBottomSheet( onDismissRequest = onDismissAlignBottomSheet, @@ -313,6 +340,7 @@ fun LedgerDetailScreen( } } +@OptIn(ExperimentalMaterial3Api::class) @Preview @Composable fun LedgerDetailScreenPreview() { diff --git a/feature/received/src/main/java/com/susu/feature/received/ledgerdetail/LedgerDetailViewModel.kt b/feature/received/src/main/java/com/susu/feature/received/ledgerdetail/LedgerDetailViewModel.kt index 9b7197b8..9b231c90 100644 --- a/feature/received/src/main/java/com/susu/feature/received/ledgerdetail/LedgerDetailViewModel.kt +++ b/feature/received/src/main/java/com/susu/feature/received/ledgerdetail/LedgerDetailViewModel.kt @@ -18,6 +18,7 @@ import com.susu.domain.usecase.ledger.GetLedgerUseCase import com.susu.feature.received.navigation.ReceivedRoute import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.datetime.toJavaLocalDateTime import kotlinx.serialization.json.Json @@ -43,6 +44,11 @@ class LedgerDetailViewModel @Inject constructor( private var isFirstVisited: Boolean = true + fun refreshData(onFinish: () -> Unit) = viewModelScope.launch { + joinAll(getLedger(), getReceivedEnvelopeList(true)) + onFinish() + } + fun filterIfNeed(filterUri: String?) { if (filterUri == null) return diff --git a/feature/received/src/main/java/com/susu/feature/received/received/ReceivedScreen.kt b/feature/received/src/main/java/com/susu/feature/received/received/ReceivedScreen.kt index f4b9d795..823a6eb8 100644 --- a/feature/received/src/main/java/com/susu/feature/received/received/ReceivedScreen.kt +++ b/feature/received/src/main/java/com/susu/feature/received/received/ReceivedScreen.kt @@ -20,11 +20,15 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshContainer +import androidx.compose.material3.pulltorefresh.PullToRefreshState +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -42,6 +46,7 @@ import com.susu.core.designsystem.component.button.SelectedFilterButton import com.susu.core.designsystem.component.button.SmallButtonStyle import com.susu.core.designsystem.component.button.SusuFloatingButton import com.susu.core.designsystem.component.button.SusuGhostButton +import com.susu.core.designsystem.theme.Gray10 import com.susu.core.designsystem.theme.Gray15 import com.susu.core.designsystem.theme.Gray50 import com.susu.core.designsystem.theme.SusuTheme @@ -61,6 +66,7 @@ import me.onebone.toolbar.CollapsingToolbarScaffold import me.onebone.toolbar.ScrollStrategy import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ReceivedRoute( viewModel: ReceivedViewModel = hiltViewModel(), @@ -76,6 +82,9 @@ fun ReceivedRoute( val uiState = viewModel.uiState.collectAsStateWithLifecycle().value val ledgerListState = rememberLazyGridState() val scope = rememberCoroutineScope() + val refreshState = rememberPullToRefreshState( + positionalThreshold = 100.dp + ) viewModel.sideEffect.collectWithLifecycle { sideEffect -> when (sideEffect) { ReceivedEffect.NavigateLedgerAdd -> navigateLedgerAdd() @@ -89,6 +98,14 @@ fun ReceivedRoute( } } + LaunchedEffect(key1 = refreshState.isRefreshing) { + if (refreshState.isRefreshing.not()) return@LaunchedEffect + + viewModel.getLedgerList(needClear = true) { + refreshState.endRefresh() + } + } + LaunchedEffect(key1 = Unit) { viewModel.initData() viewModel.filterIfNeed(filter) @@ -103,6 +120,7 @@ fun ReceivedRoute( ReceiveScreen( uiState = uiState, ledgerListState = ledgerListState, + refreshState = refreshState, padding = padding, onClickLedgerCard = viewModel::navigateLedgerDetail, onClickLedgerAddCard = viewModel::navigateLedgerAdd, @@ -124,6 +142,7 @@ fun ReceivedRoute( @Composable fun ReceiveScreen( uiState: ReceivedState = ReceivedState(), + refreshState: PullToRefreshState = rememberPullToRefreshState(), ledgerListState: LazyGridState = rememberLazyGridState(), padding: PaddingValues, onClickSearchIcon: () -> Unit = {}, @@ -141,7 +160,8 @@ fun ReceiveScreen( modifier = Modifier .background(SusuTheme.colorScheme.background15) .padding(padding) - .fillMaxSize(), + .fillMaxSize() + .nestedScroll(refreshState.nestedScrollConnection), ) { Column { Spacer(modifier = Modifier.size(44.dp)) @@ -249,6 +269,12 @@ fun ReceiveScreen( }, ) + PullToRefreshContainer( + modifier = Modifier.align(Alignment.TopCenter), + state = refreshState, + containerColor = Gray10, + ) + if (uiState.showEmptyLedger) { Text( modifier = Modifier.align(Alignment.Center), @@ -277,6 +303,7 @@ fun ReceiveScreen( } } +@OptIn(ExperimentalMaterial3Api::class) @Preview @Composable fun ReceivedScreenPreview() { diff --git a/feature/received/src/main/java/com/susu/feature/received/received/ReceivedViewModel.kt b/feature/received/src/main/java/com/susu/feature/received/received/ReceivedViewModel.kt index 3eac3ad4..750466ab 100644 --- a/feature/received/src/main/java/com/susu/feature/received/received/ReceivedViewModel.kt +++ b/feature/received/src/main/java/com/susu/feature/received/received/ReceivedViewModel.kt @@ -84,7 +84,7 @@ class ReceivedViewModel @Inject constructor( getLedgerList(true) } - fun getLedgerList(needClear: Boolean = false) = viewModelScope.launch { + fun getLedgerList(needClear: Boolean = false, onFinish: () -> Unit = {}) = viewModelScope.launch { mutex.withLock { val currentList = if (needClear) { page = 0 @@ -117,6 +117,7 @@ class ReceivedViewModel @Inject constructor( } if (needClear) postSideEffect(ReceivedEffect.ScrollToTop) + onFinish() } } From 3c230dde1d98b09d2fc644fa51dbd8db012bb413 Mon Sep 17 00:00:00 2001 From: jinukeu Date: Sun, 11 Feb 2024 19:53:55 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=EC=88=98=EC=88=98=20=ED=86=B5?= =?UTF-8?q?=EA=B3=84=20=EB=8B=B9=EA=B2=A8=EC=84=9C=20=EB=A6=AC=ED=94=84?= =?UTF-8?q?=EB=A0=88=EC=8B=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/my/MyStatisticsContent.kt | 42 +++++++++++++++++-- .../content/my/MyStatisticsViewModel.kt | 6 ++- .../content/susu/SusuStatisticsContent.kt | 38 ++++++++++++++++- .../content/susu/SusuStatisticsViewModel.kt | 4 +- 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/feature/statistics/src/main/java/com/susu/feature/statistics/content/my/MyStatisticsContent.kt b/feature/statistics/src/main/java/com/susu/feature/statistics/content/my/MyStatisticsContent.kt index 6e25298f..201140e8 100644 --- a/feature/statistics/src/main/java/com/susu/feature/statistics/content/my/MyStatisticsContent.kt +++ b/feature/statistics/src/main/java/com/susu/feature/statistics/content/my/MyStatisticsContent.kt @@ -9,17 +9,23 @@ 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.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshContainer +import androidx.compose.material3.pulltorefresh.PullToRefreshState +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -37,6 +43,7 @@ import com.susu.feature.statistics.component.StatisticsHorizontalItem import com.susu.feature.statistics.component.StatisticsVerticalItem import kotlinx.collections.immutable.toPersistentList +@OptIn(ExperimentalMaterial3Api::class) @Composable fun MyStatisticsRoute( isBlind: Boolean, @@ -46,32 +53,53 @@ fun MyStatisticsRoute( ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val refreshState = rememberPullToRefreshState( + positionalThreshold = 100.dp, + ) + viewModel.sideEffect.collectWithLifecycle { sideEffect -> when (sideEffect) { is MyStatisticsEffect.HandleException -> handleException(sideEffect.throwable, sideEffect.retry) } } + LaunchedEffect(key1 = refreshState.isRefreshing) { + if (refreshState.isRefreshing.not()) return@LaunchedEffect + + viewModel.getMyStatistics { + refreshState.endRefresh() + } + } + LaunchedEffect(key1 = Unit) { viewModel.getMyStatistics() } MyStatisticsContent( uiState = uiState, + refreshState = refreshState, isBlind = isBlind, modifier = modifier, ) } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun MyStatisticsContent( + modifier: Modifier = Modifier, uiState: MyStatisticsState, + refreshState: PullToRefreshState = rememberPullToRefreshState(), isBlind: Boolean, - modifier: Modifier = Modifier, ) { - Box(modifier = modifier.fillMaxSize()) { + Box( + modifier = modifier + .fillMaxSize() + .nestedScroll(refreshState.nestedScrollConnection), + ) { Column( - modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(SusuTheme.spacing.spacing_xxs), ) { RecentSpentGraph( @@ -137,6 +165,14 @@ fun MyStatisticsContent( Spacer(modifier = Modifier.height(SusuTheme.spacing.spacing_xxxl)) } + PullToRefreshContainer( + modifier = Modifier + .align(Alignment.TopCenter) + .offset(y = (-120).dp), + state = refreshState, + containerColor = Gray10, + ) + if (uiState.isLoading) { LoadingScreen( modifier = Modifier.align(Alignment.Center), diff --git a/feature/statistics/src/main/java/com/susu/feature/statistics/content/my/MyStatisticsViewModel.kt b/feature/statistics/src/main/java/com/susu/feature/statistics/content/my/MyStatisticsViewModel.kt index 949a85c6..2e728341 100644 --- a/feature/statistics/src/main/java/com/susu/feature/statistics/content/my/MyStatisticsViewModel.kt +++ b/feature/statistics/src/main/java/com/susu/feature/statistics/content/my/MyStatisticsViewModel.kt @@ -11,9 +11,9 @@ import javax.inject.Inject class MyStatisticsViewModel @Inject constructor( private val getMyStatisticsUseCase: GetMyStatisticsUseCase, ) : BaseViewModel(MyStatisticsState()) { - fun getMyStatistics() { + fun getMyStatistics(onFinish: (() -> Unit)? = null) { viewModelScope.launch { - intent { copy(isLoading = true) } + intent { copy(isLoading = onFinish == null) } getMyStatisticsUseCase() .onSuccess { intent { copy(statistics = it) } @@ -21,6 +21,8 @@ class MyStatisticsViewModel @Inject constructor( postSideEffect(MyStatisticsEffect.HandleException(it, ::getMyStatistics)) } intent { copy(isLoading = false) } + + onFinish?.invoke() } } } diff --git a/feature/statistics/src/main/java/com/susu/feature/statistics/content/susu/SusuStatisticsContent.kt b/feature/statistics/src/main/java/com/susu/feature/statistics/content/susu/SusuStatisticsContent.kt index 666646d0..b78b1af8 100644 --- a/feature/statistics/src/main/java/com/susu/feature/statistics/content/susu/SusuStatisticsContent.kt +++ b/feature/statistics/src/main/java/com/susu/feature/statistics/content/susu/SusuStatisticsContent.kt @@ -9,6 +9,7 @@ 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.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState @@ -16,12 +17,16 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshContainer +import androidx.compose.material3.pulltorefresh.PullToRefreshState +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState 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.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -43,6 +48,7 @@ import com.susu.feature.statistics.component.StatisticsVerticalItem import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentList +@OptIn(ExperimentalMaterial3Api::class) @Composable fun SusuStatisticsRoute( isBlind: Boolean, @@ -52,12 +58,24 @@ fun SusuStatisticsRoute( ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val refreshState = rememberPullToRefreshState( + positionalThreshold = 100.dp, + ) + viewModel.sideEffect.collectWithLifecycle { sideEffect -> when (sideEffect) { is SusuStatisticsEffect.HandleException -> handleException(sideEffect.throwable, sideEffect.retry) } } + LaunchedEffect(key1 = refreshState.isRefreshing) { + if (refreshState.isRefreshing.not()) return@LaunchedEffect + + viewModel.getSusuStatistics { + refreshState.endRefresh() + } + } + LaunchedEffect(key1 = Unit) { viewModel.getStatisticsOption() } @@ -68,6 +86,7 @@ fun SusuStatisticsRoute( SusuStatisticsScreen( uiState = uiState, + refreshState = refreshState, isBlind = isBlind, modifier = modifier, onClickAge = viewModel::showAgeSheet, @@ -87,6 +106,7 @@ fun SusuStatisticsRoute( fun SusuStatisticsScreen( modifier: Modifier = Modifier, uiState: SusuStatisticsState = SusuStatisticsState(), + refreshState: PullToRefreshState = rememberPullToRefreshState(), isBlind: Boolean = true, onClickAge: () -> Unit = {}, onClickRelationship: () -> Unit = {}, @@ -109,9 +129,15 @@ fun SusuStatisticsScreen( }.toImmutableList() } - Box(modifier = modifier.fillMaxSize()) { + Box( + modifier = modifier + .fillMaxSize() + .nestedScroll(refreshState.nestedScrollConnection), + ) { Column( - modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(SusuTheme.spacing.spacing_xxs), ) { SusuStatisticsOptionSlot( @@ -188,6 +214,12 @@ fun SusuStatisticsScreen( Spacer(modifier = Modifier.height(SusuTheme.spacing.spacing_xxxl)) } + PullToRefreshContainer( + modifier = Modifier.align(Alignment.TopCenter).offset(y = -(120).dp), + state = refreshState, + containerColor = Gray10, + ) + if (uiState.isAgeSheetOpen) { SusuSelectionBottomSheet( containerHeight = 322.dp, @@ -226,6 +258,7 @@ fun SusuStatisticsScreen( } } +@OptIn(ExperimentalMaterial3Api::class) @Preview(showBackground = true) @Composable fun SusuStatisticsScreenPreview() { @@ -236,6 +269,7 @@ fun SusuStatisticsScreenPreview() { } } +@OptIn(ExperimentalMaterial3Api::class) @Preview(showBackground = true) @Composable fun SusuStatisticsScreenBlindPreview() { diff --git a/feature/statistics/src/main/java/com/susu/feature/statistics/content/susu/SusuStatisticsViewModel.kt b/feature/statistics/src/main/java/com/susu/feature/statistics/content/susu/SusuStatisticsViewModel.kt index d72549d7..49ee90e6 100644 --- a/feature/statistics/src/main/java/com/susu/feature/statistics/content/susu/SusuStatisticsViewModel.kt +++ b/feature/statistics/src/main/java/com/susu/feature/statistics/content/susu/SusuStatisticsViewModel.kt @@ -47,7 +47,7 @@ class SusuStatisticsViewModel @Inject constructor( } } - fun getSusuStatistics() { + fun getSusuStatistics(onFinish: () -> Unit = {}) { if (currentState.relationship !in currentState.relationshipConfig && currentState.category !in currentState.categoryConfig ) { @@ -64,6 +64,8 @@ class SusuStatisticsViewModel @Inject constructor( }.onFailure { postSideEffect(SusuStatisticsEffect.HandleException(it, ::getSusuStatistics)) } + + onFinish() } } From 369d3bf29a927851ea43f712b1d884e1441d79f4 Mon Sep 17 00:00:00 2001 From: jinukeu Date: Sun, 11 Feb 2024 20:05:12 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20=ED=88=AC=ED=91=9C=20=EB=8B=B9?= =?UTF-8?q?=EA=B2=A8=EC=84=9C=20=EB=A6=AC=ED=94=84=EB=A0=88=EC=8B=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../community/community/CommunityScreen.kt | 33 ++- .../community/community/CommunityViewModel.kt | 6 + .../community/votedetail/VoteDetailScreen.kt | 257 ++++++++++-------- .../votedetail/VoteDetailViewModel.kt | 5 +- 4 files changed, 186 insertions(+), 115 deletions(-) diff --git a/feature/community/src/main/java/com/susu/feature/community/community/CommunityScreen.kt b/feature/community/src/main/java/com/susu/feature/community/community/CommunityScreen.kt index fba56c4f..d56a9212 100644 --- a/feature/community/src/main/java/com/susu/feature/community/community/CommunityScreen.kt +++ b/feature/community/src/main/java/com/susu/feature/community/community/CommunityScreen.kt @@ -18,9 +18,13 @@ import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshContainer +import androidx.compose.material3.pulltorefresh.PullToRefreshState +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -30,6 +34,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -64,6 +69,7 @@ import com.susu.feature.community.community.component.VoteCard import kotlinx.coroutines.delay import java.time.LocalDateTime +@OptIn(ExperimentalMaterial3Api::class) @Composable fun CommunityRoute( padding: PaddingValues, @@ -81,6 +87,11 @@ fun CommunityRoute( ) { val uiState = viewModel.uiState.collectAsStateWithLifecycle().value val context = LocalContext.current + + val refreshState = rememberPullToRefreshState( + positionalThreshold = 100.dp + ) + viewModel.sideEffect.collectWithLifecycle { sideEffect -> when (sideEffect) { is CommunitySideEffect.HandleException -> handleException(sideEffect.throwable, sideEffect.retry) @@ -116,6 +127,14 @@ fun CommunityRoute( val voteListState = rememberLazyListState() + LaunchedEffect(key1 = refreshState.isRefreshing) { + if (refreshState.isRefreshing.not()) return@LaunchedEffect + + viewModel.refreshData { + refreshState.endRefresh() + } + } + LaunchedEffect(key1 = Unit) { viewModel.initData() viewModel.getCategoryConfig() @@ -132,6 +151,7 @@ fun CommunityRoute( CommunityScreen( padding = padding, + refreshState = refreshState, uiState = uiState, currentTime = currentTime, voteListState = voteListState, @@ -145,11 +165,12 @@ fun CommunityRoute( ) } -@OptIn(ExperimentalFoundationApi::class) +@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable fun CommunityScreen( padding: PaddingValues, uiState: CommunityState = CommunityState(), + refreshState: PullToRefreshState = rememberPullToRefreshState(), currentTime: LocalDateTime = LocalDateTime.now(), voteListState: LazyListState = rememberLazyListState(), onClickSearchIcon: () -> Unit = {}, @@ -163,7 +184,8 @@ fun CommunityScreen( Box( modifier = Modifier .padding(padding) - .fillMaxSize(), + .fillMaxSize() + .nestedScroll(refreshState.nestedScrollConnection), ) { Column( modifier = Modifier @@ -340,6 +362,12 @@ fun CommunityScreen( } } + PullToRefreshContainer( + modifier = Modifier.align(Alignment.TopCenter), + state = refreshState, + containerColor = Gray10, + ) + SusuFloatingButton( modifier = Modifier .align(Alignment.BottomEnd) @@ -350,6 +378,7 @@ fun CommunityScreen( } } +@OptIn(ExperimentalMaterial3Api::class) @Preview @Composable fun CommunityScreenPreview() { diff --git a/feature/community/src/main/java/com/susu/feature/community/community/CommunityViewModel.kt b/feature/community/src/main/java/com/susu/feature/community/community/CommunityViewModel.kt index 9f0b7e3b..1aaafdff 100644 --- a/feature/community/src/main/java/com/susu/feature/community/community/CommunityViewModel.kt +++ b/feature/community/src/main/java/com/susu/feature/community/community/CommunityViewModel.kt @@ -15,6 +15,7 @@ import com.susu.domain.usecase.vote.GetVoteListUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.Job +import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -166,6 +167,11 @@ class CommunityViewModel @Inject constructor( } } + fun refreshData(onFinish: () -> Unit = {}) = viewModelScope.launch { + joinAll(getVoteList(true), getPopularVoteList()) + onFinish() + } + fun selectCategory(category: Category?) { intent { copy(selectedCategory = category) diff --git a/feature/community/src/main/java/com/susu/feature/community/votedetail/VoteDetailScreen.kt b/feature/community/src/main/java/com/susu/feature/community/votedetail/VoteDetailScreen.kt index 5a5b20fd..056c62c4 100644 --- a/feature/community/src/main/java/com/susu/feature/community/votedetail/VoteDetailScreen.kt +++ b/feature/community/src/main/java/com/susu/feature/community/votedetail/VoteDetailScreen.kt @@ -15,8 +15,12 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshContainer +import androidx.compose.material3.pulltorefresh.PullToRefreshState +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -26,6 +30,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -41,6 +46,7 @@ import com.susu.core.designsystem.component.badge.BadgeColor import com.susu.core.designsystem.component.badge.BadgeStyle import com.susu.core.designsystem.component.badge.SusuBadge import com.susu.core.designsystem.component.screen.LoadingScreen +import com.susu.core.designsystem.theme.Gray10 import com.susu.core.designsystem.theme.Gray15 import com.susu.core.designsystem.theme.Gray50 import com.susu.core.designsystem.theme.Orange60 @@ -58,6 +64,7 @@ import kotlinx.datetime.toJavaLocalDateTime import java.time.LocalDateTime import java.time.temporal.ChronoUnit +@OptIn(ExperimentalMaterial3Api::class) @Composable fun VoteDetailRoute( viewModel: VoteDetailViewModel = hiltViewModel(), @@ -71,6 +78,9 @@ fun VoteDetailRoute( ) { val uiState = viewModel.uiState.collectAsStateWithLifecycle().value val context = LocalContext.current + val refreshState = rememberPullToRefreshState( + positionalThreshold = 100.dp, + ) viewModel.sideEffect.collectWithLifecycle { sideEffect -> when (sideEffect) { is VoteDetailSideEffect.HandleException -> handleException(sideEffect.throwable, sideEffect.retry) @@ -129,12 +139,21 @@ fun VoteDetailRoute( viewModel.getVoteDetail() } + LaunchedEffect(key1 = refreshState.isRefreshing) { + if (refreshState.isRefreshing.not()) return@LaunchedEffect + + viewModel.getVoteDetail { + refreshState.endRefresh() + } + } + BackHandler { viewModel.popBackStack() } VoteDetailScreen( uiState = uiState, + refreshState = refreshState, currentTime = currentTime, onClickBack = viewModel::popBackStack, onClickEdit = viewModel::navigateVoteEdit, @@ -144,9 +163,11 @@ fun VoteDetailRoute( ) } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun VoteDetailScreen( uiState: VoteDetailState = VoteDetailState(), + refreshState: PullToRefreshState = rememberPullToRefreshState(), currentTime: LocalDateTime = LocalDateTime.now(), onClickBack: () -> Unit = {}, onClickReport: () -> Unit = {}, @@ -154,149 +175,163 @@ fun VoteDetailScreen( onClickDelete: () -> Unit = {}, onClickOption: (Long, Boolean) -> Unit = { _, _ -> }, ) { - Column( + Box( modifier = Modifier .fillMaxSize() - .background(SusuTheme.colorScheme.background10), + .background(SusuTheme.colorScheme.background10) + .nestedScroll(refreshState.nestedScrollConnection), ) { - SusuDefaultAppBar( - leftIcon = { - BackIcon( - onClick = onClickBack, - ) - }, - title = uiState.vote.boardName, - actions = { - if (uiState.vote.isMine) { - EditText( - onClick = onClickEdit, - ) - Spacer(modifier = Modifier.size(SusuTheme.spacing.spacing_m)) - DeleteText( - onClick = onClickDelete, - ) - } else { - Image( - modifier = Modifier - .size(24.dp) - .susuClickable(rippleEnabled = false, onClick = onClickReport), - painter = painterResource(id = R.drawable.ic_report), - contentDescription = stringResource(id = com.susu.core.ui.R.string.content_description_report_button), - ) - } - Spacer(modifier = Modifier.size(SusuTheme.spacing.spacing_m)) - }, - ) - Column( modifier = Modifier - .padding(SusuTheme.spacing.spacing_m) - .weight(1f) - .verticalScroll(rememberScrollState()), + .fillMaxSize(), ) { - Row( - horizontalArrangement = Arrangement.spacedBy(SusuTheme.spacing.spacing_xxs), - verticalAlignment = Alignment.CenterVertically, - ) { - // border를 사용하지 않은 이유 see -> https://stackoverflow.com/questions/75964726/jetpack-compose-circle-shape-border-not-being-applied-as-expected - Box { - Box( - modifier = Modifier - .size(20.dp) - .clip(CircleShape) - .background(Gray15), - ) - Image( - modifier = Modifier - .size(18.dp) - .clip(CircleShape) - .align(Alignment.Center), - painter = painterResource(id = com.susu.core.ui.R.drawable.img_default_profile), - contentDescription = null, + SusuDefaultAppBar( + leftIcon = { + BackIcon( + onClick = onClickBack, ) - } - - Text(text = stringResource(R.string.word_anonymous_susu), style = SusuTheme.typography.title_xxxs) + }, + title = uiState.vote.boardName, + actions = { + if (uiState.vote.isMine) { + EditText( + onClick = onClickEdit, + ) + Spacer(modifier = Modifier.size(SusuTheme.spacing.spacing_m)) + DeleteText( + onClick = onClickDelete, + ) + } else { + Image( + modifier = Modifier + .size(24.dp) + .susuClickable(rippleEnabled = false, onClick = onClickReport), + painter = painterResource(id = R.drawable.ic_report), + contentDescription = stringResource(id = com.susu.core.ui.R.string.content_description_report_button), + ) + } + Spacer(modifier = Modifier.size(SusuTheme.spacing.spacing_m)) + }, + ) - if (uiState.vote.isMine) { - SusuBadge( - color = BadgeColor.Gray20, - text = stringResource(R.string.word_me), - padding = BadgeStyle.extraSmallBadge, - ) - } - } + Column( + modifier = Modifier + .padding(SusuTheme.spacing.spacing_m) + .weight(1f) + .verticalScroll(rememberScrollState()), + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(SusuTheme.spacing.spacing_xxs), + verticalAlignment = Alignment.CenterVertically, + ) { + // border를 사용하지 않은 이유 see -> https://stackoverflow.com/questions/75964726/jetpack-compose-circle-shape-border-not-being-applied-as-expected + Box { + Box( + modifier = Modifier + .size(20.dp) + .clip(CircleShape) + .background(Gray15), + ) + Image( + modifier = Modifier + .size(18.dp) + .clip(CircleShape) + .align(Alignment.Center), + painter = painterResource(id = com.susu.core.ui.R.drawable.img_default_profile), + contentDescription = null, + ) + } - Spacer(modifier = Modifier.size(SusuTheme.spacing.spacing_m)) + Text(text = stringResource(R.string.word_anonymous_susu), style = SusuTheme.typography.title_xxxs) - Text( - modifier = Modifier.fillMaxWidth(), - text = uiState.vote.content, - style = SusuTheme.typography.text_xxs, - ) + if (uiState.vote.isMine) { + SusuBadge( + color = BadgeColor.Gray20, + text = stringResource(R.string.word_me), + padding = BadgeStyle.extraSmallBadge, + ) + } + } - Spacer(modifier = Modifier.size(SusuTheme.spacing.spacing_xl)) + Spacer(modifier = Modifier.size(SusuTheme.spacing.spacing_m)) - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - modifier = Modifier.size(20.dp), - painter = painterResource(id = R.drawable.ic_vote), - contentDescription = null, - tint = Orange60, + Text( + modifier = Modifier.fillMaxWidth(), + text = uiState.vote.content, + style = SusuTheme.typography.text_xxs, ) - Spacer(modifier = Modifier.size(SusuTheme.spacing.spacing_xxxxs)) + Spacer(modifier = Modifier.size(SusuTheme.spacing.spacing_xl)) - Text( - text = stringResource(R.string.vote_card_count, uiState.vote.count), - style = SusuTheme.typography.title_xxxs, - color = Orange60, - ) + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier.size(20.dp), + painter = painterResource(id = R.drawable.ic_vote), + contentDescription = null, + tint = Orange60, + ) - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.size(SusuTheme.spacing.spacing_xxxxs)) - val totalMinutes = ChronoUnit.MINUTES.between(uiState.vote.createdAt.toJavaLocalDateTime(), currentTime) + Text( + text = stringResource(R.string.vote_card_count, uiState.vote.count), + style = SusuTheme.typography.title_xxxs, + color = Orange60, + ) - val writeTime = when { - totalMinutes / 60 > 24 -> uiState.vote.createdAt.toJavaLocalDateTime().to_yyyy_dot_MM_dot_dd() - totalMinutes / 60 > 0 -> stringResource(R.string.word_before_hour, totalMinutes / 60) - totalMinutes / 60 == 0L -> stringResource(R.string.word_before_minute, totalMinutes % 60 + 1) - else -> uiState.vote.createdAt.toJavaLocalDateTime().to_yyyy_dot_MM_dot_dd() - } + Spacer(modifier = Modifier.weight(1f)) - Text( - text = writeTime, - style = SusuTheme.typography.text_xxxs, - color = Gray50, - ) - } + val totalMinutes = ChronoUnit.MINUTES.between(uiState.vote.createdAt.toJavaLocalDateTime(), currentTime) - Spacer(modifier = Modifier.size(SusuTheme.spacing.spacing_m)) + val writeTime = when { + totalMinutes / 60 > 24 -> uiState.vote.createdAt.toJavaLocalDateTime().to_yyyy_dot_MM_dot_dd() + totalMinutes / 60 > 0 -> stringResource(R.string.word_before_hour, totalMinutes / 60) + totalMinutes / 60 == 0L -> stringResource(R.string.word_before_minute, totalMinutes % 60 + 1) + else -> uiState.vote.createdAt.toJavaLocalDateTime().to_yyyy_dot_MM_dot_dd() + } - Column( - verticalArrangement = Arrangement.spacedBy(SusuTheme.spacing.spacing_xxs), - ) { - uiState.vote.optionList.forEach { - VoteItem( - title = it.content, - isPick = it.isVoted, - voteCount = it.count, - totalVoteCount = uiState.vote.count, - showResult = uiState.vote.optionList.any { it.isVoted }, - onClick = { onClickOption(it.id, it.isVoted) }, + Text( + text = writeTime, + style = SusuTheme.typography.text_xxxs, + color = Gray50, ) } + + Spacer(modifier = Modifier.size(SusuTheme.spacing.spacing_m)) + + Column( + verticalArrangement = Arrangement.spacedBy(SusuTheme.spacing.spacing_xxs), + ) { + uiState.vote.optionList.forEach { + VoteItem( + title = it.content, + isPick = it.isVoted, + voteCount = it.count, + totalVoteCount = uiState.vote.count, + showResult = uiState.vote.optionList.any { it.isVoted }, + onClick = { onClickOption(it.id, it.isVoted) }, + ) + } + } } } + + PullToRefreshContainer( + modifier = Modifier.align(Alignment.TopCenter), + state = refreshState, + containerColor = Gray10, + ) } + if (uiState.isLoading) { LoadingScreen() } } +@OptIn(ExperimentalMaterial3Api::class) @Preview @Composable fun VoteDetailScreenPreview() { diff --git a/feature/community/src/main/java/com/susu/feature/community/votedetail/VoteDetailViewModel.kt b/feature/community/src/main/java/com/susu/feature/community/votedetail/VoteDetailViewModel.kt index 3a1b9cb3..5ad4a75e 100644 --- a/feature/community/src/main/java/com/susu/feature/community/votedetail/VoteDetailViewModel.kt +++ b/feature/community/src/main/java/com/susu/feature/community/votedetail/VoteDetailViewModel.kt @@ -35,8 +35,8 @@ class VoteDetailViewModel @Inject constructor( private var initCount: Long = 0 private var initIsVoted: Boolean = false - fun getVoteDetail() = viewModelScope.launch { - intent { copy(isLoading = true) } + fun getVoteDetail(onFinish: (() -> Unit)? = null) = viewModelScope.launch { + intent { copy(isLoading = onFinish == null) } getVoteDetailUseCase( voteId, ).onSuccess { @@ -48,6 +48,7 @@ class VoteDetailViewModel @Inject constructor( postSideEffect(VoteDetailSideEffect.HandleException(it, ::getVoteDetail)) } intent { copy(isLoading = false) } + onFinish?.invoke() } fun vote(optionId: Long, isVoted: Boolean) { From 92e19579d1ef3158b6908518e638350a5fb88d71 Mon Sep 17 00:00:00 2001 From: jinukeu Date: Sun, 11 Feb 2024 20:05:52 +0900 Subject: [PATCH 6/6] chore: ktlint --- .../com/susu/feature/community/community/CommunityScreen.kt | 2 +- .../com/susu/feature/community/votedetail/VoteDetailScreen.kt | 1 - .../susu/feature/received/ledgerdetail/LedgerDetailScreen.kt | 2 +- .../java/com/susu/feature/received/received/ReceivedScreen.kt | 2 +- .../main/java/com/susu/feature/envelope/SentEnvelopeScreen.kt | 2 +- feature/sent/src/main/java/com/susu/feature/sent/SentScreen.kt | 2 +- 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/feature/community/src/main/java/com/susu/feature/community/community/CommunityScreen.kt b/feature/community/src/main/java/com/susu/feature/community/community/CommunityScreen.kt index d56a9212..6ca33f3b 100644 --- a/feature/community/src/main/java/com/susu/feature/community/community/CommunityScreen.kt +++ b/feature/community/src/main/java/com/susu/feature/community/community/CommunityScreen.kt @@ -89,7 +89,7 @@ fun CommunityRoute( val context = LocalContext.current val refreshState = rememberPullToRefreshState( - positionalThreshold = 100.dp + positionalThreshold = 100.dp, ) viewModel.sideEffect.collectWithLifecycle { sideEffect -> diff --git a/feature/community/src/main/java/com/susu/feature/community/votedetail/VoteDetailScreen.kt b/feature/community/src/main/java/com/susu/feature/community/votedetail/VoteDetailScreen.kt index 056c62c4..522ba6cc 100644 --- a/feature/community/src/main/java/com/susu/feature/community/votedetail/VoteDetailScreen.kt +++ b/feature/community/src/main/java/com/susu/feature/community/votedetail/VoteDetailScreen.kt @@ -325,7 +325,6 @@ fun VoteDetailScreen( ) } - if (uiState.isLoading) { LoadingScreen() } diff --git a/feature/received/src/main/java/com/susu/feature/received/ledgerdetail/LedgerDetailScreen.kt b/feature/received/src/main/java/com/susu/feature/received/ledgerdetail/LedgerDetailScreen.kt index 07586706..683bd0c1 100644 --- a/feature/received/src/main/java/com/susu/feature/received/ledgerdetail/LedgerDetailScreen.kt +++ b/feature/received/src/main/java/com/susu/feature/received/ledgerdetail/LedgerDetailScreen.kt @@ -87,7 +87,7 @@ fun LedgerDetailRoute( val listState = rememberLazyListState() val context = LocalContext.current val refreshState = rememberPullToRefreshState( - positionalThreshold = 100.dp + positionalThreshold = 100.dp, ) viewModel.sideEffect.collectWithLifecycle { sideEffect -> when (sideEffect) { diff --git a/feature/received/src/main/java/com/susu/feature/received/received/ReceivedScreen.kt b/feature/received/src/main/java/com/susu/feature/received/received/ReceivedScreen.kt index 823a6eb8..17d9ad98 100644 --- a/feature/received/src/main/java/com/susu/feature/received/received/ReceivedScreen.kt +++ b/feature/received/src/main/java/com/susu/feature/received/received/ReceivedScreen.kt @@ -83,7 +83,7 @@ fun ReceivedRoute( val ledgerListState = rememberLazyGridState() val scope = rememberCoroutineScope() val refreshState = rememberPullToRefreshState( - positionalThreshold = 100.dp + positionalThreshold = 100.dp, ) viewModel.sideEffect.collectWithLifecycle { sideEffect -> when (sideEffect) { diff --git a/feature/sent/src/main/java/com/susu/feature/envelope/SentEnvelopeScreen.kt b/feature/sent/src/main/java/com/susu/feature/envelope/SentEnvelopeScreen.kt index 6a826351..4417ce5c 100644 --- a/feature/sent/src/main/java/com/susu/feature/envelope/SentEnvelopeScreen.kt +++ b/feature/sent/src/main/java/com/susu/feature/envelope/SentEnvelopeScreen.kt @@ -67,7 +67,7 @@ fun SentEnvelopeRoute( val historyListState = rememberLazyListState() val refreshState = rememberPullToRefreshState( - positionalThreshold = 100.dp + positionalThreshold = 100.dp, ) viewModel.sideEffect.collectWithLifecycle { sideEffect -> diff --git a/feature/sent/src/main/java/com/susu/feature/sent/SentScreen.kt b/feature/sent/src/main/java/com/susu/feature/sent/SentScreen.kt index 17f9883d..e4967f47 100644 --- a/feature/sent/src/main/java/com/susu/feature/sent/SentScreen.kt +++ b/feature/sent/src/main/java/com/susu/feature/sent/SentScreen.kt @@ -81,7 +81,7 @@ fun SentRoute( val scope = rememberCoroutineScope() val refreshState = rememberPullToRefreshState( - positionalThreshold = 100.dp + positionalThreshold = 100.dp, ) viewModel.sideEffect.collectWithLifecycle { sideEffect ->