diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt index 5c1d6c904d..d88bdbfa68 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt @@ -24,8 +24,10 @@ import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.unit.dp @@ -756,11 +758,12 @@ open class EditorFragment : NavigationFragment(R.layout.f SpaceSyncStatusScreen( modifier = Modifier .fillMaxWidth() - .windowInsetsPadding(WindowInsets.ime) - .padding(bottom = 6.dp, start = 8.dp, end = 8.dp), + .wrapContentHeight() + .padding(bottom = 16.dp) + .windowInsetsPadding(WindowInsets.navigationBars), + modifierCard = Modifier.padding(start = 8.dp, end = 8.dp), uiState = vm.syncStatusWidget.collectAsStateWithLifecycle().value, onDismiss = vm::onSyncWidgetDismiss, - scope = lifecycleScope, onUpdateAppClick = vm::onUpdateAppClick ) } 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 df70b52efb..8059cd3006 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 @@ -24,8 +24,10 @@ import androidx.appcompat.widget.AppCompatEditText import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.unit.dp @@ -454,11 +456,12 @@ open class ObjectSetFragment : SpaceSyncStatusScreen( modifier = Modifier .fillMaxWidth() - .windowInsetsPadding(WindowInsets.ime) - .padding(bottom = 6.dp, start = 8.dp, end = 8.dp), + .wrapContentHeight() + .padding(bottom = 16.dp) + .windowInsetsPadding(WindowInsets.navigationBars), + modifierCard = Modifier.padding(start = 8.dp, end = 8.dp), uiState = vm.syncStatusWidget.collectAsStateWithLifecycle().value, onDismiss = vm::onSyncWidgetDismiss, - scope = lifecycleScope, onUpdateAppClick = vm::onUpdateAppClick ) } diff --git a/app/src/main/res/layout/fragment_object_set.xml b/app/src/main/res/layout/fragment_object_set.xml index db0e13f3ad..a82b374269 100644 --- a/app/src/main/res/layout/fragment_object_set.xml +++ b/app/src/main/res/layout/fragment_object_set.xml @@ -201,7 +201,6 @@ android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toBottomOf="parent" /> + app:layout_constraintStart_toStartOf="parent" /> \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/syncstatus/SpaceSyncStatusScreen.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/syncstatus/SpaceSyncStatusScreen.kt index a9a5241c3a..aa89902a9f 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/syncstatus/SpaceSyncStatusScreen.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/syncstatus/SpaceSyncStatusScreen.kt @@ -1,46 +1,35 @@ package com.anytypeio.anytype.core_ui.syncstatus -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.Image -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.Row 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.ime -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.CircularProgressIndicator 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.CardDefaults import androidx.compose.material3.ElevatedCard +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.graphics.painter.Painter -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.pluralStringResource 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 androidx.compose.ui.unit.dp import com.anytypeio.anytype.core_models.multiplayer.P2PStatus import com.anytypeio.anytype.core_models.multiplayer.P2PStatusUpdate @@ -50,82 +39,62 @@ import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncStatus import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncUpdate 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.views.BodyRegular import com.anytypeio.anytype.core_ui.views.Relations3 import com.anytypeio.anytype.core_ui.views.Title2 -import com.anytypeio.anytype.core_ui.widgets.DragStates import com.anytypeio.anytype.presentation.sync.SyncStatusWidgetState -import kotlin.math.roundToInt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Composable fun SpaceSyncStatusScreen( modifier: Modifier = Modifier, + modifierCard: Modifier = Modifier, uiState: SyncStatusWidgetState, onDismiss: () -> Unit, - scope: CoroutineScope, onUpdateAppClick: () -> Unit ) { - val isVisible = uiState is SyncStatusWidgetState.Success || uiState is SyncStatusWidgetState.Error - val swappableState = rememberSwipeableState(DragStates.VISIBLE) - if (swappableState.isAnimationRunning && swappableState.targetValue == DragStates.DISMISSED) { - DisposableEffect(Unit) { - onDispose { - onDismiss() - } - } - } + val bottomSheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) - if (!isVisible) { - DisposableEffect(Unit) { - onDispose { - scope.launch { swappableState.snapTo(DragStates.VISIBLE) } - } - } - } - - val sizePx = with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() } - - AnimatedVisibility( - visible = isVisible, - enter = slideInVertically { it }, - exit = slideOutVertically { it }, - modifier = Modifier - .swipeable( - state = swappableState, - orientation = Orientation.Vertical, - anchors = mapOf(0f to DragStates.VISIBLE, sizePx to DragStates.DISMISSED), - thresholds = { _, _ -> FractionalThreshold(0.3f) }) - .offset { IntOffset(0, swappableState.offset.value.roundToInt()) } - ) { - ElevatedCard( + if (uiState is SyncStatusWidgetState.Success || uiState is SyncStatusWidgetState.Error) { + ModalBottomSheet( modifier = modifier, - colors = CardDefaults.cardColors( - containerColor = colorResource(id = R.color.background_secondary) - ), - elevation = CardDefaults.elevatedCardElevation( - defaultElevation = 16.dp - ) - ) { - when (uiState) { - is SyncStatusWidgetState.Error -> ErrorState() - SyncStatusWidgetState.Hidden -> LoadingState() - is SyncStatusWidgetState.Success -> SuccessState( - spaceSyncUpdate = uiState.spaceSyncUpdate, - p2pStatus = uiState.p2PStatusUpdate, - onUpdateAppClick = onUpdateAppClick - ) + scrimColor = colorResource(id = R.color.transparent_black), + containerColor = colorResource(id = R.color.transparent_black), + shape = RoundedCornerShape(16.dp), + tonalElevation = 120.dp, + onDismissRequest = { onDismiss() }, + sheetState = bottomSheetState, + dragHandle = null, + content = { + ElevatedCard( + modifier = modifierCard, + colors = CardDefaults.cardColors( + containerColor = colorResource(id = R.color.background_secondary) + ), + elevation = CardDefaults.elevatedCardElevation( + defaultElevation = 120.dp + ) + ) { + when (uiState) { + is SyncStatusWidgetState.Error -> ErrorState() + SyncStatusWidgetState.Hidden -> LoadingState() + is SyncStatusWidgetState.Success -> SuccessState( + spaceSyncUpdate = uiState.spaceSyncUpdate, + p2pStatus = uiState.p2PStatusUpdate, + onUpdateAppClick = onUpdateAppClick + ) + } + } } - } + ) } } -enum class MenuVisibilityState(val fraction: Float) { Hidden(0f), Visible(1f) } - @Composable private fun ColumnScope.LoadingState() { CircularProgressIndicator( @@ -150,11 +119,14 @@ private fun ColumnScope.ErrorState() { } @Composable -private fun SuccessState( +private fun ColumnScope.SuccessState( spaceSyncUpdate: SpaceSyncUpdate, p2pStatus: P2PStatusUpdate, onUpdateAppClick: () -> Unit ) { + Spacer(modifier = Modifier.height(6.dp)) + Dragger(modifier = Modifier.align(Alignment.CenterHorizontally)) + Spacer(modifier = Modifier.height(6.dp)) if (spaceSyncUpdate is SpaceSyncUpdate.Update) { SpaceSyncStatusItem(spaceSyncUpdate, onUpdateAppClick) Divider() diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/StatusBadgeWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/StatusBadgeWidget.kt index e56b1db2a6..2883e9dfc7 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/StatusBadgeWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/StatusBadgeWidget.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.core_ui.widgets import android.content.Context import android.util.AttributeSet import androidx.appcompat.widget.AppCompatImageView +import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncAndP2PStatusState import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncError import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncStatus @@ -17,6 +18,9 @@ class StatusBadgeWidget @JvmOverloads constructor( attrs: AttributeSet? = null ) : AppCompatImageView(context, attrs) { + private var animatedDrawable: AnimatedVectorDrawableCompat? = + AnimatedVectorDrawableCompat.create(context, R.drawable.animated_pulsing_circle) + fun bind(status: SpaceSyncAndP2PStatusState?) { when (status) { is SpaceSyncAndP2PStatusState.Error -> { @@ -41,7 +45,9 @@ class StatusBadgeWidget @JvmOverloads constructor( } SpaceSyncStatus.SYNCING -> { visible() - setImageResource(R.drawable.ic_syncing) + setImageDrawable(animatedDrawable) + animatedDrawable?.start() + Unit } SpaceSyncStatus.ERROR -> { visible() 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 497b8b571e..e3f88cd89d 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 @@ -68,10 +68,6 @@ fun ViewerLayoutWidget( skipPartiallyExpanded = true ) - var currentCoordinates: Rect by remember { - mutableStateOf(Rect.Zero) - } - if (uiState.showWidget) { ModalBottomSheet( modifier = Modifier diff --git a/core-ui/src/main/res/animator/pulse_anim.xml b/core-ui/src/main/res/animator/pulse_anim.xml new file mode 100644 index 0000000000..b1d4155a6d --- /dev/null +++ b/core-ui/src/main/res/animator/pulse_anim.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/drawable/animated_pulsing_circle.xml b/core-ui/src/main/res/drawable/animated_pulsing_circle.xml new file mode 100644 index 0000000000..725ae35b2a --- /dev/null +++ b/core-ui/src/main/res/drawable/animated_pulsing_circle.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/drawable/ic_syncing.xml b/core-ui/src/main/res/drawable/ic_syncing.xml index 810fbf1af4..2f99d0a13c 100644 --- a/core-ui/src/main/res/drawable/ic_syncing.xml +++ b/core-ui/src/main/res/drawable/ic_syncing.xml @@ -1,19 +1,39 @@ - + + + + + + + + + + + + + + - - - + android:fillColor="@color/palette_system_green" /> + + \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/MainScreen.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/MainScreen.kt index 30f68395d8..6b30e1a1d7 100644 --- a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/MainScreen.kt +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/MainScreen.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars @@ -198,11 +197,10 @@ fun DateMainScreen( modifier = Modifier .fillMaxWidth() .wrapContentHeight() - .windowInsetsPadding(WindowInsets.ime) - .padding(bottom = 27.dp, start = 8.dp, end = 8.dp), + .windowInsetsPadding(WindowInsets.navigationBars), + modifierCard = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 16.dp), uiState = uiSyncStatusState, onDismiss = { onDateEvent(DateEvent.SyncStatusWidget.OnSyncStatusDismiss) }, - scope = scope, onUpdateAppClick = {} ) }