├── .github └── workflows │ ├── build.yml │ ├── build │ └── action.yml │ └── gerrit.yml ├── .gitignore ├── LICENSES ├── Apache-2.0.txt └── CC-BY-SA-4.0.txt ├── REUSE.toml ├── app ├── .gitignore ├── Android.bp ├── build.gradle.kts ├── initial-package-stopped-states-org.lineageos.twelve.xml ├── libs │ ├── Android.bp │ ├── androidx │ │ └── media3 │ │ │ ├── media3-common-ktx │ │ │ └── 1.6.1 │ │ │ │ ├── media3-common-ktx-1.6.1.aar │ │ │ │ └── media3-common-ktx-1.6.1.aar.license │ │ │ ├── media3-common │ │ │ └── 1.6.1 │ │ │ │ ├── media3-common-1.6.1.aar │ │ │ │ └── media3-common-1.6.1.aar.license │ │ │ ├── media3-container │ │ │ └── 1.6.1 │ │ │ │ ├── media3-container-1.6.1.aar │ │ │ │ └── media3-container-1.6.1.aar.license │ │ │ ├── media3-database │ │ │ └── 1.6.1 │ │ │ │ ├── media3-database-1.6.1.aar │ │ │ │ └── media3-database-1.6.1.aar.license │ │ │ ├── media3-datasource │ │ │ └── 1.6.1 │ │ │ │ ├── media3-datasource-1.6.1.aar │ │ │ │ └── media3-datasource-1.6.1.aar.license │ │ │ ├── media3-decoder │ │ │ └── 1.6.1 │ │ │ │ ├── media3-decoder-1.6.1.aar │ │ │ │ └── media3-decoder-1.6.1.aar.license │ │ │ ├── media3-exoplayer-hls │ │ │ └── 1.6.1 │ │ │ │ ├── media3-exoplayer-hls-1.6.1.aar │ │ │ │ └── media3-exoplayer-hls-1.6.1.aar.license │ │ │ ├── media3-exoplayer-midi │ │ │ └── 1.6.1 │ │ │ │ ├── media3-exoplayer-midi-1.6.1.aar │ │ │ │ └── media3-exoplayer-midi-1.6.1.aar.license │ │ │ ├── media3-exoplayer-rtsp │ │ │ └── 1.6.1 │ │ │ │ ├── media3-exoplayer-rtsp-1.6.1.aar │ │ │ │ └── media3-exoplayer-rtsp-1.6.1.aar.license │ │ │ ├── media3-exoplayer-smoothstreaming │ │ │ └── 1.6.1 │ │ │ │ ├── media3-exoplayer-smoothstreaming-1.6.1.aar │ │ │ │ └── media3-exoplayer-smoothstreaming-1.6.1.aar.license │ │ │ ├── media3-exoplayer │ │ │ └── 1.6.1 │ │ │ │ ├── media3-exoplayer-1.6.1.aar │ │ │ │ └── media3-exoplayer-1.6.1.aar.license │ │ │ ├── media3-extractor │ │ │ └── 1.6.1 │ │ │ │ ├── media3-extractor-1.6.1.aar │ │ │ │ └── media3-extractor-1.6.1.aar.license │ │ │ ├── media3-session │ │ │ └── 1.6.1 │ │ │ │ ├── media3-session-1.6.1.aar │ │ │ │ └── media3-session-1.6.1.aar.license │ │ │ └── media3-ui │ │ │ └── 1.6.1 │ │ │ ├── media3-ui-1.6.1.aar │ │ │ └── media3-ui-1.6.1.aar.license │ ├── com │ │ ├── github │ │ │ ├── bogerchan │ │ │ │ └── Nier-Visualizer │ │ │ │ │ └── v0.1.3 │ │ │ │ │ ├── Nier-Visualizer-v0.1.3.aar │ │ │ │ │ └── Nier-Visualizer-v0.1.3.aar.license │ │ │ └── philburk │ │ │ │ └── jsyn │ │ │ │ └── 40a41092cbab558d7d410ec43d93bb1e4121e86a │ │ │ │ └── jsyn-40a41092cbab558d7d410ec43d93bb1e4121e86a.jar │ │ └── squareup │ │ │ ├── okhttp3 │ │ │ └── okhttp │ │ │ │ └── 4.12.0 │ │ │ │ ├── okhttp-4.12.0.jar │ │ │ │ └── okhttp-4.12.0.jar.license │ │ │ └── okio │ │ │ └── okio-jvm │ │ │ └── 3.9.1 │ │ │ ├── okio-jvm-3.9.1.jar │ │ │ └── okio-jvm-3.9.1.jar.license │ └── io │ │ └── coil-kt │ │ └── coil3 │ │ ├── coil-android │ │ └── 3.0.4 │ │ │ ├── coil-release.aar │ │ │ └── coil-release.aar.license │ │ ├── coil-core-android │ │ └── 3.0.4 │ │ │ ├── coil-core-release.aar │ │ │ └── coil-core-release.aar.license │ │ ├── coil-network-core-android │ │ └── 3.0.4 │ │ │ ├── coil-network-core-release.aar │ │ │ └── coil-network-core-release.aar.license │ │ └── coil-network-okhttp-jvm │ │ └── 3.0.4 │ │ ├── coil-network-okhttp-jvm-3.0.4.jar │ │ └── coil-network-okhttp-jvm-3.0.4.jar.license ├── preinstalled-packages-org.lineageos.twelve.xml ├── proguard-rules.pro ├── schemas │ └── org.lineageos.twelve.database.TwelveDatabase │ │ ├── 1.json │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 4.json │ │ ├── 5.json │ │ ├── 6.json │ │ ├── 7.json │ │ └── 8.json └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── org │ │ └── lineageos │ │ └── twelve │ │ ├── MainActivity.kt │ │ ├── SettingsActivity.kt │ │ ├── TwelveApplication.kt │ │ ├── ViewActivity.kt │ │ ├── database │ │ ├── TwelveDatabase.kt │ │ ├── converters │ │ │ ├── InstantConverter.kt │ │ │ └── UriConverter.kt │ │ ├── dao │ │ │ ├── FavoriteDao.kt │ │ │ ├── JellyfinProviderDao.kt │ │ │ ├── MediaStatsDao.kt │ │ │ ├── PlaylistDao.kt │ │ │ ├── PlaylistItemCrossRefDao.kt │ │ │ ├── PlaylistWithItemsDao.kt │ │ │ ├── ResumptionPlaylistDao.kt │ │ │ └── SubsonicProviderDao.kt │ │ └── entities │ │ │ ├── Favorite.kt │ │ │ ├── JellyfinProvider.kt │ │ │ ├── LocalMediaStats.kt │ │ │ ├── Playlist.kt │ │ │ ├── PlaylistItemCrossRef.kt │ │ │ ├── PlaylistWithBoolean.kt │ │ │ ├── PlaylistWithItems.kt │ │ │ ├── ResumptionItem.kt │ │ │ ├── ResumptionPlaylist.kt │ │ │ ├── ResumptionPlaylistWithMediaItems.kt │ │ │ └── SubsonicProvider.kt │ │ ├── datasources │ │ ├── FileDataSource.kt │ │ ├── JellyfinDataSource.kt │ │ ├── MediaDataSource.kt │ │ ├── MediaStoreDataSource.kt │ │ ├── ProvidersManager.kt │ │ ├── SubsonicDataSource.kt │ │ ├── jellyfin │ │ │ ├── JellyfinAuthInterceptor.kt │ │ │ ├── JellyfinAuthenticator.kt │ │ │ ├── JellyfinClient.kt │ │ │ ├── models │ │ │ │ ├── AuthenticateUser.kt │ │ │ │ ├── AuthenticateUserResult.kt │ │ │ │ ├── CreatePlaylist.kt │ │ │ │ ├── CreatePlaylistResult.kt │ │ │ │ ├── Item.kt │ │ │ │ ├── ItemType.kt │ │ │ │ ├── LyricLine.kt │ │ │ │ ├── Lyrics.kt │ │ │ │ ├── Playlist.kt │ │ │ │ ├── PlaylistItems.kt │ │ │ │ ├── QueryResult.kt │ │ │ │ ├── SystemInfo.kt │ │ │ │ ├── TODO.kt │ │ │ │ └── UpdatePlaylist.kt │ │ │ └── serializers │ │ │ │ └── UUIDSerializer.kt │ │ ├── mediastore │ │ │ └── MediaStoreAudioUri.kt │ │ └── subsonic │ │ │ ├── SubsonicAuthInterceptor.kt │ │ │ ├── SubsonicClient.kt │ │ │ └── models │ │ │ ├── AlbumID3.kt │ │ │ ├── AlbumList.kt │ │ │ ├── AlbumList2.kt │ │ │ ├── AlbumWithSongsID3.kt │ │ │ ├── ArtistID3.kt │ │ │ ├── ArtistWithAlbumsID3.kt │ │ │ ├── ArtistsID3.kt │ │ │ ├── Child.kt │ │ │ ├── Contributor.kt │ │ │ ├── DiscTitle.kt │ │ │ ├── Error.kt │ │ │ ├── Genre.kt │ │ │ ├── Genres.kt │ │ │ ├── IndexID3.kt │ │ │ ├── InstantAsString.kt │ │ │ ├── InstantSerializer.kt │ │ │ ├── ItemDate.kt │ │ │ ├── ItemGenre.kt │ │ │ ├── License.kt │ │ │ ├── Line.kt │ │ │ ├── Lyrics.kt │ │ │ ├── LyricsList.kt │ │ │ ├── MediaType.kt │ │ │ ├── Playlist.kt │ │ │ ├── PlaylistWithSongs.kt │ │ │ ├── Playlists.kt │ │ │ ├── RecordLabel.kt │ │ │ ├── ReplayGain.kt │ │ │ ├── ResponseRoot.kt │ │ │ ├── ResponseStatus.kt │ │ │ ├── SearchResult3.kt │ │ │ ├── Songs.kt │ │ │ ├── Starred2.kt │ │ │ ├── StructuredLyrics.kt │ │ │ ├── SubsonicResponse.kt │ │ │ ├── UriAsString.kt │ │ │ ├── UriSerializer.kt │ │ │ └── Version.kt │ │ ├── ext │ │ ├── AndroidViewModel.kt │ │ ├── AppBarLayout.kt │ │ ├── Array.kt │ │ ├── AutoCompleteTextView.kt │ │ ├── Bitmap.kt │ │ ├── Bundle.kt │ │ ├── Call.kt │ │ ├── ClipData.kt │ │ ├── Configuration.kt │ │ ├── ConflatedCallbackFlow.kt │ │ ├── ContentResolver.kt │ │ ├── Context.kt │ │ ├── Cursor.kt │ │ ├── Enum.kt │ │ ├── Flow.kt │ │ ├── Fragment.kt │ │ ├── ImageView.kt │ │ ├── InputMethodManager.kt │ │ ├── Int.kt │ │ ├── Iterable.kt │ │ ├── Lifecycle.kt │ │ ├── LinearProgressIndicator.kt │ │ ├── List.kt │ │ ├── MediaItem.kt │ │ ├── MediaMetadata.kt │ │ ├── NavController.kt │ │ ├── Parcelable.kt │ │ ├── Player.kt │ │ ├── SharedPreferences.kt │ │ ├── StorageManager.kt │ │ ├── Uri.kt │ │ └── View.kt │ │ ├── fragments │ │ ├── ActivityFragment.kt │ │ ├── AddOrRemoveFromPlaylistsFragment.kt │ │ ├── AlbumFragment.kt │ │ ├── AlbumsFragment.kt │ │ ├── ArtistFragment.kt │ │ ├── ArtistsFragment.kt │ │ ├── CreatePlaylistDialogFragment.kt │ │ ├── GenreFragment.kt │ │ ├── GenresFragment.kt │ │ ├── LibraryFragment.kt │ │ ├── LyricsFragment.kt │ │ ├── MainFragment.kt │ │ ├── ManageProviderFragment.kt │ │ ├── MaterialDialogFragment.kt │ │ ├── MediaItemBottomSheetDialogFragment.kt │ │ ├── NowPlayingFragment.kt │ │ ├── NowPlayingStatsDialogFragment.kt │ │ ├── PlaybackControlBottomSheetDialogFragment.kt │ │ ├── PlaylistFragment.kt │ │ ├── PlaylistsFragment.kt │ │ ├── ProviderInformationBottomSheetDialogFragment.kt │ │ ├── ProviderSelectorDialogFragment.kt │ │ └── QueueFragment.kt │ │ ├── models │ │ ├── ActivityTab.kt │ │ ├── Album.kt │ │ ├── Artist.kt │ │ ├── ArtistWorks.kt │ │ ├── Audio.kt │ │ ├── AudioOutputMode.kt │ │ ├── AudioStreamInformation.kt │ │ ├── ColumnIndexCache.kt │ │ ├── DataSourceInformation.kt │ │ ├── Encoding.kt │ │ ├── Error.kt │ │ ├── FlowResult.kt │ │ ├── Genre.kt │ │ ├── GenreContent.kt │ │ ├── LocalizedString.kt │ │ ├── Lyrics.kt │ │ ├── MediaItem.kt │ │ ├── MediaType.kt │ │ ├── PlaybackProgress.kt │ │ ├── PlaybackState.kt │ │ ├── Playlist.kt │ │ ├── Provider.kt │ │ ├── ProviderArgument.kt │ │ ├── ProviderIdentifier.kt │ │ ├── ProviderType.kt │ │ ├── QueueItem.kt │ │ ├── RepeatMode.kt │ │ ├── Result.kt │ │ ├── ResumptionPlaylist.kt │ │ ├── SortingRule.kt │ │ ├── SortingStrategy.kt │ │ ├── Thumbnail.kt │ │ └── UniqueItem.kt │ │ ├── query │ │ └── Query.kt │ │ ├── repositories │ │ ├── MediaRepository.kt │ │ ├── ProvidersRepository.kt │ │ └── ResumptionPlaylistRepository.kt │ │ ├── services │ │ ├── CoilBitmapLoader.kt │ │ ├── InfoAudioProcessor.kt │ │ ├── MediaRepositoryTree.kt │ │ ├── PlaybackService.kt │ │ ├── ProxyDefaultAudioTrackBufferSizeProvider.kt │ │ └── TwelveRenderersFactory.kt │ │ ├── ui │ │ ├── coil │ │ │ └── ThumbnailMapper.kt │ │ ├── dialogs │ │ │ └── EditTextMaterialAlertDialogBuilder.kt │ │ ├── recyclerview │ │ │ ├── CenterSmoothScroller.kt │ │ │ ├── DisplayAwareGridLayoutManager.kt │ │ │ ├── SimpleListAdapter.kt │ │ │ └── UniqueItemDiffCallback.kt │ │ ├── views │ │ │ ├── ActivityTabView.kt │ │ │ ├── BaseMediaItemView.kt │ │ │ ├── FullscreenLoadingProgressBar.kt │ │ │ ├── HorizontalMediaItemView.kt │ │ │ ├── ListItem.kt │ │ │ ├── MediaItemGridItem.kt │ │ │ ├── NowPlayingBar.kt │ │ │ └── SortingChip.kt │ │ ├── visualizer │ │ │ └── VisualizerNVDataSource.kt │ │ └── widgets │ │ │ ├── AppWidgetUpdater.kt │ │ │ ├── BaseAppWidgetProvider.kt │ │ │ └── NowPlayingAppWidgetProvider.kt │ │ ├── utils │ │ ├── Api.kt │ │ ├── MimeUtils.kt │ │ ├── PermissionsChecker.kt │ │ ├── PermissionsUtils.kt │ │ └── TimestampFormatter.kt │ │ └── viewmodels │ │ ├── ActivityViewModel.kt │ │ ├── AddOrRemoveFromPlaylistsViewModel.kt │ │ ├── AlbumViewModel.kt │ │ ├── AlbumsViewModel.kt │ │ ├── ArtistViewModel.kt │ │ ├── ArtistsViewModel.kt │ │ ├── CreatePlaylistViewModel.kt │ │ ├── GenreViewModel.kt │ │ ├── GenresViewModel.kt │ │ ├── IntentsViewModel.kt │ │ ├── LocalPlayerViewModel.kt │ │ ├── LyricsViewModel.kt │ │ ├── ManageProviderViewModel.kt │ │ ├── MediaItemViewModel.kt │ │ ├── NowPlayingStatsViewModel.kt │ │ ├── NowPlayingViewModel.kt │ │ ├── PlaybackControlViewModel.kt │ │ ├── PlaylistViewModel.kt │ │ ├── PlaylistsViewModel.kt │ │ ├── ProviderViewModel.kt │ │ ├── ProvidersViewModel.kt │ │ ├── QueueViewModel.kt │ │ ├── SearchViewModel.kt │ │ ├── SettingsViewModel.kt │ │ └── TwelveViewModel.kt │ └── res │ ├── color │ ├── lyrics_line.xml │ └── textview_pressed.xml │ ├── drawable │ ├── avd_pause_to_play.xml │ ├── avd_play_to_pause.xml │ ├── bg_app_widget.xml │ ├── bg_app_widget_round_view.xml │ ├── bg_item_header_scrim.xml │ ├── ic_add.xml │ ├── ic_add_to_queue.xml │ ├── ic_album.xml │ ├── ic_arrow_drop_down.xml │ ├── ic_audio_file.xml │ ├── ic_audiofx.xml │ ├── ic_check_circle.xml │ ├── ic_circle.xml │ ├── ic_conversion_path.xml │ ├── ic_delete.xml │ ├── ic_drag_handle.xml │ ├── ic_edit.xml │ ├── ic_edit_note.xml │ ├── ic_expand_content.xml │ ├── ic_favorite.xml │ ├── ic_genres.xml │ ├── ic_graphic_eq.xml │ ├── ic_heart_filled.xml │ ├── ic_heart_unfilled.xml │ ├── ic_home.xml │ ├── ic_info.xml │ ├── ic_jellyfin.xml │ ├── ic_keyboard_arrow_down.xml │ ├── ic_launcher_background.xml │ ├── ic_launcher_foreground.xml │ ├── ic_launcher_monochrome.xml │ ├── ic_lineage_library.xml │ ├── ic_lyrics.xml │ ├── ic_media_output.xml │ ├── ic_more_vert.xml │ ├── ic_music_note.xml │ ├── ic_notification_small_icon.xml │ ├── ic_pause.xml │ ├── ic_person.xml │ ├── ic_play_arrow.xml │ ├── ic_playlist_add.xml │ ├── ic_playlist_play.xml │ ├── ic_playlist_remove.xml │ ├── ic_queue_play_next.xml │ ├── ic_remove.xml │ ├── ic_repeat.xml │ ├── ic_repeat_one.xml │ ├── ic_sailing.xml │ ├── ic_save.xml │ ├── ic_search.xml │ ├── ic_search_off.xml │ ├── ic_settings.xml │ ├── ic_shelves.xml │ ├── ic_shuffle.xml │ ├── ic_shuffle_play.xml │ ├── ic_skip_next.xml │ ├── ic_skip_previous.xml │ ├── ic_smartphone.xml │ ├── ic_sort_alphabetical_ascending.xml │ ├── ic_sort_alphabetical_descending.xml │ ├── ic_sync.xml │ ├── ic_warning.xml │ └── now_playing_marker.xml │ ├── layout-land │ ├── fragment_album.xml │ ├── fragment_artist.xml │ ├── fragment_genre.xml │ ├── fragment_main.xml │ ├── fragment_now_playing.xml │ └── fragment_playlist.xml │ ├── layout │ ├── activity_main.xml │ ├── activity_settings.xml │ ├── activity_view.xml │ ├── album_content.xml │ ├── album_labels.xml │ ├── alert_dialog_edit_text.xml │ ├── app_widget_now_playing.xml │ ├── argument_item.xml │ ├── artist_content.xml │ ├── artist_labels.xml │ ├── audio_track_index.xml │ ├── fragment_activity.xml │ ├── fragment_add_or_remove_from_playlists.xml │ ├── fragment_album.xml │ ├── fragment_albums.xml │ ├── fragment_artist.xml │ ├── fragment_artists.xml │ ├── fragment_create_playlist_dialog.xml │ ├── fragment_genre.xml │ ├── fragment_genres.xml │ ├── fragment_library.xml │ ├── fragment_lyrics.xml │ ├── fragment_main.xml │ ├── fragment_manage_provider.xml │ ├── fragment_media_item_bottom_sheet_dialog.xml │ ├── fragment_now_playing.xml │ ├── fragment_now_playing_stats_dialog.xml │ ├── fragment_playback_control_bottom_sheet_dialog.xml │ ├── fragment_playlist.xml │ ├── fragment_playlists.xml │ ├── fragment_provider_information_bottom_sheet_dialog.xml │ ├── fragment_provider_selector_dialog.xml │ ├── fragment_queue.xml │ ├── genre_content.xml │ ├── genre_labels.xml │ ├── horizontal_media_item_view.xml │ ├── item_media_item_grid.xml │ ├── list_item.xml │ ├── lyrics_line.xml │ ├── main_search_view.xml │ ├── now_playing_bar.xml │ ├── now_playing_bottom_bar.xml │ ├── now_playing_labels.xml │ ├── now_playing_lyrics.xml │ ├── now_playing_media_controls.xml │ ├── now_playing_progress_slider.xml │ ├── play_buttons.xml │ ├── playlist_content.xml │ ├── playlist_labels.xml │ ├── provider_more_button.xml │ ├── settingslib_switch.xml │ ├── settingslib_switch_compat.xml │ └── view_activity_tab.xml │ ├── menu │ ├── fragment_album_toolbar.xml │ ├── fragment_playlist_toolbar.xml │ └── navigation_bar_view_fragment_main.xml │ ├── mipmap │ └── ic_launcher.xml │ ├── navigation │ ├── fragment_add_or_remove_from_playlists.xml │ ├── fragment_album.xml │ ├── fragment_artist.xml │ ├── fragment_create_playlist_dialog.xml │ ├── fragment_genre.xml │ ├── fragment_lyrics.xml │ ├── fragment_main.xml │ ├── fragment_manage_provider.xml │ ├── fragment_media_item_bottom_sheet_dialog.xml │ ├── fragment_now_playing.xml │ ├── fragment_now_playing_stats_dialog.xml │ ├── fragment_playback_control_bottom_sheet_dialog.xml │ ├── fragment_playlist.xml │ ├── fragment_provider_information_bottom_sheet_dialog.xml │ ├── fragment_provider_selector_dialog.xml │ └── fragment_queue.xml │ ├── values-ast-rES │ └── strings.xml │ ├── values-az │ └── strings.xml │ ├── values-bg │ └── strings.xml │ ├── values-ca │ └── strings.xml │ ├── values-de │ └── strings.xml │ ├── values-el │ └── strings.xml │ ├── values-en-rAU │ └── strings.xml │ ├── values-en-rCA │ └── strings.xml │ ├── values-en-rGB │ └── strings.xml │ ├── values-en-rIN │ └── strings.xml │ ├── values-es │ └── strings.xml │ ├── values-fa │ └── strings.xml │ ├── values-fr │ └── strings.xml │ ├── values-fur-rIT │ └── strings.xml │ ├── values-ga-rIE │ └── strings.xml │ ├── values-hu │ └── strings.xml │ ├── values-in │ └── strings.xml │ ├── values-is │ └── strings.xml │ ├── values-it │ └── strings.xml │ ├── values-iw │ └── strings.xml │ ├── values-ja │ └── strings.xml │ ├── values-ko │ └── strings.xml │ ├── values-nl │ └── strings.xml │ ├── values-pl │ └── strings.xml │ ├── values-pt-rBR │ └── strings.xml │ ├── values-pt-rPT │ └── strings.xml │ ├── values-ro │ └── strings.xml │ ├── values-ru │ └── strings.xml │ ├── values-sl │ └── strings.xml │ ├── values-sq │ └── strings.xml │ ├── values-ta │ └── strings.xml │ ├── values-tr │ └── strings.xml │ ├── values-v31 │ └── themes.xml │ ├── values-vi │ └── strings.xml │ ├── values-zh-rCN │ └── strings.xml │ ├── values │ ├── attrs_AppWidget.xml │ ├── attrs_ListItem.xml │ ├── attrs_NowPlayingBar.xml │ ├── colors.xml │ ├── strings.xml │ ├── themes.xml │ └── themes_settingslib.xml │ └── xml │ ├── app_widget_now_playing.xml │ ├── automotive_app_desc.xml │ ├── network_security_config.xml │ └── root_preferences.xml ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout repo 11 | uses: actions/checkout@v4 12 | 13 | - name: Build 14 | uses: ./.github/workflows/build 15 | -------------------------------------------------------------------------------- /.github/workflows/build/action.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | runs: 4 | using: composite 5 | 6 | steps: 7 | - name: Setup JDK 17 8 | uses: actions/setup-java@v4 9 | with: 10 | distribution: 'zulu' 11 | java-version: 17 12 | cache: 'gradle' 13 | 14 | - name: Build with Gradle 15 | shell: bash 16 | run: ./gradlew assembleDebug 17 | 18 | - name: Generate Android.bp 19 | shell: bash 20 | run: | 21 | ./gradlew app:generateBp 22 | if [[ ! -z $(git status -s) ]]; then 23 | git status 24 | exit -1 25 | fi 26 | 27 | - uses: actions/upload-artifact@v4 28 | with: 29 | name: app-debug.apk 30 | path: app/build/outputs/apk/debug/app-debug.apk 31 | -------------------------------------------------------------------------------- /.github/workflows/gerrit.yml: -------------------------------------------------------------------------------- 1 | name: gerrit checks 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | ref: 7 | type: string 8 | gerrit-ref: 9 | type: string 10 | change: 11 | type: string 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: lineageos-infra/fetch-gerrit-change@main 18 | with: 19 | gerrit-ref: ${{ inputs.gerrit-ref }} 20 | ref: ${{ inputs.ref }} 21 | 22 | - name: Build 23 | uses: ./.github/workflows/build 24 | 25 | - uses: lineageos-infra/gerrit-vote@main 26 | if: always() 27 | with: 28 | auth: ${{ secrets.GERRIT_VOTE_CREDS }} 29 | change: ${{ inputs.change }} 30 | ref: ${{ inputs.ref }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 The LineageOS Project 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | version = 1 5 | SPDX-PackageName = "Twelve" 6 | SPDX-PackageSupplier = "The LineageOS Project" 7 | SPDX-PackageDownloadLocation = "https://github.com/LineageOS/android_packages_apps_Twelve" 8 | 9 | [[annotations]] 10 | path = [".github/workflows/**", ".gitignore", "app/.gitignore", "app/schemas/org.lineageos.twelve.database.TwelveDatabase/*.json"] 11 | precedence = "aggregate" 12 | SPDX-FileCopyrightText = "2024 The LineageOS Project" 13 | SPDX-License-Identifier = "Apache-2.0" 14 | 15 | [[annotations]] 16 | path = "gradle/wrapper/**" 17 | precedence = "aggregate" 18 | SPDX-FileCopyrightText = "2007-2023 Gradle, Inc." 19 | SPDX-License-Identifier = "Apache-2.0" 20 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/initial-package-stopped-states-org.lineageos.twelve.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-common-ktx/1.6.1/media3-common-ktx-1.6.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/androidx/media3/media3-common-ktx/1.6.1/media3-common-ktx-1.6.1.aar -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-common-ktx/1.6.1/media3-common-ktx-1.6.1.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The Android Open Source Project 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-common/1.6.1/media3-common-1.6.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/androidx/media3/media3-common/1.6.1/media3-common-1.6.1.aar -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-common/1.6.1/media3-common-1.6.1.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The Android Open Source Project 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-container/1.6.1/media3-container-1.6.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/androidx/media3/media3-container/1.6.1/media3-container-1.6.1.aar -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-container/1.6.1/media3-container-1.6.1.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The Android Open Source Project 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-database/1.6.1/media3-database-1.6.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/androidx/media3/media3-database/1.6.1/media3-database-1.6.1.aar -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-database/1.6.1/media3-database-1.6.1.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The Android Open Source Project 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-datasource/1.6.1/media3-datasource-1.6.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/androidx/media3/media3-datasource/1.6.1/media3-datasource-1.6.1.aar -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-datasource/1.6.1/media3-datasource-1.6.1.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The Android Open Source Project 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-decoder/1.6.1/media3-decoder-1.6.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/androidx/media3/media3-decoder/1.6.1/media3-decoder-1.6.1.aar -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-decoder/1.6.1/media3-decoder-1.6.1.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The Android Open Source Project 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-exoplayer-hls/1.6.1/media3-exoplayer-hls-1.6.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/androidx/media3/media3-exoplayer-hls/1.6.1/media3-exoplayer-hls-1.6.1.aar -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-exoplayer-hls/1.6.1/media3-exoplayer-hls-1.6.1.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The Android Open Source Project 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-exoplayer-midi/1.6.1/media3-exoplayer-midi-1.6.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/androidx/media3/media3-exoplayer-midi/1.6.1/media3-exoplayer-midi-1.6.1.aar -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-exoplayer-midi/1.6.1/media3-exoplayer-midi-1.6.1.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The Android Open Source Project 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-exoplayer-rtsp/1.6.1/media3-exoplayer-rtsp-1.6.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/androidx/media3/media3-exoplayer-rtsp/1.6.1/media3-exoplayer-rtsp-1.6.1.aar -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-exoplayer-rtsp/1.6.1/media3-exoplayer-rtsp-1.6.1.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The Android Open Source Project 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-exoplayer-smoothstreaming/1.6.1/media3-exoplayer-smoothstreaming-1.6.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/androidx/media3/media3-exoplayer-smoothstreaming/1.6.1/media3-exoplayer-smoothstreaming-1.6.1.aar -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-exoplayer-smoothstreaming/1.6.1/media3-exoplayer-smoothstreaming-1.6.1.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The Android Open Source Project 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-exoplayer/1.6.1/media3-exoplayer-1.6.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/androidx/media3/media3-exoplayer/1.6.1/media3-exoplayer-1.6.1.aar -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-exoplayer/1.6.1/media3-exoplayer-1.6.1.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The Android Open Source Project 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-extractor/1.6.1/media3-extractor-1.6.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/androidx/media3/media3-extractor/1.6.1/media3-extractor-1.6.1.aar -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-extractor/1.6.1/media3-extractor-1.6.1.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The Android Open Source Project 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-session/1.6.1/media3-session-1.6.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/androidx/media3/media3-session/1.6.1/media3-session-1.6.1.aar -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-session/1.6.1/media3-session-1.6.1.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The Android Open Source Project 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-ui/1.6.1/media3-ui-1.6.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/androidx/media3/media3-ui/1.6.1/media3-ui-1.6.1.aar -------------------------------------------------------------------------------- /app/libs/androidx/media3/media3-ui/1.6.1/media3-ui-1.6.1.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The Android Open Source Project 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/com/github/bogerchan/Nier-Visualizer/v0.1.3/Nier-Visualizer-v0.1.3.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/com/github/bogerchan/Nier-Visualizer/v0.1.3/Nier-Visualizer-v0.1.3.aar -------------------------------------------------------------------------------- /app/libs/com/github/bogerchan/Nier-Visualizer/v0.1.3/Nier-Visualizer-v0.1.3.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2017 Boger Chan 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/com/github/philburk/jsyn/40a41092cbab558d7d410ec43d93bb1e4121e86a/jsyn-40a41092cbab558d7d410ec43d93bb1e4121e86a.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/com/github/philburk/jsyn/40a41092cbab558d7d410ec43d93bb1e4121e86a/jsyn-40a41092cbab558d7d410ec43d93bb1e4121e86a.jar -------------------------------------------------------------------------------- /app/libs/com/squareup/okhttp3/okhttp/4.12.0/okhttp-4.12.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/com/squareup/okhttp3/okhttp/4.12.0/okhttp-4.12.0.jar -------------------------------------------------------------------------------- /app/libs/com/squareup/okhttp3/okhttp/4.12.0/okhttp-4.12.0.jar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Square, Inc. 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/com/squareup/okio/okio-jvm/3.9.1/okio-jvm-3.9.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/com/squareup/okio/okio-jvm/3.9.1/okio-jvm-3.9.1.jar -------------------------------------------------------------------------------- /app/libs/com/squareup/okio/okio-jvm/3.9.1/okio-jvm-3.9.1.jar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Square, Inc. 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/io/coil-kt/coil3/coil-android/3.0.4/coil-release.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/io/coil-kt/coil3/coil-android/3.0.4/coil-release.aar -------------------------------------------------------------------------------- /app/libs/io/coil-kt/coil3/coil-android/3.0.4/coil-release.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2019 Coil Contributors 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/io/coil-kt/coil3/coil-core-android/3.0.4/coil-core-release.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/io/coil-kt/coil3/coil-core-android/3.0.4/coil-core-release.aar -------------------------------------------------------------------------------- /app/libs/io/coil-kt/coil3/coil-core-android/3.0.4/coil-core-release.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2019 Coil Contributors 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/io/coil-kt/coil3/coil-network-core-android/3.0.4/coil-network-core-release.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/io/coil-kt/coil3/coil-network-core-android/3.0.4/coil-network-core-release.aar -------------------------------------------------------------------------------- /app/libs/io/coil-kt/coil3/coil-network-core-android/3.0.4/coil-network-core-release.aar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2019 Coil Contributors 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/libs/io/coil-kt/coil3/coil-network-okhttp-jvm/3.0.4/coil-network-okhttp-jvm-3.0.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LineageOS/android_packages_apps_Twelve/691f4a8396c1c765cf1355bcb0f602e15aa5d231/app/libs/io/coil-kt/coil3/coil-network-okhttp-jvm/3.0.4/coil-network-okhttp-jvm-3.0.4.jar -------------------------------------------------------------------------------- /app/libs/io/coil-kt/coil3/coil-network-okhttp-jvm/3.0.4/coil-network-okhttp-jvm-3.0.4.jar.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2019 Coil Contributors 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /app/preinstalled-packages-org.lineageos.twelve.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 The LineageOS Project 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Add project specific ProGuard rules here. 5 | # You can control the set of applied configuration files using the 6 | # proguardFiles setting in build.gradle. 7 | # 8 | # For more details, see 9 | # http://developer.android.com/guide/developing/tools/proguard.html 10 | 11 | # If your project uses WebView with JS, uncomment the following 12 | # and specify the fully qualified class name to the JavaScript interface 13 | # class: 14 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 15 | # public *; 16 | #} 17 | 18 | # Uncomment this to preserve the line number information for 19 | # debugging stack traces. 20 | #-keepattributes SourceFile,LineNumberTable 21 | 22 | # If you keep the line number information, uncomment this to 23 | # hide the original source file name. 24 | #-renamesourcefileattribute SourceFile 25 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/converters/InstantConverter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.converters 7 | 8 | import androidx.room.TypeConverter 9 | import java.time.Instant 10 | import java.time.OffsetDateTime 11 | import java.time.ZoneId 12 | 13 | class InstantConverter { 14 | @TypeConverter 15 | fun fromString(value: String?) = value?.let { OffsetDateTime.parse(it).toInstant() } 16 | 17 | @TypeConverter 18 | fun toString(value: Instant?) = value?.let { 19 | OffsetDateTime.ofInstant(value, ZoneId.of("Z")).toString() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/converters/UriConverter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.converters 7 | 8 | import android.net.Uri 9 | import androidx.core.net.toUri 10 | import androidx.room.TypeConverter 11 | 12 | class UriConverter { 13 | @TypeConverter 14 | fun fromString(value: String?) = value?.toUri() 15 | 16 | @TypeConverter 17 | fun toString(uri: Uri?) = uri?.toString() 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/dao/PlaylistItemCrossRefDao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.dao 7 | 8 | import android.net.Uri 9 | import androidx.room.Dao 10 | import androidx.room.Query 11 | import androidx.room.Transaction 12 | 13 | @Dao 14 | @Suppress("FunctionName") 15 | interface PlaylistItemCrossRefDao { 16 | /** 17 | * Add an item to a playlist (creates a cross-reference). 18 | */ 19 | @Transaction 20 | @Query( 21 | """ 22 | INSERT INTO PlaylistItemCrossRef (playlist_id, audio_uri, last_modified) 23 | VALUES (:playlistId, :audioUri, :lastModified) 24 | """ 25 | ) 26 | suspend fun _addItemToPlaylist( 27 | playlistId: Long, 28 | audioUri: Uri, 29 | lastModified: Long = System.currentTimeMillis() 30 | ) 31 | 32 | /** 33 | * Remove an item from a playlist (deletes the cross-reference). 34 | */ 35 | @Query("DELETE FROM PlaylistItemCrossRef WHERE playlist_id = :playlistId AND audio_uri = :audioUri") 36 | suspend fun _removeItemFromPlaylist(playlistId: Long, audioUri: Uri) 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/dao/PlaylistWithItemsDao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.dao 7 | 8 | import android.net.Uri 9 | import androidx.room.Dao 10 | import org.lineageos.twelve.database.TwelveDatabase 11 | 12 | @Dao 13 | abstract class PlaylistWithItemsDao(database: TwelveDatabase) { 14 | private val playlistDao = database.getPlaylistDao() 15 | private val playlistItemCrossRefDao = database.getPlaylistItemCrossRefDao() 16 | 17 | /** 18 | * Add an item to a playlist (creates a cross-reference). 19 | */ 20 | open suspend fun addItemToPlaylist(playlistId: Long, audioUri: Uri) = 21 | playlistItemCrossRefDao._addItemToPlaylist(playlistId, audioUri) 22 | 23 | /** 24 | * Remove an item from a playlist (deletes the cross-reference) and delete the item if it's the 25 | * last association or if the user never listened to it. 26 | */ 27 | open suspend fun removeItemFromPlaylist(playlistId: Long, audioUri: Uri) = 28 | playlistItemCrossRefDao._removeItemFromPlaylist(playlistId, audioUri) 29 | 30 | /** 31 | * Get a flow of the playlists that includes (or not) the given item. 32 | */ 33 | fun getPlaylistsWithItemStatus(audioUri: Uri) = 34 | playlistDao._getPlaylistsWithItemStatus(audioUri) 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/entities/Favorite.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.entities 7 | 8 | import android.net.Uri 9 | import androidx.room.ColumnInfo 10 | import androidx.room.Entity 11 | import androidx.room.Index 12 | import androidx.room.PrimaryKey 13 | import java.time.Instant 14 | 15 | /** 16 | * A table representing the favorite user items. 17 | * 18 | * @param audioUri The [Uri] of the audio 19 | * @param addedAt The date and time of when this item was added to the favorites 20 | */ 21 | @Entity( 22 | indices = [ 23 | Index(value = ["audio_uri"], unique = true), 24 | ], 25 | ) 26 | data class Favorite( 27 | @PrimaryKey @ColumnInfo(name = "audio_uri", defaultValue = "") val audioUri: Uri, 28 | @ColumnInfo(name = "added_at") val addedAt: Instant, 29 | ) 30 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/entities/JellyfinProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.entities 7 | 8 | import androidx.room.ColumnInfo 9 | import androidx.room.Entity 10 | import androidx.room.PrimaryKey 11 | 12 | /** 13 | * Jellyfin provider entity. 14 | * 15 | * @param id The unique ID of this instance 16 | * @param name The name of the provider 17 | * @param url The URL of the provider 18 | * @param username The username to use for authentication 19 | * @param password The password to use for authentication 20 | * @param deviceIdentifier The device identifier to use for authentication 21 | * @param token The token to use for authentication 22 | */ 23 | @Entity 24 | data class JellyfinProvider( 25 | @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "jellyfin_provider_id") val id: Long, 26 | @ColumnInfo(name = "name") val name: String, 27 | @ColumnInfo(name = "url") val url: String, 28 | @ColumnInfo(name = "username") val username: String, 29 | @ColumnInfo(name = "password") val password: String, 30 | @ColumnInfo(name = "device_identifier") val deviceIdentifier: String, 31 | @ColumnInfo(name = "token") val token: String? = null, 32 | ) 33 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/entities/LocalMediaStats.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.entities 7 | 8 | import android.net.Uri 9 | import androidx.room.ColumnInfo 10 | import androidx.room.Entity 11 | import androidx.room.Index 12 | import androidx.room.PrimaryKey 13 | 14 | /** 15 | * Database entity for local media stats 16 | * 17 | * @param audioUri The [Uri] of the audio 18 | * @param playCount The number of times the media has been played 19 | */ 20 | @Entity( 21 | indices = [ 22 | Index(value = ["audio_uri"], unique = true), 23 | ], 24 | ) 25 | data class LocalMediaStats( 26 | @PrimaryKey @ColumnInfo(name = "audio_uri") val audioUri: Uri, 27 | @ColumnInfo(name = "play_count", defaultValue = "1") val playCount: Long, 28 | ) 29 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/entities/Playlist.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.entities 7 | 8 | import androidx.room.ColumnInfo 9 | import androidx.room.Entity 10 | import androidx.room.PrimaryKey 11 | 12 | /** 13 | * Playlist entity. 14 | * 15 | * @param id The playlist ID 16 | * @param name The playlist name 17 | * @param createdAt The creation date of the playlist 18 | */ 19 | @Entity 20 | data class Playlist( 21 | @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "playlist_id") val id: Long, 22 | @ColumnInfo(name = "name") val name: String, 23 | @ColumnInfo(name = "created_at") val createdAt: Long, 24 | ) 25 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/entities/PlaylistItemCrossRef.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.entities 7 | 8 | import android.net.Uri 9 | import androidx.room.ColumnInfo 10 | import androidx.room.Entity 11 | import androidx.room.ForeignKey 12 | import androidx.room.Index 13 | 14 | /** 15 | * A many-to-many table to store playlists' songs. 16 | * 17 | * @param playlistId The id of the playlist 18 | * @param audioUri The [Uri] of the audio 19 | * @param lastModified The last time the item was modified 20 | */ 21 | @Entity( 22 | primaryKeys = ["playlist_id", "audio_uri"], 23 | indices = [ 24 | Index(value = ["playlist_id"]), 25 | Index(value = ["audio_uri"]), 26 | ], 27 | foreignKeys = [ 28 | ForeignKey( 29 | entity = Playlist::class, 30 | parentColumns = ["playlist_id"], 31 | childColumns = ["playlist_id"], 32 | onDelete = ForeignKey.CASCADE, 33 | onUpdate = ForeignKey.CASCADE, 34 | ), 35 | ] 36 | ) 37 | data class PlaylistItemCrossRef( 38 | @ColumnInfo(name = "playlist_id") val playlistId: Long, 39 | @ColumnInfo(name = "audio_uri", defaultValue = "") val audioUri: Uri, 40 | @ColumnInfo(name = "last_modified") val lastModified: Long = System.currentTimeMillis(), 41 | ) 42 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/entities/PlaylistWithBoolean.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024-2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.entities 7 | 8 | import androidx.room.ColumnInfo 9 | import androidx.room.Embedded 10 | 11 | /** 12 | * An item representing a playlist with a boolean value representing whether or not the requested 13 | * audio is in the playlist. 14 | * 15 | * @param playlist The playlist 16 | * @param value Whether or not the requested audio is in the playlist 17 | */ 18 | data class PlaylistWithBoolean( 19 | @Embedded val playlist: Playlist, 20 | @ColumnInfo(name = "value") val value: Boolean 21 | ) 22 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/entities/PlaylistWithItems.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024-2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.entities 7 | 8 | import android.net.Uri 9 | import androidx.room.Embedded 10 | import androidx.room.Relation 11 | 12 | /** 13 | * [Playlist] with item [Uri]s. 14 | * 15 | * @param playlist The [Playlist] entity 16 | * @param items The list of songs 17 | */ 18 | data class PlaylistWithItems( 19 | @Embedded val playlist: Playlist, 20 | @Relation( 21 | parentColumn = "playlist_id", 22 | entity = PlaylistItemCrossRef::class, 23 | entityColumn = "playlist_id", 24 | projection = ["audio_uri"], 25 | ) val items: List, 26 | ) 27 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/entities/ResumptionItem.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.entities 7 | 8 | import androidx.room.ColumnInfo 9 | import androidx.room.Entity 10 | import androidx.room.ForeignKey 11 | import androidx.room.Index 12 | import androidx.room.PrimaryKey 13 | 14 | /** 15 | * Resumption item. 16 | * 17 | * @param playlistIndex Index of this item in the playlist 18 | * @param resumptionPlaylistId ID of the resumption playlist, this is only needed to easily build a 19 | * [ResumptionPlaylistWithMediaItems] 20 | * @param mediaId ID of the media item 21 | */ 22 | @Entity( 23 | indices = [ 24 | Index(value = ["playlist_index"], unique = true), 25 | Index(value = ["resumption_playlist_id"]), 26 | ], 27 | foreignKeys = [ 28 | ForeignKey( 29 | entity = ResumptionPlaylist::class, 30 | parentColumns = ["resumption_id"], 31 | childColumns = ["resumption_playlist_id"], 32 | onDelete = ForeignKey.CASCADE, 33 | ), 34 | ], 35 | ) 36 | data class ResumptionItem( 37 | @PrimaryKey @ColumnInfo(name = "playlist_index") val playlistIndex: Long, 38 | @ColumnInfo(name = "resumption_playlist_id") val resumptionPlaylistId: Long, 39 | @ColumnInfo(name = "media_id") val mediaId: String, 40 | ) 41 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/entities/ResumptionPlaylist.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.entities 7 | 8 | import androidx.room.ColumnInfo 9 | import androidx.room.Entity 10 | import androidx.room.PrimaryKey 11 | 12 | /** 13 | * The resumption playlist, used when the user wants to resume playback from the latest state. 14 | * 15 | * @param id The ID of the resumption playlist, useless since where will be only one 16 | * @param startIndex The start index of the playlist 17 | * @param startPositionMs The start position in milliseconds of the referenced song index 18 | */ 19 | @Entity 20 | data class ResumptionPlaylist( 21 | @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "resumption_id") val id: Long, 22 | @ColumnInfo(name = "start_index") val startIndex: Int, 23 | @ColumnInfo(name = "start_position_ms") val startPositionMs: Long, 24 | ) 25 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/entities/ResumptionPlaylistWithMediaItems.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.entities 7 | 8 | import androidx.room.Embedded 9 | import androidx.room.Relation 10 | 11 | data class ResumptionPlaylistWithMediaItems( 12 | @Embedded val resumptionPlaylist: ResumptionPlaylist, 13 | @Relation( 14 | parentColumn = "resumption_id", 15 | entityColumn = "resumption_playlist_id", 16 | ) val items: List, 17 | ) 18 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/database/entities/SubsonicProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024-2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.database.entities 7 | 8 | import androidx.room.ColumnInfo 9 | import androidx.room.Entity 10 | import androidx.room.PrimaryKey 11 | 12 | /** 13 | * Subsonic provider entity. 14 | * 15 | * @param id The unique ID of this instance 16 | * @param name The name of this provider 17 | * @param url The URL of this provider 18 | * @param username The username of this provider 19 | * @param password The password of this provider 20 | * @param useLegacyAuthentication Whether to use legacy authentication 21 | */ 22 | @Entity 23 | data class SubsonicProvider( 24 | @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "subsonic_provider_id") val id: Long, 25 | @ColumnInfo(name = "name") val name: String, 26 | @ColumnInfo(name = "url") val url: String, 27 | @ColumnInfo(name = "username") val username: String, 28 | @ColumnInfo(name = "password") val password: String, 29 | @ColumnInfo(name = "use_legacy_authentication") val useLegacyAuthentication: Boolean, 30 | ) 31 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/JellyfinAuthInterceptor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.jellyfin 7 | 8 | import okhttp3.Headers 9 | import okhttp3.Interceptor 10 | import okhttp3.Response 11 | 12 | class JellyfinAuthInterceptor( 13 | private val tokenGetter: () -> String?, 14 | ) : Interceptor { 15 | override fun intercept(chain: Interceptor.Chain): Response { 16 | // If no token is found, simply proceed with the JellyfinAuthenticator 17 | val token = tokenGetter() ?: return chain.proceed(chain.request()) 18 | 19 | val request = chain.request().newBuilder() 20 | .headers(getAuthHeaders(token)) 21 | .build() 22 | 23 | return chain.proceed(request) 24 | } 25 | 26 | private fun getAuthHeaders(token: String) = Headers.Builder().apply { 27 | add("Authorization", "MediaBrowser Token=\"${token}\"") 28 | }.build() 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/models/AuthenticateUser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.jellyfin.models 7 | 8 | import kotlinx.serialization.SerialName 9 | import kotlinx.serialization.Serializable 10 | 11 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 12 | @Serializable 13 | data class AuthenticateUser( 14 | @SerialName("Username") val username: String, 15 | @SerialName("Pw") val password: String, 16 | ) 17 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/models/AuthenticateUserResult.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.jellyfin.models 7 | 8 | import kotlinx.serialization.SerialName 9 | import kotlinx.serialization.Serializable 10 | 11 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 12 | @Serializable 13 | data class AuthenticateUserResult( 14 | @SerialName("AccessToken") val accessToken: String? = null, 15 | ) 16 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/models/CreatePlaylist.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | @file:UseSerializers(UUIDSerializer::class) 7 | 8 | package org.lineageos.twelve.datasources.jellyfin.models 9 | 10 | import kotlinx.serialization.SerialName 11 | import kotlinx.serialization.Serializable 12 | import kotlinx.serialization.UseSerializers 13 | import org.lineageos.twelve.datasources.jellyfin.serializers.UUIDSerializer 14 | import java.util.UUID 15 | 16 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 17 | @Serializable 18 | data class CreatePlaylist( 19 | @SerialName("Name") val name: String, 20 | @SerialName("Ids") val ids: List, 21 | @SerialName("Users") val users: List, 22 | @SerialName("IsPublic") val isPublic: Boolean, 23 | ) 24 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/models/CreatePlaylistResult.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | @file:UseSerializers(UUIDSerializer::class) 7 | 8 | package org.lineageos.twelve.datasources.jellyfin.models 9 | 10 | import kotlinx.serialization.SerialName 11 | import kotlinx.serialization.Serializable 12 | import kotlinx.serialization.UseSerializers 13 | import org.lineageos.twelve.datasources.jellyfin.serializers.UUIDSerializer 14 | import java.util.UUID 15 | 16 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 17 | @Serializable 18 | data class CreatePlaylistResult( 19 | @SerialName("Id") val id: UUID, 20 | ) 21 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/models/Item.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024-2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | @file:UseSerializers(UUIDSerializer::class) 7 | 8 | package org.lineageos.twelve.datasources.jellyfin.models 9 | 10 | import kotlinx.serialization.SerialName 11 | import kotlinx.serialization.Serializable 12 | import kotlinx.serialization.UseSerializers 13 | import org.lineageos.twelve.datasources.jellyfin.serializers.UUIDSerializer 14 | import java.util.UUID 15 | 16 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 17 | @Serializable 18 | data class Item( 19 | @SerialName("Id") val id: UUID, 20 | @SerialName("Name") val name: String? = null, 21 | @SerialName("Artists") val artists: List? = null, 22 | @SerialName("ProductionYear") val productionYear: Int? = null, 23 | @SerialName("Container") val container: String? = null, 24 | @SerialName("SourceType") val sourceType: String? = null, 25 | @SerialName("RunTimeTicks") val runTimeTicks: Long? = null, 26 | @SerialName("Album") val album: String? = null, 27 | @SerialName("ParentIndexNumber") val parentIndexNumber: Int? = null, 28 | @SerialName("IndexNumber") val indexNumber: Int? = null, 29 | @SerialName("Genres") val genres: List? = null, 30 | @SerialName("Type") val type: ItemType? = null, 31 | @SerialName("IsFavorite") val isFavorite: Boolean? = null, 32 | ) 33 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/models/ItemType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.jellyfin.models 7 | 8 | import kotlinx.serialization.SerialName 9 | import kotlinx.serialization.Serializable 10 | 11 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 12 | @Serializable 13 | enum class ItemType( 14 | val type: String, 15 | ) { 16 | @SerialName("MusicAlbum") 17 | MUSIC_ALBUM("MusicAlbum"), 18 | 19 | @SerialName("MusicArtist") 20 | MUSIC_ARTIST("MusicArtist"), 21 | 22 | @SerialName("Person") 23 | PERSON("Person"), 24 | 25 | @SerialName("Audio") 26 | AUDIO("Audio"), 27 | 28 | @SerialName("Genre") 29 | GENRE("Genre"), 30 | 31 | @SerialName("MusicGenre") 32 | MUSIC_GENRE("MusicGenre"), 33 | 34 | @SerialName("Playlist") 35 | PLAYLIST("Playlist"), 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/models/LyricLine.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | @file:UseSerializers(UUIDSerializer::class) 7 | 8 | package org.lineageos.twelve.datasources.jellyfin.models 9 | 10 | import kotlinx.serialization.SerialName 11 | import kotlinx.serialization.Serializable 12 | import kotlinx.serialization.UseSerializers 13 | import org.lineageos.twelve.datasources.jellyfin.serializers.UUIDSerializer 14 | 15 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 16 | @Serializable 17 | data class LyricLine( 18 | @SerialName("Start") val start: Long, 19 | @SerialName("Text") val text: String 20 | ) 21 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/models/Lyrics.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | @file:UseSerializers(UUIDSerializer::class) 7 | 8 | package org.lineageos.twelve.datasources.jellyfin.models 9 | 10 | import kotlinx.serialization.SerialName 11 | import kotlinx.serialization.Serializable 12 | import kotlinx.serialization.UseSerializers 13 | import org.lineageos.twelve.datasources.jellyfin.serializers.UUIDSerializer 14 | 15 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 16 | @Serializable 17 | data class Lyrics( 18 | @SerialName("Lyrics") val lyrics: List? = null 19 | ) 20 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/models/Playlist.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | @file:UseSerializers(UUIDSerializer::class) 7 | 8 | package org.lineageos.twelve.datasources.jellyfin.models 9 | 10 | import kotlinx.serialization.SerialName 11 | import kotlinx.serialization.Serializable 12 | import kotlinx.serialization.UseSerializers 13 | import org.lineageos.twelve.datasources.jellyfin.serializers.UUIDSerializer 14 | import java.util.UUID 15 | 16 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 17 | @Serializable 18 | data class Playlist( 19 | @SerialName("Name") val name: String, 20 | @SerialName("Ids") val ids: List, 21 | @SerialName("Users") val users: List, 22 | @SerialName("IsPublic") val isPublic: Boolean, 23 | ) 24 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/models/PlaylistItems.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | @file:UseSerializers(UUIDSerializer::class) 7 | 8 | package org.lineageos.twelve.datasources.jellyfin.models 9 | 10 | import kotlinx.serialization.SerialName 11 | import kotlinx.serialization.Serializable 12 | import kotlinx.serialization.UseSerializers 13 | import org.lineageos.twelve.datasources.jellyfin.serializers.UUIDSerializer 14 | import java.util.UUID 15 | 16 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 17 | @Serializable 18 | data class PlaylistItems( 19 | @SerialName("ItemIds") val itemIds: List, 20 | ) 21 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/models/QueryResult.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.jellyfin.models 7 | 8 | import kotlinx.serialization.SerialName 9 | import kotlinx.serialization.Serializable 10 | 11 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 12 | @Serializable 13 | data class QueryResult( 14 | @SerialName("Items") val items: List, 15 | ) 16 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/models/SystemInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.jellyfin.models 7 | 8 | import kotlinx.serialization.SerialName 9 | import kotlinx.serialization.Serializable 10 | 11 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 12 | @Serializable 13 | data class SystemInfo( 14 | @SerialName("LocalAddress") val localAddress: String?, 15 | @SerialName("ServerName") val serverName: String?, 16 | @SerialName("Version") val version: String?, 17 | @SerialName("ProductName") val productName: String?, 18 | @SerialName("OperatingSystem") val operatingSystem: String?, 19 | @SerialName("Id") val id: String?, 20 | @SerialName("StartupWizardCompleted") val startupWizardCompleted: Boolean? 21 | ) 22 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/models/TODO.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.jellyfin.models 7 | 8 | typealias TODO = (Nothing?) 9 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/models/UpdatePlaylist.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.jellyfin.models 7 | 8 | import kotlinx.serialization.SerialName 9 | import kotlinx.serialization.Serializable 10 | 11 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 12 | @Serializable 13 | data class UpdatePlaylist( 14 | @SerialName("Name") val name: String, 15 | ) 16 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/jellyfin/serializers/UUIDSerializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.jellyfin.serializers 7 | 8 | import kotlinx.serialization.KSerializer 9 | import kotlinx.serialization.descriptors.PrimitiveKind 10 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 11 | import kotlinx.serialization.descriptors.SerialDescriptor 12 | import kotlinx.serialization.encoding.Decoder 13 | import kotlinx.serialization.encoding.Encoder 14 | import java.util.UUID 15 | 16 | object UUIDSerializer : KSerializer { 17 | private val UUID_REGEX = 18 | "^([\\da-z]{8})([\\da-z]{4})([\\da-z]{4})([\\da-z]{4})([\\da-z]{12})\$".toRegex() 19 | 20 | override val descriptor: SerialDescriptor = 21 | PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING) 22 | 23 | override fun serialize(encoder: Encoder, value: UUID) { 24 | encoder.encodeString(value.toString()) 25 | } 26 | 27 | override fun deserialize(decoder: Decoder): UUID = 28 | UUID.fromString(decoder.decodeString().replace(UUID_REGEX, "$1-$2-$3-$4-$5")) 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/AlbumList.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class AlbumList( 13 | val album: List, 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/AlbumList2.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class AlbumList2( 13 | val album: List, 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/ArtistID3.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class ArtistID3( 13 | val id: String, 14 | val name: String, 15 | val coverArt: String? = null, 16 | val artistImageUrl: UriAsString? = null, 17 | val albumCount: Int? = null, 18 | val starred: InstantAsString? = null, 19 | 20 | // OpenSubsonic 21 | val musicBrainzId: String? = null, 22 | val sortName: String? = null, 23 | val roles: List? = null, 24 | 25 | // Navidrome 26 | val userRating: Int? = null, 27 | ) 28 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/ArtistWithAlbumsID3.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class ArtistWithAlbumsID3( 13 | // ArtistID3 start 14 | val id: String, 15 | val name: String, 16 | val coverArt: String? = null, 17 | val artistImageUrl: UriAsString? = null, 18 | val albumCount: Int, 19 | val starred: InstantAsString? = null, 20 | 21 | // OpenSubsonic 22 | val sortName: String? = null, 23 | // ArtistID3 end 24 | 25 | val album: List, 26 | ) { 27 | fun toArtistID3() = ArtistID3( 28 | id = id, 29 | name = name, 30 | coverArt = coverArt, 31 | artistImageUrl = artistImageUrl, 32 | albumCount = albumCount, 33 | starred = starred, 34 | sortName = sortName, 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/ArtistsID3.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class ArtistsID3( 13 | val index: List, 14 | val ignoredArticles: String, 15 | 16 | // Navidrome 17 | val lastModified: Long? = null, // TODO 18 | ) 19 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/Contributor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | /** 11 | * A contributor artist for a song or an album. 12 | * 13 | * Note: OpenSubsonic only. 14 | * 15 | * @param role The contributor role 16 | * @param subRole The subRole for roles that may require it. Ex: The instrument for the performer 17 | * role (TMCL/performer tags). Note: For consistency between different tag formats, the TIPL sub 18 | * roles should be directly exposed in the role field 19 | * @param artist The artist taking on the role (Note: Only the required ArtistID3 fields should be 20 | * returned by default) 21 | */ 22 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 23 | @Serializable 24 | data class Contributor( 25 | val role: String, 26 | val subRole: String? = null, 27 | val artist: ArtistID3, 28 | ) 29 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/DiscTitle.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | /** 11 | * A disc title for an album. 12 | * 13 | * Note: OpenSubsonic only. 14 | * 15 | * @param disc The disc number 16 | * @param title The name of the disc 17 | */ 18 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 19 | @Serializable 20 | data class DiscTitle( 21 | val disc: Int, 22 | val title: String, 23 | ) 24 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/Genre.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class Genre( 13 | val value: String, 14 | val songCount: Int, 15 | val albumCount: Int, 16 | ) 17 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/Genres.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class Genres( 13 | val genre: List, 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/IndexID3.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class IndexID3( 13 | val artist: List, 14 | val name: String, 15 | ) 16 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/InstantAsString.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | import java.time.Instant 10 | 11 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 12 | typealias InstantAsString = @Serializable(with = InstantSerializer::class) Instant 13 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/InstantSerializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.KSerializer 9 | import kotlinx.serialization.descriptors.PrimitiveKind 10 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 11 | import kotlinx.serialization.encoding.Decoder 12 | import kotlinx.serialization.encoding.Encoder 13 | import java.time.Instant 14 | import java.time.OffsetDateTime 15 | import java.time.ZoneId 16 | 17 | class InstantSerializer : KSerializer { 18 | override val descriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) 19 | 20 | override fun deserialize(decoder: Decoder): Instant = 21 | OffsetDateTime.parse(decoder.decodeString()).toInstant() 22 | 23 | override fun serialize(encoder: Encoder, value: Instant) { 24 | encoder.encodeString( 25 | OffsetDateTime.ofInstant(value, ZoneId.of("Z")).toString() 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/ItemDate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | /** 11 | * A date for a media item that may be just a year, or year-month, or full date. 12 | * 13 | * Note: OpenSubsonic only. 14 | * 15 | * @param year The year 16 | * @param month The month (1-12) 17 | * @param day The day (1-31) 18 | */ 19 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 20 | @Serializable 21 | data class ItemDate( 22 | val year: Int? = null, 23 | val month: Int? = null, 24 | val day: Int? = null, 25 | ) 26 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/ItemGenre.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | /** 11 | * A genre returned in list of genres for an item. 12 | * 13 | * Note: OpenSubsonic only. 14 | * 15 | * @param name Genre name 16 | */ 17 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 18 | @Serializable 19 | data class ItemGenre( 20 | val name: String, 21 | ) 22 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/License.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class License( 13 | val valid: Boolean, 14 | val email: String? = null, 15 | val licenseExpires: InstantAsString? = null, 16 | val trialExpires: InstantAsString? = null, 17 | ) 18 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/Line.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | /** 11 | * One line of a song lyric. 12 | * 13 | * Note: OpenSubsonic only. 14 | * 15 | * @param value The actual text of this line 16 | * @param start The start time of the lyrics, relative to the start time of the track, in 17 | * milliseconds. If this is not part of synced lyrics, start must be omitted 18 | */ 19 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 20 | @Serializable 21 | data class Line( 22 | val value: String, 23 | val start: Long? = null, 24 | ) 25 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/Lyrics.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | /** 11 | * Lyrics. 12 | */ 13 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 14 | @Serializable 15 | data class Lyrics( 16 | val value: String, 17 | val artist: String? = null, 18 | val title: String? = null, 19 | ) 20 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/LyricsList.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | /** 11 | * List of structured lyrics. 12 | * 13 | * Note: OpenSubsonic only. 14 | * 15 | * @param structuredLyrics Structured lyrics. There can be multiple lyrics of the same type with the 16 | * same language 17 | */ 18 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 19 | @Serializable 20 | data class LyricsList( 21 | val structuredLyrics: List, 22 | ) 23 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/Playlist.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class Playlist( 13 | val allowedUser: List? = null, 14 | val id: String, 15 | val name: String, 16 | val comment: String? = null, 17 | val owner: String? = null, 18 | val public: Boolean? = null, 19 | val songCount: Int, 20 | val duration: Int, 21 | val created: InstantAsString, 22 | val changed: InstantAsString, 23 | val coverArt: String? = null, 24 | ) 25 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/PlaylistWithSongs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class PlaylistWithSongs( 13 | // Playlist start 14 | val allowedUser: List? = null, 15 | val id: String, 16 | val name: String, 17 | val comment: String? = null, 18 | val owner: String? = null, 19 | val public: Boolean? = null, 20 | val songCount: Int, 21 | val duration: Int? = null, // OpenSubsonic violates the API 22 | val created: InstantAsString, 23 | val changed: InstantAsString, 24 | val coverArt: String? = null, 25 | // Playlist end 26 | 27 | val entry: List? = null, 28 | ) { 29 | fun toPlaylist() = Playlist( 30 | allowedUser = allowedUser, 31 | id = id, 32 | name = name, 33 | comment = comment, 34 | owner = owner, 35 | public = public, 36 | songCount = songCount, 37 | duration = duration ?: 0, 38 | created = created, 39 | changed = changed, 40 | coverArt = coverArt, 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/Playlists.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class Playlists( 13 | val playlist: List, 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/RecordLabel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | /** 11 | * A record label for an album. 12 | * 13 | * Note: OpenSubsonic only. 14 | * 15 | * @param name The record label name 16 | */ 17 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 18 | @Serializable 19 | data class RecordLabel( 20 | val name: String, 21 | ) 22 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/ReplayGain.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | /** 11 | * The replay gain data of a song. 12 | * 13 | * Note: OpenSubsonic only. 14 | * 15 | * @param trackGain The track replay gain value (In Db) 16 | * @param albumGain The album replay gain value (In Db) 17 | * @param trackPeak The track peak value (Must be positive) 18 | * @param albumPeak The album peak value (Must be positive) 19 | * @param baseGain The base gain value (In Db) (Ogg Opus Output Gain for example) 20 | * @param fallbackGain An optional fallback gain that clients should apply when the corresponding 21 | * gain value is missing (Can be computed from the tracks or exposed as an user setting) 22 | */ 23 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 24 | @Serializable 25 | data class ReplayGain( 26 | val trackGain: Double? = null, 27 | val albumGain: Double? = null, 28 | val trackPeak: Double? = null, 29 | val albumPeak: Double? = null, 30 | val baseGain: Double? = null, 31 | val fallbackGain: Double? = null 32 | ) 33 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/ResponseRoot.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class ResponseRoot( 13 | @kotlinx.serialization.SerialName("subsonic-response") 14 | val subsonicResponse: SubsonicResponse, 15 | ) 16 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/ResponseStatus.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.KSerializer 9 | import kotlinx.serialization.Serializable 10 | import kotlinx.serialization.SerializationException 11 | import kotlinx.serialization.descriptors.PrimitiveKind 12 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 13 | import kotlinx.serialization.encoding.Decoder 14 | import kotlinx.serialization.encoding.Encoder 15 | 16 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 17 | @Serializable(with = ResponseStatus.Serializer::class) 18 | enum class ResponseStatus(val value: String) { 19 | OK("ok"), 20 | FAILED("failed"); 21 | 22 | class Serializer : KSerializer { 23 | override val descriptor = PrimitiveSerialDescriptor( 24 | "ResponseStatus", PrimitiveKind.STRING 25 | ) 26 | 27 | override fun deserialize(decoder: Decoder) = decoder.decodeString().let { 28 | fromValue(it) ?: throw SerializationException("Unknown ResponseStatus value $it") 29 | } 30 | 31 | override fun serialize(encoder: Encoder, value: ResponseStatus) { 32 | encoder.encodeString(value.value) 33 | } 34 | } 35 | 36 | companion object { 37 | fun fromValue(value: String) = entries.firstOrNull { it.value == value } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/SearchResult3.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class SearchResult3( 13 | val artist: List? = null, 14 | val album: List? = null, 15 | val song: List? = null, 16 | ) 17 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/Songs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 11 | @Serializable 12 | data class Songs( 13 | val song: List, 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/Starred2.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | /** 11 | * Starred songs, albums and artists. 12 | * 13 | * @param artist Starred artists 14 | * @param album Starred albums 15 | * @param song Starred songs 16 | */ 17 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 18 | @Serializable 19 | data class Starred2( 20 | val artist: List? = null, 21 | val album: List? = null, 22 | val song: List? = null, 23 | ) 24 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/StructuredLyrics.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | /** 11 | * List of structured lyrics. 12 | * 13 | * Note: OpenSubsonic only. 14 | * 15 | * @param lang The lyrics language (ideally ISO 639). If the language is unknown (e.g. lrc file), 16 | * the server must return `und` (ISO standard) or `xxx` (common value for taggers) 17 | * @param synced True if the lyrics are synced, false otherwise 18 | * @param line The actual lyrics. Ordered by start time (synced) or appearance order (unsynced) 19 | * @param displayArtist The artist name to display. This could be the localized name, or any other 20 | * value 21 | * @param displayTitle The title to display. This could be the song title (localized), or any other 22 | * value 23 | * @param offset The offset to apply to all lyrics, in milliseconds. Positive means lyrics appear 24 | * sooner, negative means later. If not included, the offset must be assumed to be 0 25 | */ 26 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 27 | @Serializable 28 | data class StructuredLyrics( 29 | val lang: String, 30 | val synced: Boolean, 31 | val line: List, 32 | val displayArtist: String? = null, 33 | val displayTitle: String? = null, 34 | val offset: Long? = null, 35 | ) 36 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/UriAsString.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import android.net.Uri 9 | import kotlinx.serialization.Serializable 10 | 11 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 12 | typealias UriAsString = @Serializable(with = UriSerializer::class) Uri 13 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/UriSerializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import android.net.Uri 9 | import androidx.core.net.toUri 10 | import kotlinx.serialization.KSerializer 11 | import kotlinx.serialization.descriptors.PrimitiveKind 12 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 13 | import kotlinx.serialization.encoding.Decoder 14 | import kotlinx.serialization.encoding.Encoder 15 | 16 | class UriSerializer : KSerializer { 17 | override val descriptor = PrimitiveSerialDescriptor( 18 | "Uri", PrimitiveKind.STRING 19 | ) 20 | 21 | override fun deserialize(decoder: Decoder): Uri = decoder.decodeString().toUri() 22 | 23 | override fun serialize(encoder: Encoder, value: Uri) { 24 | encoder.encodeString(value.toString()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/datasources/subsonic/models/Version.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.datasources.subsonic.models 7 | 8 | import kotlinx.serialization.KSerializer 9 | import kotlinx.serialization.Serializable 10 | import kotlinx.serialization.descriptors.PrimitiveKind 11 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 12 | import kotlinx.serialization.encoding.Decoder 13 | import kotlinx.serialization.encoding.Encoder 14 | 15 | @Suppress("PROVIDED_RUNTIME_TOO_LOW") 16 | @Serializable(with = Version.Serializer::class) 17 | data class Version( 18 | val major: Int, 19 | val minor: Int, 20 | val revision: Int, 21 | ) { 22 | val value = "$major.$minor.$revision" 23 | 24 | override fun toString() = value 25 | 26 | class Serializer : KSerializer { 27 | override val descriptor = PrimitiveSerialDescriptor( 28 | "Version", PrimitiveKind.STRING 29 | ) 30 | 31 | override fun deserialize(decoder: Decoder) = fromValue(decoder.decodeString()) 32 | 33 | override fun serialize(encoder: Encoder, value: Version) { 34 | encoder.encodeString(value.value) 35 | } 36 | } 37 | 38 | companion object { 39 | fun fromValue(value: String) = value.split('.') 40 | .map { it.toInt() } 41 | .let { (major, minor, revision) -> Version(major, minor, revision) } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/AndroidViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2023-2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.app.Application 9 | import android.content.Context 10 | import android.content.res.Resources 11 | import androidx.lifecycle.AndroidViewModel 12 | 13 | val AndroidViewModel.applicationContext: Context 14 | get() = getApplication().applicationContext 15 | 16 | val AndroidViewModel.resources: Resources 17 | get() = getApplication().resources 18 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/AppBarLayout.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import androidx.annotation.Px 9 | import androidx.coordinatorlayout.widget.CoordinatorLayout 10 | import com.google.android.material.appbar.AppBarLayout 11 | import kotlin.reflect.safeCast 12 | 13 | fun AppBarLayout.setOffset(@Px offsetPx: Int, coordinatorLayout: CoordinatorLayout) { 14 | val params = CoordinatorLayout.LayoutParams::class.safeCast(layoutParams) ?: return 15 | AppBarLayout.Behavior::class.safeCast(params.behavior)?.onNestedPreScroll( 16 | coordinatorLayout, 17 | this, 18 | this, 19 | 0, 20 | offsetPx, 21 | intArrayOf(0, 0), 22 | 0 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/Array.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022-2023 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | /** 9 | * Get the next element in the array relative to the [current] element. 10 | * 11 | * If the element is the last in the array or it's not present in the array 12 | * it will return the first element. 13 | * If the array is empty, null will be returned. 14 | * 15 | * @param current The element to use as cursor 16 | * 17 | * @return [T] Either the next element, the first element or null 18 | */ 19 | fun Array.next(current: T) = getOrElse(indexOf(current) + 1) { firstOrNull() } 20 | 21 | /** 22 | * Get the previous element in the array relative to the [current] element. 23 | * 24 | * If the element is the first in the array or it's not present in the array 25 | * it will return the last element. 26 | * If the array is empty, null will be returned. 27 | * 28 | * @param current The element to use as cursor 29 | * 30 | * @return [T] Either the previous element, the last element or null 31 | */ 32 | fun Array.previous(current: T) = getOrElse(indexOf(current) - 1) { lastOrNull() } 33 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/AutoCompleteTextView.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.widget.AutoCompleteTextView 9 | 10 | fun AutoCompleteTextView.selectItem(position: Int = 0) { 11 | setText(adapter.getItem(position).toString(), false) 12 | showDropDown() 13 | setSelection(position) 14 | listSelection = position 15 | performCompletion() 16 | dismissDropDown() 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/Bitmap.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.graphics.Bitmap 9 | import java.nio.ByteBuffer 10 | 11 | fun Bitmap.toByteArray(): ByteArray = ByteBuffer.allocate(rowBytes * height).apply { 12 | copyPixelsToBuffer(this) 13 | }.array() 14 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/Call.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Square, Inc. 3 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package org.lineageos.twelve.ext 8 | 9 | import kotlinx.coroutines.ExperimentalCoroutinesApi 10 | import kotlinx.coroutines.suspendCancellableCoroutine 11 | import okhttp3.Call 12 | import okhttp3.Callback 13 | import okhttp3.Response 14 | import okhttp3.internal.closeQuietly 15 | import okio.IOException 16 | import kotlin.coroutines.resumeWithException 17 | 18 | @OptIn(ExperimentalCoroutinesApi::class) // resume with a resource cleanup. 19 | suspend fun Call.executeAsync(): Response = suspendCancellableCoroutine { continuation -> 20 | continuation.invokeOnCancellation { 21 | this.cancel() 22 | } 23 | this.enqueue( 24 | object : Callback { 25 | override fun onFailure( 26 | call: Call, 27 | e: IOException, 28 | ) { 29 | continuation.resumeWithException(e) 30 | } 31 | 32 | override fun onResponse( 33 | call: Call, 34 | response: Response, 35 | ) { 36 | @Suppress("DEPRECATION") 37 | continuation.resume(response) { 38 | response.closeQuietly() 39 | } 40 | } 41 | }, 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/ClipData.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2023-2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.content.ClipData 9 | 10 | fun ClipData.asArray() = buildList { 11 | for (i in 0 until itemCount) { 12 | add(getItemAt(i)) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/Configuration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.content.res.Configuration 9 | 10 | /** 11 | * Return whether the orientation is [Configuration.ORIENTATION_LANDSCAPE]. 12 | */ 13 | val Configuration.isLandscape 14 | get() = orientation == Configuration.ORIENTATION_LANDSCAPE 15 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/ConflatedCallbackFlow.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import kotlinx.coroutines.channels.ProducerScope 9 | import kotlinx.coroutines.flow.Flow 10 | import kotlinx.coroutines.flow.callbackFlow 11 | import kotlinx.coroutines.flow.conflate 12 | import kotlin.experimental.ExperimentalTypeInference 13 | 14 | @OptIn(ExperimentalTypeInference::class) 15 | fun conflatedCallbackFlow( 16 | @BuilderInference block: suspend ProducerScope.() -> Unit 17 | ): Flow = callbackFlow(block).conflate() 18 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/Context.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.content.Context 9 | import android.content.pm.PackageManager 10 | import androidx.core.content.ContextCompat 11 | import androidx.lifecycle.Lifecycle 12 | import kotlinx.coroutines.flow.map 13 | import kotlinx.coroutines.flow.onStart 14 | 15 | fun Context.permissionGranted(permission: String) = 16 | ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED 17 | 18 | fun Context.permissionsGranted(permissions: Array) = permissions.all { 19 | permissionGranted(it) 20 | } 21 | 22 | fun Context.permissionsStatus(permissions: Array) = permissions.partition { 23 | permissionGranted(it) 24 | } 25 | 26 | /** 27 | * Flow of permissions granted/denied. 28 | */ 29 | fun Context.permissionsFlow(lifecycle: Lifecycle, permissions: Array) = 30 | lifecycle.eventFlow(Lifecycle.Event.ON_RESUME) 31 | .onStart { emit(Unit) } 32 | .map { 33 | permissionsStatus(permissions) 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/Cursor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2023-2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.database.Cursor 9 | import org.lineageos.twelve.models.ColumnIndexCache 10 | 11 | fun Cursor?.mapEachRow( 12 | mapping: (ColumnIndexCache) -> T, 13 | ) = this?.use { cursor -> 14 | if (!cursor.moveToFirst()) { 15 | return@use emptyList() 16 | } 17 | 18 | val columnIndexCache = ColumnIndexCache(cursor) 19 | 20 | val data = buildList { 21 | do { 22 | add(mapping(columnIndexCache)) 23 | } while (cursor.moveToNext()) 24 | } 25 | 26 | data.toList() 27 | } ?: emptyList() 28 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/Enum.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2023 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | /** 9 | * Get the previous value. 10 | */ 11 | internal inline fun > E.previous() = enumValues().previous( 12 | this 13 | ) ?: throw Exception("No enum values") 14 | 15 | /** 16 | * Get the next value. 17 | */ 18 | internal inline fun > E.next() = enumValues().next( 19 | this 20 | ) ?: throw Exception("No enum values") 21 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/Flow.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2023 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.database.Cursor 9 | import kotlinx.coroutines.flow.Flow 10 | import kotlinx.coroutines.flow.map 11 | import org.lineageos.twelve.models.ColumnIndexCache 12 | 13 | fun Flow.mapEachRow( 14 | mapping: (ColumnIndexCache) -> T, 15 | ) = map { it.mapEachRow(mapping) } 16 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/Fragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2023 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.view.View 9 | import androidx.annotation.IdRes 10 | import androidx.fragment.app.Fragment 11 | import kotlin.properties.ReadOnlyProperty 12 | 13 | inline fun getViewProperty(@IdRes viewId: Int) = 14 | ReadOnlyProperty { thisRef, _ -> 15 | thisRef.requireView().findViewById(viewId) 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/ImageView.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.widget.ImageView 9 | import androidx.annotation.DrawableRes 10 | import coil3.ImageLoader 11 | import coil3.imageLoader 12 | import coil3.request.Disposable 13 | import coil3.request.ImageRequest 14 | import coil3.request.error 15 | import coil3.request.placeholder 16 | import coil3.request.target 17 | 18 | inline fun ImageView.loadThumbnail( 19 | data: Any?, 20 | imageLoader: ImageLoader = context.imageLoader, 21 | @DrawableRes placeholder: Int? = null, 22 | builder: ImageRequest.Builder.() -> Unit = { 23 | placeholder?.let { 24 | placeholder(it) 25 | error(it) 26 | } 27 | }, 28 | ): Disposable { 29 | val request = ImageRequest.Builder(context) 30 | .data(data) 31 | .target(this) 32 | .apply(builder) 33 | .build() 34 | return imageLoader.enqueue(request) 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/Int.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package org.lineageos.twelve.ext 6 | 7 | import android.content.res.Resources.getSystem 8 | import kotlin.math.roundToInt 9 | 10 | /** 11 | * dp -> px. 12 | */ 13 | val Int.px 14 | get() = (this * getSystem().displayMetrics.density).roundToInt() 15 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/Iterable.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import kotlinx.coroutines.async 9 | import kotlinx.coroutines.awaitAll 10 | import kotlinx.coroutines.coroutineScope 11 | 12 | /** 13 | * Maps a list of elements asynchronously. 14 | */ 15 | suspend fun Iterable.mapAsync( 16 | transform: suspend (T) -> R, 17 | ): List = coroutineScope { 18 | map { 19 | async { 20 | transform(it) 21 | } 22 | }.awaitAll() 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/Lifecycle.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import androidx.lifecycle.Lifecycle 9 | import androidx.lifecycle.eventFlow 10 | import kotlinx.coroutines.flow.mapNotNull 11 | 12 | /** 13 | * Emit a [Unit] only when the lifecycle reaches the requested event 14 | * @see Lifecycle.eventFlow 15 | */ 16 | fun Lifecycle.eventFlow(event: Lifecycle.Event) = eventFlow 17 | .mapNotNull { 18 | when (it) { 19 | event -> Unit 20 | else -> null 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/LinearProgressIndicator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024-2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import com.google.android.material.progressindicator.LinearProgressIndicator 9 | import org.lineageos.twelve.models.FlowResult 10 | 11 | /** 12 | * @see LinearProgressIndicator.setProgressCompat 13 | */ 14 | fun LinearProgressIndicator.setProgressCompat(status: FlowResult) { 15 | when (status) { 16 | is FlowResult.Loading -> { 17 | if (!isIndeterminate) { 18 | hide() 19 | isIndeterminate = true 20 | } 21 | 22 | show() 23 | } 24 | 25 | else -> { 26 | hide() 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/List.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022-2023 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | /** 9 | * Get the next element in the list relative to the [current] element. 10 | * 11 | * If the element is the last in the list or it's not present in the list 12 | * it will return the first element. 13 | * If the list is empty, null will be returned. 14 | * 15 | * @param current The element to use as cursor 16 | * 17 | * @return [E] Either the next element, the first element or null 18 | */ 19 | fun List.next(current: E) = getOrElse(indexOf(current) + 1) { firstOrNull() } 20 | 21 | /** 22 | * Get the previous element in the list relative to the [current] element. 23 | * 24 | * If the element is the first in the list or it's not present in the list 25 | * it will return the last element. 26 | * If the list is empty, null will be returned. 27 | * 28 | * @param current The element to use as cursor 29 | * 30 | * @return [E] Either the previous element, the last element or null 31 | */ 32 | fun List.previous(current: E) = getOrElse(indexOf(current) - 1) { lastOrNull() } 33 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/MediaMetadata.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.content.Context 9 | import androidx.media3.common.MediaMetadata 10 | import coil3.imageLoader 11 | import coil3.request.ImageRequest 12 | import coil3.request.allowHardware 13 | import coil3.toBitmap 14 | import org.lineageos.twelve.models.Thumbnail 15 | 16 | suspend fun MediaMetadata.toThumbnail(context: Context) = Thumbnail.Builder() 17 | .setBitmap( 18 | artworkData?.let { 19 | val imageRequest = ImageRequest.Builder(context) 20 | .data(it) 21 | .allowHardware(false) 22 | .build() 23 | 24 | context.imageLoader.execute(imageRequest).image?.toBitmap() 25 | } 26 | ) 27 | .setUri(artworkUri) 28 | .setType(Thumbnail.Type.fromMedia3Value(artworkDataType)) 29 | .build() 30 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/NavController.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.os.Bundle 9 | import androidx.annotation.IdRes 10 | import androidx.navigation.NavController 11 | import androidx.navigation.NavOptions 12 | import androidx.navigation.Navigator 13 | 14 | fun NavController.navigateSafe( 15 | @IdRes id: Int, 16 | args: Bundle? = null, 17 | navOptions: NavOptions? = null, 18 | navigatorExtras: Navigator.Extras? = null, 19 | ) { 20 | currentDestination?.getAction(id)?.run { 21 | navigate(id, args, navOptions, navigatorExtras) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/Parcelable.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2023-2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.os.Build 9 | import android.os.Parcel 10 | import android.os.Parcelable 11 | import kotlin.reflect.KClass 12 | 13 | fun Parcel.readParcelable(clazz: KClass) = 14 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 15 | readParcelable(clazz.java.classLoader, clazz.java) 16 | } else { 17 | @Suppress("DEPRECATION") 18 | readParcelable(clazz.java.classLoader) 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/StorageManager.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024-2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.os.storage.StorageManager 9 | import android.os.storage.StorageVolume 10 | import kotlinx.coroutines.channels.awaitClose 11 | import kotlinx.coroutines.flow.callbackFlow 12 | import java.util.concurrent.Executors 13 | 14 | fun StorageManager.storageVolumesFlow() = callbackFlow { 15 | val update = { 16 | trySend(storageVolumes) 17 | } 18 | 19 | val storageVolumeCallback = object : StorageManager.StorageVolumeCallback() { 20 | override fun onStateChanged(volume: StorageVolume) { 21 | update() 22 | } 23 | } 24 | 25 | registerStorageVolumeCallback( 26 | Executors.newSingleThreadExecutor(), 27 | storageVolumeCallback 28 | ) 29 | 30 | update() 31 | 32 | awaitClose { 33 | unregisterStorageVolumeCallback(storageVolumeCallback) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/ext/Uri.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.ext 7 | 8 | import android.net.Uri 9 | 10 | /** 11 | * Check if this URI is relative to the given URI. 12 | * 13 | * @param other The URI to check against 14 | * @return True if this URI is relative to the given URI 15 | */ 16 | fun Uri.isRelativeTo(other: Uri): Boolean { 17 | if (scheme != other.scheme) { 18 | return false 19 | } 20 | 21 | if (authority != other.authority) { 22 | return false 23 | } 24 | 25 | val pathSegments = pathSegments 26 | val otherPathSegments = other.pathSegments 27 | 28 | if (pathSegments.size < otherPathSegments.size) { 29 | return false 30 | } 31 | 32 | for (i in otherPathSegments.indices) { 33 | if (pathSegments[i] != otherPathSegments[i]) { 34 | return false 35 | } 36 | } 37 | 38 | return true 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/fragments/MaterialDialogFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.fragments 7 | 8 | import android.app.Dialog 9 | import android.os.Bundle 10 | import androidx.annotation.LayoutRes 11 | import androidx.fragment.app.DialogFragment 12 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 13 | 14 | /** 15 | * A [DialogFragment] that uses [MaterialAlertDialogBuilder] to build the base dialog. 16 | */ 17 | abstract class MaterialDialogFragment( 18 | @LayoutRes contentLayoutId: Int, 19 | ) : DialogFragment(contentLayoutId) { 20 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = 21 | MaterialAlertDialogBuilder(requireContext()) 22 | .setView(onCreateView(layoutInflater, null, savedInstanceState)) 23 | .show() 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/models/ActivityTab.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.models 7 | 8 | data class ActivityTab( 9 | val id: String, 10 | val title: LocalizedString, 11 | val items: List>, 12 | ) : UniqueItem { 13 | override fun areItemsTheSame(other: ActivityTab) = id == other.id 14 | 15 | override fun areContentsTheSame(other: ActivityTab) = 16 | title == other.title && items.toTypedArray().contentEquals(other.items.toTypedArray()) 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/models/ArtistWorks.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.models 7 | 8 | /** 9 | * Whatever an artist has worked on. 10 | * 11 | * @param albums Albums released by the artist 12 | * @param appearsInAlbum Albums on which the artist appears 13 | * @param appearsInPlaylist Playlists on which the artist appears 14 | */ 15 | data class ArtistWorks( 16 | val albums: List, 17 | val appearsInAlbum: List, 18 | val appearsInPlaylist: List, 19 | ) 20 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/models/AudioOutputMode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024-2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.models 7 | 8 | import androidx.media3.common.util.UnstableApi 9 | import androidx.media3.exoplayer.audio.DefaultAudioSink 10 | 11 | /** 12 | * Audio output mode. 13 | */ 14 | @androidx.annotation.OptIn(UnstableApi::class) 15 | enum class AudioOutputMode(val media3OutputMode: @DefaultAudioSink.OutputMode Int) { 16 | /** 17 | * The audio sink plays PCM audio. 18 | */ 19 | PCM(DefaultAudioSink.OUTPUT_MODE_PCM), 20 | 21 | /** 22 | * The audio sink plays encoded audio in offload. 23 | */ 24 | OFFLOAD(DefaultAudioSink.OUTPUT_MODE_OFFLOAD), 25 | 26 | /** 27 | * The audio sink plays encoded audio in passthrough. 28 | */ 29 | PASSTHROUGH(DefaultAudioSink.OUTPUT_MODE_PASSTHROUGH); 30 | 31 | companion object { 32 | fun fromMedia3OutputMode( 33 | media3OutputMode: @DefaultAudioSink.OutputMode Int, 34 | ) = entries.firstOrNull { 35 | it.media3OutputMode == media3OutputMode 36 | } ?: throw Exception("Unknown output mode: $media3OutputMode") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/models/AudioStreamInformation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.models 7 | 8 | /** 9 | * Information regarding an audio stream. 10 | * 11 | * @param sampleRate The sample rate of the stream. 12 | * @param channelCount The channel count of the stream. 13 | * @param encoding The [Encoding] of the stream. 14 | */ 15 | data class AudioStreamInformation( 16 | val sampleRate: Int?, 17 | val channelCount: Int?, 18 | val encoding: Encoding?, 19 | ) 20 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/models/ColumnIndexCache.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024-2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.models 7 | 8 | import android.database.Cursor 9 | import androidx.core.database.getStringOrNull 10 | 11 | class ColumnIndexCache(private val cursor: Cursor) { 12 | private val indexMap = cursor.columnNames.associate { columnName -> 13 | columnName.lowercase().let { 14 | it to cursor.getColumnIndexOrThrow(it) 15 | } 16 | } 17 | 18 | fun getInt(columnName: String) = cursor.getInt(indexMap[columnName]!!) 19 | fun getLong(columnName: String) = cursor.getLong(indexMap[columnName]!!) 20 | fun getBoolean(columnName: String) = cursor.getInt(indexMap[columnName]!!) != 0 21 | fun getString(columnName: String): String = cursor.getString(indexMap[columnName]!!) 22 | fun getStringOrNull(columnName: String) = cursor.getStringOrNull(indexMap[columnName]!!) 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/models/DataSourceInformation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.models 7 | 8 | import org.lineageos.twelve.datasources.MediaDataSource 9 | 10 | /** 11 | * [MediaDataSource] information. 12 | * 13 | * @param key Unique key describing this information 14 | * @param keyLocalizedString The key's [LocalizedString] that will be shown to the user 15 | * @param value The value's [LocalizedString] that will be shown to the user 16 | */ 17 | data class DataSourceInformation( 18 | val key: String, 19 | val keyLocalizedString: LocalizedString, 20 | val value: LocalizedString, 21 | ) : UniqueItem { 22 | override fun areItemsTheSame(other: DataSourceInformation) = this.key == other.key 23 | 24 | override fun areContentsTheSame(other: DataSourceInformation) = 25 | this.keyLocalizedString.areContentsTheSame( 26 | other.keyLocalizedString 27 | ) && this.value.areContentsTheSame( 28 | other.value 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/models/Error.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024-2025 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.models 7 | 8 | /** 9 | * Generic errors definitions for operations result. 10 | */ 11 | enum class Error { 12 | /** 13 | * This feature isn't implemented. 14 | */ 15 | NOT_IMPLEMENTED, 16 | 17 | /** 18 | * I/O error, can also be network. 19 | */ 20 | IO, 21 | 22 | /** 23 | * Authentication error. 24 | */ 25 | AUTHENTICATION_REQUIRED, 26 | 27 | /** 28 | * Invalid credentials. 29 | */ 30 | INVALID_CREDENTIALS, 31 | 32 | /** 33 | * The item was not found. 34 | */ 35 | NOT_FOUND, 36 | 37 | /** 38 | * Value returned on write requests: The value already exists. 39 | */ 40 | ALREADY_EXISTS, 41 | 42 | /** 43 | * Response deserialization error. 44 | */ 45 | DESERIALIZATION, 46 | 47 | /** 48 | * The request was cancelled. 49 | */ 50 | CANCELLED, 51 | 52 | /** 53 | * The server returned an invalid response. 54 | */ 55 | INVALID_RESPONSE, 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/org/lineageos/twelve/models/GenreContent.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 The LineageOS Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.lineageos.twelve.models 7 | 8 | /** 9 | * Content related to a certain genre. 10 | * 11 | * @param appearsInAlbums Albums with audios related to this genre 12 | * @param appearsInPlaylists Playlists with audios related to this genre 13 | * @param audios Audios related to this genre 14 | */ 15 | data class GenreContent( 16 | val appearsInAlbums: List, 17 | val appearsInPlaylists: List, 18 | val audios: List