vfsfitvnm пре 2 година
родитељ
комит
752b29c93a

+ 0 - 105
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/TabColumn.kt

@@ -1,105 +0,0 @@
-package it.vfsfitvnm.vimusic.ui.components
-
-import androidx.compose.animation.animateColor
-import androidx.compose.animation.core.animateFloat
-import androidx.compose.animation.core.updateTransition
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text.BasicText
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.ripple.rememberRipple
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.draw.rotate
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.layout.layout
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.dp
-
-@Composable
-fun TabColumn(
-    tabIndex: Int,
-    onTabIndexChanged: (Int) -> Unit,
-    selectedTextColor: Color,
-    disabledTextColor: Color,
-    textStyle: TextStyle,
-    modifier: Modifier = Modifier,
-    content: @Composable (@Composable (Int, String, Int) -> Unit) -> Unit
-) {
-    Column(
-        modifier = modifier
-            .verticalScroll(rememberScrollState())
-    ) {
-        val transition = updateTransition(targetState = tabIndex, label = null)
-
-        content { index, text, icon ->
-            val dothAlpha by transition.animateFloat(label = "") {
-                if (it == index) 1f else 0f
-            }
-
-            val textColor by transition.animateColor(label = "") {
-                if (it == index) selectedTextColor else disabledTextColor
-            }
-
-            Row(
-                verticalAlignment = Alignment.CenterVertically,
-                modifier = Modifier
-                    .clip(RoundedCornerShape(16.dp))
-                    .clickable(
-                        indication = rememberRipple(bounded = true),
-                        interactionSource = remember { MutableInteractionSource() },
-                        onClick = { onTabIndexChanged(index) }
-                    )
-                    .padding(horizontal = 8.dp)
-            ) {
-                Image(
-                    painter = painterResource(icon),
-                    contentDescription = null,
-                    colorFilter = ColorFilter.tint(selectedTextColor),
-                    modifier = Modifier
-                        .vertical()
-                        .graphicsLayer {
-                            alpha = dothAlpha
-                            translationX = (1f - dothAlpha) * -48.dp.toPx()
-                            rotationZ = -90f
-                        }
-                        .size(12.dp)
-                )
-
-                BasicText(
-                    text = text,
-                    style = textStyle.copy(color = textColor),
-                    modifier = Modifier
-                        .vertical()
-                        .rotate(-90f)
-                        .padding(horizontal = 16.dp)
-                )
-            }
-        }
-    }
-}
-
-fun Modifier.vertical() =
-    layout { measurable, constraints ->
-        val placeable = measurable.measure(constraints)
-        layout(placeable.height, placeable.width) {
-            placeable.place(
-                x = -(placeable.width / 2 - placeable.height / 2),
-                y = -(placeable.height / 2 - placeable.width / 2)
-            )
-        }
-    }

+ 2 - 1
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/Header.kt

@@ -18,6 +18,7 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
+import it.vfsfitvnm.vimusic.ui.styling.Dimensions
 import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
 import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
 import it.vfsfitvnm.vimusic.ui.styling.shimmer
 import it.vfsfitvnm.vimusic.ui.styling.shimmer
 import it.vfsfitvnm.vimusic.utils.medium
 import it.vfsfitvnm.vimusic.utils.medium
@@ -55,7 +56,7 @@ fun Header(
         horizontalAlignment = Alignment.End,
         horizontalAlignment = Alignment.End,
         modifier = modifier
         modifier = modifier
             .padding(horizontal = 16.dp)
             .padding(horizontal = 16.dp)
-            .height(128.dp)
+            .height(Dimensions.headerHeight)
             .fillMaxWidth()
             .fillMaxWidth()
     ) {
     ) {
         Spacer(
         Spacer(

+ 134 - 0
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/NavigationRail.kt

@@ -0,0 +1,134 @@
+package it.vfsfitvnm.vimusic.ui.components.themed
+
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+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.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import it.vfsfitvnm.vimusic.ui.styling.Dimensions
+import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
+import it.vfsfitvnm.vimusic.utils.semiBold
+
+@Composable
+fun NavigationRail(
+    topIconButtonId: Int,
+    onTopIconButtonClick: () -> Unit,
+    tabIndex: Int,
+    onTabIndexChanged: (Int) -> Unit,
+    content: @Composable ColumnScope.(@Composable (Int, String, Int) -> Unit) -> Unit,
+    modifier: Modifier = Modifier
+) {
+    val (colorPalette, typography) = LocalAppearance.current
+
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally,
+        modifier = modifier
+    ) {
+        Box(
+            contentAlignment = Alignment.TopCenter,
+            modifier = Modifier
+                .size(width = Dimensions.navigationRailWidth, height = Dimensions.headerHeight)
+        ) {
+            Image(
+                painter = painterResource(topIconButtonId),
+                contentDescription = null,
+                colorFilter = ColorFilter.tint(colorPalette.textSecondary),
+                modifier = Modifier
+                    .offset(x = Dimensions.navigationRailIconOffset, y = 48.dp)
+                    .clip(CircleShape)
+                    .clickable(onClick = onTopIconButtonClick)
+                    .padding(all = 12.dp)
+                    .size(22.dp)
+            )
+        }
+
+        Column(
+            modifier = modifier
+                .verticalScroll(rememberScrollState())
+        ) {
+            val transition = updateTransition(targetState = tabIndex, label = null)
+
+            content { index, text, icon ->
+                val dothAlpha by transition.animateFloat(label = "") {
+                    if (it == index) 1f else 0f
+                }
+
+                val textColor by transition.animateColor(label = "") {
+                    if (it == index) colorPalette.text else colorPalette.textDisabled
+                }
+
+                Row(
+                    verticalAlignment = Alignment.CenterVertically,
+                    modifier = Modifier
+                        .clip(RoundedCornerShape(24.dp))
+                        .clickable(
+                            indication = rememberRipple(bounded = true),
+                            interactionSource = remember { MutableInteractionSource() },
+                            onClick = { onTabIndexChanged(index) }
+                        )
+                        .padding(horizontal = 8.dp)
+                ) {
+                    Image(
+                        painter = painterResource(icon),
+                        contentDescription = null,
+                        colorFilter = ColorFilter.tint(colorPalette.text),
+                        modifier = Modifier
+                            .vertical()
+                            .graphicsLayer {
+                                alpha = dothAlpha
+                                translationX = (1f - dothAlpha) * -48.dp.toPx()
+                                rotationZ = -90f
+                            }
+                            .size(Dimensions.navigationRailIconOffset * 2)
+                    )
+
+                    BasicText(
+                        text = text,
+                        style = typography.xs.semiBold.copy(color = textColor),
+                        modifier = Modifier
+                            .vertical()
+                            .rotate(-90f)
+                            .padding(horizontal = 16.dp)
+                    )
+                }
+            }
+        }
+    }
+}
+
+private fun Modifier.vertical() =
+    layout { measurable, constraints ->
+        val placeable = measurable.measure(constraints)
+        layout(placeable.height, placeable.width) {
+            placeable.place(
+                x = -(placeable.width / 2 - placeable.height / 2),
+                y = -(placeable.height / 2 - placeable.width / 2)
+            )
+        }
+    }

+ 6 - 5
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/Scaffold.kt

@@ -13,6 +13,7 @@ import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.padding
@@ -37,7 +38,7 @@ fun Scaffold(
     onTopIconButtonClick: () -> Unit,
     onTopIconButtonClick: () -> Unit,
     tabIndex: Int,
     tabIndex: Int,
     onTabChanged: (Int) -> Unit,
     onTabChanged: (Int) -> Unit,
-    tabColumnContent: @Composable (@Composable (Int, String, Int) -> Unit) -> Unit,
+    tabColumnContent: @Composable ColumnScope.(@Composable (Int, String, Int) -> Unit) -> Unit,
     primaryIconButtonId: Int? = null,
     primaryIconButtonId: Int? = null,
     onPrimaryIconButtonClick: () -> Unit = {},
     onPrimaryIconButtonClick: () -> Unit = {},
     modifier: Modifier = Modifier,
     modifier: Modifier = Modifier,
@@ -54,14 +55,14 @@ fun Scaffold(
             modifier = modifier
             modifier = modifier
                 .fillMaxSize()
                 .fillMaxSize()
         ) {
         ) {
-            VerticalBar(
+            NavigationRail(
                 topIconButtonId = topIconButtonId,
                 topIconButtonId = topIconButtonId,
                 onTopIconButtonClick = onTopIconButtonClick,
                 onTopIconButtonClick = onTopIconButtonClick,
                 tabIndex = tabIndex,
                 tabIndex = tabIndex,
-                onTabChanged = onTabChanged,
-                tabColumnContent = tabColumnContent,
+                onTabIndexChanged = onTabChanged,
                 modifier = Modifier
                 modifier = Modifier
-                    .padding(LocalPlayerAwarePaddingValues.current)
+                    .padding(LocalPlayerAwarePaddingValues.current),
+                content = tabColumnContent
             )
             )
 
 
             AnimatedContent(
             AnimatedContent(

+ 0 - 66
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/VerticalBar.kt

@@ -1,66 +0,0 @@
-package it.vfsfitvnm.vimusic.ui.components.themed
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.unit.dp
-import it.vfsfitvnm.vimusic.ui.components.TabColumn
-import it.vfsfitvnm.vimusic.ui.styling.Dimensions
-import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
-import it.vfsfitvnm.vimusic.utils.semiBold
-
-@Composable
-fun VerticalBar(
-    topIconButtonId: Int,
-    onTopIconButtonClick: () -> Unit,
-    tabIndex: Int,
-    onTabChanged: (Int) -> Unit,
-    tabColumnContent: @Composable (@Composable (Int, String, Int) -> Unit) -> Unit,
-    modifier: Modifier = Modifier
-) {
-    val (colorPalette, typography) = LocalAppearance.current
-
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        modifier = modifier
-            .padding(vertical = 16.dp)
-    ) {
-        Image(
-            painter = painterResource(topIconButtonId),
-            contentDescription = null,
-            colorFilter = ColorFilter.tint(colorPalette.textSecondary),
-            modifier = Modifier
-                .clip(CircleShape)
-                .clickable(onClick = onTopIconButtonClick)
-                .padding(all = 12.dp)
-                .size(22.dp)
-        )
-
-        Spacer(
-            modifier = Modifier
-                .width(Dimensions.verticalBarWidth)
-                .height(32.dp)
-        )
-
-        TabColumn(
-            tabIndex = tabIndex,
-            onTabIndexChanged = onTabChanged,
-            selectedTextColor = colorPalette.text,
-            disabledTextColor = colorPalette.textDisabled,
-            textStyle = typography.xs.semiBold,
-            content = tabColumnContent,
-        )
-    }
-}

+ 1 - 1
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumOverview.kt

@@ -130,7 +130,7 @@ fun AlbumOverview(
     }
     }
 
 
     BoxWithConstraints {
     BoxWithConstraints {
-        val thumbnailSizeDp = maxWidth - Dimensions.verticalBarWidth
+        val thumbnailSizeDp = maxWidth - Dimensions.navigationRailWidth
         val thumbnailSizePx = (thumbnailSizeDp - 32.dp).px
         val thumbnailSizePx = (thumbnailSizeDp - 32.dp).px
 
 
         albumResult?.getOrNull()?.let { album ->
         albumResult?.getOrNull()?.let { album ->

+ 1 - 1
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeSongList.kt

@@ -210,7 +210,7 @@ fun HomeSongList() {
         ScrollToTop(
         ScrollToTop(
             lazyListState = lazyListState,
             lazyListState = lazyListState,
             modifier = Modifier
             modifier = Modifier
-                .offset(x = -Dimensions.verticalBarWidth)
+                .offset(x = Dimensions.navigationRailIconOffset - Dimensions.navigationRailWidth)
                 .align(Alignment.BottomStart)
                 .align(Alignment.BottomStart)
         )
         )
     }
     }

+ 1 - 1
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/playlist/PlaylistSongList.kt

@@ -99,7 +99,7 @@ fun PlaylistSongList(
     }
     }
 
 
     BoxWithConstraints {
     BoxWithConstraints {
-        val thumbnailSizeDp = maxWidth - Dimensions.verticalBarWidth
+        val thumbnailSizeDp = maxWidth - Dimensions.navigationRailWidth
         val thumbnailSizePx = (thumbnailSizeDp - 32.dp).px
         val thumbnailSizePx = (thumbnailSizeDp - 32.dp).px
 
 
         val songThumbnailSizeDp = Dimensions.thumbnails.song
         val songThumbnailSizeDp = Dimensions.thumbnails.song

+ 12 - 2
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/OnlineSearch.kt

@@ -49,9 +49,9 @@ import it.vfsfitvnm.vimusic.query
 import it.vfsfitvnm.vimusic.savers.SearchQueryListSaver
 import it.vfsfitvnm.vimusic.savers.SearchQueryListSaver
 import it.vfsfitvnm.vimusic.savers.StringListResultSaver
 import it.vfsfitvnm.vimusic.savers.StringListResultSaver
 import it.vfsfitvnm.vimusic.ui.components.themed.Header
 import it.vfsfitvnm.vimusic.ui.components.themed.Header
-import it.vfsfitvnm.vimusic.ui.components.themed.LoadingOrError
 import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
 import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
 import it.vfsfitvnm.vimusic.utils.align
 import it.vfsfitvnm.vimusic.utils.align
+import it.vfsfitvnm.vimusic.utils.center
 import it.vfsfitvnm.vimusic.utils.medium
 import it.vfsfitvnm.vimusic.utils.medium
 import it.vfsfitvnm.vimusic.utils.produceSaveableOneShotState
 import it.vfsfitvnm.vimusic.utils.produceSaveableOneShotState
 import it.vfsfitvnm.vimusic.utils.produceSaveableState
 import it.vfsfitvnm.vimusic.utils.produceSaveableState
@@ -320,7 +320,17 @@ fun OnlineSearch(
             }
             }
         } ?: suggestionsResult?.exceptionOrNull()?.let { throwable ->
         } ?: suggestionsResult?.exceptionOrNull()?.let { throwable ->
             item {
             item {
-                LoadingOrError(errorMessage = throwable.javaClass.canonicalName) {}
+                Box(
+                    modifier = Modifier
+                        .fillMaxSize()
+                ) {
+                    BasicText(
+                        text = "An error has occurred.",
+                        style = typography.s.medium.secondary.center,
+                        modifier = Modifier
+                            .align(Alignment.Center)
+                    )
+                }
             }
             }
         }
         }
     }
     }

+ 13 - 6
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/searchresult/SearchResult.kt

@@ -4,6 +4,7 @@ import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyItemScope
 import androidx.compose.foundation.lazy.LazyItemScope
@@ -19,6 +20,7 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.input.pointer.pointerInput
+import com.valentinilk.shimmer.shimmer
 import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
 import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
 import it.vfsfitvnm.vimusic.savers.ListSaver
 import it.vfsfitvnm.vimusic.savers.ListSaver
 import it.vfsfitvnm.vimusic.savers.StringResultSaver
 import it.vfsfitvnm.vimusic.savers.StringResultSaver
@@ -141,12 +143,17 @@ inline fun <T : YouTube.Item> SearchResult(
                 }
                 }
             }
             }
         } ?: item(key = "loading") {
         } ?: item(key = "loading") {
-            repeat(if (items.isEmpty()) 8 else 3) { index ->
-                Box(
-                    modifier = Modifier
-                        .alpha(1f - index * 0.125f),
-                    content = itemShimmer
-                )
+            Column(
+                modifier = Modifier
+                    .shimmer()
+            ) {
+                repeat(if (items.isEmpty()) 8 else 3) { index ->
+                    Box(
+                        modifier = Modifier
+                            .alpha(1f - index * 0.125f),
+                        content = itemShimmer
+                    )
+                }
             }
             }
         }
         }
     }
     }

+ 3 - 1
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/styling/Dimensions.kt

@@ -10,7 +10,9 @@ import androidx.compose.ui.unit.dp
 object Dimensions {
 object Dimensions {
     val itemsVerticalPadding = 8.dp
     val itemsVerticalPadding = 8.dp
 
 
-    val verticalBarWidth = 64.dp
+    val navigationRailWidth = 64.dp
+    val navigationRailIconOffset = 6.dp
+    val headerHeight = 128.dp
 
 
     object thumbnails {
     object thumbnails {
         val album = 128.dp
         val album = 128.dp