1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-2802 Sync status | Ui fixes (#1934)

This commit is contained in:
Konstantin Ivanov 2024-12-17 17:32:02 +01:00 committed by GitHub
parent 5b803ac85c
commit d173139b54
Signed by: github
GPG key ID: B5690EEEBB952194
10 changed files with 131 additions and 103 deletions

View file

@ -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<FragmentEditorBinding>(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
)
}

View file

@ -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
)
}

View file

@ -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" />
</androidx.constraintlayout.motion.widget.MotionLayout>

View file

@ -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()

View file

@ -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()

View file

@ -68,10 +68,6 @@ fun ViewerLayoutWidget(
skipPartiallyExpanded = true
)
var currentCoordinates: Rect by remember {
mutableStateOf(Rect.Zero)
}
if (uiState.showWidget) {
ModalBottomSheet(
modifier = Modifier

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Animate scaleX -->
<objectAnimator
android:propertyName="scaleX"
android:valueFrom="0.0"
android:valueTo="1.0"
android:duration="600"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueType="floatType"/>
<!-- Animate scaleY -->
<objectAnimator
android:propertyName="scaleY"
android:valueFrom="0.0"
android:valueTo="1.0"
android:duration="600"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueType="floatType"/>
</set>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_syncing">
<target
android:name="animatable_group"
android:animation="@animator/pulse_anim" />
</animated-vector>

View file

@ -1,19 +1,39 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<!-- Group containing the outer two circles that we will scale -->
<group
android:name="animatable_group"
android:pivotX="10"
android:pivotY="10"
android:scaleX="0"
android:scaleY="0">
<!-- Outer circle (radius = 10) -->
<path
android:name="outer_circle"
android:pathData="M10,10m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:fillColor="@color/palette_system_green"
android:fillAlpha="0.2" />
<!-- Middle circle (radius = 7) -->
<path
android:name="middle_circle"
android:pathData="M10,10m-7,0a7,7 0,1 1,14 0a7,7 0,1 1,-14 0"
android:fillColor="@color/palette_system_green"
android:fillAlpha="0.4" />
</group>
<!-- Center circle (radius = 4), remains static -->
<path
android:pathData="M10,10m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeAlpha="0.2"
android:fillColor="@color/palette_system_green"
android:fillAlpha="0.2"/>
<path
android:pathData="M10,10m-7,0a7,7 0,1 1,14 0a7,7 0,1 1,-14 0"
android:strokeAlpha="0.4"
android:fillColor="@color/palette_system_green"
android:fillAlpha="0.4"/>
<path
android:name="center_circle"
android:pathData="M10,10m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"
android:fillColor="@color/palette_system_green"/>
</vector>
android:fillColor="@color/palette_system_green" />
</vector>

View file

@ -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 = {}
)
}