Skip to content

Commit 5674cd1

Browse files
committed
feat(android): implement getAlbumsByArtistAsync
1 parent 398ca02 commit 5674cd1

File tree

11 files changed

+397
-49
lines changed

11 files changed

+397
-49
lines changed

android/src/main/java/com/musiclibrary/MusicLibraryModule.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.musiclibrary.utils.ModuleUtils.withModuleScope
1111
import com.musiclibrary.tracks.GetTracks
1212
import com.musiclibrary.tracks.GetTracksByAlbum
1313
import com.musiclibrary.albums.GetAlbums
14+
import com.musiclibrary.albums.GetAlbumsByArtist
1415
import com.musiclibrary.artists.GetArtists
1516
import com.musiclibrary.tracks.GetTrackMetadataQuery
1617

@@ -31,7 +32,7 @@ class MusicLibraryModule(reactContext: ReactApplicationContext) :
3132
}
3233
}
3334

34-
override fun getTrackMetadataAsync(trackId: String, promise: Promise) {
35+
override fun getTrackMetadataAsync(trackId: String, promise: Promise) {
3536
throwUnlessPermissionsGranted(reactApplicationContext, isWrite = false) {
3637
withModuleScope(promise) {
3738
GetTrackMetadataQuery(reactApplicationContext, trackId, promise)
@@ -58,6 +59,15 @@ class MusicLibraryModule(reactContext: ReactApplicationContext) :
5859
}
5960
}
6061

62+
override fun getAlbumsByArtistAsync(artistId: String, promise: Promise) {
63+
throwUnlessPermissionsGranted(reactApplicationContext, isWrite = false) {
64+
withModuleScope(promise) {
65+
GetAlbumsByArtist(reactApplicationContext, artistId, promise)
66+
.execute()
67+
}
68+
}
69+
}
70+
6171
override fun getArtistsAsync(options: ReadableMap, promise: Promise) {
6272
throwUnlessPermissionsGranted(reactApplicationContext, isWrite = false) {
6373
withModuleScope(promise) {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.musiclibrary.albums
2+
3+
import android.content.Context
4+
import com.facebook.react.bridge.Promise
5+
import com.facebook.react.bridge.Arguments
6+
import com.musiclibrary.utils.DataConverter
7+
8+
internal class GetAlbumsByArtist(
9+
private val context: Context,
10+
private val artistId: String,
11+
private val promise: Promise
12+
) {
13+
14+
fun execute() {
15+
try {
16+
val contentResolver = context.contentResolver
17+
val albums = GetAlbumsByArtistQuery.getAlbumsByArtist(contentResolver, artistId)
18+
19+
// Convert albums to React Native bridge format
20+
val albumsArray = Arguments.createArray()
21+
albums.forEach { album ->
22+
albumsArray.pushMap(DataConverter.albumToWritableMap(album))
23+
}
24+
25+
promise.resolve(albumsArray)
26+
} catch (e: Exception) {
27+
promise.reject("QUERY_ERROR", "Failed to query albums by artist: ${e.message}", e)
28+
}
29+
}
30+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.musiclibrary.albums
2+
3+
import android.content.ContentResolver
4+
import android.provider.MediaStore
5+
import android.net.Uri
6+
import com.musiclibrary.models.Album
7+
import androidx.core.net.toUri
8+
9+
object GetAlbumsByArtistQuery {
10+
fun getAlbumsByArtist(
11+
contentResolver: ContentResolver,
12+
artistId: String,
13+
): List<Album> {
14+
val projection = arrayOf(
15+
MediaStore.Audio.Albums._ID,
16+
MediaStore.Audio.Albums.ALBUM,
17+
MediaStore.Audio.Albums.ARTIST,
18+
MediaStore.Audio.Albums.NUMBER_OF_SONGS,
19+
MediaStore.Audio.Albums.FIRST_YEAR,
20+
)
21+
22+
val selection = "${MediaStore.Audio.Albums.ARTIST_ID} = ? AND ${MediaStore.Audio.Albums.NUMBER_OF_SONGS} > 0"
23+
val selectionArgs = arrayOf(artistId)
24+
val sortOrder = "${MediaStore.Audio.Albums.ALBUM} ASC"
25+
26+
val cursor = contentResolver.query(
27+
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
28+
projection,
29+
selection,
30+
selectionArgs,
31+
sortOrder
32+
) ?: throw RuntimeException("Failed to query MediaStore: cursor is null")
33+
34+
val albums = mutableListOf<Album>()
35+
36+
cursor.use { c ->
37+
val idColumn = c.getColumnIndexOrThrow(MediaStore.Audio.Albums._ID)
38+
val albumColumn = c.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM)
39+
val artistColumn = c.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST)
40+
val trackCountColumn = c.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS)
41+
val firstYearColumn = c.getColumnIndexOrThrow(MediaStore.Audio.Albums.FIRST_YEAR)
42+
43+
while (c.moveToNext()) {
44+
try {
45+
val id = c.getLong(idColumn)
46+
val albumTitle = c.getString(albumColumn) ?: ""
47+
val artist = c.getString(artistColumn) ?: ""
48+
val trackCount = c.getInt(trackCountColumn)
49+
val firstYear = c.getInt(firstYearColumn)
50+
51+
// Skip invalid albums
52+
if (albumTitle.isEmpty() || trackCount == 0) {
53+
continue
54+
}
55+
56+
// Get artwork URI
57+
val artworkUri: Uri = "content://media/external/audio/albumart/${id}".toUri()
58+
59+
// Create an Album
60+
val album = Album(
61+
id = id.toString(),
62+
title = albumTitle,
63+
artist = artist,
64+
artwork = artworkUri.toString(),
65+
trackCount = trackCount,
66+
year = if (firstYear > 0) firstYear else null
67+
)
68+
69+
albums.add(album)
70+
} catch (e: Exception) {
71+
continue
72+
}
73+
}
74+
}
75+
76+
return albums
77+
}
78+
}

example/src/components/TrackItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const styles = StyleSheet.create({
4545
alignItems: 'center',
4646
padding: 12,
4747
borderBottomWidth: 1,
48-
borderBottomColor: '#eee',
48+
borderBottomColor: '#e0e0e0',
4949
},
5050
cover: {
5151
width: 50,

example/src/navigation/index.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ import PlayerScreen from '../pages/PlayerScreen';
66
import AlbumListScreen from '../pages/AlbumListScreen';
77
import AlbumTrackListScreen from '../pages/AlbumTrackListScreen';
88
import ArtistListScreen from '../pages/ArtistListScreen';
9-
import type { Album } from '@nodefinity/react-native-music-library';
9+
import ArtistAlbumAndTrackListScreen from '../pages/ArtistAlbumAndTrackListScreen';
10+
import type { Album, Artist } from '@nodefinity/react-native-music-library';
1011

1112
export type RootStackParamList = {
1213
Home: undefined;
1314
TrackList: undefined;
1415
AlbumList: undefined;
1516
AlbumTrackList: { album: Album };
1617
ArtistList: undefined;
18+
ArtistAlbumAndTrackList: { artist: Artist };
1719
Player: undefined;
1820
};
1921

@@ -60,6 +62,14 @@ export default function Navigation() {
6062
headerBackTitle: 'Back',
6163
}}
6264
/>
65+
<Stack.Screen
66+
name="ArtistAlbumAndTrackList"
67+
component={ArtistAlbumAndTrackListScreen}
68+
options={{
69+
title: 'Artist Albums',
70+
headerBackTitle: 'Back',
71+
}}
72+
/>
6373
<Stack.Screen
6474
name="Player"
6575
component={PlayerScreen}

example/src/pages/AlbumTrackListScreen.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,11 @@ export default function AlbumTrackListScreen({ navigation, route }: Props) {
8787
const styles = StyleSheet.create({
8888
container: {
8989
flex: 1,
90-
backgroundColor: '#fff',
9190
},
9291
header: {
9392
padding: 20,
9493
borderBottomWidth: 1,
95-
borderBottomColor: '#eee',
94+
borderBottomColor: '#e0e0e0',
9695
},
9796
albumTitle: {
9897
fontSize: 24,

0 commit comments

Comments
 (0)