mirror of
https://github.com/mollyim/monero-wallet-sdk.git
synced 2024-12-26 07:59:35 -05:00
demo: add loading spinner to screens
This commit is contained in:
parent
e022a27f04
commit
45edf35d83
@ -1,7 +1,9 @@
|
|||||||
package im.molly.monero.demo.ui
|
package im.molly.monero.demo.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
@ -12,6 +14,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import im.molly.monero.demo.ui.component.LoadingWheel
|
||||||
import im.molly.monero.demo.ui.component.Toolbar
|
import im.molly.monero.demo.ui.component.Toolbar
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -66,10 +69,16 @@ private fun HomeScreen(
|
|||||||
private fun LazyListScope.walletCards(
|
private fun LazyListScope.walletCards(
|
||||||
walletListUiState: WalletListUiState,
|
walletListUiState: WalletListUiState,
|
||||||
onWalletClick: (Long) -> Unit,
|
onWalletClick: (Long) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
when (walletListUiState) {
|
when (walletListUiState) {
|
||||||
WalletListUiState.Loading -> item {
|
WalletListUiState.Loading -> item {
|
||||||
Text(text = "Loading wallet list...") // TODO
|
LoadingWheel(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentSize(),
|
||||||
|
contentDesc = "Loading wallets",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is WalletListUiState.Loaded -> {
|
is WalletListUiState.Loaded -> {
|
||||||
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -14,6 +15,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import im.molly.monero.demo.ui.component.LoadingWheel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@ -77,7 +79,12 @@ fun WalletCardError() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WalletCardLoading() {
|
fun WalletCardLoading() {
|
||||||
Text(text = "Loading wallet...") // TODO
|
LoadingWheel(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentSize(),
|
||||||
|
contentDesc = "Loading wallet",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
//@Preview
|
//@Preview
|
||||||
|
@ -0,0 +1,174 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.molly.monero.demo.ui.component
|
||||||
|
|
||||||
|
import androidx.compose.animation.animateColor
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.LinearEasing
|
||||||
|
import androidx.compose.animation.core.RepeatMode
|
||||||
|
import androidx.compose.animation.core.StartOffset
|
||||||
|
import androidx.compose.animation.core.animateFloat
|
||||||
|
import androidx.compose.animation.core.infiniteRepeatable
|
||||||
|
import androidx.compose.animation.core.keyframes
|
||||||
|
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
|
import androidx.compose.ui.graphics.drawscope.rotate
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import im.molly.monero.demo.ui.theme.AppTheme
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoadingWheel(
|
||||||
|
contentDesc: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val infiniteTransition = rememberInfiniteTransition(label = "wheel transition")
|
||||||
|
|
||||||
|
// Specifies the float animation for slowly drawing out the lines on entering
|
||||||
|
val startValue = if (LocalInspectionMode.current) 0F else 1F
|
||||||
|
val floatAnimValues = (0 until NUM_OF_LINES).map { remember { Animatable(startValue) } }
|
||||||
|
LaunchedEffect(floatAnimValues) {
|
||||||
|
(0 until NUM_OF_LINES).map { index ->
|
||||||
|
launch {
|
||||||
|
floatAnimValues[index].animateTo(
|
||||||
|
targetValue = 0F,
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = 100,
|
||||||
|
easing = FastOutSlowInEasing,
|
||||||
|
delayMillis = 40 * index,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specifies the rotation animation of the entire Canvas composable
|
||||||
|
val rotationAnim by infiniteTransition.animateFloat(
|
||||||
|
initialValue = 0F,
|
||||||
|
targetValue = 360F,
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
animation = tween(durationMillis = ROTATION_TIME, easing = LinearEasing),
|
||||||
|
),
|
||||||
|
label = "wheel rotation animation",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Specifies the color animation for the base-to-progress line color change
|
||||||
|
val baseLineColor = MaterialTheme.colorScheme.onBackground
|
||||||
|
val progressLineColor = MaterialTheme.colorScheme.inversePrimary
|
||||||
|
|
||||||
|
val colorAnimValues = (0 until NUM_OF_LINES).map { index ->
|
||||||
|
infiniteTransition.animateColor(
|
||||||
|
initialValue = baseLineColor,
|
||||||
|
targetValue = baseLineColor,
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
animation = keyframes {
|
||||||
|
durationMillis = ROTATION_TIME / 2
|
||||||
|
progressLineColor at ROTATION_TIME / NUM_OF_LINES / 2 with LinearEasing
|
||||||
|
baseLineColor at ROTATION_TIME / NUM_OF_LINES with LinearEasing
|
||||||
|
},
|
||||||
|
repeatMode = RepeatMode.Restart,
|
||||||
|
initialStartOffset = StartOffset(ROTATION_TIME / NUM_OF_LINES / 2 * index),
|
||||||
|
),
|
||||||
|
label = "wheel color animation",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draws out the LoadingWheel Canvas composable and sets the animations
|
||||||
|
Canvas(
|
||||||
|
modifier = modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.padding(8.dp)
|
||||||
|
.graphicsLayer { rotationZ = rotationAnim }
|
||||||
|
.semantics { contentDescription = contentDesc }
|
||||||
|
.testTag("loadingWheel"),
|
||||||
|
) {
|
||||||
|
repeat(NUM_OF_LINES) { index ->
|
||||||
|
rotate(degrees = index * 30f) {
|
||||||
|
drawLine(
|
||||||
|
color = colorAnimValues[index].value,
|
||||||
|
// Animates the initially drawn 1 pixel alpha from 0 to 1
|
||||||
|
alpha = if (floatAnimValues[index].value < 1f) 1f else 0f,
|
||||||
|
strokeWidth = 4F,
|
||||||
|
cap = StrokeCap.Round,
|
||||||
|
start = Offset(size.width / 2, size.height / 4),
|
||||||
|
end = Offset(size.width / 2, floatAnimValues[index].value * size.height / 4),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OverlayLoadingWheel(
|
||||||
|
contentDesc: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
shape = RoundedCornerShape(60.dp),
|
||||||
|
shadowElevation = 8.dp,
|
||||||
|
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.83f),
|
||||||
|
modifier = modifier
|
||||||
|
.size(60.dp),
|
||||||
|
) {
|
||||||
|
LoadingWheel(
|
||||||
|
contentDesc = contentDesc,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun LoadingWheelPreview() {
|
||||||
|
AppTheme {
|
||||||
|
Surface {
|
||||||
|
LoadingWheel(contentDesc = "LoadingWheel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun OverlayLoadingWheelPreview() {
|
||||||
|
AppTheme {
|
||||||
|
Surface {
|
||||||
|
OverlayLoadingWheel(contentDesc = "LoadingWheel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val ROTATION_TIME = 12000
|
||||||
|
private const val NUM_OF_LINES = 12
|
Loading…
Reference in New Issue
Block a user