diff --git a/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt index 1d6af3d127..df70b52efb 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt @@ -411,7 +411,6 @@ open class ObjectSetFragment : ViewersWidget( state = vm.viewersWidgetState.collectAsStateWithLifecycle().value, action = vm::onViewersWidgetAction, - scope = lifecycleScope ) } } @@ -422,7 +421,6 @@ open class ObjectSetFragment : ViewerEditWidget( state = vm.viewerEditWidgetState.collectAsStateWithLifecycle().value, action = vm::onViewerEditWidgetAction, - scope = lifecycleScope ) } } @@ -433,7 +431,6 @@ open class ObjectSetFragment : ViewerLayoutWidget( uiState = vm.viewerLayoutWidgetState.collectAsStateWithLifecycle().value, action = vm::onViewerLayoutWidgetAction, - scope = lifecycleScope ) } } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerEditWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerEditWidget.kt index 9a21153fc4..e1be2bcab1 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerEditWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerEditWidget.kt @@ -1,23 +1,16 @@ package com.anytypeio.anytype.core_ui.widgets.dv -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.RoundedCornerShape @@ -25,12 +18,11 @@ import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.FractionalThreshold import androidx.compose.material.Text -import androidx.compose.material.rememberSwipeableState -import androidx.compose.material.swipeable +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -45,13 +37,10 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.boundsInRoot import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.SoftwareKeyboardController @@ -62,103 +51,57 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import com.anytypeio.anytype.core_models.DVViewerType import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.foundation.Divider -import com.anytypeio.anytype.core_ui.foundation.Dragger -import com.anytypeio.anytype.core_ui.foundation.noRippleClickable import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable import com.anytypeio.anytype.core_ui.views.Caption1Medium import com.anytypeio.anytype.core_ui.views.Title1 import com.anytypeio.anytype.core_ui.views.UXBody -import com.anytypeio.anytype.core_ui.widgets.DragStates import com.anytypeio.anytype.presentation.sets.ViewEditAction import com.anytypeio.anytype.presentation.sets.ViewerEditWidgetUi -import com.anytypeio.anytype.presentation.sets.isVisible -import kotlin.math.roundToInt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class, + ExperimentalMaterial3Api::class +) @Composable fun ViewerEditWidget( state: ViewerEditWidgetUi, action: (ViewEditAction) -> Unit, - scope: CoroutineScope ) { + val bottomSheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) + val focusRequester = remember { FocusRequester() } val focusManager = LocalFocusManager.current + val keyboardController = LocalSoftwareKeyboardController.current - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.BottomStart, - ) { - - val currentState by rememberUpdatedState(state) - val swipeableState = rememberSwipeableState(DragStates.VISIBLE) - val keyboardController = LocalSoftwareKeyboardController.current - - AnimatedVisibility( - visible = currentState.isVisible(), - enter = fadeIn(), - exit = fadeOut(tween(100)) - ) { - Box( - Modifier - .fillMaxSize() - .background(Color.Black.copy(alpha = 0.4f)) - .noRippleClickable { action(ViewEditAction.Dismiss) } - ) - } - - if (swipeableState.isAnimationRunning && swipeableState.targetValue == DragStates.DISMISSED) { - DisposableEffect(Unit) { - onDispose { - keyboardController?.hide() - focusManager.clearFocus() - action(ViewEditAction.Dismiss) - } - } - } - - if (!currentState.isVisible()) { - DisposableEffect(Unit) { - onDispose { - scope.launch { swipeableState.snapTo(DragStates.VISIBLE) } - } - } - } - - val sizePx = - with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() } - - AnimatedVisibility( - visible = currentState.isVisible(), - enter = slideInVertically { it }, - exit = slideOutVertically { it }, + if (state is ViewerEditWidgetUi.Data) { + ModalBottomSheet( modifier = Modifier - .swipeable(state = swipeableState, - orientation = Orientation.Vertical, - anchors = mapOf( - 0f to DragStates.VISIBLE, sizePx to DragStates.DISMISSED - ), - thresholds = { _, _ -> FractionalThreshold(0.3f) }) - .offset { IntOffset(0, swipeableState.offset.value.roundToInt()) } - ) { - if (state is ViewerEditWidgetUi.Data) { + .windowInsetsPadding(WindowInsets.ime) + .padding(start = 8.dp, end = 8.dp, bottom = 30.dp) + .fillMaxWidth() + .wrapContentHeight(), + scrimColor = colorResource(id = R.color.modal_screen_outside_background), + containerColor = colorResource(id = R.color.background_secondary), + shape = RoundedCornerShape(16.dp), + onDismissRequest = { action(ViewEditAction.Dismiss) }, + sheetState = bottomSheetState, + dragHandle = { DragHandle() }, + content = { ViewerEditWidgetContent(state, focusRequester, keyboardController) { focusManager.clearFocus() keyboardController?.hide() action.invoke(it) } } - } + ) } } @@ -179,11 +122,7 @@ fun ViewerEditWidgetContent( modifier = Modifier .fillMaxWidth() .wrapContentHeight() - .padding(start = 8.dp, end = 8.dp, bottom = 15.dp) - .background( - color = colorResource(id = R.color.background_secondary), - shape = RoundedCornerShape(size = 16.dp) - ), + //.padding(start = 8.dp, end = 8.dp, bottom = 15.dp) ) { Column( modifier = Modifier @@ -191,12 +130,6 @@ fun ViewerEditWidgetContent( .wrapContentHeight() .padding(bottom = 16.dp, start = 20.dp, end = 20.dp) ) { - Box(modifier = Modifier - .fillMaxWidth() - .padding(top = 6.dp, bottom = 6.dp) - ) { - Dragger(modifier = Modifier.align(Alignment.Center)) - } Box( modifier = Modifier .fillMaxWidth() @@ -477,5 +410,5 @@ fun PreviewViewerEditWidget() { id = "1", isActive = false ) - ViewerEditWidget(state = state, action = {}, scope = CoroutineScope(Dispatchers.Main)) + ViewerEditWidget(state = state, action = {}) } \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerLayoutCoverWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerLayoutCoverWidget.kt index fe445db567..ba355f0205 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerLayoutCoverWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerLayoutCoverWidget.kt @@ -1,46 +1,33 @@ package com.anytypeio.anytype.core_ui.widgets.dv import androidx.annotation.DrawableRes -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.tween -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.FractionalThreshold import androidx.compose.material.Text -import androidx.compose.material.rememberSwipeableState -import androidx.compose.material.swipeable +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect 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.LocalConfiguration -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.anytypeio.anytype.core_models.DVViewerType import com.anytypeio.anytype.core_models.Relations @@ -49,93 +36,80 @@ import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.foundation.Divider import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable import com.anytypeio.anytype.core_ui.views.BodyRegular -import com.anytypeio.anytype.core_ui.widgets.DragStates +import com.anytypeio.anytype.core_ui.views.Title1 import com.anytypeio.anytype.presentation.sets.ViewerLayoutWidgetUi +import com.anytypeio.anytype.presentation.sets.ViewerLayoutWidgetUi.Action.Dismiss import com.anytypeio.anytype.presentation.sets.ViewerLayoutWidgetUi.State.ImagePreview -import kotlin.math.roundToInt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Composable fun ViewerLayoutCoverWidget( uiState: ViewerLayoutWidgetUi, action: (ViewerLayoutWidgetUi.Action) -> Unit, - scope: CoroutineScope ) { - val swipeableState = rememberSwipeableState(DragStates.VISIBLE) - val sizePx = with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() } - - val lazyListState = rememberLazyListState() - - if (swipeableState.isAnimationRunning && swipeableState.targetValue == DragStates.DISMISSED) { - DisposableEffect(Unit) { - onDispose { - action(ViewerLayoutWidgetUi.Action.DismissCoverMenu) - } - } - } + val bottomSheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) - if (!uiState.showCoverMenu) { - DisposableEffect(Unit) { - onDispose { - scope.launch { swipeableState.snapTo(DragStates.VISIBLE) } + if (uiState.showCoverMenu) { + ModalBottomSheet( + modifier = Modifier + .windowInsetsPadding(WindowInsets.ime) + .fillMaxWidth() + .wrapContentHeight(), + scrimColor = colorResource(id = R.color.modal_screen_outside_background), + containerColor = colorResource(id = R.color.background_secondary), + shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), + onDismissRequest = { action(Dismiss) }, + sheetState = bottomSheetState, + dragHandle = { DragHandle() }, + content = { + Content( + uiState = uiState, + action = action + ) } - } + ) } +} - AnimatedVisibility( - visible = uiState.showCoverMenu, - enter = slideInVertically { it }, - exit = slideOutVertically(tween(200)) { it }, +@Composable +private fun ColumnScope.Content( + uiState: ViewerLayoutWidgetUi, + action: (ViewerLayoutWidgetUi.Action) -> Unit, +) { + val lazyListState = rememberLazyListState() + Spacer(modifier = Modifier.height(12.dp)) + Text( + modifier = Modifier.align(Alignment.CenterHorizontally), + text = stringResource(R.string.view_layout_cover_widget_title), + style = Title1, + color = colorResource(R.color.text_primary) + ) + LazyColumn( + state = lazyListState, modifier = Modifier - .swipeable(state = swipeableState, - orientation = Orientation.Vertical, - anchors = mapOf( - 0f to DragStates.VISIBLE, sizePx to DragStates.DISMISSED - ), - thresholds = { _, _ -> FractionalThreshold(0.3f) }) - .offset { IntOffset(0, swipeableState.offset.value.roundToInt()) } + .fillMaxWidth() + .padding(top = 12.dp, bottom = 250.dp) ) { - val shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) - Box( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .background( - color = colorResource(id = R.color.background_secondary), - shape = shape - ) - .clip(shape) - ) { - WidgetHeader(title = stringResource(R.string.view_layout_cover_widget_title)) - LazyColumn( - state = lazyListState, - modifier = Modifier - .fillMaxWidth() - .padding(top = 64.dp, bottom = 250.dp) + items( + count = uiState.imagePreviewItems.size, + key = { index -> uiState.imagePreviewItems[index].relationKey.key } + ) { idx -> + val item = uiState.imagePreviewItems[idx] + val title = item.getTitle() + val iconDrawableRes = when (item) { + is ImagePreview.None -> null + is ImagePreview.PageCover -> null + is ImagePreview.Custom -> R.drawable.ic_relation_attachment_24 + } + CoverItem( + text = title, + checked = item.isChecked, + iconDrawableRes = iconDrawableRes ) { - items( - count = uiState.imagePreviewItems.size, - key = { index -> uiState.imagePreviewItems[index].relationKey.key } - ) { idx -> - val item = uiState.imagePreviewItems[idx] - val title = item.getTitle() - val iconDrawableRes = when (item) { - is ImagePreview.None -> null - is ImagePreview.PageCover -> null - is ImagePreview.Custom -> R.drawable.ic_relation_attachment_24 - } - CoverItem( - text = title, - checked = item.isChecked, - iconDrawableRes = iconDrawableRes - ) { - action(ViewerLayoutWidgetUi.Action.ImagePreviewUpdate(item)) - } - } + action(ViewerLayoutWidgetUi.Action.ImagePreviewUpdate(item)) } } } @@ -217,9 +191,6 @@ fun PreviewLayoutCoverWidget() { ) ) ), - action = {}, - scope = CoroutineScope( - Dispatchers.Main - ) + action = {} ) } \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerLayoutListMenu.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerLayoutListMenu.kt index 26e04460aa..4bf9ccf5d4 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerLayoutListMenu.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerLayoutListMenu.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.anytypeio.anytype.core_ui.R +import com.anytypeio.anytype.core_ui.common.DefaultPreviews import com.anytypeio.anytype.core_ui.foundation.Divider import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable import com.anytypeio.anytype.core_ui.views.BodyCallout @@ -36,7 +37,7 @@ fun ViewerLayoutListMenu( if (show) { Column( modifier = Modifier - .offset(x = offsetX - 220.dp, y = (-52).dp) + .offset(x = offsetX - 220.dp, y = 246.dp) .width(220.dp) .wrapContentHeight() .shadow( @@ -88,4 +89,14 @@ fun ViewerLayoutListMenu( ) } } +} + +@Composable +@DefaultPreviews +fun ViewerLayoutListMenuPreview() { + ViewerLayoutListMenu( + show = true, + action = {}, + coordinates = Rect(0f, 0f, 0f, 0f) + ) } \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerLayoutWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerLayoutWidget.kt index f08385c771..497b8b571e 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerLayoutWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewerLayoutWidget.kt @@ -1,230 +1,201 @@ package com.anytypeio.anytype.core_ui.widgets.dv -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.FractionalThreshold import androidx.compose.material.Switch import androidx.compose.material.SwitchDefaults import androidx.compose.material.Text -import androidx.compose.material.rememberSwipeableState -import androidx.compose.material.swipeable +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.boundsInRoot import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.anytypeio.anytype.core_models.DVViewerType import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.foundation.Divider -import com.anytypeio.anytype.core_ui.foundation.noRippleClickable import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable import com.anytypeio.anytype.core_ui.views.Caption2Medium import com.anytypeio.anytype.core_ui.views.Caption2Regular +import com.anytypeio.anytype.core_ui.views.Title1 import com.anytypeio.anytype.core_ui.views.UXBody -import com.anytypeio.anytype.core_ui.widgets.DragStates import com.anytypeio.anytype.presentation.sets.ViewerLayoutWidgetUi +import com.anytypeio.anytype.presentation.sets.ViewerLayoutWidgetUi.Action.CardSizeMenu import com.anytypeio.anytype.presentation.sets.ViewerLayoutWidgetUi.Action.Dismiss import com.anytypeio.anytype.presentation.sets.ViewerLayoutWidgetUi.Action.FitImage import com.anytypeio.anytype.presentation.sets.ViewerLayoutWidgetUi.Action.Icon -import kotlin.math.roundToInt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch +import com.anytypeio.anytype.presentation.sets.ViewerLayoutWidgetUi.State.CardSize -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Composable fun ViewerLayoutWidget( uiState: ViewerLayoutWidgetUi, action: (ViewerLayoutWidgetUi.Action) -> Unit, - scope: CoroutineScope ) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.BottomStart, - ) { - val currentState by rememberUpdatedState(uiState) - val swipeableState = rememberSwipeableState(DragStates.VISIBLE) - - AnimatedVisibility( - visible = currentState.showWidget, - enter = fadeIn(), - exit = fadeOut( - tween(200) - ) - ) { - Box( - Modifier - .fillMaxSize() - .background(Color.Black.copy(alpha = 0.4f)) - .noRippleClickable { action(Dismiss) } - ) - } - - if (swipeableState.isAnimationRunning && swipeableState.targetValue == DragStates.DISMISSED) { - DisposableEffect(Unit) { - onDispose { - action(Dismiss) - } - } - } - - if (!currentState.showWidget) { - DisposableEffect(Unit) { - onDispose { - scope.launch { swipeableState.snapTo(DragStates.VISIBLE) } - } - } - } - - val sizePx = with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() } - - var currentCoordinates: androidx.compose.ui.geometry.Rect by remember { - mutableStateOf(androidx.compose.ui.geometry.Rect.Zero) - } - - var currentCoverItem by remember { - mutableStateOf(uiState.getActiveImagePreviewItem()) - } + val bottomSheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) - LaunchedEffect(key1 = uiState) { - currentCoverItem = uiState.getActiveImagePreviewItem() - } + var currentCoordinates: Rect by remember { + mutableStateOf(Rect.Zero) + } - AnimatedVisibility( - visible = currentState.showWidget, - enter = slideInVertically { it }, - exit = slideOutVertically(tween(200)) { it }, + if (uiState.showWidget) { + ModalBottomSheet( modifier = Modifier - .swipeable(state = swipeableState, - orientation = Orientation.Vertical, - anchors = mapOf( - 0f to DragStates.VISIBLE, sizePx to DragStates.DISMISSED - ), - thresholds = { _, _ -> FractionalThreshold(0.3f) }) - .offset { IntOffset(0, swipeableState.offset.value.roundToInt()) } - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .background( - color = colorResource(id = R.color.background_secondary), - shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) - ), - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(bottom = 20.dp) - ) { - WidgetHeader(title = stringResource(R.string.view_layout_widget_title)) - Spacer(modifier = Modifier.height(12.dp)) - LayoutIcons(uiState = currentState, action = action) - Spacer(modifier = Modifier.height(8.dp)) - LayoutSwitcherItem( - text = stringResource(id = R.string.icon), - checked = currentState.withIcon.toggled, - onCheckedChanged = { action(Icon(it)) } - ) - val isGallery = currentState.layoutType == DVViewerType.GALLERY - Divider(visible = isGallery) - ColumnItem( - modifier = Modifier - .padding(start = 20.dp, end = 20.dp) - .alpha(if (isGallery) 1f else 0f), - title = stringResource(id = R.string.card_size), - value = when (currentState.cardSize) { - ViewerLayoutWidgetUi.State.CardSize.Large -> stringResource(id = R.string.large) - ViewerLayoutWidgetUi.State.CardSize.Small -> stringResource(id = R.string.small) - }, - onClick = { - action(ViewerLayoutWidgetUi.Action.CardSizeMenu) - }, - arrow = painterResource(id = R.drawable.ic_list_arrow_18), - imageModifier = Modifier - .onGloballyPositioned { coordinates -> - if (coordinates.isAttached) { - with(coordinates.boundsInRoot()) { - currentCoordinates = this - } - } else { - currentCoordinates = androidx.compose.ui.geometry.Rect.Zero - } - } - ) - Divider(visible = isGallery) - ColumnItem( + .windowInsetsPadding(WindowInsets.ime) + .fillMaxWidth() + .wrapContentHeight(), + scrimColor = colorResource(id = R.color.modal_screen_outside_background), + containerColor = colorResource(id = R.color.background_secondary), + shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), + onDismissRequest = { action(Dismiss) }, + sheetState = bottomSheetState, + dragHandle = { DragHandle() }, + content = { + var currentCoordinates: Rect by remember { + mutableStateOf(Rect.Zero) + } + Box(modifier = Modifier.fillMaxWidth()) { + ViewerLayoutContent( modifier = Modifier - .padding(start = 20.dp, end = 20.dp) - .alpha(if (isGallery) 1f else 0f), - title = stringResource(id = R.string.cover), - value = currentCoverItem.getTitle(), - onClick = { - action(ViewerLayoutWidgetUi.Action.CoverMenu) - }, - arrow = painterResource(id = R.drawable.ic_arrow_disclosure_18) + .fillMaxWidth() + .wrapContentHeight() + .padding(bottom = 20.dp), + currentState = uiState, + action = action, + updateCurrentCoordinates = { currentCoordinates = it } ) - Divider(visible = isGallery) - LayoutSwitcherItem( - modifier = Modifier.alpha(if (isGallery) 1f else 0f), - text = stringResource(id = R.string.fit_image), - checked = currentState.fitImage.toggled, - onCheckedChanged = { action(FitImage(it)) } + ViewerLayoutListMenu( + show = uiState.showCardSize, + action = action, + coordinates = currentCoordinates ) } } - } - ViewerLayoutListMenu( - show = currentState.showCardSize, - action = action, - coordinates = currentCoordinates ) ViewerLayoutCoverWidget( uiState = uiState, action = action, - scope = scope + ) + } +} + +@Composable +private fun ViewerLayoutContent( + modifier: Modifier, + currentState: ViewerLayoutWidgetUi, + action: (ViewerLayoutWidgetUi.Action) -> Unit, + updateCurrentCoordinates: (Rect) -> Unit = {} +) { + var currentCoverItem by remember { + mutableStateOf(currentState.getActiveImagePreviewItem()) + } + + LaunchedEffect(key1 = currentState) { + currentCoverItem = currentState.getActiveImagePreviewItem() + } + + Column( + modifier = modifier + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + contentAlignment = Alignment.Center + ) { + Text( + modifier = Modifier, + text = stringResource(R.string.view_layout_widget_title), + style = Title1, + color = colorResource(R.color.text_primary) + ) + } + Spacer(modifier = Modifier.height(12.dp)) + LayoutIcons(uiState = currentState, action = action) + Spacer(modifier = Modifier.height(8.dp)) + LayoutSwitcherItem( + text = stringResource(id = R.string.icon), + checked = currentState.withIcon.toggled, + onCheckedChanged = { action(Icon(it)) } + ) + val isGallery = currentState.layoutType == DVViewerType.GALLERY + Divider(visible = isGallery) + ColumnItem( + modifier = Modifier + .padding(start = 20.dp, end = 20.dp) + .alpha(if (isGallery) 1f else 0f), + title = stringResource(id = R.string.card_size), + value = when (currentState.cardSize) { + CardSize.Large -> stringResource(id = R.string.large) + CardSize.Small -> stringResource(id = R.string.small) + }, + onClick = { + action(CardSizeMenu) + }, + arrow = painterResource(id = R.drawable.ic_list_arrow_18), + imageModifier = Modifier + .onGloballyPositioned { coordinates -> + if (coordinates.isAttached) { + with(coordinates.boundsInRoot()) { + updateCurrentCoordinates(this) + } + } else { + updateCurrentCoordinates(Rect.Zero) + } + } + ) + Divider(visible = isGallery) + ColumnItem( + modifier = Modifier + .padding(start = 20.dp, end = 20.dp) + .alpha(if (isGallery) 1f else 0f), + title = stringResource(id = R.string.cover), + value = currentCoverItem.getTitle(), + onClick = { + action(ViewerLayoutWidgetUi.Action.CoverMenu) + }, + arrow = painterResource(id = R.drawable.ic_arrow_disclosure_18) + ) + Divider(visible = isGallery) + LayoutSwitcherItem( + modifier = Modifier.alpha(if (isGallery) 1f else 0f), + text = stringResource(id = R.string.fit_image), + checked = currentState.fitImage.toggled, + onCheckedChanged = { action(FitImage(it)) } ) } } @@ -321,7 +292,7 @@ fun LayoutIcons(uiState: ViewerLayoutWidgetUi, action: (ViewerLayoutWidgetUi.Act layoutType = DVViewerType.GRID, imageResource = R.drawable.ic_layout_grid, imageResourceSelected = R.drawable.ic_layout_grid_selected, - contentDescription = "Grid", + contentDescription = stringResource(id = R.string.view_grid), click = { action(ViewerLayoutWidgetUi.Action.Type(DVViewerType.GRID)) } ) LayoutIcon( @@ -330,7 +301,7 @@ fun LayoutIcons(uiState: ViewerLayoutWidgetUi, action: (ViewerLayoutWidgetUi.Act layoutType = DVViewerType.GALLERY, imageResourceSelected = R.drawable.ic_layout_gallery_selected, imageResource = R.drawable.ic_layout_gallery, - contentDescription = "Gallery", + contentDescription = stringResource(id = R.string.view_gallery), click = { action(ViewerLayoutWidgetUi.Action.Type(DVViewerType.GALLERY)) } ) LayoutIcon( @@ -339,7 +310,7 @@ fun LayoutIcons(uiState: ViewerLayoutWidgetUi, action: (ViewerLayoutWidgetUi.Act layoutType = DVViewerType.LIST, imageResourceSelected = R.drawable.ic_layout_list_selected, imageResource = R.drawable.ic_layout_list, - contentDescription = "List", + contentDescription = stringResource(id = R.string.view_list), click = { action(ViewerLayoutWidgetUi.Action.Type(DVViewerType.LIST)) } ) LayoutIcon( @@ -348,7 +319,7 @@ fun LayoutIcons(uiState: ViewerLayoutWidgetUi, action: (ViewerLayoutWidgetUi.Act layoutType = DVViewerType.BOARD, imageResourceSelected = R.drawable.ic_layout_kanban_selected, imageResource = R.drawable.ic_layout_kanban, - contentDescription = "Kanban", + contentDescription = stringResource(id = R.string.view_kanban), click = { action(ViewerLayoutWidgetUi.Action.Type(DVViewerType.BOARD)) } ) LayoutIcon( @@ -421,7 +392,7 @@ fun PreviewLayoutScreen() { ViewerLayoutWidget( uiState = ViewerLayoutWidgetUi( showWidget = true, - layoutType = DVViewerType.GRID, + layoutType = DVViewerType.GALLERY, withIcon = ViewerLayoutWidgetUi.State.Toggle.WithIcon( toggled = true ), @@ -429,15 +400,12 @@ fun PreviewLayoutScreen() { toggled = false ), cardSize = ViewerLayoutWidgetUi.State.CardSize.Small, - showCardSize = false, + showCardSize = true, viewer = "", showCoverMenu = false, imagePreviewItems = emptyList() ), - action = {}, - scope = CoroutineScope( - Dispatchers.Main - ) + action = {} ) } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewersWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewersWidget.kt index 05f63ca37f..3aac7b107d 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewersWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ViewersWidget.kt @@ -1,401 +1,384 @@ package com.anytypeio.anytype.core_ui.widgets.dv import android.view.HapticFeedbackConstants -import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.FractionalThreshold import androidx.compose.material.Text -import androidx.compose.material.rememberSwipeableState -import androidx.compose.material.swipeable +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import androidx.constraintlayout.compose.Visibility import com.anytypeio.anytype.core_ui.R +import com.anytypeio.anytype.core_ui.extensions.swapList import com.anytypeio.anytype.core_ui.foundation.Divider import com.anytypeio.anytype.core_ui.foundation.Dragger -import com.anytypeio.anytype.core_ui.foundation.noRippleClickable import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable import com.anytypeio.anytype.core_ui.views.BodyRegular import com.anytypeio.anytype.core_ui.views.Caption2Regular import com.anytypeio.anytype.core_ui.views.HeadlineSubheading import com.anytypeio.anytype.core_ui.views.Title1 -import com.anytypeio.anytype.core_ui.widgets.DragStates import com.anytypeio.anytype.presentation.sets.ViewersWidgetUi import com.anytypeio.anytype.presentation.sets.ViewersWidgetUi.Action.Delete import com.anytypeio.anytype.presentation.sets.ViewersWidgetUi.Action.Dismiss import com.anytypeio.anytype.presentation.sets.ViewersWidgetUi.Action.DoneMode import com.anytypeio.anytype.presentation.sets.ViewersWidgetUi.Action.Edit import com.anytypeio.anytype.presentation.sets.ViewersWidgetUi.Action.EditMode -import kotlin.math.roundToInt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch +import com.anytypeio.anytype.presentation.sets.viewer.ViewerView import org.burnoutcrew.reorderable.ReorderableItem +import org.burnoutcrew.reorderable.ReorderableLazyListState import org.burnoutcrew.reorderable.detectReorder import org.burnoutcrew.reorderable.rememberReorderableLazyListState import org.burnoutcrew.reorderable.reorderable -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Composable fun ViewersWidget( state: ViewersWidgetUi, action: (ViewersWidgetUi.Action) -> Unit, - scope: CoroutineScope ) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.BottomStart, - ) { - - val currentState by rememberUpdatedState(state) - val swipeableState = rememberSwipeableState(DragStates.VISIBLE) - - AnimatedVisibility( - visible = currentState.showWidget, - enter = fadeIn(), - exit = fadeOut(tween(100)) - ) { - Box( - Modifier - .fillMaxSize() - .background(Color.Black.copy(alpha = 0.4f)) - .noRippleClickable { action(Dismiss) } - ) - } - - if (swipeableState.isAnimationRunning && swipeableState.targetValue == DragStates.DISMISSED) { - DisposableEffect(Unit) { - onDispose { - action(Dismiss) - } - } - } + val bottomSheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) - if (!currentState.showWidget) { - DisposableEffect(Unit) { - onDispose { - scope.launch { swipeableState.snapTo(DragStates.VISIBLE) } - } + if (state.showWidget) { + ModalBottomSheet( + modifier = Modifier + .windowInsetsPadding(WindowInsets.ime) + .padding(start = 8.dp, end = 8.dp, bottom = 30.dp) + .fillMaxWidth() + .wrapContentHeight(), + scrimColor = colorResource(id = R.color.modal_screen_outside_background), + containerColor = colorResource(id = R.color.background_secondary), + shape = RoundedCornerShape(16.dp), + onDismissRequest = { action(Dismiss) }, + sheetState = bottomSheetState, + dragHandle = { DragHandle() }, + content = { + ViewersWidgetContent( + modifier = Modifier.padding(bottom = 168.dp), + state = state, + action = action + ) } - } - - val sizePx = - with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() } + ) + } +} - AnimatedVisibility( - visible = currentState.showWidget, - enter = slideInVertically { it }, - exit = slideOutVertically { it }, - modifier = Modifier - .swipeable( - state = swipeableState, - orientation = Orientation.Vertical, - anchors = mapOf( - 0f to DragStates.VISIBLE, sizePx to DragStates.DISMISSED - ), - thresholds = { _, _ -> FractionalThreshold(0.3f) }) - .offset { IntOffset(0, swipeableState.offset.value.roundToInt()) } - ) { - ViewersWidgetContent(state, action) - } +@Composable +fun DragHandle() { + Column { + Spacer(modifier = Modifier.height(6.dp)) + Dragger() + Spacer(modifier = Modifier.height(6.dp)) } } @Composable private fun ViewersWidgetContent( + modifier: Modifier, state: ViewersWidgetUi, action: (ViewersWidgetUi.Action) -> Unit ) { - val currentState by rememberUpdatedState(state) - val views = remember { mutableStateOf(currentState.items) } - views.value = currentState.items + val views = remember { mutableStateListOf() } + views.swapList(state.items) - val isEditing = remember { mutableStateOf(currentState.isEditing && !state.isReadOnly) } - isEditing.value = currentState.isEditing && !state.isReadOnly - - Box( - modifier = Modifier + val lazyListState = rememberReorderableLazyListState( + onMove = { from, to -> + val newList = views.toMutableList().apply { + add(to.index, removeAt(from.index)) + } + views.swapList(newList) + }, + onDragEnd = { from, to -> + action( + ViewersWidgetUi.Action.OnMove( + currentViews = views, + from = from, + to = to + ) + ) + } + ) + + Column( + modifier = modifier .fillMaxWidth() .wrapContentHeight() - .padding(start = 8.dp, end = 8.dp, bottom = 15.dp, top = 24.dp) - .background( - color = colorResource(id = R.color.background_secondary), - shape = RoundedCornerShape(size = 16.dp) - ), ) { - Column( + Header( + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + isEditingMode = state.isEditing, + isReadOnlyState = state.isReadOnly, + action = action + ) + + LazyColumn( + state = lazyListState.listState, modifier = Modifier + .reorderable(lazyListState) .fillMaxWidth() .wrapContentHeight() - .padding(bottom = 16.dp) ) { - Box(modifier = Modifier - .fillMaxWidth() - .padding(top = 6.dp, bottom = 6.dp) - ) { - Dragger(modifier = Modifier.align(Alignment.Center)) - } - Box( - modifier = Modifier - .fillMaxWidth() - .height(48.dp) - ) { - if (!state.isReadOnly) { - Box( - modifier = Modifier - .align(Alignment.CenterStart), - ) { - if (currentState.isEditing) { - ActionText( - text = stringResource(id = R.string.done), - click = { action(DoneMode) } - ) - } else { - ActionText( - text = stringResource(id = R.string.edit), - click = { action(EditMode) } - ) - } + items( + count = views.size, + key = { index -> views[index].id }, + ) { index -> + + ReorderableItem( + modifier = Modifier.animateItem(), + reorderableState = lazyListState, + key = views[index].id + ) { isDragging -> + val currentItem = LocalView.current + if (isDragging) { + currentItem.isHapticFeedbackEnabled = true + currentItem.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) } - } - Box(modifier = Modifier.align(Alignment.Center)) { - Text( - text = stringResource(R.string.views), - style = Title1, - color = colorResource(R.color.text_primary) + Item( + modifier = Modifier, + lazyListState = lazyListState, + isDragging = isDragging, + isEditing = state.isEditing, + action = action, + view = views[index] ) + } - if (!state.isReadOnly) { - Box( - modifier = Modifier - .align(Alignment.CenterEnd) - .noRippleThrottledClickable { - action.invoke(ViewersWidgetUi.Action.Plus) - } - ) { - Image( - modifier = Modifier.padding( - start = 16.dp, - top = 12.dp, - bottom = 12.dp, - end = 16.dp - ), - painter = painterResource(id = R.drawable.ic_default_plus), - contentDescription = null - ) - } + if (index != views.size - 1) { + Divider() } } + } + } +} - val lazyListState = rememberReorderableLazyListState( - onMove = { from, to -> - views.value = views.value.toMutableList().apply { - add(to.index, removeAt(from.index)) - } - }, - onDragEnd = { from, to -> - action( - ViewersWidgetUi.Action.OnMove( - currentViews = views.value, - from = from, - to = to - ) - ) - } +@Composable +private fun Item( + modifier: Modifier, + lazyListState: ReorderableLazyListState, + isDragging: Boolean, + isEditing: Boolean, + action: (ViewersWidgetUi.Action) -> Unit, + view: ViewerView +) { + val alpha = animateFloatAsState(if (isDragging) 0.8f else 1.0f, label = "") + ConstraintLayout( + modifier = modifier + .height(52.dp) + .fillMaxWidth() + .padding(start = 20.dp, end = 20.dp) + .animateContentSize( + animationSpec = spring( + stiffness = Spring.StiffnessLow + ) ) + .alpha(alpha.value) + ) { + val (delete, text, edit, dnd, unsupported) = createRefs() + Image( + modifier = Modifier + .noRippleThrottledClickable { + action.invoke(Delete(view.id)) + } + .constrainAs(delete) { + start.linkTo(parent.start) + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + visibility = + if (isEditing && !view.isActive) Visibility.Visible else Visibility.Gone + }, + painter = painterResource(id = R.drawable.ic_relation_delete), + contentDescription = "Delete view" + ) + Image( + modifier = Modifier + .detectReorder(lazyListState) + .constrainAs(dnd) { + end.linkTo(parent.end) + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + visibility = + if (isEditing) Visibility.Visible else Visibility.Gone - LazyColumn( - state = lazyListState.listState, - modifier = Modifier - .reorderable(lazyListState) - .fillMaxWidth() - .wrapContentHeight() - ) { - itemsIndexed( - items = views.value, - key = { _, item -> item.id }) { index, view -> - ReorderableItem( - reorderableState = lazyListState, - key = view.id - ) { isDragging -> - val currentItem = LocalView.current - if (isDragging) { - currentItem.isHapticFeedbackEnabled = true - currentItem.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - } - val alpha = - animateFloatAsState(if (isDragging) 0.8f else 1.0f, label = "") - ConstraintLayout( - modifier = Modifier - .height(52.dp) - .fillMaxWidth() - .padding(start = 20.dp, end = 20.dp) - .animateContentSize( - animationSpec = spring( - stiffness = Spring.StiffnessLow - ) - ) - .alpha(alpha.value) - ) { - val (delete, text, edit, dnd, unsupported) = createRefs() - Image( - modifier = Modifier - .noRippleThrottledClickable { - action.invoke(Delete(view.id)) - } - .constrainAs(delete) { - start.linkTo(parent.start) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - visibility = - if (isEditing.value && !view.isActive) Visibility.Visible else Visibility.Gone - }, - painter = painterResource(id = R.drawable.ic_relation_delete), - contentDescription = "Delete view" - ) - Image( - modifier = Modifier - .detectReorder(lazyListState) - .constrainAs(dnd) { - end.linkTo(parent.end) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - visibility = - if (isEditing.value) Visibility.Visible else Visibility.Gone - - }, - painter = painterResource(id = R.drawable.ic_dnd), - contentDescription = "Dnd view" - ) - Image( - modifier = Modifier - .noRippleThrottledClickable { - action.invoke(Edit(id = view.id)) - } - .constrainAs(edit) { - end.linkTo(dnd.start, margin = 16.dp) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - visibility = - if (isEditing.value) Visibility.Visible else Visibility.Gone - }, - painter = painterResource(id = R.drawable.ic_edit_24), - contentDescription = "Edit view" - ) - Text( - modifier = Modifier - .constrainAs(unsupported) { - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - end.linkTo(edit.start) - visibility = - if (!isEditing.value && view.isUnsupported) Visibility.Visible else Visibility.Gone - }, - text = stringResource(id = R.string.unsupported), - color = colorResource(id = R.color.text_secondary), - style = Caption2Regular, - textAlign = TextAlign.Left - ) - Text( - modifier = Modifier - .noRippleThrottledClickable { - if (!isEditing.value) { + }, + painter = painterResource(id = R.drawable.ic_dnd), + contentDescription = "Dnd view" + ) + Image( + modifier = Modifier + .noRippleThrottledClickable { + action.invoke(Edit(id = view.id)) + } + .constrainAs(edit) { + end.linkTo(dnd.start, margin = 16.dp) + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + visibility = + if (isEditing) Visibility.Visible else Visibility.Gone + }, + painter = painterResource(id = R.drawable.ic_edit_24), + contentDescription = "Edit view" + ) + Text( + modifier = Modifier + .constrainAs(unsupported) { + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + end.linkTo(edit.start) + visibility = + if (!isEditing && view.isUnsupported) Visibility.Visible else Visibility.Gone + }, + text = stringResource(id = R.string.unsupported), + color = colorResource(id = R.color.text_secondary), + style = Caption2Regular, + textAlign = TextAlign.Left + ) + Text( + modifier = Modifier + .noRippleThrottledClickable { + if (!isEditing) { - action.invoke( - ViewersWidgetUi.Action.SetActive( - id = view.id, - type = view.type - ) - ) - } - } - .constrainAs(text) { - start.linkTo( - delete.end, - margin = 12.dp, - goneMargin = 0.dp - ) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - end.linkTo( - unsupported.start, - margin = 8.dp, - goneMargin = 38.dp - ) - width = Dimension.fillToConstraints - }, - text = view.name.ifBlank { stringResource(id = R.string.untitled) }, - color = colorResource(id = if (view.isActive) R.color.text_primary else R.color.glyph_active), - style = HeadlineSubheading, - textAlign = TextAlign.Left, - maxLines = 1, - overflow = TextOverflow.Ellipsis + action.invoke( + ViewersWidgetUi.Action.SetActive( + id = view.id, type = view.type ) - } - } - if (index != views.value.size - 1) { - Divider() + ) } } - } + .constrainAs(text) { + start.linkTo( + delete.end, margin = 12.dp, goneMargin = 0.dp + ) + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + end.linkTo( + unsupported.start, margin = 8.dp, goneMargin = 38.dp + ) + width = Dimension.fillToConstraints + }, + text = view.name.ifBlank { stringResource(id = R.string.untitled) }, + color = colorResource(id = if (view.isActive) R.color.text_primary else R.color.glyph_active), + style = HeadlineSubheading, + textAlign = TextAlign.Left, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} + +@Composable +private fun Header( + modifier: Modifier, + isReadOnlyState: Boolean, + isEditingMode: Boolean, + action: (ViewersWidgetUi.Action) -> Unit +) { + Box(modifier = modifier) { + if (!isReadOnlyState) { + ActionButtons( + modifier = Modifier.align(Alignment.CenterStart), + isEditingMode = isEditingMode, + action = action + ) + } + Text( + modifier = Modifier.align(Alignment.Center), + text = stringResource(R.string.views), + style = Title1, + color = colorResource(R.color.text_primary) + ) + if (!isReadOnlyState) { + PlusButton( + modifier = Modifier + .align(Alignment.CenterEnd) + .noRippleThrottledClickable { + action.invoke(ViewersWidgetUi.Action.Plus) + } + ) } } } @Composable -private fun ActionText(text: String, click: () -> Unit) { +private fun ActionButtons( + modifier: Modifier, + isEditingMode: Boolean, + action: (ViewersWidgetUi.Action) -> Unit +) { + if (isEditingMode) { + ActionText( + modifier = modifier, + text = stringResource(id = R.string.done), + click = { action(DoneMode) } + ) + } else { + ActionText( + modifier = modifier, + text = stringResource(id = R.string.edit), + click = { action(EditMode) } + ) + } +} + +@Composable +private fun BoxScope.PlusButton( + modifier: Modifier +) { + Image( + modifier = modifier.padding( + start = 16.dp, + top = 12.dp, + bottom = 12.dp, + end = 16.dp + ), + painter = painterResource(id = R.drawable.ic_default_plus), + contentDescription = null + ) +} + +@Composable +private fun ActionText(modifier: Modifier, text: String, click: () -> Unit) { Text( - modifier = Modifier + modifier = modifier .padding( - start = 16.dp, - top = 12.dp, - bottom = 12.dp, - end = 16.dp + start = 16.dp, top = 12.dp, bottom = 12.dp, end = 16.dp ) .noRippleThrottledClickable { click() }, text = text, diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/WidgetHeader.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/WidgetHeader.kt deleted file mode 100644 index 7c0debfd0f..0000000000 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/WidgetHeader.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.anytypeio.anytype.core_ui.widgets.dv - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.unit.dp -import com.anytypeio.anytype.core_ui.R -import com.anytypeio.anytype.core_ui.foundation.Dragger -import com.anytypeio.anytype.core_ui.views.Title1 - -@Composable -fun WidgetHeader(title: String) { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Spacer(modifier = Modifier.height(6.dp)) - Dragger() - Spacer(modifier = Modifier.height(18.dp)) - Text( - text = title, - style = Title1, - color = colorResource(R.color.text_primary) - ) - } -} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ViewersWidgetUi.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ViewersWidgetUi.kt index af25bcb47e..cdea277e1a 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ViewersWidgetUi.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ViewersWidgetUi.kt @@ -43,6 +43,4 @@ data class ViewersWidgetUi( data object Plus : Action() } -} - - +} \ No newline at end of file