Обновление

0.5.4.2
This commit is contained in:
2024-01-30 15:14:45 +05:00
parent 2a995364a2
commit c79fa89c1d
11 changed files with 117 additions and 46 deletions

View File

@@ -75,6 +75,7 @@ dependencies {
implementation(projects.composeReordering) implementation(projects.composeReordering)
implementation(libs.compose.activity) implementation(libs.compose.activity)
implementation(libs.compose.foundation) implementation(libs.compose.foundation)
implementation(libs.compose.ui) implementation(libs.compose.ui)

View File

@@ -128,6 +128,7 @@ class MainActivity : ComponentActivity(), PersistMapOwner {
override lateinit var persistMap: PersistMap override lateinit var persistMap: PersistMap
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
bindService(intent<PlayerService>(), serviceConnection, Context.BIND_AUTO_CREATE) bindService(intent<PlayerService>(), serviceConnection, Context.BIND_AUTO_CREATE)
@@ -168,7 +169,7 @@ class MainActivity : ComponentActivity(), PersistMapOwner {
val colorPaletteName = getEnum(colorPaletteNameKey, ColorPaletteName.Dynamic) val colorPaletteName = getEnum(colorPaletteNameKey, ColorPaletteName.Dynamic)
val colorPaletteMode = getEnum(colorPaletteModeKey, ColorPaletteMode.System) val colorPaletteMode = getEnum(colorPaletteModeKey, ColorPaletteMode.System)
val thumbnailRoundness = val thumbnailRoundness =
getEnum(thumbnailRoundnessKey, ThumbnailRoundness.Light) getEnum(thumbnailRoundnessKey, ThumbnailRoundness.Слабое)
val useSystemFont = getBoolean(useSystemFontKey, false) val useSystemFont = getBoolean(useSystemFontKey, false)
val applyFontPadding = getBoolean(applyFontPaddingKey, false) val applyFontPadding = getBoolean(applyFontPaddingKey, false)
@@ -267,7 +268,7 @@ class MainActivity : ComponentActivity(), PersistMapOwner {
thumbnailRoundnessKey -> { thumbnailRoundnessKey -> {
val thumbnailRoundness = val thumbnailRoundness =
sharedPreferences.getEnum(key, ThumbnailRoundness.Light) sharedPreferences.getEnum(key, ThumbnailRoundness.Слабое)
appearance = appearance.copy( appearance = appearance.copy(
thumbnailShape = thumbnailRoundness.shape() thumbnailShape = thumbnailRoundness.shape()

View File

@@ -6,17 +6,19 @@ import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
enum class ThumbnailRoundness { enum class ThumbnailRoundness {
None, Отключено,
Light, Слабое,
Medium, Среднее,
Heavy; Сильное,
Максимальное;
fun shape(): Shape { fun shape(): Shape {
return when (this) { return when (this) {
None -> RectangleShape Отключено -> RectangleShape
Light -> RoundedCornerShape(2.dp) Слабое -> RoundedCornerShape(2.dp)
Medium -> RoundedCornerShape(4.dp) Среднее -> RoundedCornerShape(4.dp)
Heavy -> RoundedCornerShape(8.dp) Сильное -> RoundedCornerShape(8.dp)
Максимальное -> RoundedCornerShape(14.dp)
} }
} }
} }

View File

@@ -176,11 +176,11 @@ fun ArtistScreen(browseId: String) {
tabIndex = tabIndex, tabIndex = tabIndex,
onTabChanged = { tabIndex = it }, onTabChanged = { tabIndex = it },
tabColumnContent = { Item -> tabColumnContent = { Item ->
Item(0, "Overview", R.drawable.sparkles) Item(0, "Обзор", R.drawable.sparkles)
Item(1, "Songs", R.drawable.musical_notes) Item(1, "Песни", R.drawable.musical_notes)
Item(2, "Albums", R.drawable.disc) Item(2, "Альбомы", R.drawable.disc)
Item(3, "Singles", R.drawable.disc) Item(3, "Синглы", R.drawable.disc)
Item(4, "Library", R.drawable.library) Item(4, "Библиотека", R.drawable.library)
}, },
) { currentTabIndex -> ) { currentTabIndex ->
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) { saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {
@@ -267,7 +267,7 @@ fun ArtistScreen(browseId: String) {
ItemsPage( ItemsPage(
tag = "artist/$browseId/albums", tag = "artist/$browseId/albums",
headerContent = headerContent, headerContent = headerContent,
emptyItemsText = "This artist didn't release any album", emptyItemsText = "Исполнитель не выпустил ни одного альбома",
itemsPageProvider = artistPage?.let { itemsPageProvider = artistPage?.let {
({ continuation -> ({ continuation ->
continuation?.let { continuation?.let {
@@ -317,7 +317,7 @@ fun ArtistScreen(browseId: String) {
ItemsPage( ItemsPage(
tag = "artist/$browseId/singles", tag = "artist/$browseId/singles",
headerContent = headerContent, headerContent = headerContent,
emptyItemsText = "This artist didn't release any single", emptyItemsText = "Исполнитель не выпустил ни одного сингла",
itemsPageProvider = artistPage?.let { itemsPageProvider = artistPage?.let {
({ continuation -> ({ continuation ->
continuation?.let { continuation?.let {

View File

@@ -53,9 +53,11 @@ import it.hamy.muza.utils.playlistSortByKey
import it.hamy.muza.utils.playlistSortOrderKey import it.hamy.muza.utils.playlistSortOrderKey
import it.hamy.muza.utils.rememberPreference import it.hamy.muza.utils.rememberPreference
@ExperimentalAnimationApi @ExperimentalAnimationApi
@ExperimentalFoundationApi @ExperimentalFoundationApi
@Composable @Composable
fun HomePlaylists( fun HomePlaylists(
onBuiltInPlaylist: (BuiltInPlaylist) -> Unit, onBuiltInPlaylist: (BuiltInPlaylist) -> Unit,
onPlaylistClick: (Playlist) -> Unit, onPlaylistClick: (Playlist) -> Unit,
@@ -67,6 +69,8 @@ fun HomePlaylists(
mutableStateOf(false) mutableStateOf(false)
} }
if (isCreatingANewPlaylist) { if (isCreatingANewPlaylist) {
TextFieldDialog( TextFieldDialog(
hintText = "Введите название плейлиста", hintText = "Введите название плейлиста",

View File

@@ -53,6 +53,10 @@ import it.hamy.muza.utils.secondary
import it.hamy.muza.utils.semiBold import it.hamy.muza.utils.semiBold
import it.hamy.muza.utils.trackLoopEnabledKey import it.hamy.muza.utils.trackLoopEnabledKey
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import it.hamy.muza.models.Info
import it.hamy.muza.ui.screens.artistRoute
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable @Composable
fun Controls( fun Controls(
@@ -79,8 +83,14 @@ fun Controls(
mutableStateOf<Long?>(null) mutableStateOf<Long?>(null)
} }
LaunchedEffect(mediaId) { var artistsInfo: List<Info>? by remember { mutableStateOf(null) }
Database.likedAt(mediaId).distinctUntilChanged().collect { likedAt = it }
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
if (artistsInfo == null) artistsInfo = Database.songArtistInfo(mediaId)
Database.likedAt(mediaId).distinctUntilChanged().collect { likedAt = it }
}
} }
val shouldBePlayingTransition = updateTransition(shouldBePlaying, label = "shouldBePlaying") val shouldBePlayingTransition = updateTransition(shouldBePlaying, label = "shouldBePlaying")
@@ -113,7 +123,11 @@ fun Controls(
text = artist ?: "", text = artist ?: "",
style = typography.s.semiBold.secondary, style = typography.s.semiBold.secondary,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis,
modifier = Modifier.clickable {
val goTo = artistRoute::global
goTo(artistsInfo?.get(0)?.id)
}
) )
Spacer( Spacer(

View File

@@ -79,6 +79,11 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService
@Composable @Composable
fun Lyrics( fun Lyrics(
@@ -91,6 +96,10 @@ fun Lyrics(
ensureSongInserted: () -> Unit, ensureSongInserted: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
AnimatedVisibility( AnimatedVisibility(
visible = isDisplayed, visible = isDisplayed,
enter = fadeIn(), enter = fadeIn(),
@@ -117,11 +126,15 @@ fun Lyrics(
mutableStateOf(false) mutableStateOf(false)
} }
val clipboardManager = ContextCompat.getSystemService(context, ClipboardManager::class.java)
LaunchedEffect(mediaId, isShowingSynchronizedLyrics) { LaunchedEffect(mediaId, isShowingSynchronizedLyrics) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
Database.lyrics(mediaId).collect { Database.lyrics(mediaId).collect {
if (isShowingSynchronizedLyrics && it?.synced == null) { if (isShowingSynchronizedLyrics && it?.synced == null) {
val mediaMetadata = mediaMetadataProvider() val mediaMetadata = mediaMetadataProvider()
var duration = withContext(Dispatchers.Main) { var duration = withContext(Dispatchers.Main) {
durationProvider() durationProvider()
} }
@@ -352,6 +365,15 @@ fun Lyrics(
} }
) )
MenuEntry(
icon = R.drawable.text,
text = "Скопировать текст",
onClick = {
menuState.hide()
clipboardManager?.setPrimaryClip(ClipData.newPlainText("Lyrics", lyrics?.fixed))
}
)
MenuEntry( MenuEntry(
icon = R.drawable.search, icon = R.drawable.search,
text = "Искать текст в интернете", text = "Искать текст в интернете",

View File

@@ -6,6 +6,8 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ContentTransform import androidx.compose.animation.ContentTransform
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
@@ -15,6 +17,9 @@ 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.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
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.Column
@@ -25,6 +30,7 @@ import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
@@ -36,6 +42,7 @@ import androidx.compose.foundation.text.BasicText
import androidx.compose.material.ripple.rememberRipple import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -44,10 +51,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.Player import androidx.media3.common.Player
@@ -82,12 +91,7 @@ import it.hamy.muza.utils.shuffleQueue
import it.hamy.muza.utils.smoothScrollToTop import it.hamy.muza.utils.smoothScrollToTop
import it.hamy.muza.utils.windows import it.hamy.muza.utils.windows
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.layout.offset
import androidx.compose.ui.unit.IntOffset
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ExperimentalFoundationApi @ExperimentalFoundationApi
@@ -188,6 +192,10 @@ fun Queue(
val musicBarsTransition = updateTransition(targetState = mediaItemIndex, label = "") val musicBarsTransition = updateTransition(targetState = mediaItemIndex, label = "")
val deleteHistory = remember {
mutableStateListOf<String>()
}
Column { Column {
Box( Box(
modifier = Modifier modifier = Modifier
@@ -209,8 +217,7 @@ fun Queue(
key = { it.uid.hashCode() } key = { it.uid.hashCode() }
) { window -> ) { window ->
val isPlayingThisMediaItem = mediaItemIndex == window.firstPeriodIndex val isPlayingThisMediaItem = mediaItemIndex == window.firstPeriodIndex
var offsetX by remember { mutableStateOf(0f) } val offsetX = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) }
SongItem( SongItem(
song = window.mediaItem, song = window.mediaItem,
thumbnailSizePx = thumbnailSizePx, thumbnailSizePx = thumbnailSizePx,
@@ -291,23 +298,43 @@ fun Queue(
reorderingState = reorderingState, reorderingState = reorderingState,
index = window.firstPeriodIndex index = window.firstPeriodIndex
) )
.draggable( .draggable(
orientation = Orientation.Horizontal, orientation = Orientation.Horizontal,
state = rememberDraggableState(onDelta = { delta -> state = rememberDraggableState(onDelta = { delta ->
if (isPlayingThisMediaItem) return@rememberDraggableState if (isPlayingThisMediaItem) return@rememberDraggableState
offsetX += delta runBlocking {
offsetX.snapTo(offsetX.value + Offset(delta, 0f))
}
}), }),
onDragStopped = { velocity -> onDragStopped = { _ ->
if ((offsetX <= -300.0f && velocity <= -3000.0f) || (offsetX >= 300.0f && velocity >= 3000.0f)) { if (offsetX.value.x >= 200.0f || offsetX.value.x <= -200.0f) {
binder.player.removeMediaItem(window.firstPeriodIndex) val currentIndex = window.firstPeriodIndex
val mediaId = window.mediaItem.mediaId
if (deleteHistory.indexOf(mediaId) != -1) return@draggable
deleteHistory.add(mediaId)
var indexToDelete = currentIndex
for (i in 0 until currentIndex) {
if (deleteHistory.indexOf(windows.elementAt(i).mediaItem.mediaId) != -1) {
indexToDelete--
}
}
if (offsetX.value.x < 0) {
offsetX.animateTo(Offset(-1500.0f, offsetX.value.y))
} else {
offsetX.animateTo(Offset(1500.0f, offsetX.value.y))
}
binder.player.removeMediaItem(indexToDelete)
deleteHistory.removeFirst()
} else { } else {
offsetX = 0f offsetX.animateTo(Offset(0f, offsetX.value.y))
} }
} }
) )
.offset{ IntOffset(offsetX.roundToInt(), 0) } .offset { IntOffset(offsetX.value.x.roundToInt(), 0) }
) )
} }
@@ -356,7 +383,7 @@ fun Queue(
.height(64.dp) .height(64.dp)
) { ) {
BasicText( BasicText(
text = "${windows.size} песни", text = "${windows.size}",
style = typography.xxs.medium, style = typography.xxs.medium,
modifier = Modifier modifier = Modifier
.background( .background(
@@ -386,7 +413,7 @@ fun Queue(
.animateContentSize() .animateContentSize()
) { ) {
BasicText( BasicText(
text = "Повтор очереди ", text = "Повтор ",
style = typography.xxs.medium, style = typography.xxs.medium,
) )
@@ -402,7 +429,7 @@ fun Queue(
} }
) { ) {
BasicText( BasicText(
text = if (it) "ВКЛ." else "ВЫКЛ.", text = if (it) "вкл." else "откл.",
style = typography.xxs.medium, style = typography.xxs.medium,
) )
} }
@@ -410,4 +437,4 @@ fun Queue(
} }
} }
} }
} }

View File

@@ -48,7 +48,7 @@ fun AppearanceSettings() {
var colorPaletteMode by rememberPreference(colorPaletteModeKey, ColorPaletteMode.System) var colorPaletteMode by rememberPreference(colorPaletteModeKey, ColorPaletteMode.System)
var thumbnailRoundness by rememberPreference( var thumbnailRoundness by rememberPreference(
thumbnailRoundnessKey, thumbnailRoundnessKey,
ThumbnailRoundness.Light ThumbnailRoundness.Слабое
) )
var useSystemFont by rememberPreference(useSystemFontKey, false) var useSystemFont by rememberPreference(useSystemFontKey, false)
var applyFontPadding by rememberPreference(applyFontPaddingKey, false) var applyFontPadding by rememberPreference(applyFontPaddingKey, false)

View File

@@ -190,7 +190,7 @@ fun OtherSettings() {
SwitchSettingEntry( SwitchSettingEntry(
title = "Invincible service", title = "Invincible service",
text = "When turning off battery optimizations is not enough", text = "Обход экономии батареи",
isChecked = isInvincibilityEnabled, isChecked = isInvincibilityEnabled,
onCheckedChange = { isInvincibilityEnabled = it } onCheckedChange = { isInvincibilityEnabled = it }
) )
@@ -199,8 +199,8 @@ fun OtherSettings() {
SettingsEntryGroupText(title = "PROXY") SettingsEntryGroupText(title = "PROXY")
SwitchSettingEntry( SwitchSettingEntry(
title = "HTTP Proxy", title = "Proxy",
text = "Enable HTTP Proxy", text = "Включить proxy",
isChecked = isProxyEnabled, isChecked = isProxyEnabled,
onCheckedChange = { isProxyEnabled = it } onCheckedChange = { isProxyEnabled = it }
) )
@@ -211,7 +211,7 @@ fun OtherSettings() {
selectedValue = proxyMode, onValueSelected = {proxyMode = it}) selectedValue = proxyMode, onValueSelected = {proxyMode = it})
TextDialogSettingEntry( TextDialogSettingEntry(
title = "Хост", title = "Хост",
text = "Введите http хост", text = "Введите хост",
currentText = proxyHost, currentText = proxyHost,
onTextSave = { proxyHost = it }) onTextSave = { proxyHost = it })
TextDialogSettingEntry( TextDialogSettingEntry(

View File

@@ -23,7 +23,7 @@ data class MusicShelfRenderer(
?: emptyList()) to ?: emptyList()) to
(musicResponsiveListItemRenderer (musicResponsiveListItemRenderer
?.flexColumns ?.flexColumns
?.lastOrNull() ?.getOrNull(1)
?.musicResponsiveListItemFlexColumnRenderer ?.musicResponsiveListItemFlexColumnRenderer
?.text ?.text
?.splitBySeparator() ?.splitBySeparator()