diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2833c70..38dbc38 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -75,6 +75,7 @@ dependencies { implementation(projects.composeReordering) + implementation(libs.compose.activity) implementation(libs.compose.foundation) implementation(libs.compose.ui) diff --git a/app/src/main/kotlin/it/hamy/muza/MainActivity.kt b/app/src/main/kotlin/it/hamy/muza/MainActivity.kt index 79e1066..9ec32d5 100644 --- a/app/src/main/kotlin/it/hamy/muza/MainActivity.kt +++ b/app/src/main/kotlin/it/hamy/muza/MainActivity.kt @@ -128,6 +128,7 @@ class MainActivity : ComponentActivity(), PersistMapOwner { override lateinit var persistMap: PersistMap + override fun onStart() { super.onStart() bindService(intent(), serviceConnection, Context.BIND_AUTO_CREATE) @@ -168,7 +169,7 @@ class MainActivity : ComponentActivity(), PersistMapOwner { val colorPaletteName = getEnum(colorPaletteNameKey, ColorPaletteName.Dynamic) val colorPaletteMode = getEnum(colorPaletteModeKey, ColorPaletteMode.System) val thumbnailRoundness = - getEnum(thumbnailRoundnessKey, ThumbnailRoundness.Light) + getEnum(thumbnailRoundnessKey, ThumbnailRoundness.Слабое) val useSystemFont = getBoolean(useSystemFontKey, false) val applyFontPadding = getBoolean(applyFontPaddingKey, false) @@ -267,7 +268,7 @@ class MainActivity : ComponentActivity(), PersistMapOwner { thumbnailRoundnessKey -> { val thumbnailRoundness = - sharedPreferences.getEnum(key, ThumbnailRoundness.Light) + sharedPreferences.getEnum(key, ThumbnailRoundness.Слабое) appearance = appearance.copy( thumbnailShape = thumbnailRoundness.shape() diff --git a/app/src/main/kotlin/it/hamy/muza/enums/ThumbnailRoundness.kt b/app/src/main/kotlin/it/hamy/muza/enums/ThumbnailRoundness.kt index 54fb97b..59c6fc3 100644 --- a/app/src/main/kotlin/it/hamy/muza/enums/ThumbnailRoundness.kt +++ b/app/src/main/kotlin/it/hamy/muza/enums/ThumbnailRoundness.kt @@ -6,17 +6,19 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp enum class ThumbnailRoundness { - None, - Light, - Medium, - Heavy; + Отключено, + Слабое, + Среднее, + Сильное, + Максимальное; fun shape(): Shape { return when (this) { - None -> RectangleShape - Light -> RoundedCornerShape(2.dp) - Medium -> RoundedCornerShape(4.dp) - Heavy -> RoundedCornerShape(8.dp) + Отключено -> RectangleShape + Слабое -> RoundedCornerShape(2.dp) + Среднее -> RoundedCornerShape(4.dp) + Сильное -> RoundedCornerShape(8.dp) + Максимальное -> RoundedCornerShape(14.dp) } } } diff --git a/app/src/main/kotlin/it/hamy/muza/ui/screens/artist/ArtistScreen.kt b/app/src/main/kotlin/it/hamy/muza/ui/screens/artist/ArtistScreen.kt index b4d5bb6..f58fd34 100644 --- a/app/src/main/kotlin/it/hamy/muza/ui/screens/artist/ArtistScreen.kt +++ b/app/src/main/kotlin/it/hamy/muza/ui/screens/artist/ArtistScreen.kt @@ -176,11 +176,11 @@ fun ArtistScreen(browseId: String) { tabIndex = tabIndex, onTabChanged = { tabIndex = it }, tabColumnContent = { Item -> - Item(0, "Overview", R.drawable.sparkles) - Item(1, "Songs", R.drawable.musical_notes) - Item(2, "Albums", R.drawable.disc) - Item(3, "Singles", R.drawable.disc) - Item(4, "Library", R.drawable.library) + Item(0, "Обзор", R.drawable.sparkles) + Item(1, "Песни", R.drawable.musical_notes) + Item(2, "Альбомы", R.drawable.disc) + Item(3, "Синглы", R.drawable.disc) + Item(4, "Библиотека", R.drawable.library) }, ) { currentTabIndex -> saveableStateHolder.SaveableStateProvider(key = currentTabIndex) { @@ -267,7 +267,7 @@ fun ArtistScreen(browseId: String) { ItemsPage( tag = "artist/$browseId/albums", headerContent = headerContent, - emptyItemsText = "This artist didn't release any album", + emptyItemsText = "Исполнитель не выпустил ни одного альбома", itemsPageProvider = artistPage?.let { ({ continuation -> continuation?.let { @@ -317,7 +317,7 @@ fun ArtistScreen(browseId: String) { ItemsPage( tag = "artist/$browseId/singles", headerContent = headerContent, - emptyItemsText = "This artist didn't release any single", + emptyItemsText = "Исполнитель не выпустил ни одного сингла", itemsPageProvider = artistPage?.let { ({ continuation -> continuation?.let { diff --git a/app/src/main/kotlin/it/hamy/muza/ui/screens/home/HomePlaylists.kt b/app/src/main/kotlin/it/hamy/muza/ui/screens/home/HomePlaylists.kt index 6cb1de5..8ba49a0 100644 --- a/app/src/main/kotlin/it/hamy/muza/ui/screens/home/HomePlaylists.kt +++ b/app/src/main/kotlin/it/hamy/muza/ui/screens/home/HomePlaylists.kt @@ -53,9 +53,11 @@ import it.hamy.muza.utils.playlistSortByKey import it.hamy.muza.utils.playlistSortOrderKey import it.hamy.muza.utils.rememberPreference + @ExperimentalAnimationApi @ExperimentalFoundationApi @Composable + fun HomePlaylists( onBuiltInPlaylist: (BuiltInPlaylist) -> Unit, onPlaylistClick: (Playlist) -> Unit, @@ -67,6 +69,8 @@ fun HomePlaylists( mutableStateOf(false) } + + if (isCreatingANewPlaylist) { TextFieldDialog( hintText = "Введите название плейлиста", diff --git a/app/src/main/kotlin/it/hamy/muza/ui/screens/player/Controls.kt b/app/src/main/kotlin/it/hamy/muza/ui/screens/player/Controls.kt index c49d80f..5fe37bb 100644 --- a/app/src/main/kotlin/it/hamy/muza/ui/screens/player/Controls.kt +++ b/app/src/main/kotlin/it/hamy/muza/ui/screens/player/Controls.kt @@ -53,6 +53,10 @@ import it.hamy.muza.utils.secondary import it.hamy.muza.utils.semiBold import it.hamy.muza.utils.trackLoopEnabledKey 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 fun Controls( @@ -79,8 +83,14 @@ fun Controls( mutableStateOf(null) } - LaunchedEffect(mediaId) { - Database.likedAt(mediaId).distinctUntilChanged().collect { likedAt = it } + var artistsInfo: List? by remember { mutableStateOf(null) } + + 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") @@ -113,7 +123,11 @@ fun Controls( text = artist ?: "", style = typography.s.semiBold.secondary, maxLines = 1, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, + modifier = Modifier.clickable { + val goTo = artistRoute::global + goTo(artistsInfo?.get(0)?.id) + } ) Spacer( diff --git a/app/src/main/kotlin/it/hamy/muza/ui/screens/player/Lyrics.kt b/app/src/main/kotlin/it/hamy/muza/ui/screens/player/Lyrics.kt index 779efb9..37b208c 100644 --- a/app/src/main/kotlin/it/hamy/muza/ui/screens/player/Lyrics.kt +++ b/app/src/main/kotlin/it/hamy/muza/ui/screens/player/Lyrics.kt @@ -79,6 +79,11 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.isActive 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 fun Lyrics( @@ -91,6 +96,10 @@ fun Lyrics( ensureSongInserted: () -> Unit, modifier: Modifier = Modifier ) { + + + + AnimatedVisibility( visible = isDisplayed, enter = fadeIn(), @@ -117,11 +126,15 @@ fun Lyrics( mutableStateOf(false) } + + val clipboardManager = ContextCompat.getSystemService(context, ClipboardManager::class.java) + LaunchedEffect(mediaId, isShowingSynchronizedLyrics) { withContext(Dispatchers.IO) { Database.lyrics(mediaId).collect { if (isShowingSynchronizedLyrics && it?.synced == null) { val mediaMetadata = mediaMetadataProvider() + var duration = withContext(Dispatchers.Main) { durationProvider() } @@ -352,6 +365,15 @@ fun Lyrics( } ) + MenuEntry( + icon = R.drawable.text, + text = "Скопировать текст", + onClick = { + menuState.hide() + clipboardManager?.setPrimaryClip(ClipData.newPlainText("Lyrics", lyrics?.fixed)) + } + ) + MenuEntry( icon = R.drawable.search, text = "Искать текст в интернете", diff --git a/app/src/main/kotlin/it/hamy/muza/ui/screens/player/Queue.kt b/app/src/main/kotlin/it/hamy/muza/ui/screens/player/Queue.kt index a554e75..b997880 100644 --- a/app/src/main/kotlin/it/hamy/muza/ui/screens/player/Queue.kt +++ b/app/src/main/kotlin/it/hamy/muza/ui/screens/player/Queue.kt @@ -6,6 +6,8 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ContentTransform import androidx.compose.animation.ExperimentalAnimationApi 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.updateTransition import androidx.compose.animation.fadeIn @@ -15,6 +17,9 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable 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.BoxScope 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.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding 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.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember 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.clip import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.media3.common.MediaItem 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.windows import kotlinx.coroutines.launch - -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 kotlinx.coroutines.runBlocking import kotlin.math.roundToInt @ExperimentalFoundationApi @@ -188,6 +192,10 @@ fun Queue( val musicBarsTransition = updateTransition(targetState = mediaItemIndex, label = "") + val deleteHistory = remember { + mutableStateListOf() + } + Column { Box( modifier = Modifier @@ -209,8 +217,7 @@ fun Queue( key = { it.uid.hashCode() } ) { window -> val isPlayingThisMediaItem = mediaItemIndex == window.firstPeriodIndex - var offsetX by remember { mutableStateOf(0f) } - + val offsetX = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) } SongItem( song = window.mediaItem, thumbnailSizePx = thumbnailSizePx, @@ -291,23 +298,43 @@ fun Queue( reorderingState = reorderingState, index = window.firstPeriodIndex ) - .draggable( orientation = Orientation.Horizontal, state = rememberDraggableState(onDelta = { delta -> if (isPlayingThisMediaItem) return@rememberDraggableState - offsetX += delta + runBlocking { + offsetX.snapTo(offsetX.value + Offset(delta, 0f)) + } }), - onDragStopped = { velocity -> - if ((offsetX <= -300.0f && velocity <= -3000.0f) || (offsetX >= 300.0f && velocity >= 3000.0f)) { - binder.player.removeMediaItem(window.firstPeriodIndex) + onDragStopped = { _ -> + if (offsetX.value.x >= 200.0f || offsetX.value.x <= -200.0f) { + 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 { - 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) ) { BasicText( - text = "${windows.size} песни", + text = "${windows.size}", style = typography.xxs.medium, modifier = Modifier .background( @@ -386,7 +413,7 @@ fun Queue( .animateContentSize() ) { BasicText( - text = "Повтор очереди ", + text = "Повтор ", style = typography.xxs.medium, ) @@ -402,7 +429,7 @@ fun Queue( } ) { BasicText( - text = if (it) "ВКЛ." else "ВЫКЛ.", + text = if (it) "вкл." else "откл.", style = typography.xxs.medium, ) } @@ -410,4 +437,4 @@ fun Queue( } } } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/it/hamy/muza/ui/screens/settings/AppearanceSettings.kt b/app/src/main/kotlin/it/hamy/muza/ui/screens/settings/AppearanceSettings.kt index 915877a..8095698 100644 --- a/app/src/main/kotlin/it/hamy/muza/ui/screens/settings/AppearanceSettings.kt +++ b/app/src/main/kotlin/it/hamy/muza/ui/screens/settings/AppearanceSettings.kt @@ -48,7 +48,7 @@ fun AppearanceSettings() { var colorPaletteMode by rememberPreference(colorPaletteModeKey, ColorPaletteMode.System) var thumbnailRoundness by rememberPreference( thumbnailRoundnessKey, - ThumbnailRoundness.Light + ThumbnailRoundness.Слабое ) var useSystemFont by rememberPreference(useSystemFontKey, false) var applyFontPadding by rememberPreference(applyFontPaddingKey, false) diff --git a/app/src/main/kotlin/it/hamy/muza/ui/screens/settings/OtherSettings.kt b/app/src/main/kotlin/it/hamy/muza/ui/screens/settings/OtherSettings.kt index d238a67..94c9605 100644 --- a/app/src/main/kotlin/it/hamy/muza/ui/screens/settings/OtherSettings.kt +++ b/app/src/main/kotlin/it/hamy/muza/ui/screens/settings/OtherSettings.kt @@ -190,7 +190,7 @@ fun OtherSettings() { SwitchSettingEntry( title = "Invincible service", - text = "When turning off battery optimizations is not enough", + text = "Обход экономии батареи", isChecked = isInvincibilityEnabled, onCheckedChange = { isInvincibilityEnabled = it } ) @@ -199,8 +199,8 @@ fun OtherSettings() { SettingsEntryGroupText(title = "PROXY") SwitchSettingEntry( - title = "HTTP Proxy", - text = "Enable HTTP Proxy", + title = "Proxy", + text = "Включить proxy", isChecked = isProxyEnabled, onCheckedChange = { isProxyEnabled = it } ) @@ -211,7 +211,7 @@ fun OtherSettings() { selectedValue = proxyMode, onValueSelected = {proxyMode = it}) TextDialogSettingEntry( title = "Хост", - text = "Введите http хост", + text = "Введите хост", currentText = proxyHost, onTextSave = { proxyHost = it }) TextDialogSettingEntry( diff --git a/innertube/src/main/kotlin/it/hamy/innertube/models/MusicShelfRenderer.kt b/innertube/src/main/kotlin/it/hamy/innertube/models/MusicShelfRenderer.kt index 31bf7eb..1b60e81 100644 --- a/innertube/src/main/kotlin/it/hamy/innertube/models/MusicShelfRenderer.kt +++ b/innertube/src/main/kotlin/it/hamy/innertube/models/MusicShelfRenderer.kt @@ -23,7 +23,7 @@ data class MusicShelfRenderer( ?: emptyList()) to (musicResponsiveListItemRenderer ?.flexColumns - ?.lastOrNull() + ?.getOrNull(1) ?.musicResponsiveListItemFlexColumnRenderer ?.text ?.splitBySeparator()