diff --git a/README.md b/README.md index d3eb311..02426fc 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,12 @@ are going to either on: ## Prerequisites * Spotify account * Android 7.0 +* Spotify Premium + +### Development Status +Currently this app is in its development phase. To use it yourself you need to make some changes before running it. Please follow these steps to get it working. +1. Create your own Spotify App with [this](https://developer.spotify.com/documentation/android/tutorials/getting-started#register-your-app) tutorial section +2. Change the Spotify Client ID build config field [here](./app/build.gradle.kts) ## License This app is licensed under [GPLv3](https://github.com/techmaved/MediaBrowser-for-Spotify/blob/main/LICENSE.md), see LICENSE.md for more information. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2ef527f..757da19 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,8 +1,10 @@ plugins { - id("com.android.application") - id("org.jetbrains.kotlin.android") - id("com.google.devtools.ksp") - id("io.github.reactivecircus.app-versioning") + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin) + alias(libs.plugins.ksp) + alias(libs.plugins.app.versioning) + alias(libs.plugins.aboutlibraries.plugin) + alias(libs.plugins.serialization.plugin) } android { @@ -102,50 +104,52 @@ android { } dependencies { - val roomVersion = "2.6.1" - val acraVersion = "5.11.3" - - implementation("androidx.core:core-ktx:1.13.1") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") - implementation("androidx.activity:activity-compose:1.8.2") - implementation(platform("androidx.compose:compose-bom:2024.05.00")) - implementation("androidx.compose.ui:ui:1.6.7") - implementation("androidx.compose.ui:ui-graphics:1.6.7") - implementation("androidx.compose.ui:ui-tooling-preview:1.6.7") - implementation("androidx.compose.material3:material3:1.2.1") + implementation(libs.core.ktx) + implementation(libs.lifecycle.runtime.ktx) + implementation(libs.activity.compose) + implementation(platform(libs.compose.bom)) + implementation(libs.compose.ui) + implementation(libs.compose.graphics) + implementation(libs.compose.tooling) + implementation(libs.compose.m3) implementation(files("../libs/spotify-app-remote-release-0.8.0.aar")) //implementation(files("../libs/spotify-auth-release-2.1.0.aar"))ยด - implementation("com.google.code.gson:gson:2.10.1") - implementation("androidx.media3:media3-exoplayer:1.3.1") - implementation("androidx.media3:media3-ui:1.3.1") - implementation("androidx.media3:media3-common:1.3.1") - implementation("androidx.media3:media3-session:1.3.1") - testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.5") - androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") - androidTestImplementation(platform("androidx.compose:compose-bom:2024.05.00")) - androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.6.7") - debugImplementation("androidx.compose.ui:ui-tooling:1.6.7") - debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.7") - implementation("com.adamratzman:spotify-api-kotlin-core:4.1.3") - implementation("androidx.appcompat:appcompat:1.6.1") - implementation("androidx.room:room-runtime:$roomVersion") - annotationProcessor("androidx.room:room-compiler:$roomVersion") - ksp("androidx.room:room-compiler:$roomVersion") - implementation("androidx.room:room-ktx:$roomVersion") - implementation("androidx.room:room-rxjava2:$roomVersion") - implementation("androidx.room:room-rxjava3:$roomVersion") - implementation("androidx.room:room-guava:$roomVersion") - testImplementation("androidx.room:room-testing:$roomVersion") - implementation("androidx.room:room-paging:$roomVersion") - implementation("ch.acra:acra-core:$acraVersion") - implementation("ch.acra:acra-dialog:$acraVersion") - compileOnly("com.google.auto.service:auto-service-annotations:1.1.1") - ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0") - ksp("com.google.auto.service:auto-service:1.1.1") - implementation("androidx.documentfile:documentfile:1.0.1") - implementation("com.mikepenz:iconics-core:5.4.0") - implementation("com.mikepenz:iconics-compose:5.4.0") - implementation("com.mikepenz:fontawesome-typeface:5.9.0.2-kotlin@aar") - implementation("androidx.datastore:datastore-preferences:1.1.1") + implementation(libs.gson) + implementation(libs.media3.exoplayer) + implementation(libs.media3.ui) + implementation(libs.media3.common) + implementation(libs.media3.session) + testImplementation(libs.junit) + androidTestImplementation(libs.ext.junit) + androidTestImplementation(libs.espresso.core) + androidTestImplementation(platform(libs.compose.bom)) + androidTestImplementation(libs.ui.test.junit4) + debugImplementation(libs.ui.tooling) + debugImplementation(libs.ui.test.manifest) + implementation(libs.spotify.api.kotlin.core) + implementation(libs.appcompat) + implementation(libs.room.runtime) + annotationProcessor(libs.room.compiler) + ksp(libs.room.compiler) + implementation(libs.room.ktx) + implementation(libs.room.rxjava2) + implementation(libs.room.rxjava3) + implementation(libs.room.guava) + testImplementation(libs.test.room) + implementation(libs.room.paging) + implementation(libs.acra.core) + implementation(libs.acra.dialog) + compileOnly(libs.auto.service.annotations) + ksp(libs.auto.service.ksp) + ksp(libs.auto.service) + implementation(libs.documentfile) + implementation(libs.iconics.core) + implementation(libs.iconics.compose) + implementation(libs.fontawesome.typeface) + implementation(libs.datastore.preferences) + implementation(libs.aboutlibraries.core) + implementation(libs.aboutlibraries.compose.m3) + implementation(libs.navigation.fragment.compose) + implementation(libs.navigation.compose) + implementation(libs.kotlinx.serialization.json) } \ No newline at end of file diff --git a/app/src/main/java/de/techmaved/mediabrowserforspotify/activities/MainActivity.kt b/app/src/main/java/de/techmaved/mediabrowserforspotify/activities/MainActivity.kt index d7bf336..d420dd1 100644 --- a/app/src/main/java/de/techmaved/mediabrowserforspotify/activities/MainActivity.kt +++ b/app/src/main/java/de/techmaved/mediabrowserforspotify/activities/MainActivity.kt @@ -15,11 +15,16 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController import de.techmaved.mediabrowserforspotify.models.Model import de.techmaved.mediabrowserforspotify.ui.components.* import de.techmaved.mediabrowserforspotify.ui.theme.MediaBrowserForSpotifyTheme +import de.techmaved.mediabrowserforspotify.ui.Info +import de.techmaved.mediabrowserforspotify.ui.Main class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -31,11 +36,25 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - Ui( - activity, - Model.credentialStore.spotifyToken != null, - isPackageInstalled(SpotifyDesign().spotifyPackage, applicationContext.packageManager) - ) + val navController = rememberNavController() + + NavHost( + navController = navController, + startDestination = Main + ) { + composable
{ + Ui( + activity, + Model.credentialStore.spotifyToken != null, + isPackageInstalled(SpotifyDesign().spotifyPackage, applicationContext.packageManager), + navController + ) + } + + composable { + Info() + } + } } } } @@ -52,10 +71,10 @@ class MainActivity : ComponentActivity() { } @Composable() -fun Ui(activity: MainActivity?, isAuthenticated: Boolean, isSpotifyInstalled: Boolean) { +fun Ui(activity: MainActivity?, isAuthenticated: Boolean, isSpotifyInstalled: Boolean, navController: NavController) { val mediaItemCount = remember { mutableStateOf(0) } - AppBarWithContainer(activity, isAuthenticated) { + AppBarWithContainer(activity, isAuthenticated, navController) { Column( modifier = Modifier.padding(start = 16.dp, end = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, @@ -65,13 +84,6 @@ fun Ui(activity: MainActivity?, isAuthenticated: Boolean, isSpotifyInstalled: Bo TextWithButtons(mediaItemCount, isAuthenticated) MirrorSection(isAuthenticated) SpotifyDesign().LinkToSpotify(isSpotifyInstalled, activity) - SourceCodeLink() } } -} - -@Composable -@Preview(showBackground = true) -fun Preview() { - Ui(null, true, true) } \ No newline at end of file diff --git a/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/Routes.kt b/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/Routes.kt new file mode 100644 index 0000000..7b698b2 --- /dev/null +++ b/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/Routes.kt @@ -0,0 +1,9 @@ +package de.techmaved.mediabrowserforspotify.ui + +import kotlinx.serialization.Serializable + +@Serializable +object Main + +@Serializable +object Info \ No newline at end of file diff --git a/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/components/Info.kt b/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/components/Info.kt index 92a4916..fbc3f24 100644 --- a/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/components/Info.kt +++ b/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/components/Info.kt @@ -1,50 +1,101 @@ package de.techmaved.mediabrowserforspotify.ui.components + import android.content.Intent +import android.graphics.Bitmap import android.net.Uri +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.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Info +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.mikepenz.iconics.compose.Image +import androidx.core.graphics.drawable.toBitmap +import androidx.navigation.NavController +import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer import com.mikepenz.iconics.typeface.library.fontawesome.FontAwesome import de.techmaved.mediabrowserforspotify.BuildConfig import de.techmaved.mediabrowserforspotify.R +import de.techmaved.mediabrowserforspotify.ui.Info @Composable -fun SourceCodeLink() { +fun Info() { val context = LocalContext.current val repoUrl = stringResource(R.string.github_repository) val intent = remember { Intent(Intent.ACTION_VIEW, Uri.parse(repoUrl)) } + val drawable = context.packageManager.getApplicationIcon(context.packageName) - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text("Version: ${BuildConfig.VERSION_NAME}") - TextButton( - onClick = { - context.startActivity(intent) - } - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Image( - asset = FontAwesome.Icon.faw_github, - colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.primary) - ) - Text("source code") + LibrariesContainer( + modifier = Modifier.fillMaxWidth(), + header = { + item { + Column( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surface) + .padding(vertical = 25.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(text = stringResource(R.string.app_name)) + + Row { + Image( + drawable.toBitmap(config = Bitmap.Config.ARGB_8888).asImageBitmap(), + contentDescription = stringResource(R.string.app_name), + modifier = Modifier + .size(100.dp) + .padding(8.dp) + ) + } + + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text("Version: ${BuildConfig.VERSION_NAME}") + TextButton( + onClick = { + context.startActivity(intent) + } + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + asset = FontAwesome.Icon.faw_github, + colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.primary) + ) + Text("source code") + } + } + } + } + } } } - } + ) + } diff --git a/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/components/MediaItems.kt b/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/components/MediaItems.kt index 85cdbda..59a12e2 100644 --- a/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/components/MediaItems.kt +++ b/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/components/MediaItems.kt @@ -226,7 +226,6 @@ fun LikedSongsHelpDialog() { verticalAlignment = Alignment.CenterVertically ) { Icon(imageVector = Icons.Outlined.Info, contentDescription = null) - Text("Info") } } diff --git a/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/components/utilities.kt b/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/components/utilities.kt index 4c3192d..08a22ce 100644 --- a/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/components/utilities.kt +++ b/app/src/main/java/de/techmaved/mediabrowserforspotify/ui/components/utilities.kt @@ -4,7 +4,11 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.twotone.AccountCircle +import androidx.compose.material.icons.twotone.MoreVert +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -16,15 +20,19 @@ import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.navigation.NavController import com.adamratzman.spotify.auth.pkce.startSpotifyClientPkceLoginActivity import de.techmaved.mediabrowserforspotify.R import de.techmaved.mediabrowserforspotify.activities.MainActivity import de.techmaved.mediabrowserforspotify.auth.SpotifyPkceLoginActivityImpl import de.techmaved.mediabrowserforspotify.auth.pkceClassBackTo +import de.techmaved.mediabrowserforspotify.ui.Info import de.techmaved.mediabrowserforspotify.utils.Store @OptIn(ExperimentalMaterial3Api::class) @@ -32,12 +40,14 @@ import de.techmaved.mediabrowserforspotify.utils.Store fun AppBarWithContainer( activity: MainActivity?, isAuthenticated: Boolean, + navController: NavController, content: @Composable () -> Unit ) { val context = LocalContext.current val store = Store(context) val userName = store.getUserName.collectAsState(initial = "") val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val dropdownMenuState = remember { mutableStateOf(false) } Surface( modifier = Modifier.fillMaxSize(), @@ -76,6 +86,28 @@ fun AppBarWithContainer( contentDescription = "Login with Spotify" ) } + + IconButton( + onClick = { + dropdownMenuState.value = !dropdownMenuState.value + } + ) { + Icon( + imageVector = Icons.TwoTone.MoreVert, + contentDescription = "More" + ) + + DropdownMenu( + expanded = dropdownMenuState.value, + onDismissRequest = { dropdownMenuState.value = false } + ) { + DropdownMenuItem( + leadingIcon = { Icon(Icons.Outlined.Info, "About") }, + text = { Text(text = "About") }, + onClick = { navController.navigate(Info) } + ) + } + } }, scrollBehavior = scrollBehavior, ) diff --git a/app/src/main/java/de/techmaved/mediabrowserforspotify/utils/MediaItemTree.kt b/app/src/main/java/de/techmaved/mediabrowserforspotify/utils/MediaItemTree.kt index 30d5fb6..18fff6e 100644 --- a/app/src/main/java/de/techmaved/mediabrowserforspotify/utils/MediaItemTree.kt +++ b/app/src/main/java/de/techmaved/mediabrowserforspotify/utils/MediaItemTree.kt @@ -84,7 +84,7 @@ object MediaItemTree { artist: String? = null, genre: String? = null, sourceUri: Uri? = null, - imageUri: Uri? = null + browsableUri: Uri? = null ): MediaItem { val metadata = MediaMetadata.Builder() @@ -94,7 +94,6 @@ object MediaItemTree { .setGenre(genre) .setIsBrowsable(isBrowsable) .setIsPlayable(isPlayable) - .setArtworkUri(imageUri) .setMediaType(mediaType) .build() @@ -103,6 +102,7 @@ object MediaItemTree { .setSubtitleConfigurations(subtitleConfigurations) .setMediaMetadata(metadata) .setUri(sourceUri) + .setTag(browsableUri) .build() } @@ -201,7 +201,7 @@ object MediaItemTree { artist = mediaItem.artist, genre = "", sourceUri = mediaItem.uri, - imageUri = mediaItem.browsableUri + browsableUri = mediaItem.browsableUri ) ) diff --git a/app/src/main/java/de/techmaved/mediabrowserforspotify/utils/PlaybackService.kt b/app/src/main/java/de/techmaved/mediabrowserforspotify/utils/PlaybackService.kt index 27eedaa..e960a97 100644 --- a/app/src/main/java/de/techmaved/mediabrowserforspotify/utils/PlaybackService.kt +++ b/app/src/main/java/de/techmaved/mediabrowserforspotify/utils/PlaybackService.kt @@ -262,26 +262,26 @@ class PlaybackService : MediaLibraryService() { if (Player.STATE_IDLE != playbackState) { val playableUri = PlayableUri.invoke(currentMediaItem?.localConfiguration?.uri.toString()) - val contextUri = ContextUri.invoke(currentMediaItem?.mediaMetadata?.artworkUri.toString()) + val contextUri = ContextUri.invoke(currentMediaItem?.localConfiguration?.tag.toString()) audioManager?.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_MUTE, AudioManager.FLAG_SHOW_UI) spotifyAppRemote?.playerApi?.play(currentMediaItem?.localConfiguration?.uri.toString())?.setResultCallback { - runBlocking { + CoroutineScope(Dispatchers.IO).launch { guardValidSpotifyApi { api: SpotifyClientApi -> try { api.player.startPlayback(contextUri = contextUri, offsetPlayableUri = playableUri) - delay(2000) + delay(1000) spotifyAppRemote?.playerApi?.playerState?.setResultCallback { if (it.isPaused) { spotifyAppRemote?.playerApi?.seekTo(0) spotifyAppRemote?.playerApi?.resume() } + + audioManager?.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_UNMUTE , AudioManager.FLAG_SHOW_UI) } } catch (e: Throwable) {} } - - audioManager?.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_UNMUTE , AudioManager.FLAG_SHOW_UI) } } } diff --git a/build.gradle.kts b/build.gradle.kts index d2f9117..8a93971 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.4.0" apply false - id("org.jetbrains.kotlin.android") version "1.9.23" apply false - id("com.google.devtools.ksp") version "1.9.23-1.0.19" apply false - id("io.github.reactivecircus.app-versioning") version "1.3.1" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin) apply false + alias(libs.plugins.ksp) apply false + alias(libs.plugins.app.versioning) apply false + alias(libs.plugins.aboutlibraries.plugin) apply false + alias(libs.plugins.serialization.plugin) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..4693c7a --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,89 @@ +[versions] +# Plugins +androidGradlePlugin = "8.4.1" +kotlin = "1.9.23" +ksp = "1.9.23-1.0.19" +appVersioning = "1.3.1" +aboutLibrariesPlugin = "11.1.4" + +# Libraries +activityCompose = "1.8.2" +appcompat = "1.7.0" +autoServiceAnnotations = "1.1.1" +autoServiceKsp = "1.1.0" +composeBom = "2024.06.00" +datastorePreferences = "1.1.1" +documentfile = "1.0.1" +espressoCore = "3.6.1" +fontawesomeTypeface = "5.9.0.2-kotlin" +iconicsCore = "5.4.0" +iconicsCompose = "5.4.0" +junit = "4.13.2" +junitVersion = "1.2.1" +kotlinxSerializationJson = "1.6.3" +lifecycleRuntimeKtx = "2.8.4" +navigationFragmentCompose = "2.8.0-rc01" +navigationCompose = "2.8.0-rc01" +room = "2.6.1" +acra = "5.11.3" +compose = "1.6.8" +gson = "2.10.1" +material3 = "1.2.1" +media3 = "1.4.0" +coreKtx = "1.13.1" +spotifyApiKotlinCore = "4.1.3" +aboutlibrariesCore = "11.1.4" + +[libraries] +room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } +room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } +room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } +room-rxjava2 = { group = "androidx.room", name = "room-rxjava2", version.ref = "room" } +room-rxjava3 = { group = "androidx.room", name = "room-rxjava3", version.ref = "room" } +room-guava = { group = "androidx.room", name = "room-guava", version.ref = "room" } +test-room = { group = "androidx.room", name = "room-testing", version.ref = "room" } +room-paging = { group = "androidx.room", name = "room-paging", version.ref = "room" } +acra-core = { group = "ch.acra" , name = "acra-core", version.ref = "acra" } +acra-dialog = { group = "ch.acra" , name = "acra-dialog", version.ref = "acra" } +compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" } +compose-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "compose" } +compose-tooling = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "compose" } +compose-m3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } +gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } +media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" } +media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3" } +media3-common = { group = "androidx.media3", name = "media3-common", version.ref = "media3" } +media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3" } +core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4", version.ref = "compose" } +ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version.ref = "compose" } +ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "compose" } +aboutlibraries-compose-m3 = { group = "com.mikepenz", name = "aboutlibraries-compose-m3", version.ref = "aboutlibrariesCore" } +aboutlibraries-core = { group = "com.mikepenz", name = "aboutlibraries-core", version.ref = "aboutlibrariesCore" } +activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +auto-service = { group = "com.google.auto.service", name = "auto-service", version.ref = "autoServiceAnnotations" } +auto-service-ksp = { group = "dev.zacsweers.autoservice", name = "auto-service-ksp", version.ref = "autoServiceKsp" } +auto-service-annotations = { group = "com.google.auto.service", name = "auto-service-annotations", version.ref = "autoServiceAnnotations" } +compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastorePreferences" } +documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" } +espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +fontawesome-typeface = { group = "com.mikepenz", name = "fontawesome-typeface", version.ref = "fontawesomeTypeface" } +iconics-compose = { group = "com.mikepenz", name = "iconics-compose", version.ref = "iconicsCompose" } +iconics-core = { group = "com.mikepenz", name = "iconics-core", version.ref = "iconicsCore" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } +lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } +navigation-fragment-compose = { group = "androidx.navigation", name = "navigation-fragment-compose", version.ref = "navigationFragmentCompose" } +spotify-api-kotlin-core = { group = "com.adamratzman", name = "spotify-api-kotlin-core", version.ref = "spotifyApiKotlinCore" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } +kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +app-versioning = { id = "io.github.reactivecircus.app-versioning", version.ref = "appVersioning" } +aboutlibraries-plugin = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutLibrariesPlugin" } +serialization-plugin = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } \ No newline at end of file