diff --git a/app/build.gradle b/app/build.gradle index 50de0c9..766d631 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'com.google.devtools.ksp' + id 'org.jetbrains.kotlin.plugin.compose' } apply plugin: 'kotlin-android' @@ -37,6 +38,9 @@ android { jvmTarget = '21' } namespace 'phone.vishnu.dialogmusicplayer' + buildFeatures { + compose true + } } dependencies { @@ -51,6 +55,17 @@ dependencies { implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.media:media:1.7.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7' + implementation 'androidx.activity:activity-compose:1.9.3' + implementation platform('androidx.compose:compose-bom:2024.04.01') + implementation 'androidx.compose.ui:ui' + implementation 'androidx.compose.ui:ui-graphics' + implementation 'androidx.compose.ui:ui-tooling-preview' + implementation 'androidx.compose.material3:material3' + androidTestImplementation platform('androidx.compose:compose-bom:2024.04.01') + androidTestImplementation 'androidx.compose.ui:ui-test-junit4' + debugImplementation 'androidx.compose.ui:ui-tooling' + debugImplementation 'androidx.compose.ui:ui-test-manifest' def room_version = "2.6.1" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 75f4c34..a167f99 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,8 +1,9 @@ - + @@ -12,16 +13,16 @@ + android:icon="@drawable/ic_icon" + android:label="@string/app_name" + android:requestLegacyExternalStorage="true" + android:supportsRtl="true" + android:theme="@style/AppTheme"> + android:name=".MainActivity" + android:exported="true" + android:launchMode="singleTask"> @@ -46,15 +47,27 @@ + + + + + + + + + + android:name=".MediaPlaybackService" + android:foregroundServiceType="mediaPlayback" + android:exported="false"> - \ No newline at end of file diff --git a/app/src/main/java/phone/vishnu/dialogmusicplayer/AudioUtils.java b/app/src/main/java/phone/vishnu/dialogmusicplayer/AudioUtils.java index 12cdfda..07cd0d9 100644 --- a/app/src/main/java/phone/vishnu/dialogmusicplayer/AudioUtils.java +++ b/app/src/main/java/phone/vishnu/dialogmusicplayer/AudioUtils.java @@ -299,4 +299,18 @@ private static String getUriToDrawable(@NonNull Context context, @AnyRes int dra + '/' + context.getResources().getResourceEntryName(drawableId); } + + static String getFormattedTime(long millis, long totalDuration, boolean isTimeReversed) { + + long minutes = (millis / 1000) / 60; + long seconds = (millis / 1000) % 60; + + String secondsStr = Long.toString(seconds); + + String secs = (secondsStr.length() >= 2) ? secondsStr.substring(0, 2) : "0" + secondsStr; + + if (!isTimeReversed) return minutes + ":" + secs; + + return "-" + getFormattedTime(totalDuration - millis, totalDuration, false); + } } diff --git a/app/src/main/java/phone/vishnu/dialogmusicplayer/ComposeActivity.kt b/app/src/main/java/phone/vishnu/dialogmusicplayer/ComposeActivity.kt new file mode 100644 index 0000000..57688c1 --- /dev/null +++ b/app/src/main/java/phone/vishnu/dialogmusicplayer/ComposeActivity.kt @@ -0,0 +1,306 @@ +package phone.vishnu.dialogmusicplayer + +import android.annotation.SuppressLint +import android.content.Context +import android.media.MediaMetadata +import android.net.Uri +import android.os.Bundle +import android.support.v4.media.MediaMetadataCompat +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +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.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import phone.vishnu.dialogmusicplayer.ui.theme.DialogMusicPlayerTheme + +private val poppinsFont = FontFamily(Font(R.font.poppins)) + +class ComposeActivity : ComponentActivity() { + + @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + + setContent { + DialogMusicPlayerTheme { + Scaffold( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp), + containerColor = Color.Transparent, + ) { _ -> + + PlayerUI( + applicationContext, + Audio( + -1, + MediaMetadataCompat.Builder().putString( + MediaMetadata.METADATA_KEY_MEDIA_ID, + "-1", + ).putString( + MediaMetadata.METADATA_KEY_DISPLAY_TITLE, + "Dreaming On", + ).putString( + MediaMetadata.METADATA_KEY_TITLE, + "Dreaming On", + ).putString( + MediaMetadata.METADATA_KEY_ARTIST, + "NEFEX", + ).putLong( + MediaMetadata.METADATA_KEY_DURATION, + 100000, + ).putBitmap( + MediaMetadata.METADATA_KEY_ALBUM_ART, + null, + ).build(), + 100000, + Uri.EMPTY, + ), + ) + } + } + } + } +} + +@Composable +private fun PlayerUI(context: Context, audio: Audio) { + var sliderState by remember { + mutableFloatStateOf( + 0f, + ) + } + var elapsed by remember { + mutableLongStateOf( + 0, + ) + } + val totalDuration = audio.mediaMetadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION) + + Column( + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Spacer(Modifier.weight(1f)) + + Image( + modifier = Modifier + .align(alignment = Alignment.CenterHorizontally) + .padding(12.dp) + .size(64.dp) + .aspectRatio(1f), + alignment = Alignment.Center, + painter = painterResource(R.drawable.ic_music_note), + contentDescription = "", + ) + + Column( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(0.35f) + .clip(RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)) + .background(MaterialTheme.colorScheme.background) + .padding(8.dp), + ) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + text = context.getString(R.string.app_name), + textAlign = TextAlign.Center, + letterSpacing = 1.1.sp, + color = MaterialTheme.colorScheme.onBackground, + fontSize = 20.sp, + fontWeight = FontWeight.SemiBold, + ) + + Text( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp, 4.dp, 8.dp, 1.dp), + fontFamily = poppinsFont, + text = audio.mediaMetadata.getText(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE) + .toString(), + maxLines = 1, + color = MaterialTheme.colorScheme.onBackground, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + ) + + Text( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp, 1.dp, 8.dp, 2.dp), + fontFamily = poppinsFont, + text = audio.mediaMetadata.getText(MediaMetadataCompat.METADATA_KEY_ARTIST) + .toString(), + letterSpacing = 1.08.sp, + maxLines = 1, + color = MaterialTheme.colorScheme.onBackground, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold, + ) + + Slider( + modifier = Modifier.padding( + vertical = 16.dp, + horizontal = 8.dp, + ), + value = sliderState, + onValueChange = { sliderState = it }, + colors = SliderDefaults.colors( + thumbColor = MaterialTheme.colorScheme.secondary, + activeTrackColor = MaterialTheme.colorScheme.secondary, + inactiveTrackColor = MaterialTheme.colorScheme.secondaryContainer, + ), + valueRange = 0f..totalDuration.toFloat(), + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + modifier = Modifier + .padding(start = 12.dp, end = 4.dp), + text = AudioUtils.getFormattedTime(elapsed, totalDuration, false), + color = MaterialTheme.colorScheme.onSurface, + fontSize = 12.sp, + ) + + Spacer(Modifier.weight(1f)) + + Text( + modifier = Modifier + .padding(start = 4.dp, end = 12.dp), + text = AudioUtils.getFormattedTime(totalDuration, totalDuration, false), + color = MaterialTheme.colorScheme.onSurface, + fontSize = 12.sp, + ) + } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + TextButton( + modifier = Modifier.padding(8.dp, 4.dp, 4.dp, 4.dp), + onClick = {}, + content = { + Text( + text = context.getString(R.string.one_x), + color = MaterialTheme.colorScheme.onSurface, + fontSize = 16.sp, + ) + }, + ) + + Spacer(Modifier.weight(1f)) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + IconButton( + modifier = Modifier + .padding(8.dp, 4.dp, 8.dp, 4.dp) + .size(36.dp), + onClick = {}, + ) { + Icon( + modifier = Modifier + .size(36.dp), + painter = painterResource(R.drawable.ic_rewind), + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = "rewind icon", + ) + } + + IconButton( + modifier = Modifier + .padding(4.dp, 2.dp, 4.dp, 4.dp) + .size(64.dp), + onClick = {}, + ) { + Icon( + modifier = Modifier + .size(64.dp), + painter = painterResource(R.drawable.ic_play), + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = "play pause icon", + ) + } + + IconButton( + modifier = Modifier + .padding(8.dp, 4.dp, 8.dp, 4.dp) + .size(36.dp), + onClick = {}, + ) { + Icon( + modifier = Modifier + .size(36.dp), + painter = painterResource(R.drawable.ic_seek), + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = "seek icon", + ) + } + } + + Spacer(Modifier.weight(1f)) + + IconButton( + modifier = Modifier + .padding(4.dp, 4.dp, 8.dp, 4.dp) + .size(36.dp), + onClick = {}, + ) { + Icon( + modifier = Modifier + .size(36.dp), + painter = painterResource(R.drawable.ic_repeat), + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = "seek icon", + ) + } + } + } + } +} diff --git a/app/src/main/java/phone/vishnu/dialogmusicplayer/ui/theme/Color.kt b/app/src/main/java/phone/vishnu/dialogmusicplayer/ui/theme/Color.kt new file mode 100644 index 0000000..569edc5 --- /dev/null +++ b/app/src/main/java/phone/vishnu/dialogmusicplayer/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package phone.vishnu.dialogmusicplayer.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) diff --git a/app/src/main/java/phone/vishnu/dialogmusicplayer/ui/theme/Theme.kt b/app/src/main/java/phone/vishnu/dialogmusicplayer/ui/theme/Theme.kt new file mode 100644 index 0000000..de11b30 --- /dev/null +++ b/app/src/main/java/phone/vishnu/dialogmusicplayer/ui/theme/Theme.kt @@ -0,0 +1,57 @@ +package phone.vishnu.dialogmusicplayer.ui.theme + +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80, +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40, + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun DialogMusicPlayerTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit, +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content, + ) +} diff --git a/app/src/main/java/phone/vishnu/dialogmusicplayer/ui/theme/Type.kt b/app/src/main/java/phone/vishnu/dialogmusicplayer/ui/theme/Type.kt new file mode 100644 index 0000000..460bee6 --- /dev/null +++ b/app/src/main/java/phone/vishnu/dialogmusicplayer/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package phone.vishnu.dialogmusicplayer.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp, + ), + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2c936f8..5f18617 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,4 +15,6 @@ Repeat Track Toggle Album Art IV + ComposeActivity + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index e8f19cd..c4bf026 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -6,7 +6,7 @@ @color/white @null - true + true true @android:color/transparent diff --git a/build.gradle b/build.gradle index 4ee3fba..a6f22c5 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,8 @@ buildscript { plugins { id "com.diffplug.spotless" version "6.19.0" id 'com.google.devtools.ksp' version '2.0.0-1.0.23' apply false + id 'org.jetbrains.kotlin.android' version '2.0.0' apply false + id 'org.jetbrains.kotlin.plugin.compose' version '2.0.0' apply false } spotless {