From 558f17794a791979fd955229460d0a390e7f00ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Dec 2023 12:41:38 +0100 Subject: [PATCH] Bump org.jlleitschuh.gradle:ktlint-gradle from 11.6.1 to 12.0.2 (#609) * Bump org.jlleitschuh.gradle:ktlint-gradle from 11.6.1 to 12.0.2 Bumps org.jlleitschuh.gradle:ktlint-gradle from 11.6.1 to 12.0.2. --- updated-dependencies: - dependency-name: org.jlleitschuh.gradle:ktlint-gradle dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Lint * Fix multiline strings --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Charlotte Van Petegem --- .editorconfig | 18 +- .../java/me/vanpetegem/accentor/Accentor.kt | 19 +- .../me/vanpetegem/accentor/api/album/Album.kt | 7 +- .../vanpetegem/accentor/api/artist/Artist.kt | 7 +- .../vanpetegem/accentor/api/auth/AuthToken.kt | 21 +- .../api/codecconversion/CodecConversion.kt | 7 +- .../accentor/api/playlist/Playlist.kt | 7 +- .../me/vanpetegem/accentor/api/plays/Play.kt | 19 +- .../me/vanpetegem/accentor/api/track/Track.kt | 7 +- .../me/vanpetegem/accentor/api/user/User.kt | 7 +- .../me/vanpetegem/accentor/api/util/Util.kt | 5 +- .../accentor/data/AccentorDatabase.kt | 455 ++++++++++-------- .../vanpetegem/accentor/data/albums/Album.kt | 92 ++-- .../accentor/data/albums/AlbumDao.kt | 68 +-- .../accentor/data/albums/AlbumRepository.kt | 126 ++--- .../accentor/data/albums/ApiAlbum.kt | 2 +- .../accentor/data/albums/DbAlbum.kt | 10 +- .../accentor/data/artists/ApiArtist.kt | 2 +- .../accentor/data/artists/Artist.kt | 39 +- .../accentor/data/artists/ArtistDao.kt | 21 +- .../accentor/data/artists/ArtistRepository.kt | 101 ++-- .../accentor/data/artists/DbArtist.kt | 2 +- .../data/authentication/AuthenticationData.kt | 2 +- .../AuthenticationDataSource.kt | 167 ++++--- .../AuthenticationRepository.kt | 69 +-- .../codecconversions/ApiCodecConversion.kt | 2 +- .../data/codecconversions/CodecConversion.kt | 22 +- .../codecconversions/CodecConversionDao.kt | 7 +- .../CodecConversionRepository.kt | 66 +-- .../codecconversions/DbCodecConversion.kt | 2 +- .../accentor/data/playlists/Access.kt | 2 +- .../accentor/data/playlists/ApiPlaylist.kt | 2 +- .../accentor/data/playlists/DbPlaylist.kt | 4 +- .../accentor/data/playlists/Playlist.kt | 72 +-- .../accentor/data/playlists/PlaylistDao.kt | 14 +- .../data/playlists/PlaylistRepository.kt | 77 +-- .../accentor/data/playlists/PlaylistType.kt | 2 +- .../vanpetegem/accentor/data/plays/ApiPlay.kt | 2 +- .../vanpetegem/accentor/data/plays/DbPlay.kt | 2 +- .../me/vanpetegem/accentor/data/plays/Play.kt | 20 +- .../accentor/data/plays/PlayRepository.kt | 105 ++-- .../accentor/data/plays/UnreportedPlay.kt | 2 +- .../data/preferences/PreferencesDataSource.kt | 46 +- .../accentor/data/tracks/ApiTrack.kt | 2 +- .../accentor/data/tracks/DbTrack.kt | 6 +- .../vanpetegem/accentor/data/tracks/Track.kt | 119 +++-- .../accentor/data/tracks/TrackDao.kt | 79 +-- .../accentor/data/tracks/TrackRepository.kt | 91 ++-- .../vanpetegem/accentor/data/users/ApiUser.kt | 2 +- .../vanpetegem/accentor/data/users/DbUser.kt | 2 +- .../accentor/data/users/Permission.kt | 2 +- .../me/vanpetegem/accentor/data/users/User.kt | 20 +- .../vanpetegem/accentor/data/users/UserDao.kt | 7 +- .../accentor/data/users/UserRepository.kt | 70 +-- .../accentor/media/MediaSessionConnection.kt | 343 +++++++------ .../vanpetegem/accentor/media/MusicService.kt | 164 ++++--- .../accentor/media/NotificationBuilder.kt | 87 ++-- .../vanpetegem/accentor/ui/AccentorTheme.kt | 118 ++--- .../accentor/ui/albums/AlbumCard.kt | 21 +- .../accentor/ui/albums/AlbumGrid.kt | 14 +- .../accentor/ui/albums/AlbumView.kt | 23 +- .../accentor/ui/albums/AlbumViewModel.kt | 32 +- .../accentor/ui/albums/AlbumsViewModel.kt | 50 +- .../accentor/ui/artists/ArtistCard.kt | 11 +- .../accentor/ui/artists/ArtistGrid.kt | 13 +- .../accentor/ui/artists/ArtistView.kt | 9 +- .../accentor/ui/artists/ArtistViewModel.kt | 49 +- .../accentor/ui/artists/ArtistsViewModel.kt | 50 +- .../accentor/ui/home/HomeFragment.kt | 6 +- .../accentor/ui/home/HomeViewModel.kt | 41 +- .../accentor/ui/login/LoginActivity.kt | 95 ++-- .../accentor/ui/login/LoginFormState.kt | 2 +- .../accentor/ui/login/LoginResult.kt | 2 +- .../accentor/ui/login/LoginViewModel.kt | 80 +-- .../accentor/ui/main/MainActivity.kt | 82 ++-- .../accentor/ui/main/MainViewModel.kt | 158 +++--- .../accentor/ui/player/BottomBar.kt | 14 +- .../vanpetegem/accentor/ui/player/Controls.kt | 31 +- .../accentor/ui/player/CurrentTrackInfo.kt | 8 +- .../accentor/ui/player/PlayerOverlay.kt | 45 +- .../accentor/ui/player/PlayerViewModel.kt | 127 +++-- .../me/vanpetegem/accentor/ui/player/Queue.kt | 56 ++- .../vanpetegem/accentor/ui/player/ToolBar.kt | 8 +- .../accentor/ui/playlists/PlaylistList.kt | 47 +- .../accentor/ui/playlists/PlaylistView.kt | 39 +- .../ui/playlists/PlaylistViewModel.kt | 39 +- .../ui/playlists/PlaylistsViewModel.kt | 54 ++- .../ui/preferences/PreferencesActivity.kt | 38 +- .../ui/preferences/PreferencesViewModel.kt | 55 ++- .../vanpetegem/accentor/ui/tracks/TrackRow.kt | 21 +- .../me/vanpetegem/accentor/ui/util/Event.kt | 1 - .../accentor/ui/util/FastScrollableGrid.kt | 42 +- .../me/vanpetegem/accentor/ui/util/Timer.kt | 5 +- .../me/vanpetegem/accentor/util/FuelGson.kt | 62 ++- .../me/vanpetegem/accentor/util/Result.kt | 2 +- .../accentor/util/RoomTypeConverters.kt | 6 +- .../accentor/util/SharedPreferenceLiveData.kt | 44 +- build.gradle | 2 +- 98 files changed, 2404 insertions(+), 1921 deletions(-) diff --git a/.editorconfig b/.editorconfig index 1d3822b3..46e18b63 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,14 @@ -[*.{kt,kts}] -indent_size=4 -insert_final_newline=true -max_line_length=160 +root = true + +[*] +end_of_line = lf +charset = utf-8 +insert_final_newline = true +indent_size = 4 +indent_style = space +trim_trailing_whitespace = true +max_line_length = 160 + +[*.kt] +ktlint_standard_function-naming = disabled +ktlint_standard_property-naming = disabled diff --git a/app/src/main/java/me/vanpetegem/accentor/Accentor.kt b/app/src/main/java/me/vanpetegem/accentor/Accentor.kt index 2d76c60a..50fe550f 100644 --- a/app/src/main/java/me/vanpetegem/accentor/Accentor.kt +++ b/app/src/main/java/me/vanpetegem/accentor/Accentor.kt @@ -11,9 +11,9 @@ import coil.disk.DiskCache import com.github.kittinunf.fuel.core.FuelManager import com.google.android.material.color.DynamicColors import dagger.hilt.android.HiltAndroidApp +import me.vanpetegem.accentor.data.preferences.PreferencesDataSource import java.io.File import javax.inject.Inject -import me.vanpetegem.accentor.data.preferences.PreferencesDataSource @HiltAndroidApp class Accentor : Application(), ImageLoaderFactory { @@ -21,14 +21,15 @@ class Accentor : Application(), ImageLoaderFactory { override fun onCreate() { super.onCreate() - version = if (Build.VERSION.SDK_INT >= 33) { - applicationContext.packageManager.getPackageInfo( - packageName, - PackageManager.PackageInfoFlags.of(0) - ).versionName - } else { - applicationContext.packageManager.getPackageInfo(packageName, 0).versionName - } + version = + if (Build.VERSION.SDK_INT >= 33) { + applicationContext.packageManager.getPackageInfo( + packageName, + PackageManager.PackageInfoFlags.of(0), + ).versionName + } else { + applicationContext.packageManager.getPackageInfo(packageName, 0).versionName + } userAgent = "Accentor/$version" FuelManager.instance.baseHeaders = mapOf("User-Agent" to userAgent) DynamicColors.applyToActivitiesIfAvailable(this) diff --git a/app/src/main/java/me/vanpetegem/accentor/api/album/Album.kt b/app/src/main/java/me/vanpetegem/accentor/api/album/Album.kt index 4e455bcc..3a868f0a 100644 --- a/app/src/main/java/me/vanpetegem/accentor/api/album/Album.kt +++ b/app/src/main/java/me/vanpetegem/accentor/api/album/Album.kt @@ -7,7 +7,10 @@ import me.vanpetegem.accentor.data.authentication.AuthenticationData import me.vanpetegem.accentor.util.Result import me.vanpetegem.accentor.util.responseObject -fun index(server: String, authenticationData: AuthenticationData): Sequence>> { +fun index( + server: String, + authenticationData: AuthenticationData, +): Sequence>> { var page = 1 fun doFetch(): Result>? { @@ -26,7 +29,7 @@ fun index(server: String, authenticationData: AuthenticationData): Sequence Result.Error(Exception("Error getting albums", e)) } + { e: Throwable -> Result.Error(Exception("Error getting albums", e)) }, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/api/artist/Artist.kt b/app/src/main/java/me/vanpetegem/accentor/api/artist/Artist.kt index 05bc9a19..650bef59 100644 --- a/app/src/main/java/me/vanpetegem/accentor/api/artist/Artist.kt +++ b/app/src/main/java/me/vanpetegem/accentor/api/artist/Artist.kt @@ -7,7 +7,10 @@ import me.vanpetegem.accentor.data.authentication.AuthenticationData import me.vanpetegem.accentor.util.Result import me.vanpetegem.accentor.util.responseObject -fun index(server: String, authenticationData: AuthenticationData): Sequence>> { +fun index( + server: String, + authenticationData: AuthenticationData, +): Sequence>> { var page = 1 fun doFetch(): Result>? { @@ -26,7 +29,7 @@ fun index(server: String, authenticationData: AuthenticationData): Sequence Result.Error(Exception("Error getting artists", e)) } + { e: Throwable -> Result.Error(Exception("Error getting artists", e)) }, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/api/auth/AuthToken.kt b/app/src/main/java/me/vanpetegem/accentor/api/auth/AuthToken.kt index 8869f781..bc2b3e21 100644 --- a/app/src/main/java/me/vanpetegem/accentor/api/auth/AuthToken.kt +++ b/app/src/main/java/me/vanpetegem/accentor/api/auth/AuthToken.kt @@ -10,26 +10,35 @@ import me.vanpetegem.accentor.util.responseObject import me.vanpetegem.accentor.version class AuthToken(val user_agent: String) + class Credentials(val name: String, val password: String, val auth_token: AuthToken) -fun create(server: String, username: String, password: String): Result { +fun create( + server: String, + username: String, + password: String, +): Result { return "$server/api/auth_tokens".httpPost() .set("Accept", "application/json") .jsonBody( Credentials( username, password, - AuthToken("Accentor $version on Android ${Build.VERSION.RELEASE} (${Build.DEVICE})") - ) + AuthToken("Accentor $version on Android ${Build.VERSION.RELEASE} (${Build.DEVICE})"), + ), ) .responseObject().third .fold( { user: AuthenticationData -> Result.Success(user) }, - { e: Throwable -> Result.Error(Exception("Error logging in", e)) } + { e: Throwable -> Result.Error(Exception("Error logging in", e)) }, ) } -fun destroy(server: String, authenticationData: AuthenticationData, id: Int): Result { +fun destroy( + server: String, + authenticationData: AuthenticationData, + id: Int, +): Result { return "$server/api/auth_tokens/$id".httpDelete() .set("Accept", "application/json") .set("X-Secret", authenticationData.secret) @@ -37,6 +46,6 @@ fun destroy(server: String, authenticationData: AuthenticationData, id: Int): Re .response().third .fold( { Result.Success(Unit) }, - { e: Throwable -> Result.Error(Exception("Error logging out", e)) } + { e: Throwable -> Result.Error(Exception("Error logging out", e)) }, ) } diff --git a/app/src/main/java/me/vanpetegem/accentor/api/codecconversion/CodecConversion.kt b/app/src/main/java/me/vanpetegem/accentor/api/codecconversion/CodecConversion.kt index 7fdd0ff6..bd50f09a 100644 --- a/app/src/main/java/me/vanpetegem/accentor/api/codecconversion/CodecConversion.kt +++ b/app/src/main/java/me/vanpetegem/accentor/api/codecconversion/CodecConversion.kt @@ -7,7 +7,10 @@ import me.vanpetegem.accentor.data.codecconversions.ApiCodecConversion import me.vanpetegem.accentor.util.Result import me.vanpetegem.accentor.util.responseObject -fun index(server: String, authenticationData: AuthenticationData): Sequence>> { +fun index( + server: String, + authenticationData: AuthenticationData, +): Sequence>> { var page = 1 fun doFetch(): Result>? { @@ -26,7 +29,7 @@ fun index(server: String, authenticationData: AuthenticationData): Sequence Result.Error(Exception("Error getting codec conversions", e)) } + { e: Throwable -> Result.Error(Exception("Error getting codec conversions", e)) }, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/api/playlist/Playlist.kt b/app/src/main/java/me/vanpetegem/accentor/api/playlist/Playlist.kt index 8565d87c..f79153e1 100644 --- a/app/src/main/java/me/vanpetegem/accentor/api/playlist/Playlist.kt +++ b/app/src/main/java/me/vanpetegem/accentor/api/playlist/Playlist.kt @@ -7,7 +7,10 @@ import me.vanpetegem.accentor.data.playlists.ApiPlaylist import me.vanpetegem.accentor.util.Result import me.vanpetegem.accentor.util.responseObject -fun index(server: String, authenticationData: AuthenticationData): Sequence>> { +fun index( + server: String, + authenticationData: AuthenticationData, +): Sequence>> { var page = 1 fun doFetch(): Result>? { @@ -26,7 +29,7 @@ fun index(server: String, authenticationData: AuthenticationData): Sequence Result.Error(Exception("Error getting playlists", e)) } + { e: Throwable -> Result.Error(Exception("Error getting playlists", e)) }, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/api/plays/Play.kt b/app/src/main/java/me/vanpetegem/accentor/api/plays/Play.kt index 37e47a2d..12d4e3e7 100644 --- a/app/src/main/java/me/vanpetegem/accentor/api/plays/Play.kt +++ b/app/src/main/java/me/vanpetegem/accentor/api/plays/Play.kt @@ -2,18 +2,24 @@ package me.vanpetegem.accentor.api.plays import com.github.kittinunf.fuel.httpGet import com.github.kittinunf.fuel.httpPost -import java.time.Instant import me.vanpetegem.accentor.api.util.retry import me.vanpetegem.accentor.data.authentication.AuthenticationData import me.vanpetegem.accentor.data.plays.ApiPlay import me.vanpetegem.accentor.util.Result import me.vanpetegem.accentor.util.jsonBody import me.vanpetegem.accentor.util.responseObject +import java.time.Instant data class Arguments(val play: PlayArguments) + data class PlayArguments(val trackId: Int, val playedAt: Instant) -fun create(server: String, authenticationData: AuthenticationData, trackId: Int, playedAt: Instant): Result { +fun create( + server: String, + authenticationData: AuthenticationData, + trackId: Int, + playedAt: Instant, +): Result { return "$server/api/plays".httpPost() .set("Accept", "application/json") .set("X-Secret", authenticationData.secret) @@ -22,11 +28,14 @@ fun create(server: String, authenticationData: AuthenticationData, trackId: Int, .responseObject().third .fold( { play: ApiPlay -> Result.Success(play) }, - { e: Throwable -> Result.Error(Exception("Error creating play", e)) } + { e: Throwable -> Result.Error(Exception("Error creating play", e)) }, ) } -fun index(server: String, authenticationData: AuthenticationData): Sequence>> { +fun index( + server: String, + authenticationData: AuthenticationData, +): Sequence>> { var page = 1 fun doFetch(): Result>? { @@ -45,7 +54,7 @@ fun index(server: String, authenticationData: AuthenticationData): Sequence Result.Error(Exception("Error getting plays", e)) } + { e: Throwable -> Result.Error(Exception("Error getting plays", e)) }, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/api/track/Track.kt b/app/src/main/java/me/vanpetegem/accentor/api/track/Track.kt index 10b483e2..83a54c7b 100644 --- a/app/src/main/java/me/vanpetegem/accentor/api/track/Track.kt +++ b/app/src/main/java/me/vanpetegem/accentor/api/track/Track.kt @@ -7,7 +7,10 @@ import me.vanpetegem.accentor.data.tracks.ApiTrack import me.vanpetegem.accentor.util.Result import me.vanpetegem.accentor.util.responseObject -fun index(server: String, authenticationData: AuthenticationData): Sequence>> { +fun index( + server: String, + authenticationData: AuthenticationData, +): Sequence>> { var page = 1 fun doFetch(): Result>? { @@ -26,7 +29,7 @@ fun index(server: String, authenticationData: AuthenticationData): Sequence Result.Error(Exception("Error getting tracks", e)) } + { e: Throwable -> Result.Error(Exception("Error getting tracks", e)) }, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/api/user/User.kt b/app/src/main/java/me/vanpetegem/accentor/api/user/User.kt index 4c63bd86..6322f96d 100644 --- a/app/src/main/java/me/vanpetegem/accentor/api/user/User.kt +++ b/app/src/main/java/me/vanpetegem/accentor/api/user/User.kt @@ -7,7 +7,10 @@ import me.vanpetegem.accentor.data.users.ApiUser import me.vanpetegem.accentor.util.Result import me.vanpetegem.accentor.util.responseObject -fun index(server: String, authenticationData: AuthenticationData): Sequence>> { +fun index( + server: String, + authenticationData: AuthenticationData, +): Sequence>> { var page = 1 fun doFetch(): Result>? { @@ -26,7 +29,7 @@ fun index(server: String, authenticationData: AuthenticationData): Sequence Result.Error(Exception("Error getting users", e)) } + { e: Throwable -> Result.Error(Exception("Error getting users", e)) }, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/api/util/Util.kt b/app/src/main/java/me/vanpetegem/accentor/api/util/Util.kt index 0cfb57da..f61d9d76 100644 --- a/app/src/main/java/me/vanpetegem/accentor/api/util/Util.kt +++ b/app/src/main/java/me/vanpetegem/accentor/api/util/Util.kt @@ -2,7 +2,10 @@ package me.vanpetegem.accentor.api.util import me.vanpetegem.accentor.util.Result -fun retry(n: Int, block: () -> Result?): Result? { +fun retry( + n: Int, + block: () -> Result?, +): Result? { var tries = 0 var result: Result? do { diff --git a/app/src/main/java/me/vanpetegem/accentor/data/AccentorDatabase.kt b/app/src/main/java/me/vanpetegem/accentor/data/AccentorDatabase.kt index da2b1c4f..1946b641 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/AccentorDatabase.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/AccentorDatabase.kt @@ -12,8 +12,6 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import java.time.Instant -import javax.inject.Singleton import me.vanpetegem.accentor.data.albums.AlbumDao import me.vanpetegem.accentor.data.albums.DbAlbum import me.vanpetegem.accentor.data.albums.DbAlbumArtist @@ -36,6 +34,8 @@ import me.vanpetegem.accentor.data.tracks.TrackDao import me.vanpetegem.accentor.data.users.DbUser import me.vanpetegem.accentor.data.users.UserDao import me.vanpetegem.accentor.util.RoomTypeConverters +import java.time.Instant +import javax.inject.Singleton @TypeConverters(RoomTypeConverters::class) @Database( @@ -52,18 +52,25 @@ import me.vanpetegem.accentor.util.RoomTypeConverters DbTrack::class, DbTrackArtist::class, DbTrackGenre::class, - UnreportedPlay::class + UnreportedPlay::class, ], - version = 12 + version = 12, ) abstract class AccentorDatabase : RoomDatabase() { abstract fun albumDao(): AlbumDao + abstract fun artistDao(): ArtistDao + abstract fun codecConversionDao(): CodecConversionDao + abstract fun playlistDao(): PlaylistDao + abstract fun playDao(): PlayDao + abstract fun trackDao(): TrackDao + abstract fun userDao(): UserDao + abstract fun unreportedPlayDao(): UnreportedPlayDao } @@ -72,235 +79,257 @@ abstract class AccentorDatabase : RoomDatabase() { internal object DatabaseModule { @Provides @Singleton - fun provideAccentorDatabase(@ApplicationContext context: Context): AccentorDatabase { + fun provideAccentorDatabase( + @ApplicationContext context: Context, + ): AccentorDatabase { return Room.databaseBuilder( context.applicationContext, AccentorDatabase::class.java, - "accentor_database" + "accentor_database", ) - .addMigrations(object : Migration(2, 3) { - override fun migrate(db: SupportSQLiteDatabase) { - db.beginTransaction() - try { - db.execSQL("ALTER TABLE `album_artists` RENAME TO `album_artists_old`") - db.execSQL( - """ - CREATE TABLE `album_artists` ( - `album_id` INTEGER NOT NULL, - `artist_id` INTEGER NOT NULL, - `name` TEXT NOT NULL, - `order` INTEGER NOT NULL, ` - separator` TEXT, - PRIMARY KEY(`album_id`, `artist_id`, `name`)) - """ - ) - db.execSQL( - """ - INSERT INTO `album_artists` (`album_id`, `artist_id`, `name`, `order`, `separator`) - SELECT `album_id`, `artist_id`, `name`, `order`, `join` AS `separator` FROM `album_artists_old` - """ - ) - db.execSQL("DROP TABLE `album_artists_old`") - db.setTransactionSuccessful() - } finally { - db.endTransaction() + .addMigrations( + object : Migration(2, 3) { + override fun migrate(db: SupportSQLiteDatabase) { + db.beginTransaction() + try { + db.execSQL("ALTER TABLE `album_artists` RENAME TO `album_artists_old`") + db.execSQL( + """ + CREATE TABLE `album_artists` ( + `album_id` INTEGER NOT NULL, + `artist_id` INTEGER NOT NULL, + `name` TEXT NOT NULL, + `order` INTEGER NOT NULL, ` + separator` TEXT, + PRIMARY KEY(`album_id`, `artist_id`, `name`)) + """, + ) + db.execSQL( + """ + INSERT INTO `album_artists` (`album_id`, `artist_id`, `name`, `order`, `separator`) + SELECT `album_id`, `artist_id`, `name`, `order`, `join` AS `separator` FROM `album_artists_old` + """, + ) + db.execSQL("DROP TABLE `album_artists_old`") + db.setTransactionSuccessful() + } finally { + db.endTransaction() + } } - } - }) - .addMigrations(object : Migration(3, 4) { - override fun migrate(db: SupportSQLiteDatabase) { - db.beginTransaction() - try { - db.execSQL("ALTER TABLE `albums` ADD COLUMN `image_500` TEXT") - db.execSQL("ALTER TABLE `albums` ADD COLUMN `image_250` TEXT") - db.execSQL("ALTER TABLE `albums` ADD COLUMN `image_100` TEXT") - db.execSQL("ALTER TABLE `artists` ADD COLUMN `image_500` TEXT") - db.execSQL("ALTER TABLE `artists` ADD COLUMN `image_250` TEXT") - db.execSQL("ALTER TABLE `artists` ADD COLUMN `image_100` TEXT") - db.setTransactionSuccessful() - } finally { - db.endTransaction() + }, + ) + .addMigrations( + object : Migration(3, 4) { + override fun migrate(db: SupportSQLiteDatabase) { + db.beginTransaction() + try { + db.execSQL("ALTER TABLE `albums` ADD COLUMN `image_500` TEXT") + db.execSQL("ALTER TABLE `albums` ADD COLUMN `image_250` TEXT") + db.execSQL("ALTER TABLE `albums` ADD COLUMN `image_100` TEXT") + db.execSQL("ALTER TABLE `artists` ADD COLUMN `image_500` TEXT") + db.execSQL("ALTER TABLE `artists` ADD COLUMN `image_250` TEXT") + db.execSQL("ALTER TABLE `artists` ADD COLUMN `image_100` TEXT") + db.setTransactionSuccessful() + } finally { + db.endTransaction() + } } - } - }) - .addMigrations(object : Migration(4, 5) { - override fun migrate(db: SupportSQLiteDatabase) { - db.beginTransaction() - try { - db.execSQL("ALTER TABLE `album_labels` RENAME TO `album_labels_old`") - db.execSQL( - """ - CREATE TABLE `album_labels` ( - `album_id` INTEGER NOT NULL, - `label_id` INTEGER NOT NULL, - `catalogue_number` TEXT, - PRIMARY KEY(`album_id`, `label_id`) + }, + ) + .addMigrations( + object : Migration(4, 5) { + override fun migrate(db: SupportSQLiteDatabase) { + db.beginTransaction() + try { + db.execSQL("ALTER TABLE `album_labels` RENAME TO `album_labels_old`") + db.execSQL( + """ + CREATE TABLE `album_labels` ( + `album_id` INTEGER NOT NULL, + `label_id` INTEGER NOT NULL, + `catalogue_number` TEXT, + PRIMARY KEY(`album_id`, `label_id`) + ) + """, + ) + db.execSQL( + """ + INSERT INTO `album_labels` (`album_id`, `label_id`, `catalogue_number`) + SELECT `album_id`, `label_id`, `catalogue_number` FROM `album_labels_old` + """, ) - """ - ) - db.execSQL( - """ - INSERT INTO `album_labels` (`album_id`, `label_id`, `catalogue_number`) - SELECT `album_id`, `label_id`, `catalogue_number` FROM `album_labels_old` - """ - ) - db.execSQL("DROP TABLE `album_labels_old`") - db.setTransactionSuccessful() - } finally { - db.endTransaction() + db.execSQL("DROP TABLE `album_labels_old`") + db.setTransactionSuccessful() + } finally { + db.endTransaction() + } } - } - }) - .addMigrations(object : Migration(5, 6) { - override fun migrate(db: SupportSQLiteDatabase) { - db.beginTransaction() - try { - // The UPDATE statements aren't correct; - // they don't strip diacritics. However, - // the correct data will be loaded from the - // server at some point, so it's not that - // bad. - db.execSQL("ALTER TABLE `albums` ADD COLUMN `normalized_title` TEXT NOT NULL DEFAULT ''") - db.execSQL("UPDATE `albums` SET `normalized_title` = LOWER(`title`)") - db.execSQL("ALTER TABLE `artists` ADD COLUMN `normalized_name` TEXT NOT NULL DEFAULT ''") - db.execSQL("UPDATE `artists` SET `normalized_name` = LOWER(`name`)") - db.execSQL("ALTER TABLE `tracks` ADD COLUMN `normalized_title` TEXT NOT NULL DEFAULT ''") - db.execSQL("UPDATE `tracks` SET `normalized_title` = LOWER(`title`)") - db.execSQL("ALTER TABLE `album_artists` ADD COLUMN `normalized_name` TEXT NOT NULL DEFAULT ''") - db.execSQL("UPDATE `album_artists` SET `normalized_name` = LOWER(`name`)") - db.execSQL("ALTER TABLE `track_artists` ADD COLUMN `normalized_name` TEXT NOT NULL DEFAULT ''") - db.execSQL("UPDATE `track_artists` SET `normalized_name` = LOWER(`name`)") - db.setTransactionSuccessful() - } finally { - db.endTransaction() + }, + ) + .addMigrations( + object : Migration(5, 6) { + override fun migrate(db: SupportSQLiteDatabase) { + db.beginTransaction() + try { + // The UPDATE statements aren't correct; + // they don't strip diacritics. However, + // the correct data will be loaded from the + // server at some point, so it's not that + // bad. + db.execSQL("ALTER TABLE `albums` ADD COLUMN `normalized_title` TEXT NOT NULL DEFAULT ''") + db.execSQL("UPDATE `albums` SET `normalized_title` = LOWER(`title`)") + db.execSQL("ALTER TABLE `artists` ADD COLUMN `normalized_name` TEXT NOT NULL DEFAULT ''") + db.execSQL("UPDATE `artists` SET `normalized_name` = LOWER(`name`)") + db.execSQL("ALTER TABLE `tracks` ADD COLUMN `normalized_title` TEXT NOT NULL DEFAULT ''") + db.execSQL("UPDATE `tracks` SET `normalized_title` = LOWER(`title`)") + db.execSQL("ALTER TABLE `album_artists` ADD COLUMN `normalized_name` TEXT NOT NULL DEFAULT ''") + db.execSQL("UPDATE `album_artists` SET `normalized_name` = LOWER(`name`)") + db.execSQL("ALTER TABLE `track_artists` ADD COLUMN `normalized_name` TEXT NOT NULL DEFAULT ''") + db.execSQL("UPDATE `track_artists` SET `normalized_name` = LOWER(`name`)") + db.setTransactionSuccessful() + } finally { + db.endTransaction() + } } - } - }) - .addMigrations(object : Migration(6, 7) { - override fun migrate(db: SupportSQLiteDatabase) { - db.beginTransaction() - try { - db.execSQL( - """ - CREATE TABLE IF NOT EXISTS `codec_conversions` ( - `id` INTEGER NOT NULL, - `name` TEXT NOT NULL, - `ffmpeg_params` TEXT NOT NULL, - `resulting_codec_id` INTEGER NOT NULL, - PRIMARY KEY(`id`) + }, + ) + .addMigrations( + object : Migration(6, 7) { + override fun migrate(db: SupportSQLiteDatabase) { + db.beginTransaction() + try { + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `codec_conversions` ( + `id` INTEGER NOT NULL, + `name` TEXT NOT NULL, + `ffmpeg_params` TEXT NOT NULL, + `resulting_codec_id` INTEGER NOT NULL, + PRIMARY KEY(`id`) + ) + """, ) - """ - ) - db.setTransactionSuccessful() - } finally { - db.endTransaction() + db.setTransactionSuccessful() + } finally { + db.endTransaction() + } } - } - }) - .addMigrations(object : Migration(7, 8) { - override fun migrate(db: SupportSQLiteDatabase) { - db.beginTransaction() - try { - db.execSQL( - """ - CREATE TABLE IF NOT EXISTS `unreported_plays` ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - `track_id` INTEGER NOT NULL, - `played_at` TEXT NOT NULL + }, + ) + .addMigrations( + object : Migration(7, 8) { + override fun migrate(db: SupportSQLiteDatabase) { + db.beginTransaction() + try { + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `unreported_plays` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `track_id` INTEGER NOT NULL, + `played_at` TEXT NOT NULL + ) + """, ) - """ - ) - db.setTransactionSuccessful() - } finally { - db.endTransaction() + db.setTransactionSuccessful() + } finally { + db.endTransaction() + } } - } - }) - .addMigrations(object : Migration(8, 9) { - override fun migrate(db: SupportSQLiteDatabase) { - db.beginTransaction() - try { - db.execSQL( - """ - CREATE TABLE IF NOT EXISTS `plays` ( - `id` INTEGER NOT NULL, - `played_at` TEXT NOT NULL, - `track_id` INTEGER NOT NULL, - `user_id` INTEGER NOT NULL, - PRIMARY KEY(`id`) + }, + ) + .addMigrations( + object : Migration(8, 9) { + override fun migrate(db: SupportSQLiteDatabase) { + db.beginTransaction() + try { + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `plays` ( + `id` INTEGER NOT NULL, + `played_at` TEXT NOT NULL, + `track_id` INTEGER NOT NULL, + `user_id` INTEGER NOT NULL, + PRIMARY KEY(`id`) + ) + """, ) - """ - ) - db.setTransactionSuccessful() - } finally { - db.endTransaction() + db.setTransactionSuccessful() + } finally { + db.endTransaction() + } } - } - }) - .addMigrations(object : Migration(9, 10) { - override fun migrate(db: SupportSQLiteDatabase) { - db.beginTransaction() - try { - val now = Instant.now() - db.execSQL("ALTER TABLE `albums` ADD COLUMN `fetched_at` TEXT NOT NULL DEFAULT '$now'") - db.execSQL("ALTER TABLE `artists` ADD COLUMN `fetched_at` TEXT NOT NULL DEFAULT '$now'") - db.execSQL("ALTER TABLE `codec_conversions` ADD COLUMN `fetched_at` TEXT NOT NULL DEFAULT '$now'") - db.execSQL("ALTER TABLE `plays` ADD COLUMN `fetched_at` TEXT NOT NULL DEFAULT '$now'") - db.execSQL("ALTER TABLE `tracks` ADD COLUMN `fetched_at` TEXT NOT NULL DEFAULT '$now'") - db.execSQL("ALTER TABLE `users` ADD COLUMN `fetched_at` TEXT NOT NULL DEFAULT '$now'") - db.setTransactionSuccessful() - } finally { - db.endTransaction() + }, + ) + .addMigrations( + object : Migration(9, 10) { + override fun migrate(db: SupportSQLiteDatabase) { + db.beginTransaction() + try { + val now = Instant.now() + db.execSQL("ALTER TABLE `albums` ADD COLUMN `fetched_at` TEXT NOT NULL DEFAULT '$now'") + db.execSQL("ALTER TABLE `artists` ADD COLUMN `fetched_at` TEXT NOT NULL DEFAULT '$now'") + db.execSQL("ALTER TABLE `codec_conversions` ADD COLUMN `fetched_at` TEXT NOT NULL DEFAULT '$now'") + db.execSQL("ALTER TABLE `plays` ADD COLUMN `fetched_at` TEXT NOT NULL DEFAULT '$now'") + db.execSQL("ALTER TABLE `tracks` ADD COLUMN `fetched_at` TEXT NOT NULL DEFAULT '$now'") + db.execSQL("ALTER TABLE `users` ADD COLUMN `fetched_at` TEXT NOT NULL DEFAULT '$now'") + db.setTransactionSuccessful() + } finally { + db.endTransaction() + } } - } - }) - .addMigrations(object : Migration(10, 11) { - override fun migrate(db: SupportSQLiteDatabase) { - db.beginTransaction() - try { - db.execSQL("ALTER TABLE `track_artists` ADD COLUMN `hidden` INTEGER NOT NULL DEFAULT 0") - db.setTransactionSuccessful() - } finally { - db.endTransaction() + }, + ) + .addMigrations( + object : Migration(10, 11) { + override fun migrate(db: SupportSQLiteDatabase) { + db.beginTransaction() + try { + db.execSQL("ALTER TABLE `track_artists` ADD COLUMN `hidden` INTEGER NOT NULL DEFAULT 0") + db.setTransactionSuccessful() + } finally { + db.endTransaction() + } } - } - }) - .addMigrations(object : Migration(11, 12) { - override fun migrate(db: SupportSQLiteDatabase) { - db.beginTransaction() - try { - db.execSQL( - """ - CREATE TABLE IF NOT EXISTS `playlists` ( - `id` INTEGER NOT NULL, - `name` TEXT NOT NULL, - `description` TEXT, - `user_id` INTEGER NOT NULL, - `playlist_type` INTEGER NOT NULL, - `created_at` TEXT NOT NULL, - `updated_at` TEXT NOT NULL, - `access` INTEGER NOT NULL, - `fetched_at` TEXT NOT NULL, - PRIMARY KEY(`id`) + }, + ) + .addMigrations( + object : Migration(11, 12) { + override fun migrate(db: SupportSQLiteDatabase) { + db.beginTransaction() + try { + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `playlists` ( + `id` INTEGER NOT NULL, + `name` TEXT NOT NULL, + `description` TEXT, + `user_id` INTEGER NOT NULL, + `playlist_type` INTEGER NOT NULL, + `created_at` TEXT NOT NULL, + `updated_at` TEXT NOT NULL, + `access` INTEGER NOT NULL, + `fetched_at` TEXT NOT NULL, + PRIMARY KEY(`id`) + ) + """, ) - """ - ) - db.execSQL( - """ - CREATE TABLE IF NOT EXISTS `playlist_items` ( - `playlist_id` INTEGER NOT NULL, - `item_id` INTEGER NOT NULL, - `order` INTEGER NOT NULL, - PRIMARY KEY(`playlist_id`, `item_id`) + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `playlist_items` ( + `playlist_id` INTEGER NOT NULL, + `item_id` INTEGER NOT NULL, + `order` INTEGER NOT NULL, + PRIMARY KEY(`playlist_id`, `item_id`) + ) + """, ) - """ - ) - db.setTransactionSuccessful() - } finally { - db.endTransaction() + db.setTransactionSuccessful() + } finally { + db.endTransaction() + } } - } - }) + }, + ) .build() } diff --git a/app/src/main/java/me/vanpetegem/accentor/data/albums/Album.kt b/app/src/main/java/me/vanpetegem/accentor/data/albums/Album.kt index 70f0a1b9..70f997ba 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/albums/Album.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/albums/Album.kt @@ -20,10 +20,9 @@ data class Album( val imageType: String?, val albumLabels: List, val albumArtists: List, - val fetchedAt: Instant + val fetchedAt: Instant, ) { - fun stringifyAlbumArtists() = - albumArtists.sortedBy { aa -> aa.order }.fold("") { acc, aa -> acc + aa.name + (aa.separator ?: "") } + fun stringifyAlbumArtists() = albumArtists.sortedBy { aa -> aa.order }.fold("") { acc, aa -> acc + aa.name + (aa.separator ?: "") } fun firstCharacter() = String(intArrayOf(title.codePointAt(0)), 0, 1) @@ -36,7 +35,11 @@ data class Album( } companion object { - fun fromDb(a: DbAlbum, labels: List, artists: List): Album = + fun fromDb( + a: DbAlbum, + labels: List, + artists: List, + ): Album = Album( a.id, a.title, @@ -54,41 +57,60 @@ data class Album( a.imageType, labels, artists, - a.fetchedAt + a.fetchedAt, ) - fun fromApi(a: ApiAlbum, fetchTime: Instant) = - Album( - a.id, - a.title, - a.normalizedTitle, - a.release, - a.reviewComment, - a.edition, - a.editionDescription, - a.createdAt, - a.updatedAt, - a.image, - a.image500, - a.image250, - a.image100, - a.imageType, - a.albumLabels, - a.albumArtists, - fetchTime - ) + fun fromApi( + a: ApiAlbum, + fetchTime: Instant, + ) = Album( + a.id, + a.title, + a.normalizedTitle, + a.release, + a.reviewComment, + a.edition, + a.editionDescription, + a.createdAt, + a.updatedAt, + a.image, + a.image500, + a.image250, + a.image100, + a.imageType, + a.albumLabels, + a.albumArtists, + fetchTime, + ) } } -fun compareAlbumEditions(a1: Album, a2: Album): Int { - if (a1.edition == null && a2.edition == null) { return 0 } - if (a1.edition == null) { return -1 } - if (a2.edition == null) { return 1 } +fun compareAlbumEditions( + a1: Album, + a2: Album, +): Int { + if (a1.edition == null && a2.edition == null) { + return 0 + } + if (a1.edition == null) { + return -1 + } + if (a2.edition == null) { + return 1 + } val order = a1.edition.compareTo(a2.edition) - if (order != 0) { return order } - if (a1.editionDescription == null && a2.editionDescription == null) { return 0 } - if (a1.editionDescription == null) { return -1 } - if (a2.editionDescription == null) { return 1 } + if (order != 0) { + return order + } + if (a1.editionDescription == null && a2.editionDescription == null) { + return 0 + } + if (a1.editionDescription == null) { + return -1 + } + if (a2.editionDescription == null) { + return 1 + } return a1.editionDescription.compareTo(a2.editionDescription) } @@ -97,10 +119,10 @@ data class AlbumArtist( val name: String, val normalizedName: String, val order: Int, - val separator: String? + val separator: String?, ) data class AlbumLabel( val labelId: Int, - val catalogueNumber: String? + val catalogueNumber: String?, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/albums/AlbumDao.kt b/app/src/main/java/me/vanpetegem/accentor/data/albums/AlbumDao.kt index a915297e..302c8fb1 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/albums/AlbumDao.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/albums/AlbumDao.kt @@ -16,21 +16,23 @@ import java.time.format.DateTimeFormatter @Dao abstract class AlbumDao { - open fun getAll(): LiveData> = getAllDbAlbums().switchMap { albums -> - albumArtistsByAlbumId().switchMap { albumArtists -> - albumLabelsByAlbumId().map { albumLabels -> - albums.map { a -> Album.fromDb(a, albumLabels.get(a.id, ArrayList()), albumArtists.get(a.id, ArrayList())) } + open fun getAll(): LiveData> = + getAllDbAlbums().switchMap { albums -> + albumArtistsByAlbumId().switchMap { albumArtists -> + albumLabelsByAlbumId().map { albumLabels -> + albums.map { a -> Album.fromDb(a, albumLabels.get(a.id, ArrayList()), albumArtists.get(a.id, ArrayList())) } + } } } - } - open fun getAllByPlayed(): LiveData> = getAllDbAlbumsByPlayed().switchMap { albums -> - albumArtistsByAlbumId().switchMap { albumArtists -> - albumLabelsByAlbumId().map { albumLabels -> - albums.map { a -> Album.fromDb(a, albumLabels.get(a.id, ArrayList()), albumArtists.get(a.id, ArrayList())) } + open fun getAllByPlayed(): LiveData> = + getAllDbAlbumsByPlayed().switchMap { albums -> + albumArtistsByAlbumId().switchMap { albumArtists -> + albumLabelsByAlbumId().map { albumLabels -> + albums.map { a -> Album.fromDb(a, albumLabels.get(a.id, ArrayList()), albumArtists.get(a.id, ArrayList())) } + } } } - } open fun getByIds(ids: List): List { val albums = getDbAlbumsByIds(ids) @@ -41,29 +43,31 @@ abstract class AlbumDao { return ids.map { Album.fromDb(albumsByIds.get(it), albumLabels.get(it, ArrayList()), albumArtists.get(it, ArrayList())) } } - open fun findByIds(ids: List): LiveData> = findDbAlbumsByIds(ids).switchMap { albums -> - albumArtistsByAlbumIdWhereAlbumIds(ids).switchMap { albumArtists -> - albumLabelsByAlbumIdWhereAlbumIds(ids).map { albumLabels -> - albums.map { a -> Album.fromDb(a, albumLabels.get(a.id, ArrayList()), albumArtists.get(a.id, ArrayList())) } + open fun findByIds(ids: List): LiveData> = + findDbAlbumsByIds(ids).switchMap { albums -> + albumArtistsByAlbumIdWhereAlbumIds(ids).switchMap { albumArtists -> + albumLabelsByAlbumIdWhereAlbumIds(ids).map { albumLabels -> + albums.map { a -> Album.fromDb(a, albumLabels.get(a.id, ArrayList()), albumArtists.get(a.id, ArrayList())) } + } } } - } - open fun findById(id: Int): LiveData = findDbAlbumById(id).switchMap { dbAlbum -> - findDbAlbumArtistsById(id).switchMap { albumArtists -> - findDbAlbumLabelsById(id).map { albumLabels -> - if (dbAlbum != null) { - Album.fromDb( - dbAlbum, - albumLabels.map { AlbumLabel(it.labelId, it.catalogueNumber) }, - albumArtists.map { AlbumArtist(it.artistId, it.name, it.normalizedName, it.order, it.separator) } - ) - } else { - null + open fun findById(id: Int): LiveData = + findDbAlbumById(id).switchMap { dbAlbum -> + findDbAlbumArtistsById(id).switchMap { albumArtists -> + findDbAlbumLabelsById(id).map { albumLabels -> + if (dbAlbum != null) { + Album.fromDb( + dbAlbum, + albumLabels.map { AlbumLabel(it.labelId, it.catalogueNumber) }, + albumArtists.map { AlbumArtist(it.artistId, it.name, it.normalizedName, it.order, it.separator) }, + ) + } else { + null + } } } } - } open fun findByDay(day: LocalDate): LiveData> = findDbAlbumsByDay(day.format(DateTimeFormatter.ISO_LOCAL_DATE).substring(4)).switchMap { albums -> @@ -84,7 +88,7 @@ abstract class AlbumDao { return Album.fromDb( dbAlbum, albumLabels.map { AlbumLabel(it.labelId, it.catalogueNumber) }, - albumArtists.map { AlbumArtist(it.artistId, it.name, it.normalizedName, it.order, it.separator) } + albumArtists.map { AlbumArtist(it.artistId, it.name, it.normalizedName, it.order, it.separator) }, ) } @@ -108,7 +112,7 @@ abstract class AlbumDao { edition ASC, edition_description ASC, id ASC - """ + """, ) protected abstract fun findDbAlbumsByDay(day: String): LiveData> @@ -209,8 +213,8 @@ abstract class AlbumDao { album.image250, album.image100, album.imageType, - album.fetchedAt - ) + album.fetchedAt, + ), ) deleteAlbumLabelsById(album.id) for (al: AlbumLabel in album.albumLabels) { @@ -233,7 +237,7 @@ abstract class AlbumDao { SELECT tracks.album_id as album_id, MAX(plays.played_at) AS played_at FROM tracks INNER JOIN plays ON tracks.id = plays.track_id GROUP BY tracks.album_id ) p ON p.album_id = albums.id ORDER BY p.played_at DESC - """ + """, ) protected abstract fun getAllDbAlbumsByPlayed(): LiveData> diff --git a/app/src/main/java/me/vanpetegem/accentor/data/albums/AlbumRepository.kt b/app/src/main/java/me/vanpetegem/accentor/data/albums/AlbumRepository.kt index 48f97604..4ee5d684 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/albums/AlbumRepository.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/albums/AlbumRepository.kt @@ -4,78 +4,86 @@ import android.util.SparseArray import androidx.lifecycle.LiveData import androidx.lifecycle.map import dagger.Reusable -import java.time.Instant -import java.time.LocalDate -import javax.inject.Inject import me.vanpetegem.accentor.api.album.index import me.vanpetegem.accentor.data.authentication.AuthenticationRepository import me.vanpetegem.accentor.util.Result +import java.time.Instant +import java.time.LocalDate +import javax.inject.Inject @Reusable -class AlbumRepository @Inject constructor( - private val albumDao: AlbumDao, - private val authenticationRepository: AuthenticationRepository -) { - val allAlbums: LiveData> = albumDao.getAll() - val allAlbumsById: LiveData> = allAlbums.map { - val map = SparseArray() - it.forEach { a -> map.put(a.id, a) } - map - } - val albumsByReleased: LiveData> = allAlbums.map { - val copy = it.toMutableList() - copy.sortWith({ a1, a2 -> a2.release.compareTo(a1.release) }) - copy - } - val albumsByAdded: LiveData> = allAlbums.map { - val copy = it.toMutableList() - copy.sortWith({ a1, a2 -> a2.createdAt.compareTo(a1.createdAt) }) - copy - } - val albumsByPlayed: LiveData> = albumDao.getAllByPlayed() - val randomAlbums: LiveData> = allAlbums.map { - val copy = it.toMutableList() - copy.shuffle() - copy - } +class AlbumRepository + @Inject + constructor( + private val albumDao: AlbumDao, + private val authenticationRepository: AuthenticationRepository, + ) { + val allAlbums: LiveData> = albumDao.getAll() + val allAlbumsById: LiveData> = + allAlbums.map { + val map = SparseArray() + it.forEach { a -> map.put(a.id, a) } + map + } + val albumsByReleased: LiveData> = + allAlbums.map { + val copy = it.toMutableList() + copy.sortWith({ a1, a2 -> a2.release.compareTo(a1.release) }) + copy + } + val albumsByAdded: LiveData> = + allAlbums.map { + val copy = it.toMutableList() + copy.sortWith({ a1, a2 -> a2.createdAt.compareTo(a1.createdAt) }) + copy + } + val albumsByPlayed: LiveData> = albumDao.getAllByPlayed() + val randomAlbums: LiveData> = + allAlbums.map { + val copy = it.toMutableList() + copy.shuffle() + copy + } + + fun findById(id: Int): LiveData = albumDao.findById(id) - fun findById(id: Int): LiveData = albumDao.findById(id) - fun getById(id: Int): Album? = albumDao.getAlbumById(id) - fun getByIds(ids: List): List = albumDao.getByIds(ids) + fun getById(id: Int): Album? = albumDao.getAlbumById(id) - fun findByIds(ids: List): LiveData> = albumDao.findByIds(ids) + fun getByIds(ids: List): List = albumDao.getByIds(ids) - fun findByDay(day: LocalDate): LiveData> = albumDao.findByDay(day) + fun findByIds(ids: List): LiveData> = albumDao.findByIds(ids) - suspend fun refresh(handler: suspend (Result) -> Unit) { - val fetchStart = Instant.now() + fun findByDay(day: LocalDate): LiveData> = albumDao.findByDay(day) - var toUpsert = ArrayList() - var count = 0 - for (result in index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { - when (result) { - is Result.Success -> { - val fetchTime = Instant.now() - toUpsert.addAll(result.data.map { Album.fromApi(it, fetchTime) }) - count += 1 - if (count >= 5) { - albumDao.upsertAll(toUpsert) - toUpsert.clear() - count = 0 + suspend fun refresh(handler: suspend (Result) -> Unit) { + val fetchStart = Instant.now() + + var toUpsert = ArrayList() + var count = 0 + for (result in index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { + when (result) { + is Result.Success -> { + val fetchTime = Instant.now() + toUpsert.addAll(result.data.map { Album.fromApi(it, fetchTime) }) + count += 1 + if (count >= 5) { + albumDao.upsertAll(toUpsert) + toUpsert.clear() + count = 0 + } + } + is Result.Error -> { + handler(Result.Error(result.exception)) + return } - } - is Result.Error -> { - handler(Result.Error(result.exception)) - return } } + albumDao.upsertAll(toUpsert) + albumDao.deleteFetchedBefore(fetchStart) + handler(Result.Success(Unit)) } - albumDao.upsertAll(toUpsert) - albumDao.deleteFetchedBefore(fetchStart) - handler(Result.Success(Unit)) - } - suspend fun clear() { - albumDao.deleteAll() + suspend fun clear() { + albumDao.deleteAll() + } } -} diff --git a/app/src/main/java/me/vanpetegem/accentor/data/albums/ApiAlbum.kt b/app/src/main/java/me/vanpetegem/accentor/data/albums/ApiAlbum.kt index 02b5c3db..df21a9c1 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/albums/ApiAlbum.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/albums/ApiAlbum.kt @@ -19,5 +19,5 @@ data class ApiAlbum( val image100: String?, val imageType: String?, val albumLabels: List, - val albumArtists: List + val albumArtists: List, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/albums/DbAlbum.kt b/app/src/main/java/me/vanpetegem/accentor/data/albums/DbAlbum.kt index 7871d746..95c705a2 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/albums/DbAlbum.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/albums/DbAlbum.kt @@ -38,12 +38,12 @@ data class DbAlbum( @ColumnInfo(name = "image_type") val imageType: String?, @ColumnInfo(name = "fetched_at") - val fetchedAt: Instant + val fetchedAt: Instant, ) @Entity( tableName = "album_artists", - primaryKeys = ["album_id", "artist_id", "name"] + primaryKeys = ["album_id", "artist_id", "name"], ) data class DbAlbumArtist( @ColumnInfo(name = "album_id") @@ -57,12 +57,12 @@ data class DbAlbumArtist( @ColumnInfo(name = "order") val order: Int, @ColumnInfo(name = "separator") - val separator: String? + val separator: String?, ) @Entity( tableName = "album_labels", - primaryKeys = ["album_id", "label_id"] + primaryKeys = ["album_id", "label_id"], ) data class DbAlbumLabel( @ColumnInfo(name = "album_id") @@ -70,5 +70,5 @@ data class DbAlbumLabel( @ColumnInfo(name = "label_id") val labelId: Int, @ColumnInfo(name = "catalogue_number") - val catalogueNumber: String? + val catalogueNumber: String?, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/artists/ApiArtist.kt b/app/src/main/java/me/vanpetegem/accentor/data/artists/ApiArtist.kt index 33e68484..873b38a3 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/artists/ApiArtist.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/artists/ApiArtist.kt @@ -13,5 +13,5 @@ data class ApiArtist( val image500: String?, val image250: String?, val image100: String?, - val imageType: String? + val imageType: String?, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/artists/Artist.kt b/app/src/main/java/me/vanpetegem/accentor/data/artists/Artist.kt index a852d744..4dbb5877 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/artists/Artist.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/artists/Artist.kt @@ -14,27 +14,12 @@ data class Artist( val image250: String?, val image100: String?, val imageType: String?, - val fetchedAt: Instant + val fetchedAt: Instant, ) { fun firstCharacter() = String(intArrayOf(name.codePointAt(0)), 0, 1) companion object { - fun fromDb(a: DbArtist): Artist = Artist( - a.id, - a.name, - a.normalizedName, - a.reviewComment, - a.createdAt, - a.updatedAt, - a.image, - a.image500, - a.image250, - a.image100, - a.imageType, - a.fetchedAt - ) - - fun fromApi(a: ApiArtist, fetchTime: Instant) = + fun fromDb(a: DbArtist): Artist = Artist( a.id, a.name, @@ -47,7 +32,25 @@ data class Artist( a.image250, a.image100, a.imageType, - fetchTime + a.fetchedAt, ) + + fun fromApi( + a: ApiArtist, + fetchTime: Instant, + ) = Artist( + a.id, + a.name, + a.normalizedName, + a.reviewComment, + a.createdAt, + a.updatedAt, + a.image, + a.image500, + a.image250, + a.image100, + a.imageType, + fetchTime, + ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/data/artists/ArtistDao.kt b/app/src/main/java/me/vanpetegem/accentor/data/artists/ArtistDao.kt index e322487d..71d2a2f3 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/artists/ArtistDao.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/artists/ArtistDao.kt @@ -11,14 +11,15 @@ import java.time.Instant @Dao abstract class ArtistDao { + open fun getAll(): LiveData> = + getAllDbArtists().map { list -> + list.map { Artist.fromDb(it) } + } - open fun getAll(): LiveData> = getAllDbArtists().map { list -> - list.map { Artist.fromDb(it) } - } - - open fun getAllByPlayed(): LiveData> = getAllDbArtistsByPlayed().map { list -> - list.map { Artist.fromDb(it) } - } + open fun getAllByPlayed(): LiveData> = + getAllDbArtistsByPlayed().map { list -> + list.map { Artist.fromDb(it) } + } @Transaction open fun upsertAll(artists: List) { @@ -36,8 +37,8 @@ abstract class ArtistDao { artist.image250, artist.image100, artist.imageType, - artist.fetchedAt - ) + artist.fetchedAt, + ), ) } } @@ -53,7 +54,7 @@ abstract class ArtistDao { track_artists INNER JOIN tracks ON track_artists.track_id = tracks.id INNER JOIN plays ON tracks.id = plays.track_id WHERE track_artists.hidden = 0 GROUP BY track_artists.artist_id ) p ON p.artist_id = artists.id ORDER BY p.played_at DESC, normalized_name ASC, id ASC - """ + """, ) protected abstract fun getAllDbArtistsByPlayed(): LiveData> diff --git a/app/src/main/java/me/vanpetegem/accentor/data/artists/ArtistRepository.kt b/app/src/main/java/me/vanpetegem/accentor/data/artists/ArtistRepository.kt index 11ed4a52..69baeb92 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/artists/ArtistRepository.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/artists/ArtistRepository.kt @@ -4,64 +4,69 @@ import android.util.SparseArray import androidx.lifecycle.LiveData import androidx.lifecycle.map import dagger.Reusable -import java.time.Instant -import javax.inject.Inject import me.vanpetegem.accentor.api.artist.index import me.vanpetegem.accentor.data.authentication.AuthenticationRepository import me.vanpetegem.accentor.util.Result +import java.time.Instant +import javax.inject.Inject @Reusable -class ArtistRepository @Inject constructor( - private val artistDao: ArtistDao, - private val authenticationRepository: AuthenticationRepository -) { - val allArtists: LiveData> = artistDao.getAll() - val allArtistsById: LiveData> = allArtists.map { - val map = SparseArray() - it.forEach { a -> map.put(a.id, a) } - map - } - val artistsByAdded: LiveData> = allArtists.map { - val copy = it.toMutableList() - copy.sortWith({ a1, a2 -> a2.createdAt.compareTo(a1.createdAt) }) - copy - } - val artistsByPlayed: LiveData> = artistDao.getAllByPlayed() - val randomArtists: LiveData> = allArtists.map { - val copy = it.toMutableList() - copy.shuffle() - copy - } +class ArtistRepository + @Inject + constructor( + private val artistDao: ArtistDao, + private val authenticationRepository: AuthenticationRepository, + ) { + val allArtists: LiveData> = artistDao.getAll() + val allArtistsById: LiveData> = + allArtists.map { + val map = SparseArray() + it.forEach { a -> map.put(a.id, a) } + map + } + val artistsByAdded: LiveData> = + allArtists.map { + val copy = it.toMutableList() + copy.sortWith({ a1, a2 -> a2.createdAt.compareTo(a1.createdAt) }) + copy + } + val artistsByPlayed: LiveData> = artistDao.getAllByPlayed() + val randomArtists: LiveData> = + allArtists.map { + val copy = it.toMutableList() + copy.shuffle() + copy + } - suspend fun refresh(handler: suspend (Result) -> Unit) { - val fetchStart = Instant.now() + suspend fun refresh(handler: suspend (Result) -> Unit) { + val fetchStart = Instant.now() - var toUpsert = ArrayList() - var count = 0 - for (result in index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { - when (result) { - is Result.Success -> { - val fetchTime = Instant.now() - toUpsert.addAll(result.data.map { Artist.fromApi(it, fetchTime) }) - count += 1 - if (count >= 5) { - artistDao.upsertAll(toUpsert) - toUpsert.clear() - count = 0 + var toUpsert = ArrayList() + var count = 0 + for (result in index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { + when (result) { + is Result.Success -> { + val fetchTime = Instant.now() + toUpsert.addAll(result.data.map { Artist.fromApi(it, fetchTime) }) + count += 1 + if (count >= 5) { + artistDao.upsertAll(toUpsert) + toUpsert.clear() + count = 0 + } + } + is Result.Error -> { + handler(Result.Error(result.exception)) + return } - } - is Result.Error -> { - handler(Result.Error(result.exception)) - return } } + artistDao.upsertAll(toUpsert) + artistDao.deleteFetchedBefore(fetchStart) + handler(Result.Success(Unit)) } - artistDao.upsertAll(toUpsert) - artistDao.deleteFetchedBefore(fetchStart) - handler(Result.Success(Unit)) - } - suspend fun clear() { - artistDao.deleteAll() + suspend fun clear() { + artistDao.deleteAll() + } } -} diff --git a/app/src/main/java/me/vanpetegem/accentor/data/artists/DbArtist.kt b/app/src/main/java/me/vanpetegem/accentor/data/artists/DbArtist.kt index 8c2b1b64..d020364d 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/artists/DbArtist.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/artists/DbArtist.kt @@ -31,5 +31,5 @@ data class DbArtist( @ColumnInfo(name = "image_type") val imageType: String?, @ColumnInfo(name = "fetched_at") - val fetchedAt: Instant + val fetchedAt: Instant, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/authentication/AuthenticationData.kt b/app/src/main/java/me/vanpetegem/accentor/data/authentication/AuthenticationData.kt index 7886d82b..7bad92ab 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/authentication/AuthenticationData.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/authentication/AuthenticationData.kt @@ -4,7 +4,7 @@ data class AuthenticationData( val id: Int, val userId: Int, val deviceId: String, - val secret: String + val secret: String, ) { override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/app/src/main/java/me/vanpetegem/accentor/data/authentication/AuthenticationDataSource.kt b/app/src/main/java/me/vanpetegem/accentor/data/authentication/AuthenticationDataSource.kt index 750e7175..af2e04d3 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/authentication/AuthenticationDataSource.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/authentication/AuthenticationDataSource.kt @@ -5,9 +5,9 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.Observer import dagger.hilt.android.qualifiers.ApplicationContext -import javax.inject.Inject import me.vanpetegem.accentor.util.intLiveData import me.vanpetegem.accentor.util.stringLiveData +import javax.inject.Inject const val ID_KEY = "id" const val SERVER_KEY = "server" @@ -15,88 +15,101 @@ const val USER_ID_KEY = "user_id" const val DEVICE_ID_KEY = "device_id" const val SECRET_KEY = "secret" -class AuthenticationDataSource @Inject constructor(@ApplicationContext context: Context) { - private val sharedPreferences = - context.getSharedPreferences("me.vanpetegem.accentor.authenticationData", Context.MODE_PRIVATE) +class AuthenticationDataSource + @Inject + constructor( + @ApplicationContext context: Context, + ) { + private val sharedPreferences = + context.getSharedPreferences("me.vanpetegem.accentor.authenticationData", Context.MODE_PRIVATE) - private val idData = sharedPreferences.intLiveData(ID_KEY) - private val userIdData = sharedPreferences.intLiveData(USER_ID_KEY) - private val deviceIdData = sharedPreferences.stringLiveData(DEVICE_ID_KEY) - private val secretData = sharedPreferences.stringLiveData(SECRET_KEY) + private val idData = sharedPreferences.intLiveData(ID_KEY) + private val userIdData = sharedPreferences.intLiveData(USER_ID_KEY) + private val deviceIdData = sharedPreferences.stringLiveData(DEVICE_ID_KEY) + private val secretData = sharedPreferences.stringLiveData(SECRET_KEY) - private val serverData = sharedPreferences.stringLiveData(SERVER_KEY) + private val serverData = sharedPreferences.stringLiveData(SERVER_KEY) - val authData: LiveData - val server: LiveData = serverData + val authData: LiveData + val server: LiveData = serverData - init { - authData = MediatorLiveData().apply { - val observer: Observer = Observer { - val id: Int = idData.value.let { - if (it != null) { - it - } else { - value = null - return@Observer - } - } - val userId: Int = userIdData.value.let { - if (it != null) { - it - } else { - value = null - return@Observer - } - } - val deviceId: String = deviceIdData.value.let { - if (it != null) { - it - } else { - value = null - return@Observer - } - } - val secret: String = secretData.value.let { - if (it != null) { - it - } else { - value = null - return@Observer - } - } - val newVal = AuthenticationData(id, userId, deviceId, secret) - if (newVal != this.value) this.value = newVal - } + init { + authData = + MediatorLiveData().apply { + val observer: Observer = + Observer { + val id: Int = + idData.value.let { + if (it != null) { + it + } else { + value = null + return@Observer + } + } + val userId: Int = + userIdData.value.let { + if (it != null) { + it + } else { + value = null + return@Observer + } + } + val deviceId: String = + deviceIdData.value.let { + if (it != null) { + it + } else { + value = null + return@Observer + } + } + val secret: String = + secretData.value.let { + if (it != null) { + it + } else { + value = null + return@Observer + } + } + val newVal = AuthenticationData(id, userId, deviceId, secret) + if (newVal != this.value) this.value = newVal + } - addSource(idData, observer) - addSource(userIdData, observer) - addSource(deviceIdData, observer) - addSource(secretData, observer) - // If we don't do this, the value will start out as null even if we have data in the prefs. - observer.onChanged(null) + addSource(idData, observer) + addSource(userIdData, observer) + addSource(deviceIdData, observer) + addSource(secretData, observer) + // If we don't do this, the value will start out as null even if we have data in the prefs. + observer.onChanged(null) + } } - } - fun setAuthData(authData: AuthenticationData?) { - if (authData == null) { - sharedPreferences.edit() - .remove(ID_KEY) - .remove(USER_ID_KEY) - .remove(DEVICE_ID_KEY) - .remove(SECRET_KEY) - .apply() - } else { - sharedPreferences.edit() - .putInt(ID_KEY, authData.id) - .putInt(USER_ID_KEY, authData.userId) - .putString(DEVICE_ID_KEY, authData.deviceId) - .putString(SECRET_KEY, authData.secret) - .apply() + fun setAuthData(authData: AuthenticationData?) { + if (authData == null) { + sharedPreferences.edit() + .remove(ID_KEY) + .remove(USER_ID_KEY) + .remove(DEVICE_ID_KEY) + .remove(SECRET_KEY) + .apply() + } else { + sharedPreferences.edit() + .putInt(ID_KEY, authData.id) + .putInt(USER_ID_KEY, authData.userId) + .putString(DEVICE_ID_KEY, authData.deviceId) + .putString(SECRET_KEY, authData.secret) + .apply() + } } - } - fun setServer(server: String?) = sharedPreferences.edit().putString(SERVER_KEY, server).apply() - fun getServer(): String? = sharedPreferences.getString(SERVER_KEY, null) - fun getSecret(): String? = sharedPreferences.getString(SECRET_KEY, null) - fun getDeviceId(): String? = sharedPreferences.getString(DEVICE_ID_KEY, null) -} + fun setServer(server: String?) = sharedPreferences.edit().putString(SERVER_KEY, server).apply() + + fun getServer(): String? = sharedPreferences.getString(SERVER_KEY, null) + + fun getSecret(): String? = sharedPreferences.getString(SECRET_KEY, null) + + fun getDeviceId(): String? = sharedPreferences.getString(DEVICE_ID_KEY, null) + } diff --git a/app/src/main/java/me/vanpetegem/accentor/data/authentication/AuthenticationRepository.kt b/app/src/main/java/me/vanpetegem/accentor/data/authentication/AuthenticationRepository.kt index d99cf672..d77ba5f6 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/authentication/AuthenticationRepository.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/authentication/AuthenticationRepository.kt @@ -2,50 +2,55 @@ package me.vanpetegem.accentor.data.authentication import androidx.lifecycle.LiveData import androidx.lifecycle.map -import javax.inject.Inject import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.withContext import me.vanpetegem.accentor.api.auth.create import me.vanpetegem.accentor.api.auth.destroy import me.vanpetegem.accentor.util.Result +import javax.inject.Inject -class AuthenticationRepository @Inject constructor( - private val prefsSource: AuthenticationDataSource -) { - val authData: LiveData = prefsSource.authData - val server: LiveData = prefsSource.server +class AuthenticationRepository + @Inject + constructor( + private val prefsSource: AuthenticationDataSource, + ) { + val authData: LiveData = prefsSource.authData + val server: LiveData = prefsSource.server - val isLoggedIn: LiveData = authData.map { it != null } + val isLoggedIn: LiveData = authData.map { it != null } - suspend fun logout() { - // Ignore bad data/errors for logout: if an error happens, it isn't that bad - if (server.value != null && authData.value != null) { - destroy(server.value!!, authData.value!!, authData.value!!.id) - } - withContext(Main) { - prefsSource.setAuthData(null) - prefsSource.setServer(null) + suspend fun logout() { + // Ignore bad data/errors for logout: if an error happens, it isn't that bad + if (server.value != null && authData.value != null) { + destroy(server.value!!, authData.value!!, authData.value!!.id) + } + withContext(Main) { + prefsSource.setAuthData(null) + prefsSource.setServer(null) + } } - } - suspend fun login( - server: String, - username: String, - password: String - ): Result { - val result = create(server, username, password) + suspend fun login( + server: String, + username: String, + password: String, + ): Result { + val result = create(server, username, password) - return when (result) { - is Result.Success -> { - setLoggedInUser(result.data, server) - Result.Success(Unit) + return when (result) { + is Result.Success -> { + setLoggedInUser(result.data, server) + Result.Success(Unit) + } + is Result.Error -> Result.Error(result.exception) } - is Result.Error -> Result.Error(result.exception) } - } - private fun setLoggedInUser(authenticationData: AuthenticationData, server: String) { - prefsSource.setAuthData(authenticationData) - prefsSource.setServer(server) + private fun setLoggedInUser( + authenticationData: AuthenticationData, + server: String, + ) { + prefsSource.setAuthData(authenticationData) + prefsSource.setServer(server) + } } -} diff --git a/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/ApiCodecConversion.kt b/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/ApiCodecConversion.kt index 0d7a66dc..6394a8d8 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/ApiCodecConversion.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/ApiCodecConversion.kt @@ -4,5 +4,5 @@ data class ApiCodecConversion( val id: Int, val name: String, val ffmpegParams: String, - val resultingCodecId: Int + val resultingCodecId: Int, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversion.kt b/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversion.kt index 9671448f..1ea66de0 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversion.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversion.kt @@ -7,7 +7,7 @@ data class CodecConversion( val name: String, val ffmpegParams: String, val resultingCodecId: Int, - val fetchedAt: Instant + val fetchedAt: Instant, ) { companion object { fun fromDb(c: DbCodecConversion) = @@ -16,16 +16,18 @@ data class CodecConversion( c.name, c.ffmpegParams, c.resultingCodecId, - c.fetchedAt + c.fetchedAt, ) - fun fromApi(c: ApiCodecConversion, fetchTime: Instant) = - CodecConversion( - c.id, - c.name, - c.ffmpegParams, - c.resultingCodecId, - fetchTime - ) + fun fromApi( + c: ApiCodecConversion, + fetchTime: Instant, + ) = CodecConversion( + c.id, + c.name, + c.ffmpegParams, + c.resultingCodecId, + fetchTime, + ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversionDao.kt b/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversionDao.kt index f5ed6c69..afb6031f 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversionDao.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversionDao.kt @@ -10,9 +10,10 @@ import java.time.Instant @Dao abstract class CodecConversionDao { - open fun getAll(): LiveData> = getAllDbCodecConversions().map { us -> - us.map { CodecConversion.fromDb(it) } - } + open fun getAll(): LiveData> = + getAllDbCodecConversions().map { us -> + us.map { CodecConversion.fromDb(it) } + } @Transaction open fun upsertAll(codecconversions: List) { diff --git a/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversionRepository.kt b/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversionRepository.kt index 7b1b6f5b..d189005b 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversionRepository.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversionRepository.kt @@ -3,46 +3,50 @@ package me.vanpetegem.accentor.data.codecconversions import android.util.SparseArray import androidx.lifecycle.LiveData import androidx.lifecycle.map -import java.time.Instant -import javax.inject.Inject import me.vanpetegem.accentor.api.codecconversion.index import me.vanpetegem.accentor.data.authentication.AuthenticationRepository import me.vanpetegem.accentor.util.Result +import java.time.Instant +import javax.inject.Inject -class CodecConversionRepository @Inject constructor( - private val codecConversionDao: CodecConversionDao, - private val authenticationRepository: AuthenticationRepository -) { - val allCodecConversions: LiveData> = codecConversionDao.getAll() - val allCodecConversionsById: LiveData> = allCodecConversions.map { - val map = SparseArray() - it.forEach { u -> map.put(u.id, u) } - map - } +class CodecConversionRepository + @Inject + constructor( + private val codecConversionDao: CodecConversionDao, + private val authenticationRepository: AuthenticationRepository, + ) { + val allCodecConversions: LiveData> = codecConversionDao.getAll() + val allCodecConversionsById: LiveData> = + allCodecConversions.map { + val map = SparseArray() + it.forEach { u -> map.put(u.id, u) } + map + } - fun getFirst(): CodecConversion? = codecConversionDao.getFirstCodecConversion() - fun getById(id: Int): CodecConversion? = codecConversionDao.getCodecConversionById(id) + fun getFirst(): CodecConversion? = codecConversionDao.getFirstCodecConversion() - suspend fun refresh(handler: suspend (Result) -> Unit) { - val fetchStart = Instant.now() + fun getById(id: Int): CodecConversion? = codecConversionDao.getCodecConversionById(id) - for (result in index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { - when (result) { - is Result.Success -> { - val fetchTime = Instant.now() - codecConversionDao.upsertAll(result.data.map { CodecConversion.fromApi(it, fetchTime) }) - } - is Result.Error -> { - handler(Result.Error(result.exception)) - return + suspend fun refresh(handler: suspend (Result) -> Unit) { + val fetchStart = Instant.now() + + for (result in index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { + when (result) { + is Result.Success -> { + val fetchTime = Instant.now() + codecConversionDao.upsertAll(result.data.map { CodecConversion.fromApi(it, fetchTime) }) + } + is Result.Error -> { + handler(Result.Error(result.exception)) + return + } } } + codecConversionDao.deleteFetchedBefore(fetchStart) + handler(Result.Success(Unit)) } - codecConversionDao.deleteFetchedBefore(fetchStart) - handler(Result.Success(Unit)) - } - suspend fun clear() { - codecConversionDao.deleteAll() + suspend fun clear() { + codecConversionDao.deleteAll() + } } -} diff --git a/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/DbCodecConversion.kt b/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/DbCodecConversion.kt index 1b005918..91a2c13c 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/DbCodecConversion.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/DbCodecConversion.kt @@ -17,5 +17,5 @@ data class DbCodecConversion( @ColumnInfo(name = "resulting_codec_id") val resultingCodecId: Int, @ColumnInfo(name = "fetched_at") - val fetchedAt: Instant + val fetchedAt: Instant, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/playlists/Access.kt b/app/src/main/java/me/vanpetegem/accentor/data/playlists/Access.kt index c253ce2c..a6fb92ab 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/playlists/Access.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/playlists/Access.kt @@ -3,5 +3,5 @@ package me.vanpetegem.accentor.data.playlists enum class Access { SHARED, PERSONAL, - SECRET + SECRET, } diff --git a/app/src/main/java/me/vanpetegem/accentor/data/playlists/ApiPlaylist.kt b/app/src/main/java/me/vanpetegem/accentor/data/playlists/ApiPlaylist.kt index d5573b16..c62d34fb 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/playlists/ApiPlaylist.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/playlists/ApiPlaylist.kt @@ -11,5 +11,5 @@ data class ApiPlaylist( val createdAt: Instant, val updatedAt: Instant, val itemIds: List, - val access: Access + val access: Access, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/playlists/DbPlaylist.kt b/app/src/main/java/me/vanpetegem/accentor/data/playlists/DbPlaylist.kt index f541ce32..0991f4ce 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/playlists/DbPlaylist.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/playlists/DbPlaylist.kt @@ -25,7 +25,7 @@ data class DbPlaylist( @ColumnInfo(name = "access") val access: Access, @ColumnInfo(name = "fetched_at") - val fetchedAt: Instant + val fetchedAt: Instant, ) @Entity(tableName = "playlist_items", primaryKeys = ["playlist_id", "item_id"]) @@ -35,5 +35,5 @@ data class DbPlaylistItem( @ColumnInfo(name = "item_id") val itemId: Int, @ColumnInfo(name = "order") - val order: Int + val order: Int, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/playlists/Playlist.kt b/app/src/main/java/me/vanpetegem/accentor/data/playlists/Playlist.kt index c946ceb3..3ef8a073 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/playlists/Playlist.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/playlists/Playlist.kt @@ -1,11 +1,11 @@ package me.vanpetegem.accentor.data.playlists import android.util.SparseArray -import java.time.Instant import me.vanpetegem.accentor.data.albums.Album import me.vanpetegem.accentor.data.albums.AlbumRepository import me.vanpetegem.accentor.data.tracks.Track import me.vanpetegem.accentor.data.tracks.TrackRepository +import java.time.Instant data class Playlist( val id: Int, @@ -17,39 +17,46 @@ data class Playlist( val updatedAt: Instant, val itemIds: List, val access: Access, - val fetchedAt: Instant + val fetchedAt: Instant, ) { companion object { - fun fromDb(p: DbPlaylist, playlistItems: List) = - Playlist( - p.id, - p.name, - p.description, - p.userId, - p.playlistType, - p.createdAt, - p.updatedAt, - playlistItems, - p.access, - p.fetchedAt - ) + fun fromDb( + p: DbPlaylist, + playlistItems: List, + ) = Playlist( + p.id, + p.name, + p.description, + p.userId, + p.playlistType, + p.createdAt, + p.updatedAt, + playlistItems, + p.access, + p.fetchedAt, + ) - fun fromApi(p: ApiPlaylist, fetchTime: Instant) = - Playlist( - p.id, - p.name, - p.description, - p.userId, - p.playlistType, - p.createdAt, - p.updatedAt, - p.itemIds, - p.access, - fetchTime - ) + fun fromApi( + p: ApiPlaylist, + fetchTime: Instant, + ) = Playlist( + p.id, + p.name, + p.description, + p.userId, + p.playlistType, + p.createdAt, + p.updatedAt, + p.itemIds, + p.access, + fetchTime, + ) } - fun toTrackAlbumPairs(trackRepository: TrackRepository, albumRepository: AlbumRepository): List> { + fun toTrackAlbumPairs( + trackRepository: TrackRepository, + albumRepository: AlbumRepository, + ): List> { return when (playlistType) { PlaylistType.TRACK -> { val albumMap = SparseArray() @@ -61,9 +68,10 @@ data class Playlist( } tracks.map { Pair(it, albumMap.get(it.albumId)) } } - PlaylistType.ALBUM -> albumRepository.getByIds(itemIds).flatMap { a -> - trackRepository.getByAlbum(a).map { t -> Pair(t, a) } - } + PlaylistType.ALBUM -> + albumRepository.getByIds(itemIds).flatMap { a -> + trackRepository.getByAlbum(a).map { t -> Pair(t, a) } + } PlaylistType.ARTIST -> { val albumMap = SparseArray() itemIds.flatMap { id -> diff --git a/app/src/main/java/me/vanpetegem/accentor/data/playlists/PlaylistDao.kt b/app/src/main/java/me/vanpetegem/accentor/data/playlists/PlaylistDao.kt index cb615948..8e07bbda 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/playlists/PlaylistDao.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/playlists/PlaylistDao.kt @@ -13,12 +13,12 @@ import java.time.Instant @Dao abstract class PlaylistDao { - - open fun getAll(): LiveData> = getAllDbPlaylists().switchMap { playlists -> - playlistItemsByPlaylistId().map { playlistItems -> - playlists.map { p -> Playlist.fromDb(p, playlistItems.get(p.id, ArrayList())) } + open fun getAll(): LiveData> = + getAllDbPlaylists().switchMap { playlists -> + playlistItemsByPlaylistId().map { playlistItems -> + playlists.map { p -> Playlist.fromDb(p, playlistItems.get(p.id, ArrayList())) } + } } - } protected open fun playlistItemsByPlaylistId(): LiveData>> = getAllPlaylistItems().map { @@ -44,8 +44,8 @@ abstract class PlaylistDao { playlist.createdAt, playlist.updatedAt, playlist.access, - playlist.fetchedAt - ) + playlist.fetchedAt, + ), ) deletePlaylistItemsById(playlist.id) for (i in 0 until playlist.itemIds.size) { diff --git a/app/src/main/java/me/vanpetegem/accentor/data/playlists/PlaylistRepository.kt b/app/src/main/java/me/vanpetegem/accentor/data/playlists/PlaylistRepository.kt index a1c846ce..3e575e30 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/playlists/PlaylistRepository.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/playlists/PlaylistRepository.kt @@ -4,53 +4,56 @@ import android.util.SparseArray import androidx.lifecycle.LiveData import androidx.lifecycle.map import dagger.Reusable -import java.time.Instant -import javax.inject.Inject import me.vanpetegem.accentor.api.playlist.index import me.vanpetegem.accentor.data.authentication.AuthenticationRepository import me.vanpetegem.accentor.util.Result +import java.time.Instant +import javax.inject.Inject @Reusable -class PlaylistRepository @Inject constructor( - private val playlistDao: PlaylistDao, - private val authenticationRepository: AuthenticationRepository -) { - val allPlaylists: LiveData> = playlistDao.getAll() - val allPlaylistsById: LiveData> = allPlaylists.map { - val map = SparseArray() - it.forEach { p -> map.put(p.id, p) } - map - } +class PlaylistRepository + @Inject + constructor( + private val playlistDao: PlaylistDao, + private val authenticationRepository: AuthenticationRepository, + ) { + val allPlaylists: LiveData> = playlistDao.getAll() + val allPlaylistsById: LiveData> = + allPlaylists.map { + val map = SparseArray() + it.forEach { p -> map.put(p.id, p) } + map + } - suspend fun refresh(handler: suspend (Result) -> Unit) { - val fetchStart = Instant.now() + suspend fun refresh(handler: suspend (Result) -> Unit) { + val fetchStart = Instant.now() - var toUpsert = ArrayList() - var count = 0 - for (result in index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { - when (result) { - is Result.Success -> { - val fetchTime = Instant.now() - toUpsert.addAll(result.data.map { Playlist.fromApi(it, fetchTime) }) - count += 1 - if (count >= 5) { - playlistDao.upsertAll(toUpsert) - toUpsert.clear() - count = 0 + var toUpsert = ArrayList() + var count = 0 + for (result in index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { + when (result) { + is Result.Success -> { + val fetchTime = Instant.now() + toUpsert.addAll(result.data.map { Playlist.fromApi(it, fetchTime) }) + count += 1 + if (count >= 5) { + playlistDao.upsertAll(toUpsert) + toUpsert.clear() + count = 0 + } + } + is Result.Error -> { + handler(Result.Error(result.exception)) + return } - } - is Result.Error -> { - handler(Result.Error(result.exception)) - return } } + playlistDao.upsertAll(toUpsert) + playlistDao.deleteFetchedBefore(fetchStart) + handler(Result.Success(Unit)) } - playlistDao.upsertAll(toUpsert) - playlistDao.deleteFetchedBefore(fetchStart) - handler(Result.Success(Unit)) - } - suspend fun clear() { - playlistDao.deleteAll() + suspend fun clear() { + playlistDao.deleteAll() + } } -} diff --git a/app/src/main/java/me/vanpetegem/accentor/data/playlists/PlaylistType.kt b/app/src/main/java/me/vanpetegem/accentor/data/playlists/PlaylistType.kt index 47fe9a47..3cf7c9ca 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/playlists/PlaylistType.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/playlists/PlaylistType.kt @@ -3,5 +3,5 @@ package me.vanpetegem.accentor.data.playlists enum class PlaylistType { ALBUM, ARTIST, - TRACK + TRACK, } diff --git a/app/src/main/java/me/vanpetegem/accentor/data/plays/ApiPlay.kt b/app/src/main/java/me/vanpetegem/accentor/data/plays/ApiPlay.kt index c797a9b0..a316f598 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/plays/ApiPlay.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/plays/ApiPlay.kt @@ -6,5 +6,5 @@ data class ApiPlay( val id: Int, val playedAt: Instant, val trackId: Int, - val userId: Int + val userId: Int, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/plays/DbPlay.kt b/app/src/main/java/me/vanpetegem/accentor/data/plays/DbPlay.kt index dc99f8fa..7ea1319b 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/plays/DbPlay.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/plays/DbPlay.kt @@ -17,5 +17,5 @@ data class DbPlay( @ColumnInfo(name = "user_id") val userId: Int, @ColumnInfo(name = "fetched_at") - val fetchedAt: Instant + val fetchedAt: Instant, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/plays/Play.kt b/app/src/main/java/me/vanpetegem/accentor/data/plays/Play.kt index 15da9139..407120af 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/plays/Play.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/plays/Play.kt @@ -7,18 +7,20 @@ data class Play( val playedAt: Instant, val trackId: Int, val userId: Int, - val fetchedAt: Instant + val fetchedAt: Instant, ) { fun toDb() = DbPlay(id, playedAt, trackId, userId, fetchedAt) companion object { - fun fromApi(p: ApiPlay, fetchTime: Instant) = - Play( - p.id, - p.playedAt, - p.trackId, - p.userId, - fetchTime - ) + fun fromApi( + p: ApiPlay, + fetchTime: Instant, + ) = Play( + p.id, + p.playedAt, + p.trackId, + p.userId, + fetchTime, + ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/data/plays/PlayRepository.kt b/app/src/main/java/me/vanpetegem/accentor/data/plays/PlayRepository.kt index 3581db24..bb5c5814 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/plays/PlayRepository.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/plays/PlayRepository.kt @@ -1,75 +1,80 @@ package me.vanpetegem.accentor.data.plays -import java.time.Instant -import javax.inject.Inject import me.vanpetegem.accentor.api.plays.create import me.vanpetegem.accentor.api.plays.index import me.vanpetegem.accentor.data.authentication.AuthenticationRepository import me.vanpetegem.accentor.util.Result +import java.time.Instant +import javax.inject.Inject -class PlayRepository @Inject constructor( - private val playDao: PlayDao, - private val unreportedPlayDao: UnreportedPlayDao, - private val authenticationRepository: AuthenticationRepository -) { - suspend fun refresh(handler: suspend (Result) -> Unit) { - val fetchStart = Instant.now() +class PlayRepository + @Inject + constructor( + private val playDao: PlayDao, + private val unreportedPlayDao: UnreportedPlayDao, + private val authenticationRepository: AuthenticationRepository, + ) { + suspend fun refresh(handler: suspend (Result) -> Unit) { + val fetchStart = Instant.now() - var toUpsert = ArrayList() - var count = 0 - for (result in index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { - when (result) { - is Result.Success -> { - val fetchTime = Instant.now() - toUpsert.addAll(result.data.map { Play.fromApi(it, fetchTime) }) - count += 1 - if (count >= 5) { - playDao.upsertAll(toUpsert) - toUpsert.clear() - count = 0 + var toUpsert = ArrayList() + var count = 0 + for (result in index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { + when (result) { + is Result.Success -> { + val fetchTime = Instant.now() + toUpsert.addAll(result.data.map { Play.fromApi(it, fetchTime) }) + count += 1 + if (count >= 5) { + playDao.upsertAll(toUpsert) + toUpsert.clear() + count = 0 + } + } + is Result.Error -> { + handler(Result.Error(result.exception)) + return } } - is Result.Error -> { - handler(Result.Error(result.exception)) - return + } + playDao.upsertAll(toUpsert) + playDao.deleteFetchedBefore(fetchStart) + reportUnreportedPlays() + handler(Result.Success(Unit)) + } + + private suspend fun reportUnreportedPlays() { + for (play in unreportedPlayDao.getAllUnreportedPlays()) { + when (val result = create(authenticationRepository.server.value!!, authenticationRepository.authData.value!!, play.trackId, play.playedAt)) { + is Result.Success -> { + unreportedPlayDao.delete(play) + val fetchTime = Instant.now() + playDao.insert(Play.fromApi(result.data, fetchTime)) + } + is Result.Error -> { + // Ignore, creation will be retried at a later time + } } } } - playDao.upsertAll(toUpsert) - playDao.deleteFetchedBefore(fetchStart) - reportUnreportedPlays() - handler(Result.Success(Unit)) - } - private suspend fun reportUnreportedPlays() { - for (play in unreportedPlayDao.getAllUnreportedPlays()) { - when (val result = create(authenticationRepository.server.value!!, authenticationRepository.authData.value!!, play.trackId, play.playedAt)) { + suspend fun reportPlay( + trackId: Int, + playedAt: Instant, + ) { + when (val result = create(authenticationRepository.server.value!!, authenticationRepository.authData.value!!, trackId, playedAt)) { is Result.Success -> { - unreportedPlayDao.delete(play) val fetchTime = Instant.now() playDao.insert(Play.fromApi(result.data, fetchTime)) + reportUnreportedPlays() } is Result.Error -> { - // Ignore, creation will be retried at a later time + unreportedPlayDao.insert(UnreportedPlay(trackId = trackId, playedAt = playedAt)) } } } - } - suspend fun reportPlay(trackId: Int, playedAt: Instant) { - when (val result = create(authenticationRepository.server.value!!, authenticationRepository.authData.value!!, trackId, playedAt)) { - is Result.Success -> { - val fetchTime = Instant.now() - playDao.insert(Play.fromApi(result.data, fetchTime)) - reportUnreportedPlays() - } - is Result.Error -> { - unreportedPlayDao.insert(UnreportedPlay(trackId = trackId, playedAt = playedAt)) - } + suspend fun clear() { + playDao.deleteAll() } } - - suspend fun clear() { - playDao.deleteAll() - } -} diff --git a/app/src/main/java/me/vanpetegem/accentor/data/plays/UnreportedPlay.kt b/app/src/main/java/me/vanpetegem/accentor/data/plays/UnreportedPlay.kt index 04f19179..31d9f081 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/plays/UnreportedPlay.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/plays/UnreportedPlay.kt @@ -12,5 +12,5 @@ data class UnreportedPlay( @ColumnInfo(name = "track_id") val trackId: Int, @ColumnInfo(name = "played_at") - val playedAt: Instant + val playedAt: Instant, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/preferences/PreferencesDataSource.kt b/app/src/main/java/me/vanpetegem/accentor/data/preferences/PreferencesDataSource.kt index 8faec618..ba7a4d87 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/preferences/PreferencesDataSource.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/preferences/PreferencesDataSource.kt @@ -4,34 +4,42 @@ import android.content.Context import androidx.lifecycle.LiveData import androidx.lifecycle.map import dagger.hilt.android.qualifiers.ApplicationContext -import java.time.Instant -import javax.inject.Inject import me.vanpetegem.accentor.util.intLiveData import me.vanpetegem.accentor.util.longLiveData import me.vanpetegem.accentor.util.stringLiveData +import java.time.Instant +import javax.inject.Inject const val CONVERSION_ID_KEY = "conversion_id" const val IMAGE_CACHE_SIZE_KEY = "image_cache_size" const val MUSIC_CACHE_SIZE_KEY = "music_cache_size" const val LAST_SYNC_FINISHED = "last_sync_finished" -class PreferencesDataSource @Inject constructor(@ApplicationContext private val context: Context) { - private val sharedPreferences = context.getSharedPreferences("me.vanpetegem.accentor.preferences", Context.MODE_PRIVATE) +class PreferencesDataSource + @Inject + constructor( + @ApplicationContext private val context: Context, + ) { + private val sharedPreferences = context.getSharedPreferences("me.vanpetegem.accentor.preferences", Context.MODE_PRIVATE) - private val conversionIdData = sharedPreferences.intLiveData(CONVERSION_ID_KEY) - private val imageCacheSizeData = sharedPreferences.longLiveData(IMAGE_CACHE_SIZE_KEY, 1024L * 1024L * 1024L) - private val musicCacheSizeData = sharedPreferences.longLiveData(MUSIC_CACHE_SIZE_KEY, 10L * 1024L * 1024L * 1024L) - private val lastSyncFinishedData = sharedPreferences.stringLiveData(LAST_SYNC_FINISHED) + private val conversionIdData = sharedPreferences.intLiveData(CONVERSION_ID_KEY) + private val imageCacheSizeData = sharedPreferences.longLiveData(IMAGE_CACHE_SIZE_KEY, 1024L * 1024L * 1024L) + private val musicCacheSizeData = sharedPreferences.longLiveData(MUSIC_CACHE_SIZE_KEY, 10L * 1024L * 1024L * 1024L) + private val lastSyncFinishedData = sharedPreferences.stringLiveData(LAST_SYNC_FINISHED) - val conversionId: LiveData = conversionIdData - val imageCacheSize: LiveData = imageCacheSizeData - val musicCacheSize: LiveData = musicCacheSizeData - val lastSyncFinished: LiveData = lastSyncFinishedData.map { - it?.let { Instant.parse(it) } - } + val conversionId: LiveData = conversionIdData + val imageCacheSize: LiveData = imageCacheSizeData + val musicCacheSize: LiveData = musicCacheSizeData + val lastSyncFinished: LiveData = + lastSyncFinishedData.map { + it?.let { Instant.parse(it) } + } - fun setConversionId(id: Int) = sharedPreferences.edit().putInt(CONVERSION_ID_KEY, id).apply() - fun setImageCacheSize(size: Long) = sharedPreferences.edit().putLong(IMAGE_CACHE_SIZE_KEY, size).apply() - fun setMusicCacheSize(size: Long) = sharedPreferences.edit().putLong(MUSIC_CACHE_SIZE_KEY, size).apply() - fun setLastSyncFinished(time: Instant) = sharedPreferences.edit().putString(LAST_SYNC_FINISHED, time.toString()).apply() -} + fun setConversionId(id: Int) = sharedPreferences.edit().putInt(CONVERSION_ID_KEY, id).apply() + + fun setImageCacheSize(size: Long) = sharedPreferences.edit().putLong(IMAGE_CACHE_SIZE_KEY, size).apply() + + fun setMusicCacheSize(size: Long) = sharedPreferences.edit().putLong(MUSIC_CACHE_SIZE_KEY, size).apply() + + fun setLastSyncFinished(time: Instant) = sharedPreferences.edit().putString(LAST_SYNC_FINISHED, time.toString()).apply() + } diff --git a/app/src/main/java/me/vanpetegem/accentor/data/tracks/ApiTrack.kt b/app/src/main/java/me/vanpetegem/accentor/data/tracks/ApiTrack.kt index 886ee978..d00ac669 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/tracks/ApiTrack.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/tracks/ApiTrack.kt @@ -16,5 +16,5 @@ data class ApiTrack( val codecId: Int?, val length: Int?, val bitrate: Int?, - val locationId: Int? + val locationId: Int?, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/tracks/DbTrack.kt b/app/src/main/java/me/vanpetegem/accentor/data/tracks/DbTrack.kt index 058da885..81ac39a7 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/tracks/DbTrack.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/tracks/DbTrack.kt @@ -33,7 +33,7 @@ data class DbTrack( @ColumnInfo(name = "location_id") val locationId: Int?, @ColumnInfo(name = "fetched_at") - val fetchedAt: Instant + val fetchedAt: Instant, ) @Entity(tableName = "track_artists", primaryKeys = ["track_id", "artist_id", "name", "role"]) @@ -51,7 +51,7 @@ data class DbTrackArtist( @ColumnInfo(name = "order") val order: Int, @ColumnInfo(name = "hidden") - val hidden: Boolean + val hidden: Boolean, ) @Entity(tableName = "track_genres", primaryKeys = ["track_id", "genre_id"]) @@ -59,5 +59,5 @@ data class DbTrackGenre( @ColumnInfo(name = "track_id") val trackId: Int, @ColumnInfo(name = "genre_id") - val genreId: Int + val genreId: Int, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/tracks/Track.kt b/app/src/main/java/me/vanpetegem/accentor/data/tracks/Track.kt index fa65515e..f7d0ba7d 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/tracks/Track.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/tracks/Track.kt @@ -1,8 +1,8 @@ package me.vanpetegem.accentor.data.tracks import android.util.SparseArray -import java.time.Instant import me.vanpetegem.accentor.data.albums.Album +import java.time.Instant data class Track( val id: Int, @@ -19,26 +19,44 @@ data class Track( val length: Int?, val bitrate: Int?, val locationId: Int?, - val fetchedAt: Instant + val fetchedAt: Instant, ) { fun stringifyTrackArtists() = trackArtists.filter { ta -> !ta.hidden }.sortedBy { ta -> ta.order }.joinToString(" / ") { ta -> ta.name } - fun compareTo(other: Track, albums: SparseArray): Int { + fun compareTo( + other: Track, + albums: SparseArray, + ): Int { val a1 = albums[this.albumId] val a2 = albums[other.albumId] - if (a1 == null && a2 == null) { return this.number - other.number } - if (a1 == null) { return 1 } - if (a2 == null) { return -1 } + if (a1 == null && a2 == null) { + return this.number - other.number + } + if (a1 == null) { + return 1 + } + if (a2 == null) { + return -1 + } val order = a1.compareToByName(a2) - if (order != 0) { return order } + if (order != 0) { + return order + } return this.number - other.number } - fun compareAlphabetically(other: Track, albums: SparseArray): Int { + fun compareAlphabetically( + other: Track, + albums: SparseArray, + ): Int { var order = normalizedTitle.compareTo(other.normalizedTitle) - if (order != 0) { return order } + if (order != 0) { + return order + } order = this.number - other.number - if (order != 0) { return order } + if (order != 0) { + return order + } return compareTo(other, albums) } @@ -47,43 +65,48 @@ data class Track( const val ARTIST = "me.vanpetegem.accentor.data.tracks.Track.ARTIST" const val YEAR = "me.vanpetegem.accentor.data.tracks.Track.YEAR" - fun fromDb(t: DbTrack, trackArtists: List, trackGenres: List) = - Track( - t.id, - t.title, - t.normalizedTitle, - t.number, - t.albumId, - t.reviewComment, - t.createdAt, - t.updatedAt, - trackGenres, - trackArtists, - t.codecId, - t.length, - t.bitrate, - t.locationId, - t.fetchedAt - ) + fun fromDb( + t: DbTrack, + trackArtists: List, + trackGenres: List, + ) = Track( + t.id, + t.title, + t.normalizedTitle, + t.number, + t.albumId, + t.reviewComment, + t.createdAt, + t.updatedAt, + trackGenres, + trackArtists, + t.codecId, + t.length, + t.bitrate, + t.locationId, + t.fetchedAt, + ) - fun fromApi(t: ApiTrack, fetchTime: Instant) = - Track( - t.id, - t.title, - t.normalizedTitle, - t.number, - t.albumId, - t.reviewComment, - t.createdAt, - t.updatedAt, - t.genreIds, - t.trackArtists, - t.codecId, - t.length, - t.bitrate, - t.locationId, - fetchTime - ) + fun fromApi( + t: ApiTrack, + fetchTime: Instant, + ) = Track( + t.id, + t.title, + t.normalizedTitle, + t.number, + t.albumId, + t.reviewComment, + t.createdAt, + t.updatedAt, + t.genreIds, + t.trackArtists, + t.codecId, + t.length, + t.bitrate, + t.locationId, + fetchTime, + ) } } @@ -93,7 +116,7 @@ data class TrackArtist( val normalizedName: String, val role: Role, val order: Int, - val hidden: Boolean + val hidden: Boolean, ) enum class Role { @@ -103,5 +126,5 @@ enum class Role { CONDUCTOR, REMIXER, PRODUCER, - ARRANGER + ARRANGER, } diff --git a/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackDao.kt b/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackDao.kt index e1c35231..0ce8e298 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackDao.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackDao.kt @@ -9,13 +9,12 @@ import androidx.room.Insert import androidx.room.Query import androidx.room.Transaction import androidx.room.Upsert -import java.time.Instant import me.vanpetegem.accentor.data.albums.Album import me.vanpetegem.accentor.data.artists.Artist +import java.time.Instant @Dao abstract class TrackDao { - @Transaction open fun getByAlbum(album: Album): List { val tracks = getDbTracksByAlbumId(album.id) @@ -34,36 +33,39 @@ abstract class TrackDao { return ids.map { Track.fromDb(tracksByIds.get(it), trackArtists.get(it, ArrayList()), trackGenres.get(it, ArrayList())) } } - open fun findByIds(ids: List): LiveData> = findDbTracksByIds(ids).switchMap { tracks -> - findTrackArtistsByTrackIdWhereTrackIds(ids).switchMap { trackArtists -> - findTrackGenresByTrackIdWhereTrackIds(ids).map { trackGenres -> - tracks.map { t -> Track.fromDb(t, trackArtists.get(t.id, ArrayList()), trackGenres.get(t.id, ArrayList())) } + open fun findByIds(ids: List): LiveData> = + findDbTracksByIds(ids).switchMap { tracks -> + findTrackArtistsByTrackIdWhereTrackIds(ids).switchMap { trackArtists -> + findTrackGenresByTrackIdWhereTrackIds(ids).map { trackGenres -> + tracks.map { t -> Track.fromDb(t, trackArtists.get(t.id, ArrayList()), trackGenres.get(t.id, ArrayList())) } + } } } - } - open fun findById(id: Int): LiveData = findDbTrackById(id).switchMap { dbTrack -> - findDbTrackArtistsById(id).switchMap { trackArtists -> - findDbTrackGenresById(id).map { trackGenres -> - dbTrack?.let { - Track.fromDb( - it, - trackArtists.map { TrackArtist(it.artistId, it.name, it.normalizedName, it.role, it.order, it.hidden) }, - trackGenres.map { it.genreId } - ) + open fun findById(id: Int): LiveData = + findDbTrackById(id).switchMap { dbTrack -> + findDbTrackArtistsById(id).switchMap { trackArtists -> + findDbTrackGenresById(id).map { trackGenres -> + dbTrack?.let { + Track.fromDb( + it, + trackArtists.map { TrackArtist(it.artistId, it.name, it.normalizedName, it.role, it.order, it.hidden) }, + trackGenres.map { it.genreId }, + ) + } } } } - } - open fun findByArtist(artist: Artist): LiveData> = findDbTracksByArtistId(artist.id).switchMap { tracks -> - val ids = tracks.map { it.id } - findTrackArtistsByTrackIdWhereTrackIds(ids).switchMap { trackArtists -> - findTrackGenresByTrackIdWhereTrackIds(ids).map { trackGenres -> - tracks.map { Track.fromDb(it, trackArtists.get(it.id, ArrayList()), trackGenres.get(it.id, ArrayList())) } + open fun findByArtist(artist: Artist): LiveData> = + findDbTracksByArtistId(artist.id).switchMap { tracks -> + val ids = tracks.map { it.id } + findTrackArtistsByTrackIdWhereTrackIds(ids).switchMap { trackArtists -> + findTrackGenresByTrackIdWhereTrackIds(ids).map { trackGenres -> + tracks.map { Track.fromDb(it, trackArtists.get(it.id, ArrayList()), trackGenres.get(it.id, ArrayList())) } + } } } - } open fun getByArtistId(id: Int): List { val tracks = getDbTracksByArtistId(id) @@ -73,14 +75,15 @@ abstract class TrackDao { return tracks.map { Track.fromDb(it, trackArtists.get(it.id, ArrayList()), trackGenres.get(it.id, ArrayList())) } } - open fun findByAlbum(album: Album): LiveData> = findDbTracksByAlbumId(album.id).switchMap { tracks -> - val ids = tracks.map { it.id } - findTrackArtistsByTrackIdWhereTrackIds(ids).switchMap { trackArtists -> - findTrackGenresByTrackIdWhereTrackIds(ids).map { trackGenres -> - tracks.map { Track.fromDb(it, trackArtists.get(it.id, ArrayList()), trackGenres.get(it.id, ArrayList())) } + open fun findByAlbum(album: Album): LiveData> = + findDbTracksByAlbumId(album.id).switchMap { tracks -> + val ids = tracks.map { it.id } + findTrackArtistsByTrackIdWhereTrackIds(ids).switchMap { trackArtists -> + findTrackGenresByTrackIdWhereTrackIds(ids).map { trackGenres -> + tracks.map { Track.fromDb(it, trackArtists.get(it.id, ArrayList()), trackGenres.get(it.id, ArrayList())) } + } } } - } @Transaction open fun getTrackById(id: Int): Track? { @@ -92,7 +95,7 @@ abstract class TrackDao { return Track.fromDb( dbTrack, trackArtists.map { TrackArtist(it.artistId, it.name, it.normalizedName, it.role, it.order, it.hidden) }, - trackGenres.map { it.genreId } + trackGenres.map { it.genreId }, ) } @@ -144,8 +147,8 @@ abstract class TrackDao { ta.normalizedName, ta.role, ta.order, - ta.hidden - ) + ta.hidden, + ), ) map.put(ta.trackId, l) } @@ -163,8 +166,8 @@ abstract class TrackDao { ta.normalizedName, ta.role, ta.order, - ta.hidden - ) + ta.hidden, + ), ) map.put(ta.trackId, l) } @@ -209,8 +212,8 @@ abstract class TrackDao { track.length, track.bitrate, track.locationId, - track.fetchedAt - ) + track.fetchedAt, + ), ) deleteTrackArtistsById(track.id) for (ta in track.trackArtists) { @@ -222,8 +225,8 @@ abstract class TrackDao { ta.normalizedName, ta.role, ta.order, - ta.hidden - ) + ta.hidden, + ), ) } deleteTrackGenresById(track.id) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackRepository.kt b/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackRepository.kt index 5a4052d7..949a4986 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackRepository.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackRepository.kt @@ -2,57 +2,66 @@ package me.vanpetegem.accentor.data.tracks import androidx.lifecycle.LiveData import dagger.Reusable -import java.time.Instant -import javax.inject.Inject import me.vanpetegem.accentor.api.track.index import me.vanpetegem.accentor.data.albums.Album import me.vanpetegem.accentor.data.artists.Artist import me.vanpetegem.accentor.data.authentication.AuthenticationRepository import me.vanpetegem.accentor.util.Result +import java.time.Instant +import javax.inject.Inject @Reusable -class TrackRepository @Inject constructor( - private val trackDao: TrackDao, - private val authenticationRepository: AuthenticationRepository -) { - fun findById(id: Int): LiveData = trackDao.findById(id) - fun getById(id: Int): Track? = trackDao.getTrackById(id) - fun findByIds(ids: List): LiveData> = trackDao.findByIds(ids) - fun getByIds(ids: List): List = trackDao.getByIds(ids) - fun findByArtist(artist: Artist): LiveData> = trackDao.findByArtist(artist) - fun getByArtistId(id: Int): List = trackDao.getByArtistId(id) - fun findByAlbum(album: Album): LiveData> = trackDao.findByAlbum(album) - fun getByAlbum(album: Album): List = trackDao.getByAlbum(album) - - suspend fun refresh(handler: suspend (Result) -> Unit) { - val fetchStart = Instant.now() - - var toUpsert = ArrayList() - var count = 0 - for (result in index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { - when (result) { - is Result.Success -> { - val fetchTime = Instant.now() - toUpsert.addAll(result.data.map { Track.fromApi(it, fetchTime) }) - count += 1 - if (count >= 5) { - trackDao.upsertAll(toUpsert) - toUpsert.clear() - count = 0 +class TrackRepository + @Inject + constructor( + private val trackDao: TrackDao, + private val authenticationRepository: AuthenticationRepository, + ) { + fun findById(id: Int): LiveData = trackDao.findById(id) + + fun getById(id: Int): Track? = trackDao.getTrackById(id) + + fun findByIds(ids: List): LiveData> = trackDao.findByIds(ids) + + fun getByIds(ids: List): List = trackDao.getByIds(ids) + + fun findByArtist(artist: Artist): LiveData> = trackDao.findByArtist(artist) + + fun getByArtistId(id: Int): List = trackDao.getByArtistId(id) + + fun findByAlbum(album: Album): LiveData> = trackDao.findByAlbum(album) + + fun getByAlbum(album: Album): List = trackDao.getByAlbum(album) + + suspend fun refresh(handler: suspend (Result) -> Unit) { + val fetchStart = Instant.now() + + var toUpsert = ArrayList() + var count = 0 + for (result in index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { + when (result) { + is Result.Success -> { + val fetchTime = Instant.now() + toUpsert.addAll(result.data.map { Track.fromApi(it, fetchTime) }) + count += 1 + if (count >= 5) { + trackDao.upsertAll(toUpsert) + toUpsert.clear() + count = 0 + } + } + is Result.Error -> { + handler(Result.Error(result.exception)) + return } - } - is Result.Error -> { - handler(Result.Error(result.exception)) - return } } + trackDao.upsertAll(toUpsert) + trackDao.deleteFetchedBefore(fetchStart) + handler(Result.Success(Unit)) } - trackDao.upsertAll(toUpsert) - trackDao.deleteFetchedBefore(fetchStart) - handler(Result.Success(Unit)) - } - suspend fun clear() { - trackDao.deleteAll() + suspend fun clear() { + trackDao.deleteAll() + } } -} diff --git a/app/src/main/java/me/vanpetegem/accentor/data/users/ApiUser.kt b/app/src/main/java/me/vanpetegem/accentor/data/users/ApiUser.kt index 5721d038..ad855d43 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/users/ApiUser.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/users/ApiUser.kt @@ -3,5 +3,5 @@ package me.vanpetegem.accentor.data.users data class ApiUser( val id: Int, val name: String, - val permission: Permission + val permission: Permission, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/users/DbUser.kt b/app/src/main/java/me/vanpetegem/accentor/data/users/DbUser.kt index 9824bc6a..d91009bb 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/users/DbUser.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/users/DbUser.kt @@ -15,5 +15,5 @@ data class DbUser( @ColumnInfo(name = "permission") val permission: Permission, @ColumnInfo(name = "fetched_at") - val fetchedAt: Instant + val fetchedAt: Instant, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/users/Permission.kt b/app/src/main/java/me/vanpetegem/accentor/data/users/Permission.kt index 3b23100b..4efb1031 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/users/Permission.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/users/Permission.kt @@ -3,5 +3,5 @@ package me.vanpetegem.accentor.data.users enum class Permission { USER, MODERATOR, - ADMIN + ADMIN, } diff --git a/app/src/main/java/me/vanpetegem/accentor/data/users/User.kt b/app/src/main/java/me/vanpetegem/accentor/data/users/User.kt index 7a849469..37591104 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/users/User.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/users/User.kt @@ -6,7 +6,7 @@ data class User( val id: Int, val name: String, val permission: Permission, - val fetchedAt: Instant + val fetchedAt: Instant, ) { companion object { fun fromDb(u: DbUser) = @@ -14,15 +14,17 @@ data class User( u.id, u.name, u.permission, - u.fetchedAt + u.fetchedAt, ) - fun fromApi(u: ApiUser, fetchTime: Instant) = - User( - u.id, - u.name, - u.permission, - fetchTime - ) + fun fromApi( + u: ApiUser, + fetchTime: Instant, + ) = User( + u.id, + u.name, + u.permission, + fetchTime, + ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/data/users/UserDao.kt b/app/src/main/java/me/vanpetegem/accentor/data/users/UserDao.kt index 3cd35aaa..167c6b8e 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/users/UserDao.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/users/UserDao.kt @@ -10,9 +10,10 @@ import java.time.Instant @Dao abstract class UserDao { - open fun getAll(): LiveData> = getAllDbUsers().map { us -> - us.map { User.fromDb(it) } - } + open fun getAll(): LiveData> = + getAllDbUsers().map { us -> + us.map { User.fromDb(it) } + } @Transaction open fun upsertAll(users: List) { diff --git a/app/src/main/java/me/vanpetegem/accentor/data/users/UserRepository.kt b/app/src/main/java/me/vanpetegem/accentor/data/users/UserRepository.kt index 06b52bee..f8f93524 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/users/UserRepository.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/users/UserRepository.kt @@ -4,48 +4,52 @@ import android.util.SparseArray import androidx.lifecycle.LiveData import androidx.lifecycle.map import androidx.lifecycle.switchMap -import java.time.Instant -import javax.inject.Inject import me.vanpetegem.accentor.api.user.index import me.vanpetegem.accentor.data.authentication.AuthenticationRepository import me.vanpetegem.accentor.util.Result +import java.time.Instant +import javax.inject.Inject -class UserRepository @Inject constructor( - private val userDao: UserDao, - private val authenticationRepository: AuthenticationRepository -) { - val allUsers: LiveData> = userDao.getAll() - val allUsersById: LiveData> = allUsers.map { - val map = SparseArray() - it.forEach { u -> map.put(u.id, u) } - map - } +class UserRepository + @Inject + constructor( + private val userDao: UserDao, + private val authenticationRepository: AuthenticationRepository, + ) { + val allUsers: LiveData> = userDao.getAll() + val allUsersById: LiveData> = + allUsers.map { + val map = SparseArray() + it.forEach { u -> map.put(u.id, u) } + map + } - val currentUser: LiveData = authenticationRepository.authData.switchMap { authData -> - authData ?: return@switchMap null - allUsersById.map { it[authData.userId] } - } + val currentUser: LiveData = + authenticationRepository.authData.switchMap { authData -> + authData ?: return@switchMap null + allUsersById.map { it[authData.userId] } + } - suspend fun refresh(handler: suspend (Result) -> Unit) { - val fetchStart = Instant.now() + suspend fun refresh(handler: suspend (Result) -> Unit) { + val fetchStart = Instant.now() - for (result in index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { - when (result) { - is Result.Success -> { - val fetchTime = Instant.now() - userDao.upsertAll(result.data.map { User.fromApi(it, fetchTime) }) - } - is Result.Error -> { - handler(Result.Error(result.exception)) - return + for (result in index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { + when (result) { + is Result.Success -> { + val fetchTime = Instant.now() + userDao.upsertAll(result.data.map { User.fromApi(it, fetchTime) }) + } + is Result.Error -> { + handler(Result.Error(result.exception)) + return + } } } + userDao.deleteFetchedBefore(fetchStart) + handler(Result.Success(Unit)) } - userDao.deleteFetchedBefore(fetchStart) - handler(Result.Success(Unit)) - } - suspend fun clear() { - userDao.deleteAll() + suspend fun clear() { + userDao.deleteAll() + } } -} diff --git a/app/src/main/java/me/vanpetegem/accentor/media/MediaSessionConnection.kt b/app/src/main/java/me/vanpetegem/accentor/media/MediaSessionConnection.kt index 45a83ac0..5501c0ab 100644 --- a/app/src/main/java/me/vanpetegem/accentor/media/MediaSessionConnection.kt +++ b/app/src/main/java/me/vanpetegem/accentor/media/MediaSessionConnection.kt @@ -14,8 +14,6 @@ import androidx.media3.session.MediaController import androidx.media3.session.SessionToken import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.MoreExecutors -import javax.inject.Inject -import javax.inject.Singleton import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch @@ -27,206 +25,233 @@ import me.vanpetegem.accentor.data.playlists.Playlist import me.vanpetegem.accentor.data.preferences.PreferencesDataSource import me.vanpetegem.accentor.data.tracks.Track import me.vanpetegem.accentor.data.tracks.TrackRepository +import javax.inject.Inject +import javax.inject.Singleton @Singleton -class MediaSessionConnection @Inject constructor( - private val application: Application, - private val albumRepository: AlbumRepository, - private val trackRepository: TrackRepository, - private val preferencesDataSource: PreferencesDataSource, - private val codecConversionRepository: CodecConversionRepository, - private val authenticationDataSource: AuthenticationDataSource -) { - private val mainScope = MainScope() - private val mediaControllerFuture: ListenableFuture = - MediaController.Builder( - application, - SessionToken(application, ComponentName(application, MusicService::class.java)) - ) - .buildAsync() - .apply { addListener({ setupController() }, MoreExecutors.directExecutor()) } - - private lateinit var mediaController: MediaController - - private val currentTrackId = MutableLiveData().apply { postValue(null) } - val currentTrack: LiveData = - currentTrackId.switchMap { id -> - _queue.switchMap { queue -> - if (queue.size > 0) id?.let { trackRepository.findById(id) } else null +class MediaSessionConnection + @Inject + constructor( + private val application: Application, + private val albumRepository: AlbumRepository, + private val trackRepository: TrackRepository, + private val preferencesDataSource: PreferencesDataSource, + private val codecConversionRepository: CodecConversionRepository, + private val authenticationDataSource: AuthenticationDataSource, + ) { + private val mainScope = MainScope() + private val mediaControllerFuture: ListenableFuture = + MediaController.Builder( + application, + SessionToken(application, ComponentName(application, MusicService::class.java)), + ) + .buildAsync() + .apply { addListener({ setupController() }, MoreExecutors.directExecutor()) } + + private lateinit var mediaController: MediaController + + private val currentTrackId = MutableLiveData().apply { postValue(null) } + val currentTrack: LiveData = + currentTrackId.switchMap { id -> + _queue.switchMap { queue -> + if (queue.size > 0) id?.let { trackRepository.findById(id) } else null + } } - } - val currentAlbum: LiveData = - currentTrack.switchMap { t -> t?.let { albumRepository.findById(t.albumId) } } - private val _currentPosition = MutableLiveData() - val currentPosition: LiveData = _currentPosition.map { (it / 1000).toInt() } - - private val _playing = MutableLiveData().apply { postValue(false) } - val playing: LiveData = _playing - - private val _buffering = MutableLiveData().apply { postValue(false) } - val buffering: LiveData = _buffering - - private val _repeatMode = MutableLiveData() - val repeatMode: LiveData = _repeatMode - - private val _shuffleMode = MutableLiveData() - val shuffleMode: LiveData = _shuffleMode - - private val _queue = MutableLiveData>().apply { postValue(ArrayList()) } - private val _queueIds: LiveData> = - _queue.map { it.map { item -> item.mediaId.toInt() } } - val queue: LiveData>> = - _queueIds.switchMap { q -> - queuePosition.switchMap { qPos -> - trackRepository.findByIds(q).switchMap { tracks -> - albumRepository.findByIds(tracks.map { it.albumId }).map { albums -> - q.mapIndexed { pos, id -> - val track = tracks.find { it.id == id } - val album = albums.find { it.id == track?.albumId } - Triple(qPos == pos + 1, track, album) + val currentAlbum: LiveData = + currentTrack.switchMap { t -> t?.let { albumRepository.findById(t.albumId) } } + private val _currentPosition = MutableLiveData() + val currentPosition: LiveData = _currentPosition.map { (it / 1000).toInt() } + + private val _playing = MutableLiveData().apply { postValue(false) } + val playing: LiveData = _playing + + private val _buffering = MutableLiveData().apply { postValue(false) } + val buffering: LiveData = _buffering + + private val _repeatMode = MutableLiveData() + val repeatMode: LiveData = _repeatMode + + private val _shuffleMode = MutableLiveData() + val shuffleMode: LiveData = _shuffleMode + + private val _queue = MutableLiveData>().apply { postValue(ArrayList()) } + private val _queueIds: LiveData> = + _queue.map { it.map { item -> item.mediaId.toInt() } } + val queue: LiveData>> = + _queueIds.switchMap { q -> + queuePosition.switchMap { qPos -> + trackRepository.findByIds(q).switchMap { tracks -> + albumRepository.findByIds(tracks.map { it.albumId }).map { albums -> + q.mapIndexed { pos, id -> + val track = tracks.find { it.id == id } + val album = albums.find { it.id == track?.albumId } + Triple(qPos == pos + 1, track, album) + } } } } } - } - val _queuePosition: MutableLiveData = MutableLiveData().apply { postValue(0) } - val queueLength: LiveData = _queue.map { it.size } - val queuePosition: LiveData = _queuePosition + val _queuePosition: MutableLiveData = MutableLiveData().apply { postValue(0) } + val queueLength: LiveData = _queue.map { it.size } + val queuePosition: LiveData = _queuePosition - val queuePosStr: LiveData = - _queue.switchMap { q -> queuePosition.map { "$it/${q.size}" } } + val queuePosStr: LiveData = + _queue.switchMap { q -> queuePosition.map { "$it/${q.size}" } } - fun setupController() { - mediaController = mediaControllerFuture.get() + fun setupController() { + mediaController = mediaControllerFuture.get() - val listener = object : Player.Listener { - fun updateQueue() { - currentTrackId.postValue(mediaController.currentMediaItem?.mediaId?.toInt()) - _queuePosition.postValue(mediaController.currentMediaItemIndex + 1) - val list = ArrayList() - for (i in 0 until mediaController.mediaItemCount) { - list.add(mediaController.getMediaItemAt(i)) - } - _queue.postValue(list) - } + val listener = + object : Player.Listener { + fun updateQueue() { + currentTrackId.postValue(mediaController.currentMediaItem?.mediaId?.toInt()) + _queuePosition.postValue(mediaController.currentMediaItemIndex + 1) + val list = ArrayList() + for (i in 0 until mediaController.mediaItemCount) { + list.add(mediaController.getMediaItemAt(i)) + } + _queue.postValue(list) + } - override fun onMediaItemTransition(item: MediaItem?, reason: Int) = updateQueue() + override fun onMediaItemTransition( + item: MediaItem?, + reason: Int, + ) = updateQueue() - override fun onTimelineChanged(timeline: Timeline, reason: Int) = updateQueue() + override fun onTimelineChanged( + timeline: Timeline, + reason: Int, + ) = updateQueue() - override fun onMediaMetadataChanged(metadata: MediaMetadata) = updateQueue() + override fun onMediaMetadataChanged(metadata: MediaMetadata) = updateQueue() - override fun onPlaybackStateChanged(playbackState: Int) { - _buffering.postValue(playbackState == Player.STATE_BUFFERING) - mainScope.launch(Main) { updateCurrentPosition() } - } + override fun onPlaybackStateChanged(playbackState: Int) { + _buffering.postValue(playbackState == Player.STATE_BUFFERING) + mainScope.launch(Main) { updateCurrentPosition() } + } - override fun onIsPlayingChanged(isPlaying: Boolean) { - _playing.postValue(isPlaying) - } + override fun onIsPlayingChanged(isPlaying: Boolean) { + _playing.postValue(isPlaying) + } - override fun onRepeatModeChanged(repeatMode: Int) { - _repeatMode.postValue(repeatMode) - } + override fun onRepeatModeChanged(repeatMode: Int) { + _repeatMode.postValue(repeatMode) + } - override fun onShuffleModeEnabledChanged(shuffle: Boolean) { - _shuffleMode.postValue(shuffle) - } + override fun onShuffleModeEnabledChanged(shuffle: Boolean) { + _shuffleMode.postValue(shuffle) + } + } + + listener.onMediaItemTransition(mediaController.currentMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) + listener.onPlaybackStateChanged(mediaController.playbackState) + listener.onIsPlayingChanged(mediaController.isPlaying) + listener.onRepeatModeChanged(mediaController.repeatMode) + listener.onShuffleModeEnabledChanged(mediaController.shuffleModeEnabled) + + mediaController.addListener(listener) } - listener.onMediaItemTransition(mediaController.currentMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) - listener.onPlaybackStateChanged(mediaController.playbackState) - listener.onIsPlayingChanged(mediaController.isPlaying) - listener.onRepeatModeChanged(mediaController.repeatMode) - listener.onShuffleModeEnabledChanged(mediaController.shuffleModeEnabled) + suspend fun stop() { + mainScope.launch(Main) { + mediaController.stop() + mediaController.clearMediaItems() + } + } - mediaController.addListener(listener) - } + suspend fun play(tracks: List>) { + mainScope.launch(Main) { + mediaController.stop() + mediaController.clearMediaItems() + mediaController.setMediaItems(tracks.map { convertTrack(it.first) }) + mediaController.prepare() + mediaController.play() + } + } - suspend fun stop() { - mainScope.launch(Main) { - mediaController.stop() - mediaController.clearMediaItems() + suspend fun play(album: Album) { + val tracks = trackRepository.getByAlbum(album).map { Pair(it, album) } + play(tracks) } - } - suspend fun play(tracks: List>) { - mainScope.launch(Main) { - mediaController.stop() - mediaController.clearMediaItems() - mediaController.setMediaItems(tracks.map { convertTrack(it.first) }) - mediaController.prepare() - mediaController.play() + suspend fun play(playlist: Playlist) { + val tracks = playlist.toTrackAlbumPairs(trackRepository, albumRepository) + play(tracks) } - } - suspend fun play(album: Album) { - val tracks = trackRepository.getByAlbum(album).map { Pair(it, album) } - play(tracks) - } + suspend fun play(track: Track) { + val album = albumRepository.getById(track.albumId) + album?.let { play(listOf(Pair(track, it))) } + } - suspend fun play(playlist: Playlist) { - val tracks = playlist.toTrackAlbumPairs(trackRepository, albumRepository) - play(tracks) - } + suspend fun addTrackToQueue(track: Track): Unit = addTrackToQueue(track, _queue.value?.size ?: 0) - suspend fun play(track: Track) { - val album = albumRepository.getById(track.albumId) - album?.let { play(listOf(Pair(track, it))) } - } + suspend fun addTracksToQueue(album: Album): Unit = addTracksToQueue(album, _queue.value?.size ?: 0) - suspend fun addTrackToQueue(track: Track): Unit = addTrackToQueue(track, _queue.value?.size ?: 0) - suspend fun addTracksToQueue(album: Album): Unit = addTracksToQueue(album, _queue.value?.size ?: 0) - suspend fun addTracksToQueue(playlist: Playlist): Unit = addTracksToQueue(playlist, _queue.value?.size ?: 0) + suspend fun addTracksToQueue(playlist: Playlist): Unit = addTracksToQueue(playlist, _queue.value?.size ?: 0) - suspend fun addTrackToQueue(track: Track, index: Int) { - val album = albumRepository.getById(track.albumId) - album?.let { addTracksToQueue(listOf(Pair(track, album)), index) } - } + suspend fun addTrackToQueue( + track: Track, + index: Int, + ) { + val album = albumRepository.getById(track.albumId) + album?.let { addTracksToQueue(listOf(Pair(track, album)), index) } + } - suspend fun addTracksToQueue(album: Album, index: Int) { - val tracks = trackRepository.getByAlbum(album).map { Pair(it, album) } - addTracksToQueue(tracks, index) - } + suspend fun addTracksToQueue( + album: Album, + index: Int, + ) { + val tracks = trackRepository.getByAlbum(album).map { Pair(it, album) } + addTracksToQueue(tracks, index) + } - suspend fun addTracksToQueue(playlist: Playlist, index: Int) { - val tracks = playlist.toTrackAlbumPairs(trackRepository, albumRepository) - addTracksToQueue(tracks, index) - } + suspend fun addTracksToQueue( + playlist: Playlist, + index: Int, + ) { + val tracks = playlist.toTrackAlbumPairs(trackRepository, albumRepository) + addTracksToQueue(tracks, index) + } - suspend fun clearQueue() = mainScope.launch(Main) { mediaController.clearMediaItems() } + suspend fun clearQueue() = mainScope.launch(Main) { mediaController.clearMediaItems() } - suspend fun addTracksToQueue(tracks: List>, index: Int) { - mainScope.launch(Main) { - mediaController.addMediaItems(index, tracks.map { convertTrack(it.first) }) + suspend fun addTracksToQueue( + tracks: List>, + index: Int, + ) { + mainScope.launch(Main) { + mediaController.addMediaItems(index, tracks.map { convertTrack(it.first) }) + } } - } - suspend fun previous() = mainScope.launch(Main) { mediaController.seekToPrevious() } + suspend fun previous() = mainScope.launch(Main) { mediaController.seekToPrevious() } - suspend fun pause() = mainScope.launch(Main) { mediaController.pause() } + suspend fun pause() = mainScope.launch(Main) { mediaController.pause() } - suspend fun play() = mainScope.launch(Main) { - mediaController.prepare() - mediaController.play() - } + suspend fun play() = + mainScope.launch(Main) { + mediaController.prepare() + mediaController.play() + } - suspend fun next() = mainScope.launch(Main) { mediaController.seekToNext() } + suspend fun next() = mainScope.launch(Main) { mediaController.seekToNext() } - suspend fun seekTo(time: Int) = mainScope.launch(Main) { mediaController.seekTo(time.toLong() * 1000) } + suspend fun seekTo(time: Int) = mainScope.launch(Main) { mediaController.seekTo(time.toLong() * 1000) } - suspend fun setRepeatMode(repeatMode: Int) = mainScope.launch(Main) { mediaController.setRepeatMode(repeatMode) } + suspend fun setRepeatMode(repeatMode: Int) = mainScope.launch(Main) { mediaController.setRepeatMode(repeatMode) } - suspend fun setShuffleMode(shuffleMode: Boolean) = mainScope.launch(Main) { mediaController.setShuffleModeEnabled(shuffleMode) } + suspend fun setShuffleMode(shuffleMode: Boolean) = mainScope.launch(Main) { mediaController.setShuffleModeEnabled(shuffleMode) } - suspend fun updateCurrentPosition() = mainScope.launch(Main) { - _currentPosition.postValue(mediaController.currentPosition) - } + suspend fun updateCurrentPosition() = + mainScope.launch(Main) { + _currentPosition.postValue(mediaController.currentPosition) + } - suspend fun skipTo(position: Int) = mainScope.launch(Main) { mediaController.seekToDefaultPosition(position) } + suspend fun skipTo(position: Int) = mainScope.launch(Main) { mediaController.seekToDefaultPosition(position) } - suspend fun removeFromQueue(position: Int) = mainScope.launch(Main) { mediaController.removeMediaItem(position) } + suspend fun removeFromQueue(position: Int) = mainScope.launch(Main) { mediaController.removeMediaItem(position) } - private fun convertTrack(track: Track): MediaItem = MediaItem.Builder().setMediaId(track.id.toString()).build() -} + private fun convertTrack(track: Track): MediaItem = MediaItem.Builder().setMediaId(track.id.toString()).build() + } diff --git a/app/src/main/java/me/vanpetegem/accentor/media/MusicService.kt b/app/src/main/java/me/vanpetegem/accentor/media/MusicService.kt index 2296e2da..2dc57e8d 100644 --- a/app/src/main/java/me/vanpetegem/accentor/media/MusicService.kt +++ b/app/src/main/java/me/vanpetegem/accentor/media/MusicService.kt @@ -26,9 +26,6 @@ import androidx.media3.session.MediaSessionService import com.google.common.collect.ImmutableList import com.google.common.util.concurrent.ListenableFuture import dagger.hilt.android.AndroidEntryPoint -import java.io.File -import java.time.Instant -import javax.inject.Inject import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.MainScope import kotlinx.coroutines.guava.future @@ -42,6 +39,9 @@ import me.vanpetegem.accentor.data.preferences.PreferencesDataSource import me.vanpetegem.accentor.data.tracks.TrackRepository import me.vanpetegem.accentor.ui.main.MainActivity import me.vanpetegem.accentor.userAgent +import java.io.File +import java.time.Instant +import javax.inject.Inject @AndroidEntryPoint class MusicService : MediaSessionService() { @@ -61,10 +61,11 @@ class MusicService : MediaSessionService() { private lateinit var mediaSession: MediaSession - private val accentorAudioAttributes = AudioAttributes.Builder() - .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC) - .setUsage(C.USAGE_MEDIA) - .build() + private val accentorAudioAttributes = + AudioAttributes.Builder() + .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC) + .setUsage(C.USAGE_MEDIA) + .build() private val baseDataSourceFactory by lazy { DefaultDataSource.Factory(this@MusicService.application, DefaultHttpDataSource.Factory().setUserAgent(userAgent)) @@ -73,7 +74,7 @@ class MusicService : MediaSessionService() { SimpleCache( File(this@MusicService.application.dataDir, "audio"), LeastRecentlyUsedCacheEvictor(preferencesDataSource.musicCacheSize.value!!), - StandaloneDatabaseProvider(this@MusicService.application) + StandaloneDatabaseProvider(this@MusicService.application), ) } @@ -86,43 +87,52 @@ class MusicService : MediaSessionService() { return CacheDataSource( cache, baseDataSourceFactory.createDataSource(), - (CacheDataSource.FLAG_BLOCK_ON_CACHE or CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR) + (CacheDataSource.FLAG_BLOCK_ON_CACHE or CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR), ) } }, - DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true) - ) + DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true), + ), ) .setWakeMode(C.WAKE_MODE_NETWORK) .setHandleAudioBecomingNoisy(true) .setAudioAttributes(accentorAudioAttributes, true) .build().apply { - addListener(object : Player.Listener { - private var trackId: Int? = null - - override fun onMediaItemTransition(item: MediaItem?, reason: Int) { - trackId = item?.mediaId?.toInt() - } + addListener( + object : Player.Listener { + private var trackId: Int? = null + + override fun onMediaItemTransition( + item: MediaItem?, + reason: Int, + ) { + trackId = item?.mediaId?.toInt() + } - override fun onPositionDiscontinuity(old: Player.PositionInfo, new: Player.PositionInfo, reason: Int) { - if (trackId != null && reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) { - reportPlay() + override fun onPositionDiscontinuity( + old: Player.PositionInfo, + new: Player.PositionInfo, + reason: Int, + ) { + if (trackId != null && reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) { + reportPlay() + } } - } - override fun onPlaybackStateChanged(state: Int) { - if (trackId != null && state == Player.STATE_ENDED) { - reportPlay() - player.pause() - player.seekTo(0, 0) + override fun onPlaybackStateChanged(state: Int) { + if (trackId != null && state == Player.STATE_ENDED) { + reportPlay() + player.pause() + player.seekTo(0, 0) + } } - } - private fun reportPlay() { - val savedTrackId = trackId!! - mainScope.launch(IO) { playRepository.reportPlay(savedTrackId, Instant.now()) } - } - }) + private fun reportPlay() { + val savedTrackId = trackId!! + mainScope.launch(IO) { playRepository.reportPlay(savedTrackId, Instant.now()) } + } + }, + ) } } @@ -132,32 +142,40 @@ class MusicService : MediaSessionService() { val openIntent = Intent(this, MainActivity::class.java) val pendingIntent = PendingIntent.getActivity(this, 0, openIntent, PendingIntent.FLAG_IMMUTABLE) - mediaSession = MediaSession.Builder(baseContext, player) - .setSessionActivity(pendingIntent) - .setCallback(object : MediaSession.Callback { - override fun onAddMediaItems( - session: MediaSession, - controller: MediaSession.ControllerInfo, - mediaItems: List - ): ListenableFuture> { - return mainScope.future(IO) { convertTracks(mediaItems) } - } - }) - .build() + mediaSession = + MediaSession.Builder(baseContext, player) + .setSessionActivity(pendingIntent) + .setCallback( + object : MediaSession.Callback { + override fun onAddMediaItems( + session: MediaSession, + controller: MediaSession.ControllerInfo, + mediaItems: List, + ): ListenableFuture> { + return mainScope.future(IO) { convertTracks(mediaItems) } + } + }, + ) + .build() val notificationBuilder = NotificationBuilder(this, mainScope) - setMediaNotificationProvider(object : MediaNotification.Provider { - override fun createNotification( - session: MediaSession, - customLayout: ImmutableList, - actionFactory: MediaNotification.ActionFactory, - onNotificationChangedCallback: MediaNotification.Provider.Callback - ): MediaNotification = - notificationBuilder.buildNotification(session, actionFactory, onNotificationChangedCallback) - - // Ignore, there are none. - override fun handleCustomCommand(session: MediaSession, action: String, extras: Bundle): Boolean = false - }) + setMediaNotificationProvider( + object : MediaNotification.Provider { + override fun createNotification( + session: MediaSession, + customLayout: ImmutableList, + actionFactory: MediaNotification.ActionFactory, + onNotificationChangedCallback: MediaNotification.Provider.Callback, + ): MediaNotification = notificationBuilder.buildNotification(session, actionFactory, onNotificationChangedCallback) + + // Ignore, there are none. + override fun handleCustomCommand( + session: MediaSession, + action: String, + extras: Bundle, + ): Boolean = false + }, + ) } override fun onGetSession(info: MediaSession.ControllerInfo): MediaSession? = mediaSession @@ -176,22 +194,24 @@ class MusicService : MediaSessionService() { val firstConversion by lazy { codecConversionRepository.getFirst() } val conversion = conversionId?.let { codecConversionRepository.getById(conversionId) } ?: firstConversion val conversionParam = conversion?.let { "&codec_conversion_id=${it.id}" } ?: "" - val mediaUri = "${authenticationDataSource.getServer()}/api/tracks/${track.id}/audio" + - "?secret=${authenticationDataSource.getSecret()}" + - "&device_id=${authenticationDataSource.getDeviceId()}" + - conversionParam - - val metadata = MediaMetadata.Builder() - .setTitle(track.title) - .setArtist(track.stringifyTrackArtists()) - .setAlbumTitle(album.title) - .setAlbumArtist(album.stringifyAlbumArtists().let { if (it.isEmpty()) application.getString(R.string.various_artists) else it }) - .setArtworkUri(album.image500?.let { Uri.parse(it) }) - .setTrackNumber(track.number) - .setReleaseYear(album.release.year) - .setReleaseMonth(album.release.monthValue) - .setReleaseDay(album.release.dayOfMonth) - .build() + val mediaUri = + "${authenticationDataSource.getServer()}/api/tracks/${track.id}/audio" + + "?secret=${authenticationDataSource.getSecret()}" + + "&device_id=${authenticationDataSource.getDeviceId()}" + + conversionParam + + val metadata = + MediaMetadata.Builder() + .setTitle(track.title) + .setArtist(track.stringifyTrackArtists()) + .setAlbumTitle(album.title) + .setAlbumArtist(album.stringifyAlbumArtists().let { if (it.isEmpty()) application.getString(R.string.various_artists) else it }) + .setArtworkUri(album.image500?.let { Uri.parse(it) }) + .setTrackNumber(track.number) + .setReleaseYear(album.release.year) + .setReleaseMonth(album.release.monthValue) + .setReleaseDay(album.release.dayOfMonth) + .build() return MediaItem.Builder() .setMediaId(track.id.toString()) diff --git a/app/src/main/java/me/vanpetegem/accentor/media/NotificationBuilder.kt b/app/src/main/java/me/vanpetegem/accentor/media/NotificationBuilder.kt index 6ea8e7a7..80e1680d 100644 --- a/app/src/main/java/me/vanpetegem/accentor/media/NotificationBuilder.kt +++ b/app/src/main/java/me/vanpetegem/accentor/media/NotificationBuilder.kt @@ -34,7 +34,7 @@ class NotificationBuilder(private val context: Context, private val scope: Corou fun buildNotification( session: MediaSession, actionFactory: MediaNotification.ActionFactory, - onNotificationChangedCallback: MediaNotification.Provider.Callback + onNotificationChangedCallback: MediaNotification.Provider.Callback, ): MediaNotification { if (shouldCreateNowPlayingChannel()) { createNowPlayingChannel() @@ -45,34 +45,39 @@ class NotificationBuilder(private val context: Context, private val scope: Corou val builder = NotificationCompat.Builder(context, NOW_PLAYING_CHANNEL) - val previousAction = actionFactory.createMediaAction( - session, - IconCompat.createWithResource(context, R.drawable.ic_previous), - context.getString(R.string.previous), - Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM - ) - val pauseAction = actionFactory.createMediaAction( - session, - IconCompat.createWithResource(context, R.drawable.ic_pause), - context.getString(R.string.pause), - Player.COMMAND_PLAY_PAUSE - ) - val playAction = actionFactory.createMediaAction( - session, - IconCompat.createWithResource(context, R.drawable.ic_play), - context.getString(R.string.play), - Player.COMMAND_PLAY_PAUSE - ) - val nextAction = actionFactory.createMediaAction( - session, - IconCompat.createWithResource(context, R.drawable.ic_next), - context.getString(R.string.next), - Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM - ) - val stopPendingIntent = actionFactory.createMediaActionPendingIntent( - session, - Player.COMMAND_STOP.toLong() - ) + val previousAction = + actionFactory.createMediaAction( + session, + IconCompat.createWithResource(context, R.drawable.ic_previous), + context.getString(R.string.previous), + Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, + ) + val pauseAction = + actionFactory.createMediaAction( + session, + IconCompat.createWithResource(context, R.drawable.ic_pause), + context.getString(R.string.pause), + Player.COMMAND_PLAY_PAUSE, + ) + val playAction = + actionFactory.createMediaAction( + session, + IconCompat.createWithResource(context, R.drawable.ic_play), + context.getString(R.string.play), + Player.COMMAND_PLAY_PAUSE, + ) + val nextAction = + actionFactory.createMediaAction( + session, + IconCompat.createWithResource(context, R.drawable.ic_next), + context.getString(R.string.next), + Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, + ) + val stopPendingIntent = + actionFactory.createMediaActionPendingIntent( + session, + Player.COMMAND_STOP.toLong(), + ) builder.addAction(previousAction) if (player.isPlaying) { @@ -109,25 +114,25 @@ class NotificationBuilder(private val context: Context, private val scope: Corou return MediaNotification( NOW_PLAYING_NOTIFICATION, - builder.build() + builder.build(), ) } private fun shouldCreateNowPlayingChannel() = !nowPlayingChannelExists() - private fun nowPlayingChannelExists() = - platformNotificationManager.getNotificationChannel(NOW_PLAYING_CHANNEL) != null + private fun nowPlayingChannelExists() = platformNotificationManager.getNotificationChannel(NOW_PLAYING_CHANNEL) != null private fun createNowPlayingChannel() { - val notificationChannel = NotificationChannel( - NOW_PLAYING_CHANNEL, - context.getString(R.string.now_playing), - NotificationManager.IMPORTANCE_LOW - ) - .apply { - description = context.getString(R.string.notification_channel_description) - setShowBadge(false) - } + val notificationChannel = + NotificationChannel( + NOW_PLAYING_CHANNEL, + context.getString(R.string.now_playing), + NotificationManager.IMPORTANCE_LOW, + ) + .apply { + description = context.getString(R.string.notification_channel_description) + setShowBadge(false) + } platformNotificationManager.createNotificationChannel(notificationChannel) } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/AccentorTheme.kt b/app/src/main/java/me/vanpetegem/accentor/ui/AccentorTheme.kt index 22b9f93e..c8f88564 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/AccentorTheme.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/AccentorTheme.kt @@ -70,65 +70,67 @@ val md_theme_dark_inversePrimary = Color(0xFF0061A4) val md_theme_dark_shadow = Color(0xFF000000) val md_theme_dark_surfaceTint = Color(0xFF9ECAFF) -private val LightColors = lightColorScheme( - primary = md_theme_light_primary, - onPrimary = md_theme_light_onPrimary, - primaryContainer = md_theme_light_primaryContainer, - onPrimaryContainer = md_theme_light_onPrimaryContainer, - secondary = md_theme_light_secondary, - onSecondary = md_theme_light_onSecondary, - secondaryContainer = md_theme_light_secondaryContainer, - onSecondaryContainer = md_theme_light_onSecondaryContainer, - tertiary = md_theme_light_tertiary, - onTertiary = md_theme_light_onTertiary, - tertiaryContainer = md_theme_light_tertiaryContainer, - onTertiaryContainer = md_theme_light_onTertiaryContainer, - error = md_theme_light_error, - errorContainer = md_theme_light_errorContainer, - onError = md_theme_light_onError, - onErrorContainer = md_theme_light_onErrorContainer, - background = md_theme_light_background, - onBackground = md_theme_light_onBackground, - surface = md_theme_light_surface, - onSurface = md_theme_light_onSurface, - surfaceVariant = md_theme_light_surfaceVariant, - onSurfaceVariant = md_theme_light_onSurfaceVariant, - outline = md_theme_light_outline, - inverseOnSurface = md_theme_light_inverseOnSurface, - inverseSurface = md_theme_light_inverseSurface, - inversePrimary = md_theme_light_inversePrimary, - surfaceTint = md_theme_light_surfaceTint -) +private val LightColors = + lightColorScheme( + primary = md_theme_light_primary, + onPrimary = md_theme_light_onPrimary, + primaryContainer = md_theme_light_primaryContainer, + onPrimaryContainer = md_theme_light_onPrimaryContainer, + secondary = md_theme_light_secondary, + onSecondary = md_theme_light_onSecondary, + secondaryContainer = md_theme_light_secondaryContainer, + onSecondaryContainer = md_theme_light_onSecondaryContainer, + tertiary = md_theme_light_tertiary, + onTertiary = md_theme_light_onTertiary, + tertiaryContainer = md_theme_light_tertiaryContainer, + onTertiaryContainer = md_theme_light_onTertiaryContainer, + error = md_theme_light_error, + errorContainer = md_theme_light_errorContainer, + onError = md_theme_light_onError, + onErrorContainer = md_theme_light_onErrorContainer, + background = md_theme_light_background, + onBackground = md_theme_light_onBackground, + surface = md_theme_light_surface, + onSurface = md_theme_light_onSurface, + surfaceVariant = md_theme_light_surfaceVariant, + onSurfaceVariant = md_theme_light_onSurfaceVariant, + outline = md_theme_light_outline, + inverseOnSurface = md_theme_light_inverseOnSurface, + inverseSurface = md_theme_light_inverseSurface, + inversePrimary = md_theme_light_inversePrimary, + surfaceTint = md_theme_light_surfaceTint, + ) -private val DarkColors = darkColorScheme( - primary = md_theme_dark_primary, - onPrimary = md_theme_dark_onPrimary, - primaryContainer = md_theme_dark_primaryContainer, - onPrimaryContainer = md_theme_dark_onPrimaryContainer, - secondary = md_theme_dark_secondary, - onSecondary = md_theme_dark_onSecondary, - secondaryContainer = md_theme_dark_secondaryContainer, - onSecondaryContainer = md_theme_dark_onSecondaryContainer, - tertiary = md_theme_dark_tertiary, - onTertiary = md_theme_dark_onTertiary, - tertiaryContainer = md_theme_dark_tertiaryContainer, - onTertiaryContainer = md_theme_dark_onTertiaryContainer, - error = md_theme_dark_error, - errorContainer = md_theme_dark_errorContainer, - onError = md_theme_dark_onError, - onErrorContainer = md_theme_dark_onErrorContainer, - background = md_theme_dark_background, - onBackground = md_theme_dark_onBackground, - surface = md_theme_dark_surface, - onSurface = md_theme_dark_onSurface, - surfaceVariant = md_theme_dark_surfaceVariant, - onSurfaceVariant = md_theme_dark_onSurfaceVariant, - outline = md_theme_dark_outline, - inverseOnSurface = md_theme_dark_inverseOnSurface, - inverseSurface = md_theme_dark_inverseSurface, - inversePrimary = md_theme_dark_inversePrimary, - surfaceTint = md_theme_dark_surfaceTint -) +private val DarkColors = + darkColorScheme( + primary = md_theme_dark_primary, + onPrimary = md_theme_dark_onPrimary, + primaryContainer = md_theme_dark_primaryContainer, + onPrimaryContainer = md_theme_dark_onPrimaryContainer, + secondary = md_theme_dark_secondary, + onSecondary = md_theme_dark_onSecondary, + secondaryContainer = md_theme_dark_secondaryContainer, + onSecondaryContainer = md_theme_dark_onSecondaryContainer, + tertiary = md_theme_dark_tertiary, + onTertiary = md_theme_dark_onTertiary, + tertiaryContainer = md_theme_dark_tertiaryContainer, + onTertiaryContainer = md_theme_dark_onTertiaryContainer, + error = md_theme_dark_error, + errorContainer = md_theme_dark_errorContainer, + onError = md_theme_dark_onError, + onErrorContainer = md_theme_dark_onErrorContainer, + background = md_theme_dark_background, + onBackground = md_theme_dark_onBackground, + surface = md_theme_dark_surface, + onSurface = md_theme_dark_onSurface, + surfaceVariant = md_theme_dark_surfaceVariant, + onSurfaceVariant = md_theme_dark_onSurfaceVariant, + outline = md_theme_dark_outline, + inverseOnSurface = md_theme_dark_inverseOnSurface, + inverseSurface = md_theme_dark_inverseSurface, + inversePrimary = md_theme_dark_inversePrimary, + surfaceTint = md_theme_dark_surfaceTint, + ) @Composable fun AccentorTheme(content: @Composable () -> Unit) { diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumCard.kt b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumCard.kt index fcc190bb..33d8bf1d 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumCard.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumCard.kt @@ -43,7 +43,12 @@ import me.vanpetegem.accentor.data.albums.Album import me.vanpetegem.accentor.ui.player.PlayerViewModel @Composable -public fun AlbumCard(album: Album, navController: NavController, playerViewModel: PlayerViewModel, hideArtist: Int? = null) { +public fun AlbumCard( + album: Album, + navController: NavController, + playerViewModel: PlayerViewModel, + hideArtist: Int? = null, +) { val scope = rememberCoroutineScope() Card(modifier = Modifier.padding(8.dp).clickable { navController.navigate("albums/${album.id}") }) { Column { @@ -53,7 +58,7 @@ public fun AlbumCard(album: Album, navController: NavController, playerViewModel placeholder = painterResource(R.drawable.ic_album), contentDescription = stringResource(R.string.album_image), contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxWidth().aspectRatio(1f) + modifier = Modifier.fillMaxWidth().aspectRatio(1f), ) Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { Column(modifier = Modifier.weight(1f)) { @@ -62,7 +67,7 @@ public fun AlbumCard(album: Album, navController: NavController, playerViewModel maxLines = 1, modifier = Modifier.padding(top = 4.dp, start = 4.dp, end = 4.dp), style = MaterialTheme.typography.bodyLarge, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, ) Text( album.stringifyAlbumArtists().let { @@ -72,7 +77,7 @@ public fun AlbumCard(album: Album, navController: NavController, playerViewModel modifier = Modifier.padding(bottom = 4.dp, start = 4.dp, end = 4.dp), style = MaterialTheme.typography.bodyMedium, color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, ) } var expanded by remember { mutableStateOf(false) } @@ -86,21 +91,21 @@ public fun AlbumCard(album: Album, navController: NavController, playerViewModel expanded = false scope.launch(IO) { playerViewModel.play(album) } }, - text = { Text(stringResource(R.string.play_now)) } + text = { Text(stringResource(R.string.play_now)) }, ) DropdownMenuItem( onClick = { expanded = false scope.launch(IO) { playerViewModel.addTracksToQueue(album, maxOf(0, playerViewModel.queuePosition.value ?: 0)) } }, - text = { Text(stringResource(R.string.play_next)) } + text = { Text(stringResource(R.string.play_next)) }, ) DropdownMenuItem( onClick = { expanded = false scope.launch(IO) { playerViewModel.addTracksToQueue(album) } }, - text = { Text(stringResource(R.string.play_last)) } + text = { Text(stringResource(R.string.play_last)) }, ) for (aa in album.albumArtists.sortedBy { it.order }) { if (aa.artistId != hideArtist) { @@ -109,7 +114,7 @@ public fun AlbumCard(album: Album, navController: NavController, playerViewModel expanded = false navController.navigate("artists/${aa.artistId}") }, - text = { Text(stringResource(R.string.go_to, aa.name)) } + text = { Text(stringResource(R.string.go_to, aa.name)) }, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumGrid.kt b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumGrid.kt index fb092240..47191732 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumGrid.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumGrid.kt @@ -19,7 +19,11 @@ import me.vanpetegem.accentor.ui.player.PlayerViewModel import me.vanpetegem.accentor.ui.util.FastScrollableGrid @Composable -fun AlbumGrid(navController: NavController, playerViewModel: PlayerViewModel, albumsViewModel: AlbumsViewModel = hiltViewModel()) { +fun AlbumGrid( + navController: NavController, + playerViewModel: PlayerViewModel, + albumsViewModel: AlbumsViewModel = hiltViewModel(), +) { val albums by albumsViewModel.filteredAlbums.observeAsState() if (albums != null) { FastScrollableGrid(albums!!, { it.firstCharacter().uppercase() }) { album -> AlbumCard(album, navController, playerViewModel) } @@ -27,7 +31,11 @@ fun AlbumGrid(navController: NavController, playerViewModel: PlayerViewModel, al } @Composable -fun AlbumToolbar(drawerState: DrawerState, mainViewModel: MainViewModel, albumsViewModel: AlbumsViewModel = hiltViewModel()) { +fun AlbumToolbar( + drawerState: DrawerState, + mainViewModel: MainViewModel, + albumsViewModel: AlbumsViewModel = hiltViewModel(), +) { val searching by albumsViewModel.searching.observeAsState() if (searching ?: false) { val query by albumsViewModel.query.observeAsState() @@ -43,7 +51,7 @@ fun AlbumToolbar(drawerState: DrawerState, mainViewModel: MainViewModel, albumsV IconButton(onClick = { albumsViewModel.setSearching(true) }) { Icon(Icons.Filled.Search, contentDescription = stringResource(R.string.search)) } - } + }, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumView.kt b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumView.kt index d417e405..41bda248 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumView.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumView.kt @@ -28,20 +28,20 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import coil.compose.AsyncImage -import java.time.LocalDate -import java.time.format.DateTimeFormatter import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.launch import me.vanpetegem.accentor.R import me.vanpetegem.accentor.ui.player.PlayerViewModel import me.vanpetegem.accentor.ui.tracks.TrackRow +import java.time.LocalDate +import java.time.format.DateTimeFormatter @Composable fun AlbumView( id: Int, navController: NavController, playerViewModel: PlayerViewModel, - albumViewModel: AlbumViewModel = hiltViewModel() + albumViewModel: AlbumViewModel = hiltViewModel(), ) { val scope = rememberCoroutineScope() val albumState by albumViewModel.getAlbum(id).observeAsState() @@ -56,7 +56,7 @@ fun AlbumView( fallback = painterResource(R.drawable.ic_album), placeholder = painterResource(R.drawable.ic_album), contentDescription = stringResource(R.string.album_image), - modifier = Modifier.width(128.dp).aspectRatio(1f) + modifier = Modifier.width(128.dp).aspectRatio(1f), ) Column { Text( @@ -64,7 +64,7 @@ fun AlbumView( style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(start = 8.dp), maxLines = 2, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, ) Text( album.stringifyAlbumArtists().let { if (it.isEmpty()) stringResource(R.string.various_artists) else it }, @@ -72,7 +72,7 @@ fun AlbumView( color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), modifier = Modifier.padding(start = 8.dp), maxLines = 1, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, ) Text( if (album.edition == null) album.release.format() else "${album.release.format()} (${album.edition.format()})", @@ -80,7 +80,7 @@ fun AlbumView( color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), modifier = Modifier.padding(start = 8.dp), maxLines = 1, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, ) Row(modifier = Modifier.padding(8.dp)) { IconButton(onClick = { scope.launch(IO) { playerViewModel.play(album) } }) { @@ -101,7 +101,12 @@ fun AlbumView( } @Composable -fun AlbumViewDropdown(id: Int, navController: NavController, dismiss: (() -> Unit), albumViewModel: AlbumViewModel = hiltViewModel()) { +fun AlbumViewDropdown( + id: Int, + navController: NavController, + dismiss: (() -> Unit), + albumViewModel: AlbumViewModel = hiltViewModel(), +) { val albumState by albumViewModel.getAlbum(id).observeAsState() if (albumState != null) { val album = albumState!! @@ -111,7 +116,7 @@ fun AlbumViewDropdown(id: Int, navController: NavController, dismiss: (() -> Uni dismiss() navController.navigate("artists/${aa.artistId}") }, - text = { Text(stringResource(R.string.go_to, aa.name)) } + text = { Text(stringResource(R.string.go_to, aa.name)) }, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumViewModel.kt b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumViewModel.kt index cae331a8..86bdee49 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumViewModel.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumViewModel.kt @@ -5,25 +5,29 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.map import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import me.vanpetegem.accentor.data.albums.Album import me.vanpetegem.accentor.data.albums.AlbumRepository import me.vanpetegem.accentor.data.tracks.Track import me.vanpetegem.accentor.data.tracks.TrackRepository +import javax.inject.Inject @HiltViewModel -class AlbumViewModel @Inject constructor( - application: Application, - private val albumRepository: AlbumRepository, - private val trackRepository: TrackRepository -) : AndroidViewModel(application) { - fun getAlbum(id: Int): LiveData = albumRepository.allAlbumsById.map { albums -> - albums[id] - } +class AlbumViewModel + @Inject + constructor( + application: Application, + private val albumRepository: AlbumRepository, + private val trackRepository: TrackRepository, + ) : AndroidViewModel(application) { + fun getAlbum(id: Int): LiveData = + albumRepository.allAlbumsById.map { albums -> + albums[id] + } - fun tracksForAlbum(album: Album): LiveData> = trackRepository.findByAlbum(album).map { tracks -> - val copy = tracks.toMutableList() - copy.sortWith({ t1, t2 -> t1.number.compareTo(t2.number) }) - copy + fun tracksForAlbum(album: Album): LiveData> = + trackRepository.findByAlbum(album).map { tracks -> + val copy = tracks.toMutableList() + copy.sortWith({ t1, t2 -> t1.number.compareTo(t2.number) }) + copy + } } -} diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumsViewModel.kt b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumsViewModel.kt index 0f284398..0eed7ef6 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumsViewModel.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumsViewModel.kt @@ -7,34 +7,42 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.map import androidx.lifecycle.switchMap import dagger.hilt.android.lifecycle.HiltViewModel -import java.text.Normalizer -import javax.inject.Inject import me.vanpetegem.accentor.data.albums.Album import me.vanpetegem.accentor.data.albums.AlbumRepository +import java.text.Normalizer +import javax.inject.Inject @HiltViewModel -class AlbumsViewModel @Inject constructor( - application: Application, - private val albumRepository: AlbumRepository -) : AndroidViewModel(application) { - val allAlbums: LiveData> = albumRepository.allAlbums +class AlbumsViewModel + @Inject + constructor( + application: Application, + private val albumRepository: AlbumRepository, + ) : AndroidViewModel(application) { + val allAlbums: LiveData> = albumRepository.allAlbums - private val _searching = MutableLiveData(false) - val searching: LiveData = _searching + private val _searching = MutableLiveData(false) + val searching: LiveData = _searching - private val _query = MutableLiveData("") - val query: LiveData = _query + private val _query = MutableLiveData("") + val query: LiveData = _query - val filteredAlbums: LiveData> = allAlbums.switchMap { albums -> - query.map { query -> - if (query.equals("")) { - albums - } else { - albums.filter { a -> a.normalizedTitle.contains(Normalizer.normalize(query, Normalizer.Form.NFKD), ignoreCase = true) } + val filteredAlbums: LiveData> = + allAlbums.switchMap { albums -> + query.map { query -> + if (query.equals("")) { + albums + } else { + albums.filter { a -> a.normalizedTitle.contains(Normalizer.normalize(query, Normalizer.Form.NFKD), ignoreCase = true) } + } + } } + + fun setSearching(value: Boolean) { + _searching.value = value } - } - fun setSearching(value: Boolean) { _searching.value = value } - fun setQuery(value: String) { _query.value = value } -} + fun setQuery(value: String) { + _query.value = value + } + } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistCard.kt b/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistCard.kt index ecb22d07..4ab7b090 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistCard.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistCard.kt @@ -20,9 +20,12 @@ import me.vanpetegem.accentor.R import me.vanpetegem.accentor.data.artists.Artist @Composable -public fun ArtistCard(navController: NavController, artist: Artist) { +public fun ArtistCard( + navController: NavController, + artist: Artist, +) { Card( - modifier = Modifier.padding(8.dp).clickable { navController.navigate("artists/${artist.id}") } + modifier = Modifier.padding(8.dp).clickable { navController.navigate("artists/${artist.id}") }, ) { Column { AsyncImage( @@ -31,13 +34,13 @@ public fun ArtistCard(navController: NavController, artist: Artist) { placeholder = painterResource(R.drawable.ic_artist), contentDescription = stringResource(R.string.artist_image), modifier = Modifier.fillMaxWidth().aspectRatio(1f), - contentScale = ContentScale.Crop + contentScale = ContentScale.Crop, ) Text( artist.name, maxLines = 1, modifier = Modifier.padding(4.dp), - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistGrid.kt b/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistGrid.kt index f90950f4..acb77485 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistGrid.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistGrid.kt @@ -18,7 +18,10 @@ import me.vanpetegem.accentor.ui.main.SearchToolbar import me.vanpetegem.accentor.ui.util.FastScrollableGrid @Composable -fun ArtistGrid(navController: NavController, artistsViewModel: ArtistsViewModel = hiltViewModel()) { +fun ArtistGrid( + navController: NavController, + artistsViewModel: ArtistsViewModel = hiltViewModel(), +) { val artists by artistsViewModel.filteredArtists.observeAsState() if (artists != null) { FastScrollableGrid(artists!!, { it.firstCharacter().uppercase() }) { artist -> ArtistCard(navController, artist) } @@ -26,7 +29,11 @@ fun ArtistGrid(navController: NavController, artistsViewModel: ArtistsViewModel } @Composable -fun ArtistToolbar(drawerState: DrawerState, mainViewModel: MainViewModel, artistsViewModel: ArtistsViewModel = hiltViewModel()) { +fun ArtistToolbar( + drawerState: DrawerState, + mainViewModel: MainViewModel, + artistsViewModel: ArtistsViewModel = hiltViewModel(), +) { val searching by artistsViewModel.searching.observeAsState() if (searching ?: false) { val query by artistsViewModel.query.observeAsState() @@ -42,7 +49,7 @@ fun ArtistToolbar(drawerState: DrawerState, mainViewModel: MainViewModel, artist IconButton(onClick = { artistsViewModel.setSearching(true) }) { Icon(Icons.Filled.Search, contentDescription = stringResource(R.string.search)) } - } + }, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistView.kt b/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistView.kt index 5ad737cc..29c38a6f 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistView.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistView.kt @@ -31,7 +31,12 @@ import me.vanpetegem.accentor.ui.player.PlayerViewModel import me.vanpetegem.accentor.ui.tracks.TrackRow @Composable -fun ArtistView(id: Int, navController: NavController, playerViewModel: PlayerViewModel, artistViewModel: ArtistViewModel = hiltViewModel()) { +fun ArtistView( + id: Int, + navController: NavController, + playerViewModel: PlayerViewModel, + artistViewModel: ArtistViewModel = hiltViewModel(), +) { val artistState by artistViewModel.getArtist(id).observeAsState() if (artistState != null) { val artist = artistState!! @@ -46,7 +51,7 @@ fun ArtistView(id: Int, navController: NavController, playerViewModel: PlayerVie fallback = painterResource(R.drawable.ic_artist), contentDescription = stringResource(R.string.artist_image), contentScale = ContentScale.Crop, - modifier = Modifier.width(80.dp).aspectRatio(1f).clip(CircleShape) + modifier = Modifier.width(80.dp).aspectRatio(1f).clip(CircleShape), ) Text(artist.name, style = MaterialTheme.typography.headlineLarge, modifier = Modifier.padding(start = 8.dp)) } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistViewModel.kt b/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistViewModel.kt index c6483281..e702c022 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistViewModel.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistViewModel.kt @@ -6,36 +6,41 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.map import androidx.lifecycle.switchMap import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import me.vanpetegem.accentor.data.albums.Album import me.vanpetegem.accentor.data.albums.AlbumRepository import me.vanpetegem.accentor.data.artists.Artist import me.vanpetegem.accentor.data.artists.ArtistRepository import me.vanpetegem.accentor.data.tracks.Track import me.vanpetegem.accentor.data.tracks.TrackRepository +import javax.inject.Inject @HiltViewModel -class ArtistViewModel @Inject constructor( - application: Application, - private val artistRepository: ArtistRepository, - private val albumRepository: AlbumRepository, - private val trackRepository: TrackRepository -) : AndroidViewModel(application) { - fun getArtist(id: Int): LiveData = artistRepository.allArtistsById.map { artists -> - artists[id] - } +class ArtistViewModel + @Inject + constructor( + application: Application, + private val artistRepository: ArtistRepository, + private val albumRepository: AlbumRepository, + private val trackRepository: TrackRepository, + ) : AndroidViewModel(application) { + fun getArtist(id: Int): LiveData = + artistRepository.allArtistsById.map { artists -> + artists[id] + } - fun albumsForArtist(artist: Artist): LiveData> = albumRepository.albumsByReleased.map { albums -> - val result = albums.filter { it.albumArtists.any { it.artistId == artist.id } }.toMutableList() - result.reverse() - result - } + fun albumsForArtist(artist: Artist): LiveData> = + albumRepository.albumsByReleased.map { albums -> + val result = albums.filter { it.albumArtists.any { it.artistId == artist.id } }.toMutableList() + result.reverse() + result + } - fun tracksForArtist(artist: Artist): LiveData> = trackRepository.findByArtist(artist).switchMap { tracks -> - albumRepository.allAlbumsById.map { albums -> - val copy = tracks.toMutableList() - copy.sortWith({ t1, t2 -> t1.compareAlphabetically(t2, albums) }) - copy - } + fun tracksForArtist(artist: Artist): LiveData> = + trackRepository.findByArtist(artist).switchMap { tracks -> + albumRepository.allAlbumsById.map { albums -> + val copy = tracks.toMutableList() + copy.sortWith({ t1, t2 -> t1.compareAlphabetically(t2, albums) }) + copy + } + } } -} diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistsViewModel.kt b/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistsViewModel.kt index 518310b5..c7236718 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistsViewModel.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistsViewModel.kt @@ -7,34 +7,42 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.map import androidx.lifecycle.switchMap import dagger.hilt.android.lifecycle.HiltViewModel -import java.text.Normalizer -import javax.inject.Inject import me.vanpetegem.accentor.data.artists.Artist import me.vanpetegem.accentor.data.artists.ArtistRepository +import java.text.Normalizer +import javax.inject.Inject @HiltViewModel -class ArtistsViewModel @Inject constructor( - application: Application, - private val artistRepository: ArtistRepository -) : AndroidViewModel(application) { - val allArtists: LiveData> = artistRepository.allArtists +class ArtistsViewModel + @Inject + constructor( + application: Application, + private val artistRepository: ArtistRepository, + ) : AndroidViewModel(application) { + val allArtists: LiveData> = artistRepository.allArtists - private val _searching = MutableLiveData(false) - val searching: LiveData = _searching + private val _searching = MutableLiveData(false) + val searching: LiveData = _searching - private val _query = MutableLiveData("") - val query: LiveData = _query + private val _query = MutableLiveData("") + val query: LiveData = _query - val filteredArtists: LiveData> = allArtists.switchMap { artists -> - query.map { query -> - if (query.equals("")) { - artists - } else { - artists.filter { a -> a.normalizedName.contains(Normalizer.normalize(query, Normalizer.Form.NFKD), ignoreCase = true) } + val filteredArtists: LiveData> = + allArtists.switchMap { artists -> + query.map { query -> + if (query.equals("")) { + artists + } else { + artists.filter { a -> a.normalizedName.contains(Normalizer.normalize(query, Normalizer.Form.NFKD), ignoreCase = true) } + } + } } + + fun setSearching(value: Boolean) { + _searching.value = value } - } - fun setSearching(value: Boolean) { _searching.value = value } - fun setQuery(value: String) { _query.value = value } -} + fun setQuery(value: String) { + _query.value = value + } + } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/home/HomeFragment.kt b/app/src/main/java/me/vanpetegem/accentor/ui/home/HomeFragment.kt index a8358a0a..d5ca481e 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/home/HomeFragment.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/home/HomeFragment.kt @@ -23,7 +23,11 @@ import me.vanpetegem.accentor.ui.player.PlayerViewModel import me.vanpetegem.accentor.ui.util.Timer @Composable -fun Home(navController: NavController, playerViewModel: PlayerViewModel, homeViewModel: HomeViewModel = hiltViewModel()) { +fun Home( + navController: NavController, + playerViewModel: PlayerViewModel, + homeViewModel: HomeViewModel = hiltViewModel(), +) { LazyColumn(modifier = Modifier.fillMaxSize()) { item { val albums by homeViewModel.recentlyReleasedAlbums.observeAsState() diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/home/HomeViewModel.kt b/app/src/main/java/me/vanpetegem/accentor/ui/home/HomeViewModel.kt index 48c0111e..9f7c9dc3 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/home/HomeViewModel.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/home/HomeViewModel.kt @@ -5,28 +5,31 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import dagger.hilt.android.lifecycle.HiltViewModel -import java.time.LocalDate -import javax.inject.Inject import me.vanpetegem.accentor.data.albums.AlbumRepository import me.vanpetegem.accentor.data.artists.ArtistRepository +import java.time.LocalDate +import javax.inject.Inject @HiltViewModel -class HomeViewModel @Inject constructor( - application: Application, - private val albumRepository: AlbumRepository, - private val artistRepository: ArtistRepository -) : AndroidViewModel(application) { - private val _currentDay = MutableLiveData(LocalDate.now()) +class HomeViewModel + @Inject + constructor( + application: Application, + private val albumRepository: AlbumRepository, + private val artistRepository: ArtistRepository, + ) : AndroidViewModel(application) { + private val _currentDay = MutableLiveData(LocalDate.now()) + + val recentlyReleasedAlbums = albumRepository.albumsByReleased + val recentlyAddedAlbums = albumRepository.albumsByAdded + val recentlyPlayedAlbums = albumRepository.albumsByPlayed + val randomAlbums = albumRepository.randomAlbums + val recentlyAddedArtists = artistRepository.artistsByAdded + val recentlyPlayedArtists = artistRepository.artistsByPlayed + val randomArtists = artistRepository.randomArtists + val currentDay: LiveData = _currentDay - val recentlyReleasedAlbums = albumRepository.albumsByReleased - val recentlyAddedAlbums = albumRepository.albumsByAdded - val recentlyPlayedAlbums = albumRepository.albumsByPlayed - val randomAlbums = albumRepository.randomAlbums - val recentlyAddedArtists = artistRepository.artistsByAdded - val recentlyPlayedArtists = artistRepository.artistsByPlayed - val randomArtists = artistRepository.randomArtists - val currentDay: LiveData = _currentDay + fun albumsForDay(day: LocalDate) = albumRepository.findByDay(day) - fun albumsForDay(day: LocalDate) = albumRepository.findByDay(day) - fun updateCurrentDay() = _currentDay.postValue(LocalDate.now()) -} + fun updateCurrentDay() = _currentDay.postValue(LocalDate.now()) + } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginActivity.kt b/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginActivity.kt index 8e764d8f..75ac6bab 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginActivity.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginActivity.kt @@ -68,7 +68,7 @@ class LoginActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { - AccentorTheme() { + AccentorTheme { Content() } } @@ -80,7 +80,11 @@ fun Content(loginViewModel: LoginViewModel = viewModel()) { val context = LocalContext.current val scope = rememberCoroutineScope() - suspend fun tryLogin(server: String, username: String, password: String) { + suspend fun tryLogin( + server: String, + username: String, + password: String, + ) { val result: LoginResult = loginViewModel.login(server, username, password) withContext(Main) { if (result.error != null) { @@ -99,7 +103,7 @@ fun Content(loginViewModel: LoginViewModel = viewModel()) { content = { innerPadding -> Column( Modifier.fillMaxSize().padding(innerPadding).verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, ) { var server by rememberSaveable { mutableStateOf("https://") } var username by rememberSaveable { mutableStateOf("") } @@ -112,21 +116,23 @@ fun Content(loginViewModel: LoginViewModel = viewModel()) { server = value loginViewModel.loginDataChanged(server, username, password) }, - modifier = Modifier.semantics { - if (formState?.serverError != null) { - error(context.getString(formState!!.serverError!!)) - } - }.fillMaxWidth().padding(top = 16.dp, start = 16.dp, end = 16.dp), + modifier = + Modifier.semantics { + if (formState?.serverError != null) { + error(context.getString(formState!!.serverError!!)) + } + }.fillMaxWidth().padding(top = 16.dp, start = 16.dp, end = 16.dp), label = { Text(stringResource(R.string.prompt_server)) }, isError = !(formState?.serverError == null), singleLine = true, - keyboardOptions = KeyboardOptions( - autoCorrect = false, - capitalization = KeyboardCapitalization.None, - imeAction = ImeAction.Next, - keyboardType = KeyboardType.Uri - ), - keyboardActions = KeyboardActions(onNext = { usernameFocusRequester.requestFocus() }) + keyboardOptions = + KeyboardOptions( + autoCorrect = false, + capitalization = KeyboardCapitalization.None, + imeAction = ImeAction.Next, + keyboardType = KeyboardType.Uri, + ), + keyboardActions = KeyboardActions(onNext = { usernameFocusRequester.requestFocus() }), ) val passwordFocusRequester = remember { FocusRequester() } OutlinedTextField( @@ -136,17 +142,19 @@ fun Content(loginViewModel: LoginViewModel = viewModel()) { loginViewModel.loginDataChanged(server, username, password) }, label = { Text(stringResource(R.string.prompt_username)) }, - modifier = Modifier.autofill(LocalAutofill.current, LocalAutofillTree.current, listOf(AutofillType.Username)) { - username = it - loginViewModel.loginDataChanged(server, username, password) - }.fillMaxWidth().padding(start = 16.dp, end = 16.dp).focusRequester(usernameFocusRequester), - keyboardOptions = KeyboardOptions( - autoCorrect = false, - capitalization = KeyboardCapitalization.None, - imeAction = ImeAction.Next - ), + modifier = + Modifier.autofill(LocalAutofill.current, LocalAutofillTree.current, listOf(AutofillType.Username)) { + username = it + loginViewModel.loginDataChanged(server, username, password) + }.fillMaxWidth().padding(start = 16.dp, end = 16.dp).focusRequester(usernameFocusRequester), + keyboardOptions = + KeyboardOptions( + autoCorrect = false, + capitalization = KeyboardCapitalization.None, + imeAction = ImeAction.Next, + ), keyboardActions = KeyboardActions(onNext = { passwordFocusRequester.requestFocus() }), - singleLine = true + singleLine = true, ) val keyboardController = LocalSoftwareKeyboardController.current val loading by loginViewModel.loading.observeAsState() @@ -158,21 +166,24 @@ fun Content(loginViewModel: LoginViewModel = viewModel()) { }, label = { Text(stringResource(R.string.prompt_password)) }, singleLine = true, - modifier = Modifier.autofill(LocalAutofill.current, LocalAutofillTree.current, listOf(AutofillType.Password)) { - password = it - loginViewModel.loginDataChanged(server, username, password) - }.fillMaxWidth().padding(start = 16.dp, end = 16.dp).focusRequester(passwordFocusRequester), + modifier = + Modifier.autofill(LocalAutofill.current, LocalAutofillTree.current, listOf(AutofillType.Password)) { + password = it + loginViewModel.loginDataChanged(server, username, password) + }.fillMaxWidth().padding(start = 16.dp, end = 16.dp).focusRequester(passwordFocusRequester), visualTransformation = PasswordVisualTransformation(), - keyboardOptions = KeyboardOptions( - autoCorrect = false, - capitalization = KeyboardCapitalization.None, - keyboardType = KeyboardType.Password, - imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions { - keyboardController?.hide() - scope.launch(IO) { tryLogin(server, username, password) } - } + keyboardOptions = + KeyboardOptions( + autoCorrect = false, + capitalization = KeyboardCapitalization.None, + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done, + ), + keyboardActions = + KeyboardActions { + keyboardController?.hide() + scope.launch(IO) { tryLogin(server, username, password) } + }, ) Button( onClick = { @@ -180,7 +191,7 @@ fun Content(loginViewModel: LoginViewModel = viewModel()) { scope.launch(IO) { tryLogin(server, username, password) } }, enabled = formState?.isDataValid ?: false, - modifier = Modifier.padding(bottom = 16.dp, start = 16.dp, end = 16.dp, top = 8.dp) + modifier = Modifier.padding(bottom = 16.dp, start = 16.dp, end = 16.dp, top = 8.dp), ) { Text(stringResource(R.string.sign_in), style = MaterialTheme.typography.labelLarge) } @@ -188,7 +199,7 @@ fun Content(loginViewModel: LoginViewModel = viewModel()) { CircularProgressIndicator() } } - } + }, ) } @@ -196,7 +207,7 @@ fun Modifier.autofill( autofill: Autofill?, autofillTree: AutofillTree, autofillTypes: List, - onFill: ((String) -> Unit) + onFill: ((String) -> Unit), ): Modifier { val node = AutofillNode(onFill = onFill, autofillTypes = autofillTypes) autofillTree += node diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginFormState.kt b/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginFormState.kt index 838dc7ca..93ec8a61 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginFormState.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginFormState.kt @@ -2,5 +2,5 @@ package me.vanpetegem.accentor.ui.login data class LoginFormState( val serverError: Int? = null, - val isDataValid: Boolean = false + val isDataValid: Boolean = false, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginResult.kt b/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginResult.kt index beede3da..e7e130ac 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginResult.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginResult.kt @@ -1,5 +1,5 @@ package me.vanpetegem.accentor.ui.login data class LoginResult( - val error: Int? = null + val error: Int? = null, ) diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginViewModel.kt b/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginViewModel.kt index 0d61ef9d..eaa0bae6 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginViewModel.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/login/LoginViewModel.kt @@ -5,52 +5,62 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import dagger.hilt.android.lifecycle.HiltViewModel -import java.net.URI -import java.net.URISyntaxException -import javax.inject.Inject import me.vanpetegem.accentor.R import me.vanpetegem.accentor.data.authentication.AuthenticationRepository import me.vanpetegem.accentor.util.Result +import java.net.URI +import java.net.URISyntaxException +import javax.inject.Inject @HiltViewModel -class LoginViewModel @Inject constructor( - application: Application, - private val repository: AuthenticationRepository -) : AndroidViewModel(application) { - private val _loginForm = MutableLiveData() - val loginFormState: LiveData = _loginForm +class LoginViewModel + @Inject + constructor( + application: Application, + private val repository: AuthenticationRepository, + ) : AndroidViewModel(application) { + private val _loginForm = MutableLiveData() + val loginFormState: LiveData = _loginForm - private val _loading = MutableLiveData() - val loading: LiveData = _loading + private val _loading = MutableLiveData() + val loading: LiveData = _loading - suspend fun login(server: String, username: String, password: String): LoginResult { - _loading.postValue(true) - val result = repository.login(server, username, password) - _loading.postValue(false) - return when (result) { - is Result.Success -> LoginResult() - is Result.Error -> { - LoginResult(error = R.string.login_failed) + suspend fun login( + server: String, + username: String, + password: String, + ): LoginResult { + _loading.postValue(true) + val result = repository.login(server, username, password) + _loading.postValue(false) + return when (result) { + is Result.Success -> LoginResult() + is Result.Error -> { + LoginResult(error = R.string.login_failed) + } } } - } - fun loginDataChanged(server: String, username: String, password: String) { - if (!isServerValid(server)) { - _loginForm.value = LoginFormState(serverError = R.string.invalid_server) - } else if (server.equals("") || username.equals("") || password.equals("")) { - _loginForm.value = LoginFormState() - } else { - _loginForm.value = LoginFormState(isDataValid = true) + fun loginDataChanged( + server: String, + username: String, + password: String, + ) { + if (!isServerValid(server)) { + _loginForm.value = LoginFormState(serverError = R.string.invalid_server) + } else if (server.equals("") || username.equals("") || password.equals("")) { + _loginForm.value = LoginFormState() + } else { + _loginForm.value = LoginFormState(isDataValid = true) + } } - } - private fun isServerValid(server: String): Boolean { - return try { - URI(server) - server.startsWith("http", ignoreCase = true) - } catch (e: URISyntaxException) { - false + private fun isServerValid(server: String): Boolean { + return try { + URI(server) + server.startsWith("http", ignoreCase = true) + } catch (e: URISyntaxException) { + false + } } } -} diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/main/MainActivity.kt b/app/src/main/java/me/vanpetegem/accentor/ui/main/MainActivity.kt index c1975f29..c12ea77f 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/main/MainActivity.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/main/MainActivity.kt @@ -95,7 +95,7 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { - AccentorTheme() { + AccentorTheme { Content() } } @@ -103,7 +103,10 @@ class MainActivity : ComponentActivity() { } @Composable -fun Content(mainViewModel: MainViewModel = viewModel(), playerViewModel: PlayerViewModel = viewModel()) { +fun Content( + mainViewModel: MainViewModel = viewModel(), + playerViewModel: PlayerViewModel = viewModel(), +) { val navController = rememberNavController() val loginState by mainViewModel.loginState.observeAsState() @@ -139,7 +142,7 @@ fun Content(mainViewModel: MainViewModel = viewModel(), playerViewModel: PlayerV navController, mainViewModel, playerViewModel, - toolbar = { ArtistToolbar(it, mainViewModel) } + toolbar = { ArtistToolbar(it, mainViewModel) }, ) { ArtistGrid(navController) } } composable("artists/{artistId}", arguments = listOf(navArgument("artistId") { type = NavType.IntType })) { entry -> @@ -150,7 +153,7 @@ fun Content(mainViewModel: MainViewModel = viewModel(), playerViewModel: PlayerV navController, mainViewModel, playerViewModel, - toolbar = { AlbumToolbar(it, mainViewModel) } + toolbar = { AlbumToolbar(it, mainViewModel) }, ) { AlbumGrid(navController, playerViewModel) } } composable("albums/{albumId}", arguments = listOf(navArgument("albumId") { type = NavType.IntType })) { entry -> @@ -164,9 +167,9 @@ fun Content(mainViewModel: MainViewModel = viewModel(), playerViewModel: PlayerV mainViewModel, extraDropdownItems = { AlbumViewDropdown(entry.arguments!!.getInt("albumId"), navController, it) - } + }, ) - } + }, ) { AlbumView(entry.arguments!!.getInt("albumId"), navController, playerViewModel) } @@ -176,7 +179,7 @@ fun Content(mainViewModel: MainViewModel = viewModel(), playerViewModel: PlayerV navController, mainViewModel, playerViewModel, - toolbar = { PlaylistToolbar(it, mainViewModel) } + toolbar = { PlaylistToolbar(it, mainViewModel) }, ) { PlaylistList(navController, playerViewModel) } } composable("playlists/{playlistId}", arguments = listOf(navArgument("playlistId") { type = NavType.IntType })) { entry -> @@ -194,7 +197,7 @@ fun Base( mainViewModel: MainViewModel = viewModel(), playerViewModel: PlayerViewModel = viewModel(), toolbar: @Composable ((DrawerState) -> Unit) = { drawerState -> BaseToolbar(drawerState, mainViewModel) }, - mainContent: @Composable (() -> Unit) + mainContent: @Composable (() -> Unit), ) { val drawerState = rememberDrawerState(DrawerValue.Closed) val scope = rememberCoroutineScope() @@ -233,10 +236,10 @@ fun Base( } } }, - gesturesEnabled = !(isPlayerOpen ?: false) + gesturesEnabled = !(isPlayerOpen ?: false), ) { Scaffold( - topBar = { toolbar(drawerState) } + topBar = { toolbar(drawerState) }, ) { contentPadding -> val isRefreshing by mainViewModel.isRefreshing.observeAsState() val state = rememberPullRefreshState(isRefreshing ?: false, { mainViewModel.refresh() }) @@ -253,7 +256,7 @@ fun BaseToolbar( drawerState: DrawerState, mainViewModel: MainViewModel = viewModel(), extraActions: @Composable (() -> Unit)? = null, - extraDropdownItems: @Composable ((() -> Unit) -> Unit)? = null + extraDropdownItems: @Composable ((() -> Unit) -> Unit)? = null, ) { val scope = rememberCoroutineScope() TopAppBar( @@ -285,30 +288,34 @@ fun BaseToolbar( mainViewModel.refresh() expanded = false }, - text = { Text(stringResource(R.string.action_refresh)) } + text = { Text(stringResource(R.string.action_refresh)) }, ) DropdownMenuItem( onClick = { mainViewModel.logout() expanded = false }, - text = { Text(stringResource(R.string.action_sign_out)) } + text = { Text(stringResource(R.string.action_sign_out)) }, ) } } - } + }, ) } @Composable -fun SearchToolbar(value: String, update: (String) -> Unit, exit: () -> Unit) { +fun SearchToolbar( + value: String, + update: (String) -> Unit, + exit: () -> Unit, +) { val keyboardController = LocalSoftwareKeyboardController.current val focusRequester = remember { FocusRequester() } TopAppBar( navigationIcon = { IconButton( onClick = { exit() }, - modifier = Modifier.padding(start = 8.dp) + modifier = Modifier.padding(start = 8.dp), ) { Icon(Icons.Filled.ArrowBack, contentDescription = stringResource(R.string.stop_searching)) } @@ -321,26 +328,28 @@ fun SearchToolbar(value: String, update: (String) -> Unit, exit: () -> Unit) { placeholder = { Text( stringResource(R.string.search), - color = MaterialTheme.colorScheme.contentColorFor(MaterialTheme.colorScheme.primaryContainer).copy(ContentAlpha.medium) + color = MaterialTheme.colorScheme.contentColorFor(MaterialTheme.colorScheme.primaryContainer).copy(ContentAlpha.medium), ) }, - colors = TextFieldDefaults.colors( - focusedContainerColor = Color.Transparent, - unfocusedContainerColor = Color.Transparent, - disabledContainerColor = Color.Transparent, - cursorColor = LocalContentColor.current.copy(LocalContentAlpha.current), - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent - ), + colors = + TextFieldDefaults.colors( + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + disabledContainerColor = Color.Transparent, + cursorColor = LocalContentColor.current.copy(LocalContentAlpha.current), + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + ), modifier = Modifier.fillMaxSize().focusRequester(focusRequester), - keyboardActions = KeyboardActions( - onDone = { - keyboardController?.hide() - focusRequester.freeFocus() - } - ) + keyboardActions = + KeyboardActions( + onDone = { + keyboardController?.hide() + focusRequester.freeFocus() + }, + ), ) - } + }, ) LaunchedEffect(focusRequester) { focusRequester.requestFocus() @@ -349,12 +358,17 @@ fun SearchToolbar(value: String, update: (String) -> Unit, exit: () -> Unit) { } @Composable -fun DrawerRow(title: String, selected: Boolean, icon: Int, onClick: () -> Unit) { +fun DrawerRow( + title: String, + selected: Boolean, + icon: Int, + onClick: () -> Unit, +) { NavigationDrawerItem( label = { Text(title, modifier = Modifier.padding(16.dp, 8.dp)) }, selected = selected, onClick = onClick, modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding), - icon = { Icon(painterResource(icon), contentDescription = stringResource(R.string.navigation_icon)) } + icon = { Icon(painterResource(icon), contentDescription = stringResource(R.string.navigation_icon)) }, ) } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/main/MainViewModel.kt b/app/src/main/java/me/vanpetegem/accentor/ui/main/MainViewModel.kt index 1a351d99..568c8848 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/main/MainViewModel.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/main/MainViewModel.kt @@ -7,8 +7,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.map import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import java.time.Instant -import javax.inject.Inject import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.launch @@ -25,93 +23,97 @@ import me.vanpetegem.accentor.data.users.User import me.vanpetegem.accentor.data.users.UserRepository import me.vanpetegem.accentor.ui.util.Event import me.vanpetegem.accentor.util.Result +import java.time.Instant +import javax.inject.Inject @HiltViewModel -class MainViewModel @Inject constructor( - application: Application, - private val authenticationRepository: AuthenticationRepository, - private val userRepository: UserRepository, - private val albumRepository: AlbumRepository, - private val artistRepository: ArtistRepository, - private val trackRepository: TrackRepository, - private val codecConversionRepository: CodecConversionRepository, - private val playlistRepository: PlaylistRepository, - private val playRepository: PlayRepository, - private val preferencesDataSource: PreferencesDataSource -) : AndroidViewModel(application) { - private val refreshing = MutableLiveData(0) - val isRefreshing: LiveData = refreshing.map { if (it != null) it > 0 else false } - private var errorSinceLastRefresh: Boolean = false - - private val _latestError = MutableLiveData?>(null) - val latestError: LiveData?> = _latestError - - val currentUser: LiveData = userRepository.currentUser - val loginState: LiveData = authenticationRepository.isLoggedIn - - fun refresh() { - if ((refreshing.value ?: 0) > 0) return - - errorSinceLastRefresh = false - - refreshing.value?.let { refreshing.value = it + 1 } - viewModelScope.launch(IO) { - codecConversionRepository.refresh { decrementRefresh(it) } - } - - refreshing.value?.let { refreshing.value = it + 1 } - viewModelScope.launch(IO) { - userRepository.refresh { decrementRefresh(it) } - } +class MainViewModel + @Inject + constructor( + application: Application, + private val authenticationRepository: AuthenticationRepository, + private val userRepository: UserRepository, + private val albumRepository: AlbumRepository, + private val artistRepository: ArtistRepository, + private val trackRepository: TrackRepository, + private val codecConversionRepository: CodecConversionRepository, + private val playlistRepository: PlaylistRepository, + private val playRepository: PlayRepository, + private val preferencesDataSource: PreferencesDataSource, + ) : AndroidViewModel(application) { + private val refreshing = MutableLiveData(0) + val isRefreshing: LiveData = refreshing.map { if (it != null) it > 0 else false } + private var errorSinceLastRefresh: Boolean = false + + private val _latestError = MutableLiveData?>(null) + val latestError: LiveData?> = _latestError + + val currentUser: LiveData = userRepository.currentUser + val loginState: LiveData = authenticationRepository.isLoggedIn + + fun refresh() { + if ((refreshing.value ?: 0) > 0) return + + errorSinceLastRefresh = false + + refreshing.value?.let { refreshing.value = it + 1 } + viewModelScope.launch(IO) { + codecConversionRepository.refresh { decrementRefresh(it) } + } - refreshing.value?.let { refreshing.value = it + 1 } - viewModelScope.launch(IO) { - trackRepository.refresh { decrementRefresh(it) } - } + refreshing.value?.let { refreshing.value = it + 1 } + viewModelScope.launch(IO) { + userRepository.refresh { decrementRefresh(it) } + } - refreshing.value?.let { refreshing.value = it + 1 } - viewModelScope.launch(IO) { - artistRepository.refresh { decrementRefresh(it) } - } + refreshing.value?.let { refreshing.value = it + 1 } + viewModelScope.launch(IO) { + trackRepository.refresh { decrementRefresh(it) } + } - refreshing.value?.let { refreshing.value = it + 1 } - viewModelScope.launch(IO) { - albumRepository.refresh { decrementRefresh(it) } - } + refreshing.value?.let { refreshing.value = it + 1 } + viewModelScope.launch(IO) { + artistRepository.refresh { decrementRefresh(it) } + } - refreshing.value?.let { refreshing.value = it + 1 } - viewModelScope.launch(IO) { - playRepository.refresh { decrementRefresh(it) } - } + refreshing.value?.let { refreshing.value = it + 1 } + viewModelScope.launch(IO) { + albumRepository.refresh { decrementRefresh(it) } + } - refreshing.value?.let { refreshing.value = it + 1 } - viewModelScope.launch(IO) { - playlistRepository.refresh { decrementRefresh(it) } - } - } + refreshing.value?.let { refreshing.value = it + 1 } + viewModelScope.launch(IO) { + playRepository.refresh { decrementRefresh(it) } + } - suspend fun decrementRefresh(result: Result) { - withContext(Main) { - refreshing.value?.let { refreshing.value = it - 1 } - if (result is Result.Error) { - errorSinceLastRefresh = true - _latestError.value = Event(result.exception.message!!) + refreshing.value?.let { refreshing.value = it + 1 } + viewModelScope.launch(IO) { + playlistRepository.refresh { decrementRefresh(it) } } + } - if (refreshing.value == 0 && !errorSinceLastRefresh) { - withContext(IO) { preferencesDataSource.setLastSyncFinished(Instant.now()) } + suspend fun decrementRefresh(result: Result) { + withContext(Main) { + refreshing.value?.let { refreshing.value = it - 1 } + if (result is Result.Error) { + errorSinceLastRefresh = true + _latestError.value = Event(result.exception.message!!) + } + + if (refreshing.value == 0 && !errorSinceLastRefresh) { + withContext(IO) { preferencesDataSource.setLastSyncFinished(Instant.now()) } + } } } - } - fun logout() { - viewModelScope.launch(IO) { userRepository.clear() } - viewModelScope.launch(IO) { albumRepository.clear() } - viewModelScope.launch(IO) { artistRepository.clear() } - viewModelScope.launch(IO) { trackRepository.clear() } - viewModelScope.launch(IO) { playRepository.clear() } - viewModelScope.launch(IO) { playlistRepository.clear() } - viewModelScope.launch(IO) { codecConversionRepository.clear() } - viewModelScope.launch(IO) { authenticationRepository.logout() } + fun logout() { + viewModelScope.launch(IO) { userRepository.clear() } + viewModelScope.launch(IO) { albumRepository.clear() } + viewModelScope.launch(IO) { artistRepository.clear() } + viewModelScope.launch(IO) { trackRepository.clear() } + viewModelScope.launch(IO) { playRepository.clear() } + viewModelScope.launch(IO) { playlistRepository.clear() } + viewModelScope.launch(IO) { codecConversionRepository.clear() } + viewModelScope.launch(IO) { authenticationRepository.logout() } + } } -} diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/player/BottomBar.kt b/app/src/main/java/me/vanpetegem/accentor/ui/player/BottomBar.kt index 583a3015..3a2ca017 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/player/BottomBar.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/player/BottomBar.kt @@ -43,26 +43,26 @@ fun ControlBar(playerViewModel: PlayerViewModel = viewModel()) { fallback = painterResource(R.drawable.ic_album), contentDescription = stringResource(R.string.album_cover_of_current_track), modifier = Modifier.fillMaxHeight().aspectRatio(1f).background(MaterialTheme.colorScheme.surface), - contentScale = ContentScale.Crop + contentScale = ContentScale.Crop, ) Column(modifier = Modifier.padding(start = 8.dp).weight(1f)) { Text( currentTrack?.title ?: "", style = MaterialTheme.typography.titleLarge, maxLines = 1, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, ) Text( currentTrack?.stringifyTrackArtists() ?: "", maxLines = 1, style = MaterialTheme.typography.titleMedium, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, ) } IconButton( onClick = { scope.launch(IO) { playerViewModel.previous() } - } + }, ) { Icon(painterResource(R.drawable.ic_previous), contentDescription = stringResource(R.string.previous)) } @@ -70,7 +70,7 @@ fun ControlBar(playerViewModel: PlayerViewModel = viewModel()) { IconButton( onClick = { scope.launch(IO) { playerViewModel.pause() } - } + }, ) { Icon(painterResource(R.drawable.ic_pause), contentDescription = stringResource(R.string.pause)) } @@ -78,7 +78,7 @@ fun ControlBar(playerViewModel: PlayerViewModel = viewModel()) { IconButton( onClick = { scope.launch(IO) { playerViewModel.play() } - } + }, ) { Icon(painterResource(R.drawable.ic_play), contentDescription = stringResource(R.string.play)) } @@ -86,7 +86,7 @@ fun ControlBar(playerViewModel: PlayerViewModel = viewModel()) { IconButton( onClick = { scope.launch(IO) { playerViewModel.next() } - } + }, ) { Icon(painterResource(R.drawable.ic_next), contentDescription = stringResource(R.string.next)) } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/player/Controls.kt b/app/src/main/java/me/vanpetegem/accentor/ui/player/Controls.kt index e46aca17..f3b6d8c1 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/player/Controls.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/player/Controls.kt @@ -48,7 +48,7 @@ fun Controls(playerViewModel: PlayerViewModel = viewModel()) { painterResource(R.drawable.ic_repeat_all), contentDescription = stringResource(R.string.repeat_all), modifier = Modifier.height(32.dp).aspectRatio(1f), - tint = MaterialTheme.colorScheme.primary.copy(alpha = LocalContentAlpha.current) + tint = MaterialTheme.colorScheme.primary.copy(alpha = LocalContentAlpha.current), ) } } @@ -58,7 +58,7 @@ fun Controls(playerViewModel: PlayerViewModel = viewModel()) { painterResource(R.drawable.ic_repeat_one), contentDescription = stringResource(R.string.repeat_one), modifier = Modifier.height(32.dp).aspectRatio(1f), - tint = MaterialTheme.colorScheme.primary.copy(alpha = LocalContentAlpha.current) + tint = MaterialTheme.colorScheme.primary.copy(alpha = LocalContentAlpha.current), ) } } @@ -67,7 +67,7 @@ fun Controls(playerViewModel: PlayerViewModel = viewModel()) { Icon( painterResource(R.drawable.ic_repeat_off), contentDescription = stringResource(R.string.repeat_off), - modifier = Modifier.height(32.dp).aspectRatio(1f) + modifier = Modifier.height(32.dp).aspectRatio(1f), ) } } @@ -76,51 +76,48 @@ fun Controls(playerViewModel: PlayerViewModel = viewModel()) { IconButton( onClick = { scope.launch(IO) { playerViewModel.previous() } - } + }, ) { Icon( painterResource(R.drawable.ic_previous), contentDescription = stringResource(R.string.previous), - modifier = Modifier.height(56.dp).aspectRatio(1f) + modifier = Modifier.height(56.dp).aspectRatio(1f), ) } if (isPlaying ?: false) { IconButton( onClick = { scope.launch(IO) { playerViewModel.pause() } - } + }, ) { Icon( painterResource(R.drawable.ic_pause), contentDescription = stringResource(R.string.pause), - - modifier = Modifier.height(56.dp).aspectRatio(1f) + modifier = Modifier.height(56.dp).aspectRatio(1f), ) } } else { IconButton( onClick = { scope.launch(IO) { playerViewModel.play() } - } + }, ) { Icon( painterResource(R.drawable.ic_play), contentDescription = stringResource(R.string.play), - - modifier = Modifier.height(56.dp).aspectRatio(1f) + modifier = Modifier.height(56.dp).aspectRatio(1f), ) } } IconButton( onClick = { scope.launch(IO) { playerViewModel.next() } - } + }, ) { Icon( painterResource(R.drawable.ic_next), contentDescription = stringResource(R.string.next), - - modifier = Modifier.height(56.dp).aspectRatio(1f) + modifier = Modifier.height(56.dp).aspectRatio(1f), ) } Spacer(Modifier.weight(1f)) @@ -130,7 +127,7 @@ fun Controls(playerViewModel: PlayerViewModel = viewModel()) { painterResource(R.drawable.ic_shuffle_all), contentDescription = stringResource(R.string.shuffle_all), modifier = Modifier.height(32.dp).aspectRatio(1f), - tint = MaterialTheme.colorScheme.primary.copy(alpha = LocalContentAlpha.current) + tint = MaterialTheme.colorScheme.primary.copy(alpha = LocalContentAlpha.current), ) } } else { @@ -138,7 +135,7 @@ fun Controls(playerViewModel: PlayerViewModel = viewModel()) { Icon( painterResource(R.drawable.ic_shuffle_none), contentDescription = stringResource(R.string.shuffle_none), - modifier = Modifier.height(32.dp).aspectRatio(1f) + modifier = Modifier.height(32.dp).aspectRatio(1f), ) } } @@ -161,7 +158,7 @@ fun Controls(playerViewModel: PlayerViewModel = viewModel()) { seekPosition = null }, enabled = !buffering, - valueRange = 0f..(trackLength.toFloat()) + valueRange = 0f..(trackLength.toFloat()), ) } Text(currentTrack?.length.formatTrackLength()) diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/player/CurrentTrackInfo.kt b/app/src/main/java/me/vanpetegem/accentor/ui/player/CurrentTrackInfo.kt index 22377dbb..0806ccf3 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/player/CurrentTrackInfo.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/player/CurrentTrackInfo.kt @@ -35,14 +35,14 @@ fun CurrentTrackInfo(playerViewModel: PlayerViewModel = viewModel()) { contentDescription = stringResource(R.string.album_cover_of_current_track), modifier = Modifier.weight(1f).fillMaxWidth().background(MaterialTheme.colorScheme.surface), contentScale = ContentScale.Fit, - alignment = Alignment.TopCenter + alignment = Alignment.TopCenter, ) Text( currentTrack?.title ?: "", maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(horizontal = 8.dp), - style = MaterialTheme.typography.titleLarge + style = MaterialTheme.typography.titleLarge, ) Text( currentTrack?.stringifyTrackArtists() ?: "", @@ -50,7 +50,7 @@ fun CurrentTrackInfo(playerViewModel: PlayerViewModel = viewModel()) { overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(horizontal = 8.dp), style = MaterialTheme.typography.titleMedium, - color = LocalContentColor.current.copy(alpha = ContentAlpha.medium) + color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), ) Text( currentAlbum?.title ?: "", @@ -58,7 +58,7 @@ fun CurrentTrackInfo(playerViewModel: PlayerViewModel = viewModel()) { overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(horizontal = 8.dp), style = MaterialTheme.typography.titleSmall, - color = LocalContentColor.current.copy(alpha = ContentAlpha.medium) + color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/player/PlayerOverlay.kt b/app/src/main/java/me/vanpetegem/accentor/ui/player/PlayerOverlay.kt index 75958611..420c157e 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/player/PlayerOverlay.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/player/PlayerOverlay.kt @@ -40,16 +40,17 @@ import kotlinx.coroutines.launch fun PlayerOverlay( navController: NavController, playerViewModel: PlayerViewModel = viewModel(), - content: @Composable (() -> Unit) + content: @Composable (() -> Unit), ) { val scope = rememberCoroutineScope() var totalHeight by remember { mutableStateOf(null) } var toolbarHeight by remember { mutableStateOf(0) } val height = ((totalHeight ?: 0) - toolbarHeight).toFloat() - val swipeableState = rememberSwipeableState(false) { - playerViewModel.setOpen(it) - true - } + val swipeableState = + rememberSwipeableState(false) { + playerViewModel.setOpen(it) + true + } val anchors = mapOf(0f to true, height to false) val showQueue by playerViewModel.showQueue.observeAsState() val queueLength by playerViewModel.queueLength.observeAsState() @@ -75,24 +76,26 @@ fun PlayerOverlay( } } Column( - modifier = Modifier - .offset { IntOffset(0, swipeableState.offset.value.toInt()) } - .fillMaxSize() + modifier = + Modifier + .offset { IntOffset(0, swipeableState.offset.value.toInt()) } + .fillMaxSize(), ) { Box( - modifier = Modifier - .swipeable( - state = swipeableState, - anchors = anchors, - orientation = Orientation.Vertical, - thresholds = { _, _ -> FixedThreshold(224.dp) } - ) - .onSizeChanged { size -> toolbarHeight = size.height } - .clickable { - scope.launch { - swipeableState.animateTo(!swipeableState.currentValue, SwipeableDefaults.AnimationSpec) - } - } + modifier = + Modifier + .swipeable( + state = swipeableState, + anchors = anchors, + orientation = Orientation.Vertical, + thresholds = { _, _ -> FixedThreshold(224.dp) }, + ) + .onSizeChanged { size -> toolbarHeight = size.height } + .clickable { + scope.launch { + swipeableState.animateTo(!swipeableState.currentValue, SwipeableDefaults.AnimationSpec) + } + }, ) { if (swipeableState.currentValue) { ToolBar(!(isLandscape && !isMultiWindow), closePlayer = closePlayer) diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/player/PlayerViewModel.kt b/app/src/main/java/me/vanpetegem/accentor/ui/player/PlayerViewModel.kt index ecfd7bd0..036d6d41 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/player/PlayerViewModel.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/player/PlayerViewModel.kt @@ -5,61 +5,88 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import me.vanpetegem.accentor.data.albums.Album import me.vanpetegem.accentor.data.playlists.Playlist import me.vanpetegem.accentor.data.tracks.Track import me.vanpetegem.accentor.media.MediaSessionConnection +import javax.inject.Inject @HiltViewModel -class PlayerViewModel @Inject constructor( - application: Application, - private val mediaSessionConnection: MediaSessionConnection -) : AndroidViewModel(application) { - private val _isOpen = MutableLiveData(false) - val isOpen: LiveData = _isOpen - - private val _showQueue = MutableLiveData(false) - val showQueue: LiveData = _showQueue - - val currentTrack = mediaSessionConnection.currentTrack - val currentAlbum = mediaSessionConnection.currentAlbum - val currentPosition = mediaSessionConnection.currentPosition - val playing = mediaSessionConnection.playing - val buffering = mediaSessionConnection.buffering - val repeatMode = mediaSessionConnection.repeatMode - val shuffleMode = mediaSessionConnection.shuffleMode - val queue = mediaSessionConnection.queue - val queueLength = mediaSessionConnection.queueLength - val queuePosition = mediaSessionConnection.queuePosition - val queuePosStr = mediaSessionConnection.queuePosStr - - fun toggleQueue() { - _showQueue.value = !(_showQueue.value ?: false) - } +class PlayerViewModel + @Inject + constructor( + application: Application, + private val mediaSessionConnection: MediaSessionConnection, + ) : AndroidViewModel(application) { + private val _isOpen = MutableLiveData(false) + val isOpen: LiveData = _isOpen - fun setOpen(isOpen: Boolean) { - _isOpen.value = isOpen - } + private val _showQueue = MutableLiveData(false) + val showQueue: LiveData = _showQueue + + val currentTrack = mediaSessionConnection.currentTrack + val currentAlbum = mediaSessionConnection.currentAlbum + val currentPosition = mediaSessionConnection.currentPosition + val playing = mediaSessionConnection.playing + val buffering = mediaSessionConnection.buffering + val repeatMode = mediaSessionConnection.repeatMode + val shuffleMode = mediaSessionConnection.shuffleMode + val queue = mediaSessionConnection.queue + val queueLength = mediaSessionConnection.queueLength + val queuePosition = mediaSessionConnection.queuePosition + val queuePosStr = mediaSessionConnection.queuePosStr + + fun toggleQueue() { + _showQueue.value = !(_showQueue.value ?: false) + } + + fun setOpen(isOpen: Boolean) { + _isOpen.value = isOpen + } + + suspend fun stop() = mediaSessionConnection.stop() + + suspend fun play(album: Album) = mediaSessionConnection.play(album) + + suspend fun play(track: Track) = mediaSessionConnection.play(track) + + suspend fun play(playlist: Playlist) = mediaSessionConnection.play(playlist) + + suspend fun addTrackToQueue(track: Track) = mediaSessionConnection.addTrackToQueue(track) + + suspend fun addTracksToQueue(album: Album) = mediaSessionConnection.addTracksToQueue(album) + + suspend fun addTracksToQueue(playlist: Playlist) = mediaSessionConnection.addTracksToQueue(playlist) - suspend fun stop() = mediaSessionConnection.stop() - suspend fun play(album: Album) = mediaSessionConnection.play(album) - suspend fun play(track: Track) = mediaSessionConnection.play(track) - suspend fun play(playlist: Playlist) = mediaSessionConnection.play(playlist) - suspend fun addTrackToQueue(track: Track) = mediaSessionConnection.addTrackToQueue(track) - suspend fun addTracksToQueue(album: Album) = mediaSessionConnection.addTracksToQueue(album) - suspend fun addTracksToQueue(playlist: Playlist) = mediaSessionConnection.addTracksToQueue(playlist) - suspend fun addTrackToQueue(track: Track, index: Int) = mediaSessionConnection.addTrackToQueue(track, index) - suspend fun addTracksToQueue(album: Album, index: Int) = mediaSessionConnection.addTracksToQueue(album, index) - suspend fun clearQueue() = mediaSessionConnection.clearQueue() - suspend fun previous() = mediaSessionConnection.previous() - suspend fun pause() = mediaSessionConnection.pause() - suspend fun play() = mediaSessionConnection.play() - suspend fun next() = mediaSessionConnection.next() - suspend fun seekTo(time: Int) = mediaSessionConnection.seekTo(time) - suspend fun skipTo(position: Int) = mediaSessionConnection.skipTo(position) - suspend fun removeFromQueue(position: Int) = mediaSessionConnection.removeFromQueue(position) - suspend fun setRepeatMode(repeatMode: Int) = mediaSessionConnection.setRepeatMode(repeatMode) - suspend fun setShuffleMode(shuffleMode: Boolean) = mediaSessionConnection.setShuffleMode(shuffleMode) - suspend fun updateCurrentPosition() = mediaSessionConnection.updateCurrentPosition() -} + suspend fun addTrackToQueue( + track: Track, + index: Int, + ) = mediaSessionConnection.addTrackToQueue(track, index) + + suspend fun addTracksToQueue( + album: Album, + index: Int, + ) = mediaSessionConnection.addTracksToQueue(album, index) + + suspend fun clearQueue() = mediaSessionConnection.clearQueue() + + suspend fun previous() = mediaSessionConnection.previous() + + suspend fun pause() = mediaSessionConnection.pause() + + suspend fun play() = mediaSessionConnection.play() + + suspend fun next() = mediaSessionConnection.next() + + suspend fun seekTo(time: Int) = mediaSessionConnection.seekTo(time) + + suspend fun skipTo(position: Int) = mediaSessionConnection.skipTo(position) + + suspend fun removeFromQueue(position: Int) = mediaSessionConnection.removeFromQueue(position) + + suspend fun setRepeatMode(repeatMode: Int) = mediaSessionConnection.setRepeatMode(repeatMode) + + suspend fun setShuffleMode(shuffleMode: Boolean) = mediaSessionConnection.setShuffleMode(shuffleMode) + + suspend fun updateCurrentPosition() = mediaSessionConnection.updateCurrentPosition() + } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/player/Queue.kt b/app/src/main/java/me/vanpetegem/accentor/ui/player/Queue.kt index dc46090b..ba49b32d 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/player/Queue.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/player/Queue.kt @@ -48,7 +48,11 @@ import me.vanpetegem.accentor.data.tracks.Track import me.vanpetegem.accentor.util.formatTrackLength @Composable -fun Queue(navController: NavController, closePlayer: (() -> Unit), playerViewModel: PlayerViewModel = viewModel()) { +fun Queue( + navController: NavController, + closePlayer: (() -> Unit), + playerViewModel: PlayerViewModel = viewModel(), +) { val queue by playerViewModel.queue.observeAsState() val queuePosition by playerViewModel.queuePosition.observeAsState() val state = rememberLazyListState((queuePosition ?: 1) - 1) @@ -65,41 +69,43 @@ fun QueueItem( navController: NavController, index: Int, item: Triple, - closePlayer: (() -> Unit) + closePlayer: (() -> Unit), ) { if (index != 0) { Divider() } val scope = rememberCoroutineScope() - val dismissState = rememberDismissState { - if (it == DismissValue.DismissedToEnd || it == DismissValue.DismissedToStart) { - scope.launch(IO) { playerViewModel.removeFromQueue(index) } - true - } else { - false + val dismissState = + rememberDismissState { + if (it == DismissValue.DismissedToEnd || it == DismissValue.DismissedToStart) { + scope.launch(IO) { playerViewModel.removeFromQueue(index) } + true + } else { + false + } } - } SwipeToDismiss( state = dismissState, - background = { Surface() {} }, + background = { Surface {} }, dismissContent = { Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .padding(8.dp) - .clickable { - scope.launch(IO) { - playerViewModel.skipTo(index) - playerViewModel.play() - } - } + modifier = + Modifier + .padding(8.dp) + .clickable { + scope.launch(IO) { + playerViewModel.skipTo(index) + playerViewModel.play() + } + }, ) { val track = item.second if (item.first) { Icon( painterResource(R.drawable.ic_play), contentDescription = stringResource(R.string.now_playing), - modifier = Modifier.padding(end = 8.dp) + modifier = Modifier.padding(end = 8.dp), ) } if (track != null) { @@ -108,21 +114,21 @@ fun QueueItem( track.title, maxLines = 1, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, ) Text( track.stringifyTrackArtists(), maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleSmall, - color = LocalContentColor.current.copy(alpha = ContentAlpha.medium) + color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), ) } Text( track.length.formatTrackLength(), maxLines = 1, style = MaterialTheme.typography.bodyMedium, - color = LocalContentColor.current.copy(alpha = ContentAlpha.medium) + color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), ) Box(modifier = Modifier.height(40.dp).aspectRatio(1f).wrapContentSize(Alignment.TopStart)) { var expanded by remember { mutableStateOf(false) } @@ -136,7 +142,7 @@ fun QueueItem( navController.navigate("albums/${track.albumId}") closePlayer() }, - text = { Text(stringResource(R.string.go_to_album)) } + text = { Text(stringResource(R.string.go_to_album)) }, ) for (ta in track.trackArtists.sortedBy { ta -> ta.order }) { DropdownMenuItem( @@ -145,13 +151,13 @@ fun QueueItem( navController.navigate("artists/${ta.artistId}") closePlayer() }, - text = { Text(stringResource(R.string.go_to, ta.name)) } + text = { Text(stringResource(R.string.go_to, ta.name)) }, ) } } } } } - } + }, ) } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/player/ToolBar.kt b/app/src/main/java/me/vanpetegem/accentor/ui/player/ToolBar.kt index 95244e6f..3ff3e25a 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/player/ToolBar.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/player/ToolBar.kt @@ -39,7 +39,7 @@ import me.vanpetegem.accentor.R fun ToolBar( showQueueButton: Boolean, playerViewModel: PlayerViewModel = viewModel(), - closePlayer: (() -> Unit) + closePlayer: (() -> Unit), ) { val scope = rememberCoroutineScope() val queuePosStr by playerViewModel.queuePosStr.observeAsState() @@ -53,13 +53,13 @@ fun ToolBar( stringResource(R.string.now_playing), maxLines = 1, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleLarge + style = MaterialTheme.typography.titleLarge, ) Text( queuePosStr ?: "0/0", maxLines = 1, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, ) } if (showQueueButton) { @@ -78,7 +78,7 @@ fun ToolBar( expanded = false scope.launch(IO) { playerViewModel.clearQueue() } }, - text = { Text(stringResource(R.string.clear_queue)) } + text = { Text(stringResource(R.string.clear_queue)) }, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistList.kt b/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistList.kt index 32c2a266..39c08571 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistList.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistList.kt @@ -35,7 +35,11 @@ import me.vanpetegem.accentor.ui.main.SearchToolbar import me.vanpetegem.accentor.ui.player.PlayerViewModel @Composable -fun PlaylistList(navController: NavController, playerViewModel: PlayerViewModel, playlistsViewModel: PlaylistsViewModel = hiltViewModel()) { +fun PlaylistList( + navController: NavController, + playerViewModel: PlayerViewModel, + playlistsViewModel: PlaylistsViewModel = hiltViewModel(), +) { val playlists by playlistsViewModel.filteredPlaylists.observeAsState() val users by playlistsViewModel.allUsersById.observeAsState() val state = rememberLazyListState() @@ -47,7 +51,11 @@ fun PlaylistList(navController: NavController, playerViewModel: PlayerViewModel, } @Composable -fun PlaylistToolbar(drawerState: DrawerState, mainViewModel: MainViewModel, playlistsViewModel: PlaylistsViewModel = hiltViewModel()) { +fun PlaylistToolbar( + drawerState: DrawerState, + mainViewModel: MainViewModel, + playlistsViewModel: PlaylistsViewModel = hiltViewModel(), +) { val searching by playlistsViewModel.searching.observeAsState() if (searching ?: false) { val query by playlistsViewModel.query.observeAsState() @@ -63,37 +71,44 @@ fun PlaylistToolbar(drawerState: DrawerState, mainViewModel: MainViewModel, play IconButton(onClick = { playlistsViewModel.setSearching(true) }) { Icon(Icons.Filled.Search, contentDescription = stringResource(R.string.search)) } - } + }, ) } } @Composable -fun PlaylistListItem(navController: NavController, playerViewModel: PlayerViewModel, index: Int, playlist: Playlist, user: User?) { +fun PlaylistListItem( + navController: NavController, + playerViewModel: PlayerViewModel, + index: Int, + playlist: Playlist, + user: User?, +) { if (index != 0) { Divider() } - val itemInfo = pluralStringResource( - when (playlist.playlistType) { - PlaylistType.ALBUM -> R.plurals.playlist_albums - PlaylistType.ARTIST -> R.plurals.playlist_artists - PlaylistType.TRACK -> R.plurals.playlist_tracks - }, - playlist.itemIds.size, - playlist.itemIds.size - ) + val itemInfo = + pluralStringResource( + when (playlist.playlistType) { + PlaylistType.ALBUM -> R.plurals.playlist_albums + PlaylistType.ARTIST -> R.plurals.playlist_artists + PlaylistType.TRACK -> R.plurals.playlist_tracks + }, + playlist.itemIds.size, + playlist.itemIds.size, + ) Row( - modifier = Modifier.padding(8.dp).clickable { navController.navigate("playlists/${playlist.id}") } + modifier = Modifier.padding(8.dp).clickable { navController.navigate("playlists/${playlist.id}") }, ) { Column(modifier = Modifier.weight(1f)) { Text( playlist.name, - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, ) Text( (user?.name ?: "") + " · " + itemInfo, style = MaterialTheme.typography.titleSmall, - color = LocalContentColor.current.copy(alpha = ContentAlpha.medium) + color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistView.kt b/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistView.kt index 18b4c38e..c0fef49d 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistView.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistView.kt @@ -50,7 +50,7 @@ fun PlaylistView( id: Int, navController: NavController, playerViewModel: PlayerViewModel, - playlistViewModel: PlaylistViewModel = hiltViewModel() + playlistViewModel: PlaylistViewModel = hiltViewModel(), ) { val scope = rememberCoroutineScope() val users by playlistViewModel.allUsersById.observeAsState() @@ -58,25 +58,26 @@ fun PlaylistView( if (playlistState != null) { val playlist = playlistState!! val user = users!!.get(playlist.userId) - val itemInfo = pluralStringResource( - when (playlist.playlistType) { - PlaylistType.ALBUM -> R.plurals.playlist_albums - PlaylistType.ARTIST -> R.plurals.playlist_artists - PlaylistType.TRACK -> R.plurals.playlist_tracks - }, - playlist.itemIds.size, - playlist.itemIds.size - ) - Column() { + val itemInfo = + pluralStringResource( + when (playlist.playlistType) { + PlaylistType.ALBUM -> R.plurals.playlist_albums + PlaylistType.ARTIST -> R.plurals.playlist_artists + PlaylistType.TRACK -> R.plurals.playlist_tracks + }, + playlist.itemIds.size, + playlist.itemIds.size, + ) + Column { Column(modifier = Modifier.fillMaxWidth().padding(8.dp)) { Text( playlist.name, - style = MaterialTheme.typography.headlineLarge + style = MaterialTheme.typography.headlineLarge, ) Text( (user?.name ?: "") + " · " + itemInfo, style = MaterialTheme.typography.headlineMedium, - color = LocalContentColor.current.copy(alpha = ContentAlpha.medium) + color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), ) Row(modifier = Modifier.padding(8.dp)) { IconButton(onClick = { scope.launch(IO) { playerViewModel.play(playlist) } }) { @@ -101,7 +102,7 @@ fun PlaylistAlbumContent( navController: NavController, playerViewModel: PlayerViewModel, playlistViewModel: PlaylistViewModel, - playlist: Playlist + playlist: Playlist, ) { val albums by playlistViewModel.allAlbumsById.observeAsState() var boxSize by remember { mutableStateOf(IntSize.Zero) } @@ -111,7 +112,7 @@ fun PlaylistAlbumContent( LazyVerticalGrid( columns = if (cardsPerRow >= 2) GridCells.Adaptive(minSize = 192.dp) else GridCells.Fixed(2), state = gridState, - modifier = Modifier.onGloballyPositioned { boxSize = it.size } + modifier = Modifier.onGloballyPositioned { boxSize = it.size }, ) { items(playlist.itemIds.size) { i -> val album = albums!![playlist.itemIds[i]] @@ -129,7 +130,7 @@ fun PlaylistAlbumContent( fun PlaylistArtistContent( navController: NavController, playlistViewModel: PlaylistViewModel, - playlist: Playlist + playlist: Playlist, ) { val artists by playlistViewModel.allArtistsById.observeAsState() var boxSize by remember { mutableStateOf(IntSize.Zero) } @@ -139,7 +140,7 @@ fun PlaylistArtistContent( LazyVerticalGrid( columns = if (cardsPerRow >= 2) GridCells.Adaptive(minSize = 192.dp) else GridCells.Fixed(2), state = gridState, - modifier = Modifier.onGloballyPositioned { boxSize = it.size } + modifier = Modifier.onGloballyPositioned { boxSize = it.size }, ) { items(playlist.itemIds.size) { i -> val artist = artists!![playlist.itemIds[i]] @@ -158,11 +159,11 @@ fun PlaylistTrackContent( navController: NavController, playerViewModel: PlayerViewModel, playlistViewModel: PlaylistViewModel, - playlist: Playlist + playlist: Playlist, ) { val tracks by playlistViewModel.getTracksForPlaylist(playlist).observeAsState() if (tracks != null) { - LazyColumn() { + LazyColumn { items(playlist.itemIds.size) { i -> val track = tracks!![playlist.itemIds[i]] if (track != null) { diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistViewModel.kt b/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistViewModel.kt index af111efe..ac8cbe3f 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistViewModel.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistViewModel.kt @@ -6,7 +6,6 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.map import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import me.vanpetegem.accentor.data.albums.Album import me.vanpetegem.accentor.data.albums.AlbumRepository import me.vanpetegem.accentor.data.artists.Artist @@ -17,25 +16,29 @@ import me.vanpetegem.accentor.data.tracks.Track import me.vanpetegem.accentor.data.tracks.TrackRepository import me.vanpetegem.accentor.data.users.User import me.vanpetegem.accentor.data.users.UserRepository +import javax.inject.Inject @HiltViewModel -class PlaylistViewModel @Inject constructor( - application: Application, - private val playlistRepository: PlaylistRepository, - private val userRepository: UserRepository, - private val albumRepository: AlbumRepository, - private val artistRepository: ArtistRepository, - private val trackRepository: TrackRepository -) : AndroidViewModel(application) { - val allUsersById: LiveData> = userRepository.allUsersById - val allAlbumsById: LiveData> = albumRepository.allAlbumsById - val allArtistsById: LiveData> = artistRepository.allArtistsById +class PlaylistViewModel + @Inject + constructor( + application: Application, + private val playlistRepository: PlaylistRepository, + private val userRepository: UserRepository, + private val albumRepository: AlbumRepository, + private val artistRepository: ArtistRepository, + private val trackRepository: TrackRepository, + ) : AndroidViewModel(application) { + val allUsersById: LiveData> = userRepository.allUsersById + val allAlbumsById: LiveData> = albumRepository.allAlbumsById + val allArtistsById: LiveData> = artistRepository.allArtistsById - fun getPlaylist(id: Int): LiveData = playlistRepository.allPlaylistsById.map { playlists -> playlists[id] } + fun getPlaylist(id: Int): LiveData = playlistRepository.allPlaylistsById.map { playlists -> playlists[id] } - fun getTracksForPlaylist(playlist: Playlist): LiveData> = trackRepository.findByIds(playlist.itemIds).map { - val map = SparseArray() - it.forEach { t -> map.put(t.id, t) } - map + fun getTracksForPlaylist(playlist: Playlist): LiveData> = + trackRepository.findByIds(playlist.itemIds).map { + val map = SparseArray() + it.forEach { t -> map.put(t.id, t) } + map + } } -} diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistsViewModel.kt b/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistsViewModel.kt index 8d9a876d..95ea8bae 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistsViewModel.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/playlists/PlaylistsViewModel.kt @@ -8,38 +8,46 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.map import androidx.lifecycle.switchMap import dagger.hilt.android.lifecycle.HiltViewModel -import java.text.Normalizer -import javax.inject.Inject import me.vanpetegem.accentor.data.playlists.Playlist import me.vanpetegem.accentor.data.playlists.PlaylistRepository import me.vanpetegem.accentor.data.users.User import me.vanpetegem.accentor.data.users.UserRepository +import java.text.Normalizer +import javax.inject.Inject @HiltViewModel -class PlaylistsViewModel @Inject constructor( - application: Application, - private val playlistRepository: PlaylistRepository, - private val userRepository: UserRepository -) : AndroidViewModel(application) { - val allPlaylists: LiveData> = playlistRepository.allPlaylists - val allUsersById: LiveData> = userRepository.allUsersById +class PlaylistsViewModel + @Inject + constructor( + application: Application, + private val playlistRepository: PlaylistRepository, + private val userRepository: UserRepository, + ) : AndroidViewModel(application) { + val allPlaylists: LiveData> = playlistRepository.allPlaylists + val allUsersById: LiveData> = userRepository.allUsersById - private val _searching = MutableLiveData(false) - val searching: LiveData = _searching + private val _searching = MutableLiveData(false) + val searching: LiveData = _searching - private val _query = MutableLiveData("") - val query: LiveData = _query + private val _query = MutableLiveData("") + val query: LiveData = _query - val filteredPlaylists: LiveData> = allPlaylists.switchMap { playlists -> - query.map { query -> - if (query.equals("")) { - playlists - } else { - playlists.filter { p -> p.name.contains(Normalizer.normalize(query, Normalizer.Form.NFKD), ignoreCase = true) } + val filteredPlaylists: LiveData> = + allPlaylists.switchMap { playlists -> + query.map { query -> + if (query.equals("")) { + playlists + } else { + playlists.filter { p -> p.name.contains(Normalizer.normalize(query, Normalizer.Form.NFKD), ignoreCase = true) } + } + } } + + fun setSearching(value: Boolean) { + _searching.value = value } - } - fun setSearching(value: Boolean) { _searching.value = value } - fun setQuery(value: String) { _query.value = value } -} + fun setQuery(value: String) { + _query.value = value + } + } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/preferences/PreferencesActivity.kt b/app/src/main/java/me/vanpetegem/accentor/ui/preferences/PreferencesActivity.kt index c8bf0da8..7f5dc8d5 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/preferences/PreferencesActivity.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/preferences/PreferencesActivity.kt @@ -49,7 +49,7 @@ class PreferencesActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { - AccentorTheme() { + AccentorTheme { Content() } } @@ -67,9 +67,9 @@ fun Content(preferencesViewModel: PreferencesViewModel = viewModel()) { IconButton(onClick = { (context as Activity).finish() }) { Icon(Icons.Filled.ArrowBack, contentDescription = stringResource(R.string.close_preferences)) } - } + }, ) - } + }, ) { innerPadding -> val currentUser by preferencesViewModel.currentUser.observeAsState() val server by preferencesViewModel.server.observeAsState() @@ -78,9 +78,10 @@ fun Content(preferencesViewModel: PreferencesViewModel = viewModel()) { val musicCacheSize by preferencesViewModel.musicCacheSize.observeAsState() val conversion by preferencesViewModel.conversion.observeAsState() val possibleConversions by preferencesViewModel.possibleConversions.observeAsState() - val formattedTime = lastSyncFinished?.let { - DateUtils.formatDateTime(context, it.toEpochMilli(), DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE) - } ?: stringResource(R.string.not_finished_yet) + val formattedTime = + lastSyncFinished?.let { + DateUtils.formatDateTime(context, it.toEpochMilli(), DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE) + } ?: stringResource(R.string.not_finished_yet) Column(modifier = Modifier.padding(innerPadding)) { Setting(stringResource(R.string.logged_in_as, "${currentUser?.name}"), server!!) Setting(stringResource(R.string.last_sync_finished), formattedTime) @@ -96,7 +97,7 @@ fun Content(preferencesViewModel: PreferencesViewModel = viewModel()) { stringResource(R.string.change_music_cache_size), musicCacheSizeValid, { preferencesViewModel.setMusicCacheSize(newMusicCacheValue.toLong() * 1024L * 1024L) }, - { musicCacheOpen = false } + { musicCacheOpen = false }, ) { Column { Text(stringResource(R.string.music_cache_explanation), modifier = Modifier.padding(bottom = 16.dp)) @@ -115,7 +116,7 @@ fun Content(preferencesViewModel: PreferencesViewModel = viewModel()) { stringResource(R.string.change_image_cache_size), imageCacheSizeValid, { preferencesViewModel.setImageCacheSize(newImageCacheValue.toLong() * 1024L * 1024L) }, - { imageCacheOpen = false } + { imageCacheOpen = false }, ) { Column { Text(stringResource(R.string.image_cache_explanation), modifier = Modifier.padding(bottom = 16.dp)) @@ -136,7 +137,7 @@ fun Content(preferencesViewModel: PreferencesViewModel = viewModel()) { preferencesViewModel.setConversionId(pConversion.id) conversionsExpanded = false }, - text = { Text(pConversion.name) } + text = { Text(pConversion.name) }, ) } } @@ -154,12 +155,16 @@ fun Header(text: String) { text, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall, - modifier = Modifier.padding(start = 8.dp, top = 16.dp) + modifier = Modifier.padding(start = 8.dp, top = 16.dp), ) } @Composable -fun Setting(text: String, subtext: String? = null, onClick: (() -> Unit)? = null) { +fun Setting( + text: String, + subtext: String? = null, + onClick: (() -> Unit)? = null, +) { var modifier = Modifier.fillMaxWidth() if (onClick != null) { modifier = modifier.clickable(onClick = onClick) @@ -171,7 +176,7 @@ fun Setting(text: String, subtext: String? = null, onClick: (() -> Unit)? = null subtext, modifier = Modifier.padding(bottom = 8.dp, start = 8.dp), style = MaterialTheme.typography.bodyMedium, - color = LocalContentColor.current.copy(alpha = ContentAlpha.medium) + color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), ) } } @@ -184,7 +189,7 @@ fun SettingDialog( canSave: Boolean, save: (() -> Unit), dismiss: (() -> Unit), - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { if (opened) { AlertDialog( @@ -195,8 +200,11 @@ fun SettingDialog( TextButton(onClick = dismiss) { Text(stringResource(R.string.cancel)) } }, confirmButton = { - TextButton(onClick = { save(); dismiss() }, enabled = canSave) { Text(stringResource(R.string.save)) } - } + TextButton(onClick = { + save() + dismiss() + }, enabled = canSave) { Text(stringResource(R.string.save)) } + }, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/preferences/PreferencesViewModel.kt b/app/src/main/java/me/vanpetegem/accentor/ui/preferences/PreferencesViewModel.kt index f5843045..583663bb 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/preferences/PreferencesViewModel.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/preferences/PreferencesViewModel.kt @@ -6,38 +6,43 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.map import androidx.lifecycle.switchMap import dagger.hilt.android.lifecycle.HiltViewModel -import java.time.Instant -import javax.inject.Inject import me.vanpetegem.accentor.data.authentication.AuthenticationRepository import me.vanpetegem.accentor.data.codecconversions.CodecConversion import me.vanpetegem.accentor.data.codecconversions.CodecConversionRepository import me.vanpetegem.accentor.data.preferences.PreferencesDataSource import me.vanpetegem.accentor.data.users.User import me.vanpetegem.accentor.data.users.UserRepository +import java.time.Instant +import javax.inject.Inject @HiltViewModel -class PreferencesViewModel @Inject constructor( - application: Application, - private val preferencesDataSource: PreferencesDataSource, - private val userRepository: UserRepository, - private val authenticationRepository: AuthenticationRepository, - private val codecConversionRepository: CodecConversionRepository -) : AndroidViewModel(application) { - private val conversionId: LiveData = preferencesDataSource.conversionId +class PreferencesViewModel + @Inject + constructor( + application: Application, + private val preferencesDataSource: PreferencesDataSource, + private val userRepository: UserRepository, + private val authenticationRepository: AuthenticationRepository, + private val codecConversionRepository: CodecConversionRepository, + ) : AndroidViewModel(application) { + private val conversionId: LiveData = preferencesDataSource.conversionId - val currentUser: LiveData = userRepository.currentUser - val server: LiveData = authenticationRepository.server - val imageCacheSize: LiveData = preferencesDataSource.imageCacheSize - val musicCacheSize: LiveData = preferencesDataSource.musicCacheSize - val lastSyncFinished: LiveData = preferencesDataSource.lastSyncFinished - val conversion: LiveData = codecConversionRepository.allCodecConversionsById.switchMap { ccsMap -> - codecConversionRepository.allCodecConversions.switchMap { ccs -> - conversionId.map { it?.let { ccsMap[it] } ?: ccs.firstOrNull() } - } - } - val possibleConversions = codecConversionRepository.allCodecConversions + val currentUser: LiveData = userRepository.currentUser + val server: LiveData = authenticationRepository.server + val imageCacheSize: LiveData = preferencesDataSource.imageCacheSize + val musicCacheSize: LiveData = preferencesDataSource.musicCacheSize + val lastSyncFinished: LiveData = preferencesDataSource.lastSyncFinished + val conversion: LiveData = + codecConversionRepository.allCodecConversionsById.switchMap { ccsMap -> + codecConversionRepository.allCodecConversions.switchMap { ccs -> + conversionId.map { it?.let { ccsMap[it] } ?: ccs.firstOrNull() } + } + } + val possibleConversions = codecConversionRepository.allCodecConversions + + fun setMusicCacheSize(newSize: Long) = preferencesDataSource.setMusicCacheSize(newSize) - fun setMusicCacheSize(newSize: Long) = preferencesDataSource.setMusicCacheSize(newSize) - fun setImageCacheSize(newSize: Long) = preferencesDataSource.setImageCacheSize(newSize) - fun setConversionId(newId: Int) = preferencesDataSource.setConversionId(newId) -} + fun setImageCacheSize(newSize: Long) = preferencesDataSource.setImageCacheSize(newSize) + + fun setConversionId(newId: Int) = preferencesDataSource.setConversionId(newId) + } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/tracks/TrackRow.kt b/app/src/main/java/me/vanpetegem/accentor/ui/tracks/TrackRow.kt index d3bb71b7..0568f1d2 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/tracks/TrackRow.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/tracks/TrackRow.kt @@ -44,27 +44,28 @@ fun TrackRow( navController: NavController, playerViewModel: PlayerViewModel, hideAlbum: Boolean = false, - hideArtist: Int? = null + hideArtist: Int? = null, ) { val scope = rememberCoroutineScope() Row( - modifier = Modifier.fillMaxWidth().padding(8.dp).clickable { - scope.launch(IO) { playerViewModel.play(track) } - } + modifier = + Modifier.fillMaxWidth().padding(8.dp).clickable { + scope.launch(IO) { playerViewModel.play(track) } + }, ) { Column(modifier = Modifier.weight(1f)) { Text( track.title, maxLines = 1, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, ) Text( track.stringifyTrackArtists(), maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleSmall, - color = LocalContentColor.current.copy(alpha = ContentAlpha.medium) + color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), ) } var expanded by remember { mutableStateOf(false) } @@ -78,14 +79,14 @@ fun TrackRow( expanded = false scope.launch(IO) { playerViewModel.addTrackToQueue(track, maxOf(0, playerViewModel.queuePosition.value ?: 0)) } }, - text = { Text(stringResource(R.string.play_next)) } + text = { Text(stringResource(R.string.play_next)) }, ) DropdownMenuItem( onClick = { expanded = false scope.launch(IO) { playerViewModel.addTrackToQueue(track) } }, - text = { Text(stringResource(R.string.play_last)) } + text = { Text(stringResource(R.string.play_last)) }, ) if (!hideAlbum) { DropdownMenuItem( @@ -93,7 +94,7 @@ fun TrackRow( expanded = false navController.navigate("albums/${track.albumId}") }, - text = { Text(stringResource(R.string.go_to_album)) } + text = { Text(stringResource(R.string.go_to_album)) }, ) } val used = HashSet>() @@ -105,7 +106,7 @@ fun TrackRow( expanded = false navController.navigate("artists/${ta.artistId}") }, - text = { Text(stringResource(R.string.go_to, ta.name)) } + text = { Text(stringResource(R.string.go_to, ta.name)) }, ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/util/Event.kt b/app/src/main/java/me/vanpetegem/accentor/ui/util/Event.kt index c3c90ece..aa908064 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/util/Event.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/util/Event.kt @@ -1,7 +1,6 @@ package me.vanpetegem.accentor.ui.util open class Event(private val content: T) { - var handled = false private set diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/util/FastScrollableGrid.kt b/app/src/main/java/me/vanpetegem/accentor/ui/util/FastScrollableGrid.kt index ada8b40c..a080dfa6 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/util/FastScrollableGrid.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/util/FastScrollableGrid.kt @@ -45,14 +45,14 @@ fun ScrollBar( state: LazyGridState, width: Dp = 8.dp, minimumHeight: Dp = 48.dp, - getSectionName: ((Int) -> String) + getSectionName: ((Int) -> String), ) { var dragging by remember { mutableStateOf(false) } val targetAlpha = if (state.isScrollInProgress || dragging) 1f else 0f val duration = if (state.isScrollInProgress || dragging) 150 else 500 val alpha by animateFloatAsState( targetValue = targetAlpha, - animationSpec = tween(duration) + animationSpec = tween(duration), ) val color = MaterialTheme.colorScheme.secondary val coroutineScope = rememberCoroutineScope() @@ -77,7 +77,7 @@ fun ScrollBar( Surface( modifier = Modifier.height(minimumHeight).width(minimumHeight).offset(-width * 2, topDistance), shape = RoundedCornerShape(50, 50, 0, 50), - color = color + color = color, ) { Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { Text(sectionName, style = MaterialTheme.typography.titleLarge) @@ -86,18 +86,20 @@ fun ScrollBar( } Canvas( - modifier = Modifier.fillMaxHeight().width(width * 2).draggable( - orientation = Orientation.Vertical, - state = rememberDraggableState { delta -> - val percentage = delta / boxHeight - val newPosition = maxOf(0f, currentPosition + percentage * totalHeight) - val newIndex = (newPosition / itemHeight).toInt() - val newOffset = (newPosition - (newIndex * itemHeight)).toInt() - coroutineScope.launch { state.scrollToItem(newIndex, newOffset) } - }, - onDragStarted = { _ -> dragging = true }, - onDragStopped = { _ -> dragging = false } - ) + modifier = + Modifier.fillMaxHeight().width(width * 2).draggable( + orientation = Orientation.Vertical, + state = + rememberDraggableState { delta -> + val percentage = delta / boxHeight + val newPosition = maxOf(0f, currentPosition + percentage * totalHeight) + val newIndex = (newPosition / itemHeight).toInt() + val newOffset = (newPosition - (newIndex * itemHeight)).toInt() + coroutineScope.launch { state.scrollToItem(newIndex, newOffset) } + }, + onDragStarted = { _ -> dragging = true }, + onDragStopped = { _ -> dragging = false }, + ), ) { val scrollbarHeight = maxOf(boxHeight * (boxHeight.toFloat() / totalHeight), minimumHeight.toPx()) val scrollbarDiff = maxOf(0f, scrollbarHeight - boxHeight * (boxHeight.toFloat() / totalHeight)) @@ -109,14 +111,18 @@ fun ScrollBar( cornerRadius = CornerRadius(width.toPx() / 2, width.toPx() / 2), topLeft = Offset(if (layoutDirection == LayoutDirection.Ltr) width.toPx() else 0.0f, scrollbarOffsetY), size = Size(width.toPx(), scrollbarHeight), - alpha = alpha + alpha = alpha, ) } } } @Composable -fun FastScrollableGrid(gridItems: List, getSectionName: (T) -> String, itemView: @Composable (T) -> Unit) { +fun FastScrollableGrid( + gridItems: List, + getSectionName: (T) -> String, + itemView: @Composable (T) -> Unit, +) { val gridState = rememberLazyGridState() var boxSize by remember { mutableStateOf(IntSize.Zero) } val cardsPerRow: Int = with(LocalDensity.current) { boxSize.width / 192.dp.toPx().toInt() } @@ -124,7 +130,7 @@ fun FastScrollableGrid(gridItems: List, getSectionName: (T) -> String, it LazyVerticalGrid( columns = if (cardsPerRow >= 2) GridCells.Adaptive(minSize = 192.dp) else GridCells.Fixed(2), state = gridState, - modifier = Modifier.onGloballyPositioned { boxSize = it.size } + modifier = Modifier.onGloballyPositioned { boxSize = it.size }, ) { items(gridItems.size) { i -> itemView(gridItems[i]) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/util/Timer.kt b/app/src/main/java/me/vanpetegem/accentor/ui/util/Timer.kt index 77c02685..3b2ab3e0 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/util/Timer.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/util/Timer.kt @@ -10,7 +10,10 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable -fun Timer(delayTime: Long = 100L, onTick: suspend () -> Unit) { +fun Timer( + delayTime: Long = 100L, + onTick: suspend () -> Unit, +) { val scope = rememberCoroutineScope() DisposableEffect(onTick) { var running = true diff --git a/app/src/main/java/me/vanpetegem/accentor/util/FuelGson.kt b/app/src/main/java/me/vanpetegem/accentor/util/FuelGson.kt index eec38785..80331967 100644 --- a/app/src/main/java/me/vanpetegem/accentor/util/FuelGson.kt +++ b/app/src/main/java/me/vanpetegem/accentor/util/FuelGson.kt @@ -12,25 +12,25 @@ import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter -import java.io.Reader -import java.time.Instant -import java.time.LocalDate -import java.time.format.DateTimeFormatter import me.vanpetegem.accentor.data.playlists.Access import me.vanpetegem.accentor.data.playlists.PlaylistType import me.vanpetegem.accentor.data.tracks.Role import me.vanpetegem.accentor.data.users.Permission +import java.io.Reader +import java.time.Instant +import java.time.LocalDate +import java.time.format.DateTimeFormatter -inline fun Request.responseObject() = - response(gsonDeserializer()) +inline fun Request.responseObject() = response(gsonDeserializer()) -inline fun gsonDeserializer() = object : ResponseDeserializable { - override fun deserialize(reader: Reader): T? = gsonObject().fromJson(reader, object : TypeToken() {}.type) -} +inline fun gsonDeserializer() = + object : ResponseDeserializable { + override fun deserialize(reader: Reader): T? = gsonObject().fromJson(reader, object : TypeToken() {}.type) + } inline fun Request.jsonBody(src: T) = this.jsonBody( - gsonObject().toJson(src, object : TypeToken() {}.type) as String + gsonObject().toJson(src, object : TypeToken() {}.type) as String, ) fun gsonObject(): Gson { @@ -39,59 +39,74 @@ fun gsonObject(): Gson { builder.registerTypeAdapter( Permission::class.java, object : TypeAdapter() { - override fun write(out: JsonWriter, value: Permission) { + override fun write( + out: JsonWriter, + value: Permission, + ) { out.value(value.name.lowercase()) } override fun read(`in`: JsonReader): Permission { return Permission.valueOf(`in`.nextString().uppercase()) } - } + }, ) builder.registerTypeAdapter( Access::class.java, object : TypeAdapter() { - override fun write(out: JsonWriter, value: Access) { + override fun write( + out: JsonWriter, + value: Access, + ) { out.value(value.name.lowercase()) } override fun read(`in`: JsonReader): Access { return Access.valueOf(`in`.nextString().uppercase()) } - } + }, ) builder.registerTypeAdapter( PlaylistType::class.java, object : TypeAdapter() { - override fun write(out: JsonWriter, value: PlaylistType) { + override fun write( + out: JsonWriter, + value: PlaylistType, + ) { out.value(value.name.lowercase()) } override fun read(`in`: JsonReader): PlaylistType { return PlaylistType.valueOf(`in`.nextString().uppercase()) } - } + }, ) builder.registerTypeAdapter( Role::class.java, object : TypeAdapter() { - override fun write(out: JsonWriter, value: Role) { + override fun write( + out: JsonWriter, + value: Role, + ) { out.value(value.name.lowercase()) } override fun read(`in`: JsonReader): Role { return Role.valueOf(`in`.nextString().uppercase()) } - } + }, ) builder.registerTypeAdapter( LocalDate::class.java, object : TypeAdapter() { - override fun write(out: JsonWriter, value: LocalDate?) { + override fun write( + out: JsonWriter, + value: LocalDate?, + ) { if (value == null) { out.nullValue() } else { @@ -106,13 +121,16 @@ fun gsonObject(): Gson { } return LocalDate.parse(`in`.nextString(), DateTimeFormatter.ISO_LOCAL_DATE) } - } + }, ) builder.registerTypeAdapter( Instant::class.java, object : TypeAdapter() { - override fun write(out: JsonWriter, value: Instant?) { + override fun write( + out: JsonWriter, + value: Instant?, + ) { if (value == null) { out.nullValue() } else { @@ -127,7 +145,7 @@ fun gsonObject(): Gson { } return Instant.parse(`in`.nextString()) } - } + }, ) return builder.create() diff --git a/app/src/main/java/me/vanpetegem/accentor/util/Result.kt b/app/src/main/java/me/vanpetegem/accentor/util/Result.kt index 50f92d39..78982383 100644 --- a/app/src/main/java/me/vanpetegem/accentor/util/Result.kt +++ b/app/src/main/java/me/vanpetegem/accentor/util/Result.kt @@ -5,8 +5,8 @@ package me.vanpetegem.accentor.util * @param */ sealed class Result { - data class Success(val data: T) : Result() + data class Error(val exception: Exception) : Result() override fun toString(): String { diff --git a/app/src/main/java/me/vanpetegem/accentor/util/RoomTypeConverters.kt b/app/src/main/java/me/vanpetegem/accentor/util/RoomTypeConverters.kt index 8460e83e..fec2ff26 100644 --- a/app/src/main/java/me/vanpetegem/accentor/util/RoomTypeConverters.kt +++ b/app/src/main/java/me/vanpetegem/accentor/util/RoomTypeConverters.kt @@ -1,13 +1,13 @@ package me.vanpetegem.accentor.util import androidx.room.TypeConverter -import java.time.Instant -import java.time.LocalDate -import java.time.format.DateTimeFormatter import me.vanpetegem.accentor.data.playlists.Access import me.vanpetegem.accentor.data.playlists.PlaylistType import me.vanpetegem.accentor.data.tracks.Role import me.vanpetegem.accentor.data.users.Permission +import java.time.Instant +import java.time.LocalDate +import java.time.format.DateTimeFormatter class RoomTypeConverters { @TypeConverter diff --git a/app/src/main/java/me/vanpetegem/accentor/util/SharedPreferenceLiveData.kt b/app/src/main/java/me/vanpetegem/accentor/util/SharedPreferenceLiveData.kt index 46159cdc..de7a3af4 100644 --- a/app/src/main/java/me/vanpetegem/accentor/util/SharedPreferenceLiveData.kt +++ b/app/src/main/java/me/vanpetegem/accentor/util/SharedPreferenceLiveData.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.LiveData abstract class SharedPreferenceLiveData( protected val sharedPrefs: SharedPreferences, - private val key: String + private val key: String, ) : LiveData(), SharedPreferences.OnSharedPreferenceChangeListener { abstract fun getValueFromPreferences(key: String): T? @@ -20,7 +20,10 @@ abstract class SharedPreferenceLiveData( super.onInactive() } - override fun onSharedPreferenceChanged(_sp: SharedPreferences?, key: String?) { + override fun onSharedPreferenceChanged( + _sp: SharedPreferences?, + key: String?, + ) { if (key == this.key) { value = getValueFromPreferences(this.key) } @@ -33,8 +36,7 @@ class SharedPreferenceIntLiveData(sharedPrefs: SharedPreferences, key: String, p value = this.getValueFromPreferences(key) } - override fun getValueFromPreferences(key: String): Int? = - if (sharedPrefs.contains(key)) sharedPrefs.getInt(key, 0) else default + override fun getValueFromPreferences(key: String): Int? = if (sharedPrefs.contains(key)) sharedPrefs.getInt(key, 0) else default } class SharedPreferenceStringLiveData(sharedPrefs: SharedPreferences, key: String, private val default: String?) : @@ -43,8 +45,7 @@ class SharedPreferenceStringLiveData(sharedPrefs: SharedPreferences, key: String value = this.getValueFromPreferences(key) } - override fun getValueFromPreferences(key: String): String? = - if (sharedPrefs.contains(key)) sharedPrefs.getString(key, default) else default + override fun getValueFromPreferences(key: String): String? = if (sharedPrefs.contains(key)) sharedPrefs.getString(key, default) else default } class SharedPreferenceBooleanLiveData(sharedPrefs: SharedPreferences, key: String, private val default: Boolean?) : @@ -53,8 +54,7 @@ class SharedPreferenceBooleanLiveData(sharedPrefs: SharedPreferences, key: Strin value = this.getValueFromPreferences(key) } - override fun getValueFromPreferences(key: String): Boolean? = - if (sharedPrefs.contains(key)) sharedPrefs.getBoolean(key, false) else default + override fun getValueFromPreferences(key: String): Boolean? = if (sharedPrefs.contains(key)) sharedPrefs.getBoolean(key, false) else default } class SharedPreferenceFloatLiveData(sharedPrefs: SharedPreferences, key: String, private val default: Float?) : @@ -63,8 +63,7 @@ class SharedPreferenceFloatLiveData(sharedPrefs: SharedPreferences, key: String, value = this.getValueFromPreferences(key) } - override fun getValueFromPreferences(key: String): Float? = - if (sharedPrefs.contains(key)) sharedPrefs.getFloat(key, .0f) else default + override fun getValueFromPreferences(key: String): Float? = if (sharedPrefs.contains(key)) sharedPrefs.getFloat(key, .0f) else default } class SharedPreferenceLongLiveData(sharedPrefs: SharedPreferences, key: String, private val default: Long?) : @@ -78,22 +77,37 @@ class SharedPreferenceLongLiveData(sharedPrefs: SharedPreferences, key: String, } } -fun SharedPreferences.intLiveData(key: String, default: Int? = null): SharedPreferenceLiveData { +fun SharedPreferences.intLiveData( + key: String, + default: Int? = null, +): SharedPreferenceLiveData { return SharedPreferenceIntLiveData(this, key, default) } -fun SharedPreferences.stringLiveData(key: String, default: String? = null): SharedPreferenceLiveData { +fun SharedPreferences.stringLiveData( + key: String, + default: String? = null, +): SharedPreferenceLiveData { return SharedPreferenceStringLiveData(this, key, default) } -fun SharedPreferences.booleanLiveData(key: String, default: Boolean? = null): SharedPreferenceLiveData { +fun SharedPreferences.booleanLiveData( + key: String, + default: Boolean? = null, +): SharedPreferenceLiveData { return SharedPreferenceBooleanLiveData(this, key, default) } -fun SharedPreferences.floatLiveData(key: String, default: Float? = null): SharedPreferenceLiveData { +fun SharedPreferences.floatLiveData( + key: String, + default: Float? = null, +): SharedPreferenceLiveData { return SharedPreferenceFloatLiveData(this, key, default) } -fun SharedPreferences.longLiveData(key: String, default: Long? = null): SharedPreferenceLiveData { +fun SharedPreferences.longLiveData( + key: String, + default: Long? = null, +): SharedPreferenceLiveData { return SharedPreferenceLongLiveData(this, key, default) } diff --git a/build.gradle b/build.gradle index 4904c861..aa745c9f 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ buildscript { classpath 'com.android.tools.build:gradle:8.1.4' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jlleitschuh.gradle:ktlint-gradle:11.6.1" + classpath "org.jlleitschuh.gradle:ktlint-gradle:12.0.2" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }