├── .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