diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ecc5a36..5120b37 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "com.platon.easymusicandroid" minSdk = 23 targetSdk = 34 - versionCode = 3 - versionName = "1.2" + versionCode = 4 + versionName = "2.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -51,14 +51,17 @@ android { } dependencies { - implementation("androidx.activity:activity-compose:1.7.0") - implementation("androidx.activity:activity:1.9.3") - implementation("androidx.compose.material3:material3:1.0.1") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.0") - implementation("com.google.android.exoplayer:exoplayer:2.19.1") - implementation ("com.google.android.material:material:1.9.0") - implementation ("junit:junit:4.13.2") - implementation ("androidx.test.ext:junit:1.1.5") - implementation ("androidx.test.espresso:espresso-core:3.5.1") - implementation ("androidx.media:media:1.6.0") + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.activity) + implementation(libs.material3) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.exoplayer) + implementation(libs.material) + implementation(libs.junit) + implementation(libs.androidx.junit) + implementation(libs.androidx.espresso.core) + implementation(libs.androidx.media) + implementation(libs.androidx.foundation) + implementation(libs.androidx.ui.text.google.fonts) + implementation(libs.androidx.material.icons.extended) } \ No newline at end of file diff --git a/app/src/main/java/com/platon/easymusicandroid/MainActivity.kt b/app/src/main/java/com/platon/easymusicandroid/MainActivity.kt index eff9a79..23894a3 100644 --- a/app/src/main/java/com/platon/easymusicandroid/MainActivity.kt +++ b/app/src/main/java/com/platon/easymusicandroid/MainActivity.kt @@ -1,22 +1,21 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + package com.platon.easymusicandroid import android.annotation.SuppressLint import android.content.Intent import android.net.Uri +import android.os.Build import android.os.Bundle import android.view.View import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.ArrowForward -import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.Pause import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -31,7 +30,26 @@ import androidx.compose.ui.unit.sp import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.MediaItem import androidx.activity.enableEdgeToEdge +import androidx.annotation.RequiresApi +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.ui.focus.focusModifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.foundation.pager.* +import androidx.compose.material.icons.Icons +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.geometry.times +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontVariation +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.lerp +import androidx.compose.ui.unit.times +import kotlin.math.* data class Station( val name: String, @@ -40,7 +58,6 @@ data class Station( val gradientColors: List ) - private val stations = listOf( Station( name = "Tabris FM", @@ -79,6 +96,7 @@ class MainActivity : ComponentActivity() { private var isPlaying = mutableStateOf(false) private var currentStationIndex = mutableStateOf(0) + @RequiresApi(Build.VERSION_CODES.Q) override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) @@ -92,7 +110,7 @@ class MainActivity : ComponentActivity() { startService(musicServiceIntent) setContent { - EasyMusicApp( + MusicUI( stations = stations, currentStationIndex = currentStationIndex.value, isPlaying = isPlaying.value, @@ -100,15 +118,15 @@ class MainActivity : ComponentActivity() { if (isPlaying.value) { player.pause() isPlaying.value = false - } else { + } + else { playStation(stations[currentStationIndex.value]) isPlaying.value = true } }, onStationChange = { newIndex -> currentStationIndex.value = newIndex - playStation(stations[newIndex]) - isPlaying.value = true + if (isPlaying.value) playStation(stations[newIndex]) } ) } @@ -127,128 +145,164 @@ class MainActivity : ComponentActivity() { } } +@OptIn(ExperimentalTextApi::class) @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") -@OptIn(ExperimentalMaterial3Api::class) @Composable -fun EasyMusicApp( +fun MusicUI( stations: List, currentStationIndex: Int, isPlaying: Boolean, onPlayPauseToggle: () -> Unit, onStationChange: (Int) -> Unit ) { - val station = stations[currentStationIndex] + val pagerState = rememberPagerState(pageCount = { stations.size }) + val pagesSizes = List( + stations.size, + {i -> + 48.dp + (350 - 48).dp * min(max(1f - pagerState.currentPageOffsetFraction * 2f, 0f), 1f) + } + ) + + val robotoFlex = + Font( + R.font.robotoflex, + variationSettings = FontVariation.Settings( + FontVariation.weight(700), + FontVariation.width(150f) + ) + ) + + val animatedStationGradient = listOf( + animateColorAsState( + targetValue = stations[pagerState.currentPage].gradientColors[0], // Изменяем прозрачность + animationSpec = tween(durationMillis = 300), + label = "gradientcolorone" // Добавляем анимацию + ).value, + animateColorAsState( + targetValue = stations[pagerState.currentPage].gradientColors[1], // Изменяем прозрачность + animationSpec = tween(durationMillis = 300), + label = "gradientcolortwo" // Добавляем анимацию + ).value + ) + + val bottomSheetState = rememberStandardBottomSheetState(initialValue = SheetValue.PartiallyExpanded) + val scaffoldState = rememberBottomSheetScaffoldState(bottomSheetState = bottomSheetState) + val context = LocalContext.current val intent = remember { Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/easymusicplatonoferon/")) } - Scaffold( - modifier = Modifier - .background( - brush = Brush.verticalGradient( - colors = station.gradientColors - ) + BottomSheetScaffold( + scaffoldState = scaffoldState, + modifier = Modifier.background( + brush = Brush.verticalGradient( + colors = animatedStationGradient ) - .safeDrawingPadding(), - containerColor = Color.Transparent - ) { - Box { - TextButton( - onClick = { - try { - context.startActivity(intent) - } - catch (e: Exception) { - Toast.makeText(context, "Ошибка. Ищи вручную:\n@easymusicplatonoferon", Toast.LENGTH_LONG).show() - } - }, - colors = ButtonDefaults.buttonColors( - containerColor = Color.Transparent, - contentColor = Color(0xA0FFFFFF) - ), - modifier = Modifier.padding(horizontal = 20.dp) - ) { - Text(text = "тг чат") - } - + ), + contentColor = Color.Transparent, + containerColor = Color.Transparent, + sheetContainerColor = Color(0xff1f1f1f), + sheetContent = { Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), + modifier = Modifier.fillMaxWidth().height(300.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - Spacer(modifier = Modifier.height(16.dp)) - - Text( - text = station.name, - style = MaterialTheme.typography.titleLarge.copy( - fontSize = 28.sp, - fontWeight = FontWeight.Bold, - fontFamily = FontFamily.SansSerif - ), - color = Color.White, - modifier = Modifier.padding(bottom = 8.dp) - ) - - Text( - text = station.description, - style = MaterialTheme.typography.bodyMedium.copy( - fontFamily = FontFamily.SansSerif - ), - color = Color.White, - textAlign = TextAlign.Center, - modifier = Modifier.padding(horizontal = 16.dp) - ) - - Spacer(modifier = Modifier.weight(1f)) - - Text( - text = if (isPlaying) "Now Playing: Streaming" else "Now Playing: Paused", - color = Color.White, - modifier = Modifier.padding(bottom = 32.dp) - ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + Button( + onClick = { onPlayPauseToggle() }, + shape = CircleShape, + modifier = Modifier.size(96.dp), + colors = ButtonColors( + Color.White, + Color(0xff1f1f1f), + Color.White, + Color(0xff1f1f1f) + ) ) { - IconButton(onClick = { - if (currentStationIndex > 0) onStationChange(currentStationIndex - 1) - }) { - Icon( - imageVector = Icons.Filled.ArrowBack, - contentDescription = "Previous", - tint = Color.White - ) - } - - Box( + Icon( + imageVector = if (isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow, + contentDescription = "Play", + tint = Color(0xff1f1f1f), + modifier = Modifier.size(36.dp) + ) + } + Spacer(modifier = Modifier.height(72.dp)) + Button( + onClick = { + try { + context.startActivity(intent) + } + catch (e: Exception) { + Toast.makeText(context, "Ошибка. Ищи вручную:\n@easymusicplatonoferon", Toast.LENGTH_LONG).show() + } + }, + colors = ButtonColors( + Color.White, + Color(0xff1f1f1f), + Color.White, + Color(0xff1f1f1f) + ) + ) { + Text( + text = "ТГ чат", + color = Color(0xff1f1f1f) + ) + } + } + }, + sheetPeekHeight = 200.dp + ) { + Box { + VerticalPager( + state = pagerState, + contentPadding = PaddingValues(bottom = 300.dp, + top = WindowInsets.systemBars.asPaddingValues(LocalDensity.current) + .calculateTopPadding() + ), + beyondViewportPageCount = pagerState.pageCount + ) { page -> + Column { + Text( + text = stations[page].name, + fontFamily = FontFamily(robotoFlex), + fontSize = 40.sp, + color = Color.White, modifier = Modifier - .size(80.dp) - .background(Color.White, CircleShape) - .clickable { onPlayPauseToggle() }, - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = if (isPlaying) Icons.Filled.Close else Icons.Filled.PlayArrow, - contentDescription = if (isPlaying) "Pause" else "Play", - tint = Color.Red - ) - } - - IconButton(onClick = { - if (currentStationIndex < stations.size - 1) onStationChange( - currentStationIndex + 1 - ) - }) { - Icon( - imageVector = Icons.Filled.ArrowForward, - contentDescription = "Next", - tint = Color.White - ) - } + .fillMaxWidth() + .graphicsLayer { + if (pagerState.offsetForPage(page) >= 0) { + translationY = pagerState.offsetForPage(page) * (pagerState.layoutInfo.pageSize - 40.sp.toPx() - 4.dp.toPx()) + } + else { + translationY = minOf(0f, pagerState.offsetForPage(page) + 1f) * (pagerState.layoutInfo.pageSize - 40.sp.toPx() - 4.dp.toPx()) + } + } + .alpha(maxOf(0f, (1f - abs(pagerState.offsetForPage(page))) / 2) + 0.5f) + .padding(horizontal = 8.dp, vertical = 2.dp) + ) + Text( + text = stations[page].description, + fontSize = 18.sp, + color = Color.White, + modifier = Modifier + .fillMaxWidth() + .graphicsLayer { + if (pagerState.offsetForPage(page) >= 0) { + translationY = pagerState.offsetForPage(page) * (pagerState.layoutInfo.pageSize - 40.sp.toPx() - 4.dp.toPx()) + } + else { + translationY = minOf(0f, pagerState.offsetForPage(page) + 1f) * (pagerState.layoutInfo.pageSize - 40.sp.toPx() - 4.dp.toPx()) + } + } + .alpha(maxOf(0f, 1f - abs(pagerState.offsetForPage(page)))) + .padding(horizontal = 8.dp, vertical = 12.dp) + ) } } } } -} \ No newline at end of file + if (pagerState.settledPage != currentStationIndex) { + onStationChange(pagerState.settledPage) + } +} + +//следующее нагло стырено из инета, да, я сам бы до такого не додумался, извините +fun PagerState.offsetForPage(page: Int) = (currentPage - page) + currentPageOffsetFraction diff --git a/app/src/main/res/font/robotoflex.ttf b/app/src/main/res/font/robotoflex.ttf new file mode 100644 index 0000000..2e5c2a2 Binary files /dev/null and b/app/src/main/res/font/robotoflex.ttf differ diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000..2fabaf2 --- /dev/null +++ b/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,8 @@ + + + Радио, на котором крутятся отобранные, нормальные, приятные треки многих жанров. Плейлист пополняется ежедневно в районе от 18:00 до 22:00. + Радио для концентрации, успокоения, наслаждения. На радио играет спокойная музыка различных исполнителей. Идеально подойдет для ночных прогулок и поездок. + Радио для тех, кого обычные треки не устраивают и они слушают альтернативные жанры. На радио играют треки таких исполнителей как Dekma, Кишлак и тд. + Радио, на котором собрана вся музыка, все жанры, характеры и исполнители. Идеально подойдет для меломанов. + Полная сборная солянка от красивых и уникальных жанров до рофл гей-ремиксов и блатных треков, часто проводятся подкасты на разные темы в прямом эфире от простого общения до политики. + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e6a7a29..7528d7e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,8 @@ AeroCast + Radio, where plays selected, normal, pleasant tracks of many genres. The playlist is updated daily between 18:00 and 22:00. + Radio for concentration, calmness, enjoyment. On radio is playing calm music by various artists. Perfect for night walks and trips. + Radio for those who are not satisfied with the usual tracks and who listen to alternative genres. The radio plays tracks by artists such as Dekma, Kishlak, etc. + Radio that contains all the music, all genres, characters, and performers. Perfect for music lovers. + A complete hodgepodge of beautiful and unique genres to rofl gay remixes and thug tracks, often live podcasts on various topics from simple communication to politics. \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e07f2ea..5793081 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,8 @@ [versions] -agp = "8.6.1" +activity = "1.9.3" +agp = "8.7.3" +exoplayer = "2.19.1" +foundation = "1.7.6" kotlin = "1.9.0" coreKtx = "1.15.0" junit = "4.13.2" @@ -8,9 +11,20 @@ espressoCore = "3.6.1" lifecycleRuntimeKtx = "2.8.7" activityCompose = "1.9.3" composeBom = "2024.04.01" +material = "1.12.0" +material3 = "1.3.1" +media = "1.7.0" +pagingCommonAndroid = "3.3.5" +uiTextGoogleFonts = "1.7.6" [libraries] +androidx-activity = { module = "androidx.activity:activity", version.ref = "activity" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "foundation" } +androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" } +androidx-media = { module = "androidx.media:media", version.ref = "media" } +androidx-ui-text-google-fonts = { module = "androidx.compose.ui:ui-text-google-fonts", version.ref = "uiTextGoogleFonts" } +exoplayer = { module = "com.google.android.exoplayer:exoplayer", version.ref = "exoplayer" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } @@ -24,6 +38,9 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-paging-common-android = { group = "androidx.paging", name = "paging-common-android", version.ref = "pagingCommonAndroid" } +material = { module = "com.google.android.material:material", version.ref = "material" } +material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }