This commit is contained in:
2024-02-27 22:09:30 +03:00
parent bfa3231823
commit 38a3141d43
479 changed files with 36348 additions and 10142 deletions

1
core/data/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
build

View File

@@ -0,0 +1,22 @@
plugins {
alias(libs.plugins.kotlin.android)
alias(libs.plugins.android.library)
}
android {
namespace = "it.hamy.compose.core.data"
compileSdk = 34
defaultConfig {
minSdk = 21
}
}
kotlin {
jvmToolchain(libs.versions.jvm.get().toInt())
}
dependencies {
detektPlugins(libs.detekt.compose)
detektPlugins(libs.detekt.formatting)
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>
</manifest>

View File

@@ -0,0 +1,7 @@
package it.hamy.muza.enums
enum class AlbumSortBy {
Title,
Year,
DateAdded
}

View File

@@ -0,0 +1,6 @@
package it.hamy.muza.enums
enum class ArtistSortBy {
Name,
DateAdded
}

View File

@@ -0,0 +1,7 @@
package it.hamy.muza.enums
enum class BuiltInPlaylist {
Favorites,
Offline,
Top
}

View File

@@ -0,0 +1,13 @@
package it.hamy.muza.enums
import it.hamy.muza.utils.mb
@Suppress("unused", "EnumEntryName")
enum class CoilDiskCacheSize(val bytes: Long) {
`64MB`(bytes = 64.mb),
`128MB`(bytes = 128.mb),
`256MB`(bytes = 256.mb),
`512MB`(bytes = 512.mb),
`1GB`(bytes = 1024.mb),
`2GB`(bytes = 2048.mb)
}

View File

@@ -0,0 +1,17 @@
package it.hamy.muza.enums
import it.hamy.muza.utils.mb
@Suppress("EnumEntryName", "unused")
enum class ExoPlayerDiskCacheSize(val bytes: Long) {
`32MB`(bytes = 32.mb),
`64MB`(bytes = 64.mb),
`128MB`(bytes = 128.mb),
`256MB`(bytes = 256.mb),
`512MB`(bytes = 512.mb),
`1GB`(bytes = 1024.mb),
`2GB`(bytes = 2048.mb),
`4GB`(bytes = 4096.mb),
`8GB`(bytes = 8192.mb),
Unlimited(bytes = 0)
}

View File

@@ -0,0 +1,7 @@
package it.hamy.muza.enums
enum class PlaylistSortBy {
Name,
DateAdded,
SongCount
}

View File

@@ -0,0 +1,7 @@
package it.hamy.muza.enums
enum class SongSortBy {
PlayTime,
Title,
DateAdded
}

View File

@@ -0,0 +1,11 @@
package it.hamy.muza.enums
enum class SortOrder {
Ascending,
Descending;
operator fun not() = when (this) {
Ascending -> Descending
Descending -> Ascending
}
}

View File

@@ -0,0 +1,3 @@
package it.hamy.muza.utils
val Int.mb get() = this * 1_048_576L

View File

@@ -0,0 +1,20 @@
package it.hamy.muza.utils
inline val String.version get() = Version(value = this)
@JvmInline
value class Version(private val parts: List<Int>) {
constructor(value: String) : this(value.split(".").mapNotNull { it.toIntOrNull() })
val major get() = parts.firstOrNull()
val minor get() = parts.getOrNull(1)
val patch get() = parts.getOrNull(2)
companion object {
private val comparator = compareBy<Version> { it.major } then
compareBy { it.minor } then
compareBy { it.patch }
}
operator fun compareTo(other: Version) = comparator.compare(this, other)
}

1
core/ui/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
build

50
core/ui/build.gradle.kts Normal file
View File

@@ -0,0 +1,50 @@
plugins {
alias(libs.plugins.kotlin.android)
alias(libs.plugins.android.library)
}
android {
namespace = "it.hamy.compose.core.ui"
compileSdk = 34
defaultConfig {
minSdk = 21
}
sourceSets.all {
kotlin.srcDir("src/$name/kotlin")
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
}
kotlinOptions {
freeCompilerArgs = freeCompilerArgs + listOf("-Xcontext-receivers")
}
}
dependencies {
implementation(projects.core.data)
implementation(platform(libs.compose.bom))
implementation(libs.compose.animation)
implementation(libs.compose.foundation)
implementation(libs.compose.ripple)
implementation(libs.compose.shimmer)
implementation(libs.compose.ui)
implementation(libs.compose.ui.util)
implementation(libs.compose.material3)
implementation(libs.palette)
detektPlugins(libs.detekt.compose)
detektPlugins(libs.detekt.formatting)
}
kotlin {
jvmToolchain(libs.versions.jvm.get().toInt())
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>
</manifest>

View File

@@ -0,0 +1,6 @@
package it.hamy.muza
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.unit.Dp
val Dp.roundedShape get() = RoundedCornerShape(this)

View File

@@ -0,0 +1,7 @@
package it.hamy.muza.enums
enum class ColorPaletteMode {
Light,
Dark,
System
}

View File

@@ -0,0 +1,8 @@
package it.hamy.muza.enums
enum class ColorPaletteName {
Default,
Dynamic,
PureBlack,
AMOLED
}

View File

@@ -0,0 +1,16 @@
package it.hamy.muza.enums
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import it.hamy.muza.roundedShape
enum class ThumbnailRoundness(val dp: Dp) {
None(0.dp),
Light(2.dp),
Medium(8.dp),
Heavy(12.dp),
Heavier(16.dp),
Heaviest(16.dp);
val shape get() = dp.roundedShape
}

View File

@@ -0,0 +1,34 @@
package it.hamy.muza.ui.styling
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import it.hamy.muza.roundedShape
data class Appearance(
val colorPalette: ColorPalette,
val typography: Typography,
val thumbnailShapeCorners: Dp
) {
val thumbnailShape = thumbnailShapeCorners.roundedShape
operator fun component4() = thumbnailShape
companion object AppearanceSaver : Saver<Appearance, List<Any>> {
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any>) = Appearance(
colorPalette = ColorPalette.restore(value[0] as List<Any>),
typography = Typography.restore(value[1] as List<Any>),
thumbnailShapeCorners = (value[2] as Float).dp
)
override fun SaverScope.save(value: Appearance) = listOf(
with(ColorPalette.Companion) { save(value.colorPalette) },
with(Typography.Companion) { save(value.typography) },
value.thumbnailShapeCorners.value
)
}
}
val LocalAppearance by lazy { staticCompositionLocalOf<Appearance> { error("No appearance provided") } }

View File

@@ -0,0 +1,221 @@
package it.hamy.muza.ui.styling
import android.graphics.Bitmap
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.core.graphics.ColorUtils
import androidx.palette.graphics.Palette
import it.hamy.muza.enums.ColorPaletteMode
import it.hamy.muza.enums.ColorPaletteName
@Immutable
data class ColorPalette(
val background0: Color,
val background1: Color,
val background2: Color,
val accent: Color,
val onAccent: Color,
val red: Color = Color(0xffbf4040),
val blue: Color = Color(0xff4472cf),
val text: Color,
val textSecondary: Color,
val textDisabled: Color,
val isDark: Boolean,
val isAmoled: Boolean
) {
companion object : Saver<ColorPalette, List<Any>> {
override fun restore(value: List<Any>) = when (val accent = value[0] as Int) {
0 -> DefaultDarkColorPalette
1 -> DefaultLightColorPalette
2 -> PureBlackColorPalette
else -> dynamicColorPaletteOf(
accentColor = accent,
isDark = value[1] as Boolean,
isAmoled = value[2] as Boolean
)
}
override fun SaverScope.save(value: ColorPalette) = listOf(
when {
value === DefaultDarkColorPalette -> 0
value === DefaultLightColorPalette -> 1
value === PureBlackColorPalette -> 2
else -> value.accent.toArgb()
},
value.isDark,
value.isAmoled
)
}
}
val DefaultDarkColorPalette = ColorPalette(
background0 = Color(0xff16171d),
background1 = Color(0xff1f2029),
background2 = Color(0xff2b2d3b),
text = Color(0xffe1e1e2),
textSecondary = Color(0xffa3a4a6),
textDisabled = Color(0xff6f6f73),
accent = Color(0xff5055c0),
onAccent = Color.White,
isDark = true,
isAmoled = false
)
val DefaultLightColorPalette = ColorPalette(
background0 = Color(0xfffdfdfe),
background1 = Color(0xfff8f8fc),
background2 = Color(0xffeaeaf5),
text = Color(0xff212121),
textSecondary = Color(0xff656566),
textDisabled = Color(0xff9d9d9d),
accent = Color(0xff5055c0),
onAccent = Color.White,
isDark = false,
isAmoled = false
)
val PureBlackColorPalette = DefaultDarkColorPalette.copy(
background0 = Color.Black,
background1 = Color.Black,
background2 = Color.Black
)
fun colorPaletteOf(
name: ColorPaletteName,
mode: ColorPaletteMode,
isDark: Boolean
) = when (name) {
ColorPaletteName.Default,
ColorPaletteName.Dynamic -> when (mode) {
ColorPaletteMode.Light -> DefaultLightColorPalette
ColorPaletteMode.Dark -> DefaultDarkColorPalette
ColorPaletteMode.System -> if (isDark) DefaultDarkColorPalette else DefaultLightColorPalette
}
ColorPaletteName.PureBlack -> PureBlackColorPalette
ColorPaletteName.AMOLED -> PureBlackColorPalette.copy(isAmoled = true)
}
fun dynamicColorPaletteOf(
bitmap: Bitmap,
isDark: Boolean,
isAmoled: Boolean
): ColorPalette? {
val palette = Palette
.from(bitmap)
.maximumColorCount(8)
.addFilter(if (isDark) ({ _, hsl -> hsl[0] !in 36f..100f }) else null)
.generate()
val hsl = if (isDark) {
palette.dominantSwatch ?: Palette
.from(bitmap)
.maximumColorCount(8)
.generate()
.dominantSwatch
} else {
palette.dominantSwatch
}?.hsl ?: return null
return dynamicColorPaletteOf(
hsl = if (hsl[1] < 0.08)
palette.swatches
.map(Palette.Swatch::getHsl)
.sortedByDescending(FloatArray::component2)
.find { it[1] != 0f }
?: hsl
else hsl,
isDark = isDark,
isAmoled = isAmoled
)
}
fun dynamicColorPaletteOf(
hsl: FloatArray,
isDark: Boolean,
isAmoled: Boolean
) = hsl.let { (hue, saturation) ->
colorPaletteOf(
name = if (isAmoled) ColorPaletteName.AMOLED else ColorPaletteName.Dynamic,
mode = if (isDark || isAmoled) ColorPaletteMode.Dark else ColorPaletteMode.Light,
isDark = false
).copy(
background0 = if (isAmoled) PureBlackColorPalette.background0 else Color.hsl(
hue = hue,
saturation = saturation.coerceAtMost(0.1f),
lightness = if (isDark) 0.10f else 0.925f
),
background1 = if (isAmoled) PureBlackColorPalette.background1 else Color.hsl(
hue = hue,
saturation = saturation.coerceAtMost(0.3f),
lightness = if (isDark) 0.15f else 0.90f
),
background2 = if (isAmoled) PureBlackColorPalette.background2 else Color.hsl(
hue = hue,
saturation = saturation.coerceAtMost(0.4f),
lightness = if (isDark) 0.2f else 0.85f
),
accent = Color.hsl(
hue = hue,
saturation = saturation.coerceAtMost(if (isAmoled) 0.4f else 0.5f),
lightness = 0.5f
),
text = if (isAmoled) PureBlackColorPalette.text else Color.hsl(
hue = hue,
saturation = saturation.coerceAtMost(0.02f),
lightness = if (isDark) 0.88f else 0.12f
),
textSecondary = if (isAmoled) PureBlackColorPalette.textSecondary else Color.hsl(
hue = hue,
saturation = saturation.coerceAtMost(0.1f),
lightness = if (isDark) 0.65f else 0.40f
),
textDisabled = if (isAmoled) PureBlackColorPalette.textDisabled else Color.hsl(
hue = hue,
saturation = saturation.coerceAtMost(0.2f),
lightness = if (isDark) 0.40f else 0.65f
)
)
}
fun dynamicColorPaletteOf(
accentColor: Color,
isDark: Boolean,
isAmoled: Boolean
) = dynamicColorPaletteOf(
accentColor = accentColor.toArgb(),
isDark = isDark,
isAmoled = isAmoled
)
fun dynamicColorPaletteOf(
accentColor: Int,
isDark: Boolean,
isAmoled: Boolean
) = dynamicColorPaletteOf(
hsl = FloatArray(3).apply { ColorUtils.colorToHSL(accentColor, this) },
isDark = isDark,
isAmoled = isAmoled
)
inline val ColorPalette.isDefault
get() =
this === DefaultDarkColorPalette || this === DefaultLightColorPalette || this === PureBlackColorPalette
inline val ColorPalette.collapsedPlayerProgressBar get() = if (isDefault) text else accent
inline val ColorPalette.favoritesIcon get() = if (isDefault) red else accent
inline val ColorPalette.shimmer get() = if (isDefault) Color(0xff838383) else accent
inline val ColorPalette.primaryButton
get() = if (this === PureBlackColorPalette || isAmoled) Color(0xFF272727) else background2
@Suppress("UnusedReceiverParameter")
inline val ColorPalette.overlay get() = PureBlackColorPalette.background0.copy(alpha = 0.75f)
@Suppress("UnusedReceiverParameter")
inline val ColorPalette.onOverlay get() = PureBlackColorPalette.text
@Suppress("UnusedReceiverParameter")
inline val ColorPalette.onOverlayShimmer get() = PureBlackColorPalette.shimmer

View File

@@ -0,0 +1,44 @@
package it.hamy.muza.ui.styling
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
object Dimensions {
object Thumbnails {
val album = 108.dp
val artist = 92.dp
val song = 54.dp
val playlist = album
val player = Player
object Player {
val song
@Composable get() = with(LocalConfiguration.current) {
minOf(screenHeightDp, screenWidthDp)
}.dp
}
}
val thumbnails = Thumbnails
object Items {
val moodHeight = 64.dp
val headerHeight = 140.dp
val collapsedPlayerHeight = 64.dp
val verticalPadding = 8.dp
val horizontalPadding = 16.dp
}
val items = Items
object NavigationRail {
val width = 64.dp
val widthLandscape = 128.dp
val iconOffset = 6.dp
}
val navigationRail = NavigationRail
}

View File

@@ -0,0 +1,85 @@
package it.hamy.muza.ui.styling
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.PlatformTextStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import it.hamy.compose.core.ui.R
@Immutable
data class Typography(
val xxs: TextStyle,
val xs: TextStyle,
val s: TextStyle,
val m: TextStyle,
val l: TextStyle,
val xxl: TextStyle
) {
fun copy(color: Color) = Typography(
xxs = xxs.copy(color = color),
xs = xs.copy(color = color),
s = s.copy(color = color),
m = m.copy(color = color),
l = l.copy(color = color),
xxl = xxl.copy(color = color)
)
companion object : Saver<Typography, List<Any>> {
override fun restore(value: List<Any>) = typographyOf(
Color((value[0] as Long).toULong()),
value[1] as Boolean,
value[2] as Boolean
)
override fun SaverScope.save(value: Typography) = listOf(
value.xxs.color.value.toLong(),
value.xxs.fontFamily == FontFamily.Default,
value.xxs.platformStyle?.paragraphStyle?.includeFontPadding ?: false
)
}
}
fun typographyOf(color: Color, useSystemFont: Boolean, applyFontPadding: Boolean): Typography {
val textStyle = TextStyle(
fontFamily = if (useSystemFont) FontFamily.Default else FontFamily(
Font(
resId = R.font.poppins_w300,
weight = FontWeight.Light
),
Font(
resId = R.font.poppins_w400,
weight = FontWeight.Normal
),
Font(
resId = R.font.poppins_w500,
weight = FontWeight.Medium
),
Font(
resId = R.font.poppins_w600,
weight = FontWeight.SemiBold
),
Font(
resId = R.font.poppins_w700,
weight = FontWeight.Bold
)
),
fontWeight = FontWeight.Normal,
color = color,
platformStyle = PlatformTextStyle(includeFontPadding = applyFontPadding)
)
return Typography(
xxs = textStyle.copy(fontSize = 12.sp),
xs = textStyle.copy(fontSize = 14.sp),
s = textStyle.copy(fontSize = 16.sp),
m = textStyle.copy(fontSize = 18.sp),
l = textStyle.copy(fontSize = 20.sp),
xxl = textStyle.copy(fontSize = 32.sp)
)
}

View File

@@ -0,0 +1,21 @@
package it.hamy.muza.utils
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import kotlin.math.roundToInt
@Suppress("NOTHING_TO_INLINE")
@JvmInline
value class Px(val value: Int) {
inline val dp @Composable get() = dp(LocalDensity.current)
inline fun dp(density: Density) = with(density) { value.toDp() }
}
inline val Int.px inline get() = Px(value = this)
inline val Float.px inline get() = roundToInt().px
inline val Dp.px: Int
@Composable
inline get() = with(LocalDensity.current) { roundToPx() }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.