Skip to content

Commit 363d5e5

Browse files
authored
Random songs screen (#354)
1 parent 67a168a commit 363d5e5

File tree

73 files changed

+1308
-266
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1308
-266
lines changed

app/build.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ dependencies {
7575
implementation project(":core:room")
7676
implementation project(":core:properties")
7777
implementation project(":shared:player")
78+
implementation project(":shared:favorites")
79+
implementation project(":shared:song:random")
7880
implementation project(':feature:album:list')
7981
implementation project(':feature:album:info')
8082
implementation project(':feature:artist:list')
@@ -95,6 +97,7 @@ dependencies {
9597
implementation project(":feature:personal")
9698
implementation project(":feature:explore")
9799
implementation project(":feature:library")
100+
implementation project(":feature:song:random")
98101
implementation(libs.kotlin.serialization.core)
99102
implementation(libs.bundles.koin)
100103
implementation(libs.navigation.compose)

app/src/main/java/ru/stersh/youamp/ApiProviderImpl.kt

+26-23
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,23 @@ package ru.stersh.youamp
33
import kotlinx.coroutines.flow.Flow
44
import kotlinx.coroutines.flow.map
55
import kotlinx.coroutines.flow.mapNotNull
6-
import kotlinx.coroutines.sync.Mutex
7-
import kotlinx.coroutines.sync.withLock
86
import ru.stersh.youamp.core.api.ApiDefaults
97
import ru.stersh.youamp.core.api.SubsonicApi
108
import ru.stersh.youamp.core.api.provider.ApiProvider
119
import ru.stersh.youamp.core.api.provider.NoActiveServerSettingsFound
1210
import ru.stersh.youamp.core.room.server.SubsonicServerDao
1311
import ru.stersh.youamp.core.room.server.SubsonicServerDb
12+
import java.util.concurrent.ConcurrentHashMap
1413

1514
internal class ApiProviderImpl(
1615
private val subsonicServerDao: SubsonicServerDao
1716
) : ApiProvider {
18-
private val mutex = Mutex()
19-
private var apiSonic: SubsonicApi? = null
2017

21-
override suspend fun getApi(): SubsonicApi = mutex.withLock {
18+
private val apiCache = ConcurrentHashMap<Long, SubsonicApi>()
19+
20+
override suspend fun getApi(): SubsonicApi {
2221
val currentServerSettings = subsonicServerDao.getActive() ?: throw NoActiveServerSettingsFound()
23-
return@withLock requireApi(currentServerSettings)
22+
return requireApi(currentServerSettings)
2423
}
2524

2625
override suspend fun requireApiId(): Long {
@@ -43,22 +42,34 @@ internal class ApiProviderImpl(
4342
return subsonicServerDao
4443
.flowActive()
4544
.map {
46-
getApiOrNull(it)
45+
if (it == null) {
46+
null
47+
} else {
48+
getApi(it.id)
49+
}
4750
}
4851
}
4952

50-
private fun getApiOrNull(subsonicServer: SubsonicServerDb?): SubsonicApi? {
51-
if (subsonicServer == null) {
52-
return null
53+
override fun flowApiId(): Flow<Long?> {
54+
return subsonicServerDao
55+
.flowActive()
56+
.map { it?.id }
57+
}
58+
59+
override suspend fun getApi(id: Long): SubsonicApi? {
60+
return apiCache.getOrPut(id) {
61+
subsonicServerDao.getServer(id)?.let {
62+
createNewApi(it)
63+
}
5364
}
54-
return requireApi(subsonicServer)
65+
}
66+
67+
override suspend fun requireApi(id: Long): SubsonicApi {
68+
return requireNotNull(getApi(id))
5569
}
5670

5771
private fun requireApi(subsonicServer: SubsonicServerDb): SubsonicApi {
58-
if (!isSameSettings(subsonicServer)) {
59-
apiSonic = createNewApi(subsonicServer)
60-
}
61-
return requireNotNull(apiSonic)
72+
return apiCache.getOrPut(subsonicServer.id) { createNewApi(subsonicServer) }
6273
}
6374

6475
private fun createNewApi(subsonicServer: SubsonicServerDb): SubsonicApi {
@@ -71,12 +82,4 @@ internal class ApiProviderImpl(
7182
useLegacyAuth = subsonicServer.useLegacyAuth
7283
)
7384
}
74-
75-
private fun isSameSettings(subsonicServer: SubsonicServerDb): Boolean {
76-
val currentApiSonic = apiSonic ?: return false
77-
return currentApiSonic.username == subsonicServer.username &&
78-
currentApiSonic.password == subsonicServer.password &&
79-
currentApiSonic.url == subsonicServer.url &&
80-
currentApiSonic.useLegacyAuth == subsonicServer.useLegacyAuth
81-
}
8285
}

app/src/main/java/ru/stersh/youamp/Di.kt

+8-4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ import ru.stresh.youamp.feature.about.aboutModule
3636
import ru.stresh.youamp.feature.explore.exploreModule
3737
import ru.stresh.youamp.feature.favorite.list.favoriteListModule
3838
import ru.stresh.youamp.feature.library.libraryModule
39+
import ru.stresh.youamp.feature.song.random.songRandomModule
40+
import ru.stresh.youamp.shared.favorites.favoritesSharedModule
41+
import ru.stresh.youamp.shared.song.random.songRandomSharedModule
3942

4043
internal fun setupDi(application: Application) {
4144
startKoin {
@@ -50,7 +53,9 @@ private val core = listOf(
5053
)
5154

5255
private val shared = listOf(
53-
playerSharedModule
56+
playerSharedModule,
57+
favoritesSharedModule,
58+
songRandomSharedModule
5459
)
5560

5661
private val feature = listOf(
@@ -72,16 +77,15 @@ private val feature = listOf(
7277
exploreModule,
7378
mainModule,
7479
libraryModule,
75-
songInfoModule
80+
songInfoModule,
81+
songRandomModule
7682
)
7783

7884
private val impl = module {
7985
single {
8086
AppProperties(
8187
name = get<Context>().getString(R.string.app_name),
8288
version = BuildConfig.VERSION_NAME,
83-
googlePlayAppUri = "market://details?id=ru.stersh.youamp".toUri(),
84-
googlePlayBrowserUri = "https://play.google.com/store/apps/details?id=ru.stersh.youamp".toUri(),
8589
githubUri = "https://github.com/siper/Youamp".toUri(),
8690
fdroidUri = "https://f-droid.org/packages/ru.stersh.youamp/".toUri(),
8791
crwodinUri = "https://crowdin.com/project/youamp".toUri()

app/src/main/java/ru/stersh/youamp/main/ui/MainActivity.kt

+24
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import ru.stresh.youamp.feature.explore.ui.ExploreScreen
5353
import ru.stresh.youamp.feature.favorite.list.ui.FavoriteSongsScreen
5454
import ru.stresh.youamp.feature.library.ui.LibraryScreen
5555
import ru.stresh.youamp.feature.settings.ui.SettingsScreen
56+
import ru.stresh.youamp.feature.song.random.ui.RandomSongsScreen
5657

5758
class MainActivity : ComponentActivity() {
5859

@@ -150,6 +151,9 @@ class MainActivity : ComponentActivity() {
150151
onSearchClick = {
151152
rootNavController.navigate(Search)
152153
},
154+
onRandomSongsClick = {
155+
rootNavController.navigate(RandomSongs)
156+
},
153157
onSongClick = {
154158
songInfoProperties = SongInfoProperties(
155159
songId = it,
@@ -412,6 +416,26 @@ class MainActivity : ComponentActivity() {
412416
)
413417
}
414418
}
419+
composable<RandomSongs> {
420+
ScreenWithMiniPlayer(
421+
viewModelStoreOwner = viewModelStoreOwner,
422+
onMiniPlayerClick = {
423+
rootNavController.navigate(Player)
424+
}
425+
) {
426+
RandomSongsScreen(
427+
onBackClick = {
428+
rootNavController.popBackStack()
429+
},
430+
onSongClick = {
431+
songInfoProperties = SongInfoProperties(
432+
songId = it,
433+
showAlbum = true
434+
)
435+
}
436+
)
437+
}
438+
}
415439
}
416440
songInfoProperties?.let { songProperties ->
417441
ModalBottomSheet(

app/src/main/java/ru/stersh/youamp/main/ui/Navigation.kt

+4-1
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,7 @@ object Artists
5656
object Playlists
5757

5858
@Serializable
59-
object FavoriteSongs
59+
object FavoriteSongs
60+
61+
@Serializable
62+
object RandomSongs

core/api/src/main/java/ru/stersh/youamp/core/api/provider/ApiProvider.kt

+6
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ interface ApiProvider {
1111

1212
suspend fun requireApiId(): Long
1313

14+
suspend fun getApi(id: Long): SubsonicApi?
15+
16+
suspend fun requireApi(id: Long): SubsonicApi
17+
1418
fun flowApi(): Flow<SubsonicApi>
1519

1620
fun flowApiOrNull(): Flow<SubsonicApi?>
21+
22+
fun flowApiId(): Flow<Long?>
1723
}

core/properties/src/main/java/ru/stresh/youamp/core/properties/app/AppProperties.kt

-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import android.net.Uri
55
data class AppProperties(
66
val name: String,
77
val version: String,
8-
val googlePlayAppUri: Uri,
9-
val googlePlayBrowserUri: Uri,
108
val githubUri: Uri,
119
val fdroidUri: Uri,
1210
val crwodinUri: Uri

core/ui/src/main/java/ru/stersh/youamp/core/ui/Artist.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import androidx.compose.material.icons.rounded.Person
1313
import androidx.compose.material3.MaterialTheme
1414
import androidx.compose.material3.Text
1515
import androidx.compose.runtime.Composable
16-
import androidx.compose.runtime.NonRestartableComposable
1716
import androidx.compose.runtime.Stable
1817
import androidx.compose.ui.Modifier
1918
import androidx.compose.ui.draw.clip
@@ -98,7 +97,7 @@ private fun ArtistArtwork(
9897
}
9998

10099
@Composable
101-
@NonRestartableComposable
100+
102101
private fun PlayButtonBackground(modifier: Modifier = Modifier) {
103102
Box(
104103
modifier = modifier
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package ru.stersh.youamp.core.ui
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.RowScope
7+
import androidx.compose.foundation.layout.fillMaxWidth
8+
import androidx.compose.foundation.layout.padding
9+
import androidx.compose.material3.MaterialTheme
10+
import androidx.compose.material3.Text
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.text.style.TextAlign
15+
import androidx.compose.ui.unit.dp
16+
17+
18+
@Composable
19+
fun HeaderLayout(
20+
title: @Composable () -> Unit,
21+
actions: @Composable RowScope.() -> Unit,
22+
modifier: Modifier = Modifier
23+
) {
24+
Column(
25+
horizontalAlignment = Alignment.CenterHorizontally,
26+
verticalArrangement = Arrangement.spacedBy(16.dp),
27+
modifier = modifier
28+
.padding(horizontal = 24.dp)
29+
.padding(bottom = 12.dp)
30+
) {
31+
title()
32+
Row(
33+
modifier = Modifier.fillMaxWidth(),
34+
horizontalArrangement = Arrangement.SpaceEvenly
35+
) {
36+
actions()
37+
}
38+
}
39+
}
40+
41+
@Composable
42+
fun HeaderTitle(
43+
text: String,
44+
modifier: Modifier = Modifier
45+
) {
46+
Text(
47+
text = text,
48+
textAlign = TextAlign.Center,
49+
style = MaterialTheme.typography.headlineLarge,
50+
maxLines = 1,
51+
minLines = 1,
52+
modifier = modifier.padding(vertical = 100.dp, horizontal = 24.dp)
53+
)
54+
}

core/ui/src/main/java/ru/stersh/youamp/core/ui/PlayButton.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import androidx.compose.material3.IconButton
1212
import androidx.compose.material3.IconButtonDefaults
1313
import androidx.compose.material3.MaterialTheme
1414
import androidx.compose.runtime.Composable
15-
import androidx.compose.runtime.NonRestartableComposable
1615
import androidx.compose.ui.Modifier
1716
import androidx.compose.ui.tooling.preview.Preview
1817
import androidx.compose.ui.unit.dp
@@ -39,7 +38,7 @@ fun PlayButtonOutlined(
3938
}
4039

4140
@Composable
42-
@NonRestartableComposable
41+
4342
fun PlayButton(
4443
isPlaying: Boolean,
4544
onClick: () -> Unit,

feature/about/src/main/java/ru/stresh/youamp/feature/about/ui/AboutAppViewModel.kt

-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ internal class AboutAppViewModel(appPropertiesStorage: AppPropertiesStorage) : V
1616
return AboutStateUi(
1717
name = name,
1818
version = version,
19-
googlePlayAppUri = googlePlayAppUri,
20-
googlePlayBrowserUri = googlePlayBrowserUri,
2119
githubUri = githubUri,
2220
fdroidUri = fdroidUri,
2321
crwodinUri = crwodinUri

feature/about/src/main/java/ru/stresh/youamp/feature/about/ui/AboutScreen.kt

-2
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,6 @@ private fun AboutScreenPreview() {
229229
state = AboutStateUi(
230230
name = "Youamp",
231231
version = "1.0.0-alpha04",
232-
googlePlayAppUri = Uri.EMPTY,
233-
googlePlayBrowserUri = Uri.EMPTY,
234232
githubUri = Uri.EMPTY,
235233
fdroidUri = Uri.EMPTY,
236234
crwodinUri = Uri.EMPTY

feature/about/src/main/java/ru/stresh/youamp/feature/about/ui/AboutStateUi.kt

-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import androidx.compose.runtime.Immutable
77
internal data class AboutStateUi(
88
val name: String,
99
val version: String,
10-
val googlePlayAppUri: Uri,
11-
val googlePlayBrowserUri: Uri,
1210
val githubUri: Uri,
1311
val fdroidUri: Uri,
1412
val crwodinUri: Uri

feature/album/info/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies {
1515
implementation project(":core:api")
1616
implementation project(":core:utils")
1717
implementation project(":shared:player")
18+
implementation project(":shared:favorites")
1819
implementation(libs.bundles.lifecycle)
1920
implementation(libs.coil.compose)
2021
implementation(libs.bundles.koin)

feature/album/info/src/main/java/ru/stersh/youamp/feature/album/Di.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import ru.stersh.youamp.feature.album.domain.AlbumInfoRepository
99
import ru.stersh.youamp.feature.album.ui.AlbumInfoViewModel
1010

1111
val albumInfoModule = module {
12-
single<AlbumInfoRepository> { AlbumInfoRepositoryImpl(get()) }
12+
single<AlbumInfoRepository> { AlbumInfoRepositoryImpl(get(), get()) }
1313
single<AlbumFavoriteRepository> { AlbumFavoriteRepositoryImpl(get()) }
1414

1515
viewModel { (id: String) ->
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
package ru.stersh.youamp.feature.album.data
22

3-
import ru.stersh.youamp.core.api.provider.ApiProvider
43
import ru.stersh.youamp.feature.album.domain.AlbumFavoriteRepository
4+
import ru.stresh.youamp.shared.favorites.AlbumFavoritesStorage
55

66
internal class AlbumFavoriteRepositoryImpl(
7-
private val apiProvider: ApiProvider
7+
private val albumFavoritesStorage: AlbumFavoritesStorage
88
) : AlbumFavoriteRepository {
99

1010
override suspend fun setFavorite(id: String, isFavorite: Boolean) {
11-
val api = apiProvider.getApi()
12-
if (isFavorite) {
13-
api.starAlbum(id)
14-
} else {
15-
api.unstarAlbum(id)
16-
}
11+
albumFavoritesStorage.setAlbumFavorite(id, isFavorite)
1712
}
1813
}

0 commit comments

Comments
 (0)