iu improvements

master
Isaac Iwasaki 3 months ago
parent 4057af1c55
commit fdfb0aa679

@ -0,0 +1,117 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

@ -4,6 +4,14 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2024-09-25T08:00:24.944510800Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=3128450" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>

@ -3,15 +3,19 @@
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
@ -27,18 +31,23 @@
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

@ -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)
}

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
@ -9,8 +11,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Player"
>
android:theme="@style/Theme.Player">
<activity
android:name=".MainActivity"
android:exported="true"

@ -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 <T> 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<NotaDescriptor>() }
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 = {
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<NotaDescriptor>, 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 = ""),
)
)
}

@ -1,8 +0,0 @@
package usr.empty.player
interface Nota {
fun play()
fun pause()
fun stop()
fun seek()
}

@ -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
}
}

@ -1,5 +1,6 @@
package usr.empty.player
class NotaPlayer {
}

@ -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<MediaItem>
mediaSession: MediaSession, controller: MediaSession.ControllerInfo, mediaItems: MutableList<MediaItem>
): ListenableFuture<MutableList<MediaItem>> {
val updatedMediaItems = mediaItems.map {
it.buildUpon().setUri(it.mediaId).build()

@ -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
)
}

@ -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" }

Loading…
Cancel
Save