11package com.neki.android.feature.archive.impl.photo_detail
22
3+ import androidx.compose.animation.AnimatedVisibility
34import androidx.compose.animation.core.spring
5+ import androidx.compose.animation.expandVertically
6+ import androidx.compose.animation.fadeIn
7+ import androidx.compose.animation.fadeOut
8+ import androidx.compose.animation.shrinkVertically
49import androidx.compose.foundation.background
10+ import androidx.compose.foundation.layout.Box
511import androidx.compose.foundation.layout.Column
12+ import androidx.compose.foundation.layout.WindowInsets
13+ import androidx.compose.foundation.layout.exclude
614import androidx.compose.foundation.layout.fillMaxSize
715import androidx.compose.foundation.layout.fillMaxWidth
16+ import androidx.compose.foundation.layout.ime
17+ import androidx.compose.foundation.layout.imePadding
18+ import androidx.compose.foundation.layout.padding
19+ import androidx.compose.foundation.layout.width
20+ import androidx.compose.foundation.layout.windowInsetsPadding
821import androidx.compose.foundation.pager.HorizontalPager
922import androidx.compose.foundation.pager.PagerState
1023import androidx.compose.foundation.pager.rememberPagerState
@@ -16,17 +29,19 @@ import androidx.compose.runtime.remember
1629import androidx.compose.runtime.rememberCoroutineScope
1730import androidx.compose.runtime.setValue
1831import androidx.compose.runtime.snapshotFlow
32+ import androidx.compose.ui.Alignment
1933import androidx.compose.ui.Modifier
2034import androidx.compose.ui.draw.clipToBounds
35+ import androidx.compose.ui.graphics.Color
2136import androidx.compose.ui.layout.onSizeChanged
22- import androidx.compose.foundation.layout.width
23- import androidx.compose.ui.Alignment
2437import androidx.compose.ui.platform.LocalContext
2538import androidx.compose.ui.platform.LocalDensity
39+ import androidx.compose.ui.unit.Dp
2640import androidx.compose.ui.unit.IntOffset
2741import androidx.compose.ui.unit.dp
2842import androidx.lifecycle.compose.collectAsStateWithLifecycle
2943import com.neki.android.core.designsystem.DevicePreview
44+ import com.neki.android.core.designsystem.modifier.noRippleClickableSingle
3045import com.neki.android.core.designsystem.topbar.BackTitleOptionTopBar
3146import com.neki.android.core.designsystem.ui.theme.NekiTheme
3247import com.neki.android.core.model.Photo
@@ -37,10 +52,12 @@ import com.neki.android.core.ui.compose.collectWithLifecycle
3752import com.neki.android.core.ui.toast.NekiToast
3853import com.neki.android.feature.archive.api.PhotoDetailResult
3954import com.neki.android.feature.archive.impl.component.DeletePhotoDialog
55+ import com.neki.android.feature.archive.impl.photo_detail.component.MemoTextField
4056import com.neki.android.feature.archive.impl.photo_detail.component.PhotoDetailActionBar
4157import com.neki.android.feature.archive.impl.photo_detail.component.PhotoDetailImageItem
4258import com.neki.android.feature.archive.impl.util.ImageDownloader
4359import kotlinx.collections.immutable.persistentListOf
60+ import kotlinx.coroutines.flow.filter
4461import kotlinx.coroutines.launch
4562import timber.log.Timber
4663
@@ -66,6 +83,14 @@ internal fun PhotoDetailRoute(
6683 }
6784 }
6885
86+ LaunchedEffect (pagerState) {
87+ snapshotFlow { pagerState.isScrollInProgress }
88+ .filter { it }
89+ .collect {
90+ viewModel.store.onIntent(PhotoDetailIntent .PageScrollStarted )
91+ }
92+ }
93+
6994 viewModel.store.sideEffects.collectWithLifecycle { sideEffect ->
7095 when (sideEffect) {
7196 PhotoDetailSideEffect .NavigateBack -> navigateBack()
@@ -111,8 +136,6 @@ internal fun PhotoDetailScreen(
111136 onIntent : (PhotoDetailIntent ) -> Unit = {},
112137 pagerState : PagerState = rememberPagerState { uiState.photos.size.coerceAtLeast(1) },
113138) {
114- val isMemoActive = uiState.currentMemoMode == MemoMode .Expanded ||
115- uiState.currentMemoMode == MemoMode .Editing
116139 val density = LocalDensity .current
117140 var actionBarHeightDp by remember { mutableStateOf(0 .dp) }
118141
@@ -127,46 +150,24 @@ internal fun PhotoDetailScreen(
127150 onClickIcon = { onIntent(PhotoDetailIntent .ClickOptionIcon ) },
128151 )
129152
130- HorizontalPager (
153+ PhotoDetailPagerArea (
131154 modifier = Modifier
132155 .fillMaxWidth()
133156 .weight(1f )
134157 .clipToBounds(),
135- state = pagerState,
136- beyondViewportPageCount = 1 ,
137- userScrollEnabled = ! isMemoActive,
138- ) { page ->
139- val index = if (uiState.photos.isEmpty()) 0 else page.coerceIn(0 , uiState.photos.lastIndex)
140-
141- val photo = uiState.photos.getOrNull(index)
142- val pageMemoMode = uiState.memoModeOf(photo?.id ? : 0L )
143- val pageMemo = if (index == uiState.currentIndex) uiState.memo
144- else photo?.memo.orEmpty()
145-
146- PhotoDetailImageItem (
147- imageUrl = photo?.imageUrl,
148- memo = pageMemo,
149- memoMode = pageMemoMode,
150- actionBarHeight = actionBarHeightDp,
151- isScrollInProgress = pagerState.isScrollInProgress,
152- isTapEnabled = pageMemoMode != MemoMode .Expanded && pageMemoMode != MemoMode .Editing ,
153- onClickLeft = { onIntent(PhotoDetailIntent .ClickLeftPhoto ) },
154- onClickRight = { onIntent(PhotoDetailIntent .ClickRightPhoto ) },
155- onClickMemoMore = { onIntent(PhotoDetailIntent .ClickMemoMore ) },
156- onClickMemoText = { onIntent(PhotoDetailIntent .ClickMemoText ) },
157- onClickMemoFold = { onIntent(PhotoDetailIntent .ClickMemoFold ) },
158- onClickMemoCancel = { onIntent(PhotoDetailIntent .ClickMemoCancel ) },
159- onClickMemoDone = { onIntent(PhotoDetailIntent .ClickMemoDone (it)) },
160- onMemoTextChanged = { onIntent(PhotoDetailIntent .MemoTextChanged (it)) },
161- )
162- }
158+ uiState = uiState,
159+ actionBarHeightDp = actionBarHeightDp,
160+ pagerState = pagerState,
161+ onIntent = onIntent,
162+ )
163163
164- if (uiState.currentMemoMode != MemoMode .Editing ) {
164+ if (uiState.memoMode != MemoMode .Editing ) {
165165 PhotoDetailActionBar (
166166 modifier = Modifier .onSizeChanged { size ->
167167 actionBarHeightDp = with (density) { size.height.toDp() }
168168 },
169169 isFavorite = uiState.photo.isFavorite,
170+ hasMemo = uiState.photo.memo.isNotEmpty(),
170171 onClickDownload = { onIntent(PhotoDetailIntent .ClickDownloadIcon ) },
171172 onClickFavorite = { onIntent(PhotoDetailIntent .ClickFavoriteIcon ) },
172173 onClickMemo = { onIntent(PhotoDetailIntent .ClickMemoIcon ) },
@@ -204,6 +205,78 @@ internal fun PhotoDetailScreen(
204205 }
205206}
206207
208+ @Composable
209+ private fun PhotoDetailPagerArea (
210+ modifier : Modifier ,
211+ uiState : PhotoDetailState ,
212+ actionBarHeightDp : Dp ,
213+ pagerState : PagerState ,
214+ onIntent : (PhotoDetailIntent ) -> Unit ,
215+ ) {
216+ val isMemoActive = uiState.memoMode == MemoMode .Expanded ||
217+ uiState.memoMode == MemoMode .Editing
218+
219+ Box (modifier = modifier) {
220+ HorizontalPager (
221+ modifier = Modifier
222+ .fillMaxSize()
223+ .padding(bottom = if (uiState.memoMode == MemoMode .Editing ) actionBarHeightDp else 0 .dp),
224+ state = pagerState,
225+ beyondViewportPageCount = 1 ,
226+ userScrollEnabled = ! isMemoActive,
227+ ) { page ->
228+ val index = if (uiState.photos.isEmpty()) 0 else page.coerceIn(0 , uiState.photos.lastIndex)
229+ val photo = uiState.photos.getOrNull(index)
230+
231+ PhotoDetailImageItem (
232+ imageUrl = photo?.imageUrl,
233+ isScrollInProgress = pagerState.isScrollInProgress,
234+ isTapEnabled = ! isMemoActive,
235+ onClickLeft = { onIntent(PhotoDetailIntent .ClickLeftPhoto ) },
236+ onClickRight = { onIntent(PhotoDetailIntent .ClickRightPhoto ) },
237+ )
238+ }
239+
240+ AnimatedVisibility (
241+ visible = isMemoActive,
242+ enter = fadeIn(),
243+ exit = fadeOut(),
244+ ) {
245+ Box (
246+ modifier = Modifier
247+ .fillMaxSize()
248+ .background(Color (0x80202227 ))
249+ .noRippleClickableSingle { onIntent(PhotoDetailIntent .ClickMemoFold ) },
250+ )
251+ }
252+
253+ AnimatedVisibility (
254+ modifier = Modifier
255+ .align(Alignment .BottomCenter )
256+ .then(
257+ if (uiState.memoMode == MemoMode .Editing ) Modifier .imePadding()
258+ else Modifier .windowInsetsPadding(
259+ WindowInsets .ime.exclude(WindowInsets (bottom = actionBarHeightDp)),
260+ ),
261+ ),
262+ visible = uiState.memoMode != MemoMode .Closed ,
263+ enter = expandVertically(expandFrom = Alignment .Top ),
264+ exit = shrinkVertically(shrinkTowards = Alignment .Top ),
265+ ) {
266+ MemoTextField (
267+ memo = uiState.memo,
268+ memoMode = uiState.memoMode,
269+ onClickMemoMore = { onIntent(PhotoDetailIntent .ClickMemoMore ) },
270+ onClickMemoText = { onIntent(PhotoDetailIntent .ClickMemoText ) },
271+ onClickMemoFold = { onIntent(PhotoDetailIntent .ClickMemoFold ) },
272+ onClickMemoCancel = { onIntent(PhotoDetailIntent .ClickMemoCancel ) },
273+ onClickMemoDone = { onIntent(PhotoDetailIntent .ClickMemoDone (it)) },
274+ onMemoTextChanged = { onIntent(PhotoDetailIntent .MemoTextChanged (it)) },
275+ )
276+ }
277+ }
278+ }
279+
207280@DevicePreview
208281@Composable
209282private fun PhotoDetailScreenPreview () {
0 commit comments