diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..4bec4ea
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index b268ef3..6ef6d40 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,6 +4,14 @@
+
+
+
+
+
+
+
+
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index a07bb9d..6806f5a 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -3,15 +3,19 @@
+
+
+
+
@@ -27,18 +31,23 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 6d5e95f..6695ae7 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -12,7 +12,7 @@ android {
minSdk = 34
targetSdk = 34
versionCode = 1
- versionName = "1.0"
+ versionName = "0.0.1α"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -24,8 +24,7 @@ android {
release {
isMinifyEnabled = false
proguardFiles(
- getDefaultProguardFile("proguard-android-optimize.txt"),
- "proguard-rules.pro"
+ getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
}
}
@@ -50,6 +49,7 @@ android {
}
dependencies {
+ implementation(libs.kotlinx.serialization.json)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
@@ -62,11 +62,12 @@ dependencies {
implementation(libs.androidx.media3.ui)
implementation(libs.androidx.media3.common)
implementation(libs.androidx.media3.session)
- testImplementation(libs.junit)
+
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
+
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7786bd9..d4e9dbe 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,6 +1,8 @@
-
-
+
+
+ android:theme="@style/Theme.Player">
-
+
\ No newline at end of file
diff --git a/app/src/main/java/usr/empty/player/MainActivity.kt b/app/src/main/java/usr/empty/player/MainActivity.kt
index 704f661..6a1d3d8 100644
--- a/app/src/main/java/usr/empty/player/MainActivity.kt
+++ b/app/src/main/java/usr/empty/player/MainActivity.kt
@@ -1,29 +1,59 @@
package usr.empty.player
import android.content.Intent
+import android.media.MediaMetadataRetriever
+import android.net.Uri
import android.os.Bundle
+import android.os.Environment
+import android.provider.Settings
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import usr.empty.player.ui.theme.PlayerTheme
+inline fun nullifyException(block: () -> T) = try {
+ block()
+} catch (_: Exception) {
+ null
+}
+
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
+ if (!Environment.isExternalStorageManager()) {
+ val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
+ val uri = Uri.fromParts("package", packageName, null)
+ intent.setData(uri)
+ startActivity(intent)
+ }
+
startService(Intent(this, PlayerService::class.java))
+
enableEdgeToEdge()
setContent {
PlayerTheme {
@@ -35,22 +65,74 @@ class MainActivity : ComponentActivity() {
}
}
+fun uriToPath(uri: Uri): String {
+ val result = uri.pathSegments!![1].replace(Regex("^primary"), "emulated/0").replace(":", "/")
+ return "/storage/" + result.replace("primary:", "/storage/emulated/0/")
+}
+
@Composable
fun MainLayout(modifier: Modifier = Modifier) {
+ val notas = remember { mutableStateListOf() }
+
val pickAudioLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { audioUri ->
audioUri?.run {
- path?.let {
- Log.d("meow", it)
+ uriToPath(this).let {
+ MediaMetadataRetriever().apply {
+ setDataSource(it)
+ notas.add(NotaDescriptor(name = nullifyException {
+ extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)
+ } ?: it.split('/').last().split('.').first(), artist = nullifyException {
+ extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST)
+ } ?: "Unknown Artist", sourceType = NotaDescriptor.Source.LOCAL, source = it))
+ }
}
}
}
- Column (modifier.fillMaxSize()) {
- Button(modifier = Modifier.align(Alignment.CenterHorizontally), onClick = {
+
+ Column(modifier.fillMaxSize()) {
+ Button(shape = RectangleShape, colors = ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.secondaryContainer, contentColor = MaterialTheme.colorScheme.secondary
+ ), modifier = Modifier.align(Alignment.CenterHorizontally), onClick = {
pickAudioLauncher.launch("audio/*")
}) {
Text("add track")
}
+ NotaList(notas)
+ }
+}
+
+@Composable
+fun NotaList(notas: List, modifier: Modifier = Modifier) {
+ LazyColumn(
+ modifier = modifier,
+ ) {
+ items(notas) { nota ->
+ Column(modifier = Modifier
+ .fillParentMaxWidth()
+ .clickable { }
+ .padding(4.dp)
+ .padding(start = 16.dp)) {
+ Text(
+ nota.name, fontSize = 16.sp
+ )
+ Text(
+ nota.artist, fontSize = 12.sp
+ )
+ }
+ Log.d("nya", nota.toString())
+ }
}
+}
+
+@Preview
+@Composable
+fun Preview() {
+ NotaList(
+ listOf(
+ NotaDescriptor(name = "hello", artist = "there", sourceType = NotaDescriptor.Source.LOCAL, source = ""),
+ NotaDescriptor(name = "preview", artist = "me", sourceType = NotaDescriptor.Source.LOCAL, source = ""),
+ )
+ )
}
\ No newline at end of file
diff --git a/app/src/main/java/usr/empty/player/Nota.kt b/app/src/main/java/usr/empty/player/Nota.kt
deleted file mode 100644
index 082f1a4..0000000
--- a/app/src/main/java/usr/empty/player/Nota.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package usr.empty.player
-
-interface Nota {
- fun play()
- fun pause()
- fun stop()
- fun seek()
-}
\ No newline at end of file
diff --git a/app/src/main/java/usr/empty/player/NotaDescriptor.kt b/app/src/main/java/usr/empty/player/NotaDescriptor.kt
new file mode 100644
index 0000000..7f1ad11
--- /dev/null
+++ b/app/src/main/java/usr/empty/player/NotaDescriptor.kt
@@ -0,0 +1,16 @@
+package usr.empty.player
+
+import kotlinx.serialization.Serializable
+
+
+@Serializable
+data class NotaDescriptor(
+ val name: String,
+ val artist: String,
+ val sourceType: Source,
+ val source: String,
+) {
+ enum class Source {
+ LOCAL
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/usr/empty/player/NotaPlayer.kt b/app/src/main/java/usr/empty/player/NotaPlayer.kt
index e3467fb..9f2600d 100644
--- a/app/src/main/java/usr/empty/player/NotaPlayer.kt
+++ b/app/src/main/java/usr/empty/player/NotaPlayer.kt
@@ -1,5 +1,6 @@
package usr.empty.player
+
class NotaPlayer {
}
\ No newline at end of file
diff --git a/app/src/main/java/usr/empty/player/PlayerService.kt b/app/src/main/java/usr/empty/player/PlayerService.kt
index fde3045..f48c1cd 100644
--- a/app/src/main/java/usr/empty/player/PlayerService.kt
+++ b/app/src/main/java/usr/empty/player/PlayerService.kt
@@ -1,5 +1,6 @@
package usr.empty.player
+import android.content.Intent
import android.util.Log
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
@@ -8,19 +9,31 @@ import androidx.media3.session.MediaSessionService
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
+
class PlayerService : MediaSessionService() {
private var mediaSession: MediaSession? = null
var player: ExoPlayer? = null
override fun onCreate() {
super.onCreate()
- player = ExoPlayer.Builder(this).build()
- mediaSession = MediaSession.Builder(this, player!!)
- .setCallback(MediaSessionCallback())
- .build()
+ with(ExoPlayer.Builder(this).build()) {
+ player = this
+ mediaSession = MediaSession.Builder(this@PlayerService, this).setCallback(MediaSessionCallback()).build()
+ }
Log.d("meow", "memememe")
}
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ intent?.run {
+
+ }
+ return super.onStartCommand(intent, flags, startId)
+ }
+
+ private fun start() {
+
+ }
+
override fun onDestroy() {
player?.release()
mediaSession?.release()
@@ -32,9 +45,7 @@ class PlayerService : MediaSessionService() {
private inner class MediaSessionCallback : MediaSession.Callback {
override fun onAddMediaItems(
- mediaSession: MediaSession,
- controller: MediaSession.ControllerInfo,
- mediaItems: MutableList
+ mediaSession: MediaSession, controller: MediaSession.ControllerInfo, mediaItems: MutableList
): ListenableFuture> {
val updatedMediaItems = mediaItems.map {
it.buildUpon().setUri(it.mediaId).build()
diff --git a/app/src/main/java/usr/empty/player/ui/theme/Theme.kt b/app/src/main/java/usr/empty/player/ui/theme/Theme.kt
index 46e119e..c784779 100644
--- a/app/src/main/java/usr/empty/player/ui/theme/Theme.kt
+++ b/app/src/main/java/usr/empty/player/ui/theme/Theme.kt
@@ -7,49 +7,31 @@ import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
-private val DarkColorScheme = darkColorScheme(
- background = Color.Black,
- primary = Purple80,
- secondary = PurpleGrey80,
- tertiary = Pink80
-)
-
-private val LightColorScheme = lightColorScheme(
- background = Color.White,
- 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 PlayerTheme(
- darkTheme: Boolean = isSystemInDarkTheme(),
- // Dynamic color is available on Android 12+
-// dynamicColor: Boolean = true,
- content: @Composable () -> Unit
+ darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+
+ dynamicColor: Boolean = true, content: @Composable () -> Unit
) {
val colorScheme = when {
-// dynamicColor -> {
-// val context = LocalContext.current
-// if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
-// }
- darkTheme -> DarkColorScheme
- else -> LightColorScheme
+ dynamicColor -> {
+ if (darkTheme) darkColorScheme(
+ background = Color.Black
+ ) else lightColorScheme(
+ background = Color.White
+ )
+ }
+
+ darkTheme -> darkColorScheme(
+ background = Color.Black
+ )
+
+ else -> lightColorScheme(
+ background = Color.White
+ )
}
MaterialTheme(
- colorScheme = colorScheme,
- typography = Typography,
- content = content
+ colorScheme = colorScheme, typography = Typography, content = content
)
}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2ca45ed..1ee673b 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,16 +1,20 @@
[versions]
agp = "8.6.0"
+core = "1.6.1"
kotlin = "1.9.0"
coreKtx = "1.13.1"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
+kotlinxSerializationJson = "1.7.1"
lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.9.2"
composeBom = "2024.04.01"
media3Exoplayer = "1.4.1"
+junitJupiter = "5.8.1"
[libraries]
+androidx-core = { module = "androidx.test:core", version.ref = "core" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-media3-common = { module = "androidx.media3:media3-common", version.ref = "media3Exoplayer" }
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Exoplayer" }
@@ -29,6 +33,11 @@ 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" }
+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
+junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junitJupiter" }
+mockito-core = { module = "org.mockito:mockito-core", version = "5.10.0" }
+mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "5.4.0" }
+mockk = { module = "io.mockk:mockk", version = "1.13.10" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }