1
0
Fork 0
mirror of https://github.com/emptyynes/EmptyPlayer.git synced 2025-04-27 04:46:31 +03:00

iu improvements

This commit is contained in:
Isaac Iwasaki 2024-09-25 22:26:24 +07:00
parent 4057af1c55
commit fdfb0aa679
13 changed files with 298 additions and 64 deletions

View file

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

View file

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

View file

@ -4,6 +4,14 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <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> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>

View file

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

View file

@ -12,7 +12,7 @@ android {
minSdk = 34 minSdk = 34
targetSdk = 34 targetSdk = 34
versionCode = 1 versionCode = 1
versionName = "1.0" versionName = "0.0.1α"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@ -24,8 +24,7 @@ android {
release { release {
isMinifyEnabled = false isMinifyEnabled = false
proguardFiles( proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
"proguard-rules.pro"
) )
} }
} }
@ -50,6 +49,7 @@ android {
} }
dependencies { dependencies {
implementation(libs.kotlinx.serialization.json)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
@ -62,11 +62,12 @@ dependencies {
implementation(libs.androidx.media3.ui) implementation(libs.androidx.media3.ui)
implementation(libs.androidx.media3.common) implementation(libs.androidx.media3.common)
implementation(libs.androidx.media3.session) implementation(libs.androidx.media3.session)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4) androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest) debugImplementation(libs.androidx.ui.test.manifest)
} }

View file

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-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 <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
@ -9,8 +11,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Player" android:theme="@style/Theme.Player">
>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
@ -21,6 +22,6 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<service android:name=".PlayerService"/> <service android:name=".PlayerService" />
</application> </application>
</manifest> </manifest>

View file

@ -1,29 +1,59 @@
package usr.empty.player package usr.empty.player
import android.content.Intent import android.content.Intent
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import android.util.Log import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding 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.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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 import usr.empty.player.ui.theme.PlayerTheme
inline fun <T> nullifyException(block: () -> T) = try {
block()
} catch (_: Exception) {
null
}
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) 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)) startService(Intent(this, PlayerService::class.java))
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
PlayerTheme { 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 @Composable
fun MainLayout(modifier: Modifier = Modifier) { fun MainLayout(modifier: Modifier = Modifier) {
val notas = remember { mutableStateListOf<NotaDescriptor>() }
val pickAudioLauncher = rememberLauncherForActivityResult( val pickAudioLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent() ActivityResultContracts.GetContent()
) { audioUri -> ) { audioUri ->
audioUri?.run { audioUri?.run {
path?.let { uriToPath(this).let {
Log.d("meow", it) 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/*") pickAudioLauncher.launch("audio/*")
}) { }) {
Text("add track") 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 = ""),
)
)
}

View file

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

View file

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

View file

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

View file

@ -1,5 +1,6 @@
package usr.empty.player package usr.empty.player
import android.content.Intent
import android.util.Log import android.util.Log
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer 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.Futures
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
class PlayerService : MediaSessionService() { class PlayerService : MediaSessionService() {
private var mediaSession: MediaSession? = null private var mediaSession: MediaSession? = null
var player: ExoPlayer? = null var player: ExoPlayer? = null
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
player = ExoPlayer.Builder(this).build() with(ExoPlayer.Builder(this).build()) {
mediaSession = MediaSession.Builder(this, player!!) player = this
.setCallback(MediaSessionCallback()) mediaSession = MediaSession.Builder(this@PlayerService, this).setCallback(MediaSessionCallback()).build()
.build() }
Log.d("meow", "memememe") 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() { override fun onDestroy() {
player?.release() player?.release()
mediaSession?.release() mediaSession?.release()
@ -32,9 +45,7 @@ class PlayerService : MediaSessionService() {
private inner class MediaSessionCallback : MediaSession.Callback { private inner class MediaSessionCallback : MediaSession.Callback {
override fun onAddMediaItems( override fun onAddMediaItems(
mediaSession: MediaSession, mediaSession: MediaSession, controller: MediaSession.ControllerInfo, mediaItems: MutableList<MediaItem>
controller: MediaSession.ControllerInfo,
mediaItems: MutableList<MediaItem>
): ListenableFuture<MutableList<MediaItem>> { ): ListenableFuture<MutableList<MediaItem>> {
val updatedMediaItems = mediaItems.map { val updatedMediaItems = mediaItems.map {
it.buildUpon().setUri(it.mediaId).build() it.buildUpon().setUri(it.mediaId).build()

View file

@ -7,49 +7,31 @@ import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color 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 @Composable
fun PlayerTheme( fun PlayerTheme(
darkTheme: Boolean = isSystemInDarkTheme(), darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+
// Dynamic color is available on Android 12+ dynamicColor: Boolean = true, content: @Composable () -> Unit
// dynamicColor: Boolean = true,
content: @Composable () -> Unit
) { ) {
val colorScheme = when { val colorScheme = when {
// dynamicColor -> { dynamicColor -> {
// val context = LocalContext.current if (darkTheme) darkColorScheme(
// if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) background = Color.Black
// } ) else lightColorScheme(
darkTheme -> DarkColorScheme background = Color.White
else -> LightColorScheme )
}
darkTheme -> darkColorScheme(
background = Color.Black
)
else -> lightColorScheme(
background = Color.White
)
} }
MaterialTheme( MaterialTheme(
colorScheme = colorScheme, colorScheme = colorScheme, typography = Typography, content = content
typography = Typography,
content = content
) )
} }

View file

@ -1,16 +1,20 @@
[versions] [versions]
agp = "8.6.0" agp = "8.6.0"
core = "1.6.1"
kotlin = "1.9.0" kotlin = "1.9.0"
coreKtx = "1.13.1" coreKtx = "1.13.1"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.2.1" junitVersion = "1.2.1"
espressoCore = "3.6.1" espressoCore = "3.6.1"
kotlinxSerializationJson = "1.7.1"
lifecycleRuntimeKtx = "2.6.1" lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.9.2" activityCompose = "1.9.2"
composeBom = "2024.04.01" composeBom = "2024.04.01"
media3Exoplayer = "1.4.1" media3Exoplayer = "1.4.1"
junitJupiter = "5.8.1"
[libraries] [libraries]
androidx-core = { module = "androidx.test:core", version.ref = "core" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } 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-common = { module = "androidx.media3:media3-common", version.ref = "media3Exoplayer" }
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", 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-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" } 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] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }