├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── copyright │ ├── Apache_Version_2.xml │ └── profiles_settings.xml ├── encodings.xml ├── icon.png ├── inspectionProfiles │ └── Project_Default.xml ├── runConfigurations │ └── Search_Dependency_Updates.xml └── vcs.xml ├── LICENSE ├── PRIVACY.md ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro ├── sampledata │ └── songs.json ├── src │ ├── debug │ │ └── res │ │ │ └── values │ │ │ └── colors.xml │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── fr │ │ │ │ └── nihilus │ │ │ │ └── music │ │ │ │ ├── HomeActivity.kt │ │ │ │ ├── OdeonApplication.kt │ │ │ │ ├── dagger │ │ │ │ └── AppModule.kt │ │ │ │ ├── ui │ │ │ │ ├── MusicLibraryViewModel.kt │ │ │ │ └── nowplaying │ │ │ │ │ ├── NowPlayingFragment.kt │ │ │ │ │ ├── NowPlayingViewModel.kt │ │ │ │ │ └── ProgressAutoUpdater.kt │ │ │ │ └── view │ │ │ │ ├── BottomSheetHost.kt │ │ │ │ ├── FloatingPlayPauseButton.kt │ │ │ │ ├── PlayPauseButton.kt │ │ │ │ └── PlayPauseDelegate.kt │ │ ├── res │ │ │ ├── anim │ │ │ │ └── linear_out_slow_in.xml │ │ │ ├── color │ │ │ │ └── activation_state_list.xml │ │ │ ├── drawable-v26 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ │ ├── avd_pausetoplay.xml │ │ │ │ ├── avd_playtopause.xml │ │ │ │ ├── ic_chevron_down_24dp.xml │ │ │ │ ├── ic_chevron_up_24dp.xml │ │ │ │ ├── ic_level_repeat.xml │ │ │ │ ├── ic_repeat_24dp.xml │ │ │ │ ├── ic_repeat_one_24dp.xml │ │ │ │ ├── ic_skip_next_24dp.xml │ │ │ │ ├── ic_skip_previous_24dp.xml │ │ │ │ ├── level_chevron_up_down.xml │ │ │ │ └── level_play_pause.xml │ │ │ ├── layout-w600dp │ │ │ │ └── fragment_now_playing_top.xml │ │ │ ├── layout │ │ │ │ ├── activity_home.xml │ │ │ │ ├── fragment_now_playing.xml │ │ │ │ └── fragment_now_playing_top.xml │ │ │ ├── menu │ │ │ │ └── menu_main.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ └── ic_launcher.xml │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── navigation │ │ │ │ └── main_graph.xml │ │ │ ├── values-fr │ │ │ │ └── strings.xml │ │ │ ├── values-land │ │ │ │ └── dimens.xml │ │ │ ├── values-pl │ │ │ │ └── strings.xml │ │ │ └── values │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ └── web_hi_res_512.png │ └── staging │ │ └── res │ │ └── values │ │ └── colors.xml └── staging-rules.pro ├── build-logic ├── build.gradle.kts ├── gradle.properties ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── AndroidKotlin.kt │ ├── AndroidVersion.kt │ ├── JvmToolchain.kt │ └── odeon │ ├── plugins │ ├── AndroidApplicationConventionPlugin.kt │ ├── AndroidHiltConventionPlugin.kt │ ├── AndroidLibraryConventionPlugin.kt │ └── ComposeConventionPlugin.kt │ └── tasks │ └── RefreshMediaStore.kt ├── build.gradle.kts ├── core ├── common │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ └── src │ │ ├── androidTest │ │ └── kotlin │ │ │ └── fr │ │ │ └── nihilus │ │ │ └── music │ │ │ └── core │ │ │ └── provider │ │ │ └── IconProviderTest.kt │ │ ├── debug │ │ └── res │ │ │ ├── values-fr │ │ │ └── strings.xml │ │ │ └── values │ │ │ ├── flags.xml │ │ │ └── strings.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── fr │ │ │ │ └── nihilus │ │ │ │ └── music │ │ │ │ └── core │ │ │ │ ├── CommonModule.kt │ │ │ │ ├── collections │ │ │ │ ├── Collections.kt │ │ │ │ └── ListDiff.kt │ │ │ │ ├── context │ │ │ │ ├── AppCoroutineScope.kt │ │ │ │ ├── AppDispatchers.kt │ │ │ │ └── ExecutionContextModule.kt │ │ │ │ ├── files │ │ │ │ └── FileSize.kt │ │ │ │ ├── flow │ │ │ │ └── FlowMaterialize.kt │ │ │ │ ├── lifecycle │ │ │ │ └── ApplicationLifecycle.kt │ │ │ │ ├── media │ │ │ │ ├── MediaId.kt │ │ │ │ └── MediaItems.kt │ │ │ │ ├── os │ │ │ │ ├── Clock.kt │ │ │ │ ├── FileModule.kt │ │ │ │ └── FileSystem.kt │ │ │ │ ├── permissions │ │ │ │ ├── PermissionRepository.kt │ │ │ │ └── RuntimePermission.kt │ │ │ │ ├── playback │ │ │ │ └── RepeatMode.kt │ │ │ │ ├── provider │ │ │ │ ├── AndroidResourcesExt.kt │ │ │ │ └── IconProvider.kt │ │ │ │ └── settings │ │ │ │ ├── QueueReloadStrategy.kt │ │ │ │ ├── Settings.kt │ │ │ │ ├── SettingsModule.kt │ │ │ │ └── SharedPreferencesSettings.kt │ │ └── res │ │ │ ├── values-fr │ │ │ └── strings.xml │ │ │ ├── values-v29 │ │ │ └── preferences.xml │ │ │ └── values │ │ │ ├── flags.xml │ │ │ ├── preferences.xml │ │ │ ├── public.xml │ │ │ └── strings.xml │ │ └── test │ │ └── kotlin │ │ └── fr │ │ └── nihilus │ │ └── music │ │ └── core │ │ ├── collections │ │ └── DiffTest.kt │ │ ├── files │ │ └── FileSizeTest.kt │ │ ├── media │ │ └── MediaIdTest.kt │ │ ├── permissions │ │ └── PermissionRepositoryTest.kt │ │ └── settings │ │ └── SharedPreferencesSettingsTest.kt ├── compose │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── fr │ │ └── nihilus │ │ └── music │ │ └── core │ │ └── compose │ │ └── theme │ │ ├── Colors.kt │ │ ├── OdeonTheme.kt │ │ ├── Shapes.kt │ │ └── Typography.kt ├── database │ ├── build.gradle.kts │ ├── schemas │ │ └── fr.nihilus.music.core.database.AppDatabase │ │ │ ├── 1.json │ │ │ ├── 2.json │ │ │ ├── 3.json │ │ │ ├── 4.json │ │ │ ├── 5.json │ │ │ ├── 6.json │ │ │ └── 7.json │ └── src │ │ ├── androidTest │ │ └── kotlin │ │ │ └── fr │ │ │ └── nihilus │ │ │ └── music │ │ │ └── core │ │ │ └── database │ │ │ ├── DatabaseMigrationTest.kt │ │ │ └── playlists │ │ │ └── PlaylistDaoTest.kt │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── fr │ │ └── nihilus │ │ └── music │ │ └── core │ │ └── database │ │ ├── AppDatabase.kt │ │ ├── DatabaseMigration.kt │ │ ├── DatabaseModule.kt │ │ ├── SQLiteDatabaseModule.kt │ │ ├── exclusion │ │ ├── TrackExclusion.kt │ │ └── TrackExclusionDao.kt │ │ ├── playlists │ │ ├── Playlist.kt │ │ ├── PlaylistConverters.kt │ │ ├── PlaylistDao.kt │ │ └── PlaylistTrack.kt │ │ └── usage │ │ ├── MediaUsageEvent.kt │ │ └── UsageDao.kt ├── instrumentation │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── fr │ │ └── nihilus │ │ └── music │ │ └── core │ │ └── instrumentation │ │ ├── provider │ │ └── ProviderTestRule.kt │ │ └── runner │ │ └── HiltJUnitRunner.kt ├── testing │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── fr │ │ └── nihilus │ │ └── music │ │ └── core │ │ └── test │ │ ├── Assertions.kt │ │ ├── coroutines │ │ ├── CoroutineTestRule.kt │ │ └── flow │ │ │ └── Flows.kt │ │ └── os │ │ └── TestClock.kt └── ui │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ └── src │ ├── main │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── fr │ │ │ └── nihilus │ │ │ └── music │ │ │ └── core │ │ │ └── ui │ │ │ ├── ConfirmDialogFragment.kt │ │ │ ├── DurationFormatter.kt │ │ │ ├── Event.kt │ │ │ ├── ProgressTimeLatch.kt │ │ │ ├── UiStateFlows.kt │ │ │ ├── actions │ │ │ ├── DeleteTracksAction.kt │ │ │ ├── ExcludeTrackAction.kt │ │ │ └── ManagePlaylistAction.kt │ │ │ ├── base │ │ │ ├── BaseActivity.kt │ │ │ ├── BaseDialogFragment.kt │ │ │ ├── BaseFragment.kt │ │ │ ├── BaseHolder.kt │ │ │ └── ListAdapter.kt │ │ │ ├── client │ │ │ ├── BrowserClient.kt │ │ │ └── BrowserClientImpl.kt │ │ │ ├── dagger │ │ │ └── CoreUiModule.kt │ │ │ ├── extensions │ │ │ ├── Colors.kt │ │ │ ├── UiExtensions.kt │ │ │ └── ViewExtensions.kt │ │ │ ├── glide │ │ │ ├── GlideExtensions.kt │ │ │ ├── GlideModule.kt │ │ │ ├── SwitcherTarget.kt │ │ │ └── palette │ │ │ │ ├── AlbumArt.kt │ │ │ │ ├── AlbumArtCaching.kt │ │ │ │ └── AlbumPalette.kt │ │ │ ├── motion │ │ │ ├── MirrorView.kt │ │ │ ├── SharedFade.kt │ │ │ └── Stagger.kt │ │ │ └── view │ │ │ ├── DividerItemDecoration.kt │ │ │ └── RatioImageView.kt │ └── res │ │ ├── animator │ │ └── card_state_list.xml │ │ ├── drawable │ │ ├── ic_audiotrack_24dp.xml │ │ ├── ic_play_arrow_24dp.xml │ │ ├── ic_shuffle_24dp.xml │ │ ├── ui_ic_arrow_back_24dp.xml │ │ ├── ui_ic_clear_24dp.xml │ │ └── ui_ic_delete_24dp.xml │ │ ├── font │ │ ├── quicksand.ttf │ │ ├── quicksand_bold.ttf │ │ ├── quicksand_light.ttf │ │ ├── quicksand_medium.ttf │ │ └── quicksand_semibold.ttf │ │ ├── values-fr │ │ └── strings.xml │ │ ├── values-night-v29 │ │ └── themes.xml │ │ ├── values-night │ │ ├── colors.xml │ │ └── themes.xml │ │ ├── values-pl │ │ └── strings.xml │ │ ├── values-v29 │ │ └── colors.xml │ │ ├── values-w600dp │ │ └── dimens.xml │ │ └── values │ │ ├── attrs_album_palette.xml │ │ ├── attrs_ratioimageview.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ids.xml │ │ ├── layout.xml │ │ ├── motion.xml │ │ ├── public.xml │ │ ├── shape.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ ├── themes.xml │ │ └── typography.xml │ └── test │ └── kotlin │ └── fr │ └── nihilus │ └── music │ └── core │ └── ui │ ├── actions │ ├── DeleteTracksActionTest.kt │ ├── ExcludeTrackActionTest.kt │ └── ManagePlaylistActionTest.kt │ └── glide │ └── palette │ └── PrimaryHueFilterTest.kt ├── fastlane └── metadata │ └── android │ ├── en-US │ ├── changelogs │ │ ├── 200110.txt │ │ ├── 200204.txt │ │ ├── 200205.txt │ │ ├── 200206.txt │ │ └── 201005.txt │ ├── full_description.txt │ ├── images │ │ ├── icon.png │ │ └── phoneScreenshots │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ └── 5.png │ ├── short_description.txt │ └── title.txt │ └── fr-FR │ ├── changelogs │ ├── 200110.txt │ ├── 200204.txt │ ├── 200205.txt │ ├── 200206.txt │ └── 201005.txt │ ├── full_description.txt │ ├── short_description.txt │ └── title.txt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── screenshot_album_detail.png ├── screenshot_albums.png ├── screenshot_home.png └── screenshot_player.png ├── lint.xml ├── media ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── fr │ │ │ └── nihilus │ │ │ └── music │ │ │ └── media │ │ │ ├── MediaContent.kt │ │ │ ├── albums │ │ │ ├── Album.kt │ │ │ ├── AlbumLocalSource.kt │ │ │ └── AlbumRepository.kt │ │ │ ├── artists │ │ │ ├── Artist.kt │ │ │ ├── ArtistLocalSource.kt │ │ │ └── ArtistRepository.kt │ │ │ ├── browser │ │ │ ├── BrowserTree.kt │ │ │ ├── BrowserTreeImpl.kt │ │ │ ├── MediaSearchEngine.kt │ │ │ ├── MediaTreeDsl.kt │ │ │ ├── SearchQuery.kt │ │ │ └── provider │ │ │ │ ├── AlbumChildrenProvider.kt │ │ │ │ ├── ArtistChildrenProvider.kt │ │ │ │ ├── CategoryChildrenProvider.kt │ │ │ │ ├── ChildrenProvider.kt │ │ │ │ ├── ChildrenProviderModule.kt │ │ │ │ ├── PlaylistChildrenProvider.kt │ │ │ │ └── TrackChildrenProvider.kt │ │ │ ├── dagger │ │ │ └── MediaSourceModule.kt │ │ │ ├── playlists │ │ │ └── PlaylistRepository.kt │ │ │ ├── provider │ │ │ ├── ContentUris.kt │ │ │ ├── MediaStoreInternals.kt │ │ │ └── MediaStoreModule.kt │ │ │ ├── tracks │ │ │ ├── DeleteTracksResult.kt │ │ │ ├── Track.kt │ │ │ ├── TrackRepository.kt │ │ │ └── local │ │ │ │ ├── LocalTrack.kt │ │ │ │ └── TrackLocalSource.kt │ │ │ └── usage │ │ │ ├── DisposableTrack.kt │ │ │ ├── UsageManager.kt │ │ │ └── UsageManagerImpl.kt │ └── res │ │ ├── drawable │ │ ├── svc_ic_most_rated_128dp.xml │ │ └── svc_ic_most_recent_128dp.xml │ │ ├── values-fr │ │ └── strings.xml │ │ └── values │ │ ├── colors.xml │ │ └── strings.xml │ └── test │ └── kotlin │ └── fr │ └── nihilus │ └── music │ └── media │ ├── albums │ ├── AlbumFixtures.kt │ ├── AlbumLocalSourceTest.kt │ └── AlbumRepositoryTest.kt │ ├── artists │ ├── ArtistFixtures.kt │ ├── ArtistLocalSourceTest.kt │ └── ArtistRepositoryTest.kt │ ├── browser │ ├── BrowserTreeTest.kt │ ├── FakeChildrenProvider.kt │ ├── MediaContentFixtures.kt │ ├── MediaSearchEngineTest.kt │ ├── SampleMedia.kt │ ├── SamplePlaylists.kt │ ├── SearchQueryTest.kt │ └── provider │ │ ├── AlbumChildrenProviderTest.kt │ │ ├── ArtistChildrenProviderTest.kt │ │ ├── PlaylistChildrenProviderTest.kt │ │ └── TrackChildrenProviderTest.kt │ ├── playlists │ ├── PlaylistFixtures.kt │ └── PlaylistRepositoryTest.kt │ ├── provider │ ├── AudioMediaDatabase.kt │ ├── ContentResolverTestRule.kt │ └── FakeAudioMediaProvider.kt │ ├── tracks │ ├── TrackFixtures.kt │ ├── TrackRepositoryTest.kt │ └── local │ │ ├── LocalTracks.kt │ │ └── TrackLocalSourceTest.kt │ └── usage │ └── UsageManagerTest.kt ├── release └── debug.keystore ├── service ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── fr │ │ │ └── nihilus │ │ │ └── music │ │ │ └── service │ │ │ ├── BaseBrowserService.kt │ │ │ ├── CachingSubscriptionManager.kt │ │ │ ├── MediaSessionConnector.kt │ │ │ ├── MediaSessionModule.kt │ │ │ ├── MusicService.kt │ │ │ ├── MusicServiceModule.kt │ │ │ ├── PackageValidator.kt │ │ │ ├── PaginationOptions.kt │ │ │ ├── ServiceCoroutineScope.kt │ │ │ ├── SubscriptionManager.kt │ │ │ ├── extensions │ │ │ ├── GlideCoroutineExt.kt │ │ │ ├── MediaMetadataExt.kt │ │ │ └── PlaybackStateExt.kt │ │ │ ├── metadata │ │ │ ├── IconDownloader.kt │ │ │ └── MetadataProducer.kt │ │ │ ├── notification │ │ │ └── MediaNotificationBuilder.kt │ │ │ └── playback │ │ │ ├── AudioOnlyExtractorsFactory.kt │ │ │ ├── AudioOnlyRenderersFactory.kt │ │ │ ├── ErrorHandler.kt │ │ │ ├── MediaQueueManager.kt │ │ │ ├── OdeonPlaybackPreparer.kt │ │ │ └── PlaybackModule.kt │ └── res │ │ ├── drawable │ │ ├── svc_ic_blank_24.xml │ │ ├── svc_ic_pause_48dp.xml │ │ ├── svc_ic_play_arrow_48dp.xml │ │ ├── svc_ic_skip_next_36dp.xml │ │ ├── svc_ic_skip_previous_36dp.xml │ │ ├── svc_notif_pause.xml │ │ └── svc_notif_play_arrow.xml │ │ ├── values-fr │ │ └── strings.xml │ │ ├── values │ │ ├── strings.xml │ │ └── untranslatable.xml │ │ └── xml │ │ ├── svc_allowed_media_browser_callers.xml │ │ └── svc_automotive_app_desc.xml │ └── test │ └── kotlin │ └── fr │ └── nihilus │ └── music │ └── service │ ├── SubscriptionManagerTest.kt │ ├── TestBrowserTree.kt │ ├── TestTools.kt │ └── metadata │ └── MetadataProducerTest.kt ├── settings.gradle.kts └── ui ├── cleanup ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── sampledata │ ├── songs.json │ └── track_usage.json └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── fr │ │ └── nihilus │ │ └── music │ │ └── ui │ │ └── cleanup │ │ ├── CleanupActivity.kt │ │ ├── CleanupScreen.kt │ │ ├── CleanupState.kt │ │ ├── CleanupViewModel.kt │ │ ├── ConfirmDeleteDialog.kt │ │ └── TrackRow.kt │ └── res │ ├── navigation │ └── cleanup_graph.xml │ ├── values-fr │ ├── plurals.xml │ └── strings.xml │ ├── values-pl │ ├── plurals.xml │ └── strings.xml │ └── values │ ├── plurals.xml │ ├── public.xml │ └── strings.xml ├── library ├── build.gradle.kts ├── sampledata │ ├── albums.json │ ├── artists.json │ ├── playlists.json │ └── songs.json └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── fr │ │ └── nihilus │ │ └── music │ │ └── ui │ │ └── library │ │ ├── DeleteTracksConfirmation.kt │ │ ├── HomeFragment.kt │ │ ├── HomeViewModel.kt │ │ ├── albums │ │ ├── AlbumDetailFragment.kt │ │ ├── AlbumDetailUiState.kt │ │ ├── AlbumDetailViewModel.kt │ │ ├── AlbumGridUiState.kt │ │ ├── AlbumGridViewModel.kt │ │ ├── AlbumsAdapter.kt │ │ ├── AlbumsFragment.kt │ │ └── TrackAdapter.kt │ │ ├── artists │ │ ├── ArtistAdapter.kt │ │ ├── ArtistsFragment.kt │ │ ├── ArtistsScreenUiState.kt │ │ ├── ArtistsViewModel.kt │ │ └── detail │ │ │ ├── ArtistAlbumsAdapter.kt │ │ │ ├── ArtistDetailFragment.kt │ │ │ ├── ArtistDetailUiState.kt │ │ │ ├── ArtistDetailViewModel.kt │ │ │ └── ArtistTracksAdapter.kt │ │ ├── extensions │ │ └── ThemeAttributes.kt │ │ ├── playlists │ │ ├── AddToPlaylistDialog.kt │ │ ├── NewPlaylistDialog.kt │ │ ├── PlaylistDialogUiState.kt │ │ ├── PlaylistManagementViewModel.kt │ │ ├── PlaylistsAdapter.kt │ │ ├── PlaylistsFragment.kt │ │ ├── PlaylistsScreenUiState.kt │ │ ├── PlaylistsViewModel.kt │ │ └── details │ │ │ ├── PlaylistDetailFragment.kt │ │ │ ├── PlaylistDetailsUiState.kt │ │ │ ├── PlaylistDetailsViewModel.kt │ │ │ └── PlaylistTracksAdapter.kt │ │ ├── search │ │ ├── AlbumHolder.kt │ │ ├── ArtistHolder.kt │ │ ├── PlaylistHolder.kt │ │ ├── SearchFragment.kt │ │ ├── SearchResult.kt │ │ ├── SearchResultsAdapter.kt │ │ ├── SearchScreenUiState.kt │ │ ├── SearchViewModel.kt │ │ └── TrackHolder.kt │ │ └── tracks │ │ ├── AllTracksFragment.kt │ │ ├── DeleteTrackDialog.kt │ │ ├── TrackEvent.kt │ │ ├── TrackListAdapter.kt │ │ ├── TrackListUiState.kt │ │ └── TracksViewModel.kt │ └── res │ ├── drawable │ ├── currently_playing_decoration.xml │ ├── ic_album_24dp.xml │ ├── ic_block_24.xml │ ├── ic_overflow_24dp.xml │ ├── ic_person_24dp.xml │ ├── ic_playlist_24dp.xml │ ├── ic_playlist_add_24dp.xml │ ├── ic_search_24dp.xml │ ├── ic_settings_24dp.xml │ └── sad_panda.xml │ ├── layout │ ├── album_grid_item.xml │ ├── album_track_item.xml │ ├── artist_grid_item.xml │ ├── artist_track_item.xml │ ├── fragment_album_detail.xml │ ├── fragment_albums.xml │ ├── fragment_all_tracks.xml │ ├── fragment_artist_detail.xml │ ├── fragment_artists.xml │ ├── fragment_home.xml │ ├── fragment_playlist.xml │ ├── fragment_playlist_detail.xml │ ├── fragment_search.xml │ ├── item_search_suggestion.xml │ ├── new_playlist_input.xml │ ├── playlist_item.xml │ ├── playlist_list_item.xml │ ├── playlist_track_item.xml │ ├── section_header_item.xml │ └── song_list_item.xml │ ├── menu │ ├── menu_home.xml │ ├── menu_playlist_details.xml │ ├── menu_search.xml │ └── track_popup_menu.xml │ ├── navigation │ └── library_graph.xml │ ├── values-fr │ ├── plurals.xml │ └── strings.xml │ ├── values-pl │ ├── plurals.xml │ └── strings.xml │ ├── values-w600dp │ └── integers.xml │ └── values │ ├── dimens.xml │ ├── ids.xml │ ├── integers.xml │ ├── plurals.xml │ ├── public.xml │ ├── strings.xml │ └── styles.xml └── settings ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro └── src └── main ├── AndroidManifest.xml ├── kotlin └── fr │ └── nihilus │ └── music │ └── ui │ └── settings │ ├── MainPreferenceFragment.kt │ ├── SettingsActivity.kt │ └── exclusion │ ├── ExcludedTrackRow.kt │ ├── ExcludedTrackUiState.kt │ ├── ExcludedTracksFragment.kt │ ├── ExcludedTracksScreen.kt │ └── ExcludedTracksViewModel.kt └── res ├── layout └── activity_settings.xml ├── navigation └── settings_graph.xml ├── values-fr └── strings.xml ├── values-pl └── strings.xml ├── values ├── arrays.xml ├── public.xml └── strings.xml └── xml └── prefs_main.xml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize line endings to LF for all text files by default 2 | * text=auto 3 | # Enforce LF for shell scripts 4 | *.sh text eol=lf 5 | # Enforce CRLF for batch scripts 6 | *.bat text eol=crlf 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gradle 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 5 -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - develop 6 | pull_request: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v4 14 | 15 | - name: Setup JDK 16 | uses: actions/setup-java@v3 17 | with: 18 | java-version: '17' 19 | distribution: 'temurin' 20 | 21 | - uses: gradle/gradle-build-action@v2 22 | name: Build and check app 23 | with: 24 | arguments: app:assembleDebug testDebug lint 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Gradle 2 | .gradle 3 | build 4 | 5 | # Local only files 6 | local.properties 7 | captures 8 | mappings 9 | 10 | # IntelliJ 11 | *.iml 12 | 13 | # General 14 | .DS_Store 15 | .externalNativeBuild 16 | 17 | # JVM failure logs 18 | *.log 19 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all files in .idea directory 2 | /* 3 | 4 | #Except the following 5 | !/.gitignore 6 | !/codeStyles 7 | !/copyright 8 | !/inspectionProfiles 9 | !/encodings.xml 10 | !/icon.png 11 | !/vcs.xml 12 | !/runConfigurations 13 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/copyright/Apache_Version_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/.idea/icon.png -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Search_Dependency_Updates.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | true 20 | false 21 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | Odeon is an application I maintain on my spare time. It's not a corporate product that tries to make 4 | money using your personal data. It's goal is to be useful to its users, nothing more. 5 | 6 | The only data Odeon collects are to do its job: find, manage and play music files stored on your 7 | device's storage. Because Odeon does not interact with any remote server, any personal data used by 8 | the app is not shared with anyone else. 9 | 10 | ## Personal data that Odeon collects 11 | 12 | Here is an exhaustive list of data that Odeon collects. These data are only stored on the device and 13 | are not shared with other applications. 14 | 15 | - The list of all music files stored on the device's storage. This includes their title, artist 16 | name, album, and the date at which they were added to the device. 17 | - The number of times each file has been played. This information is used to determine which are 18 | your favorite songs. 19 | 20 | ## Notable integrations with the Android system 21 | 22 | In order to provide the best user-experience, Odeon relies on features of the Android system that 23 | may allow other apps to control playback and list available media. 24 | 25 | The Google Assistant and Android Auto are such applications. If you use one of these applications, 26 | they might collect information on your music library. 27 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /debug 3 | /staging 4 | /release -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # Uncomment this to preserve the line number information for 9 | # debugging stack traces. 10 | #-keepattributes SourceFile,LineNumberTable 11 | 12 | # If you keep the line number information, uncomment this to 13 | # hide the original source file name. 14 | #-renamesourcefileattribute SourceFile 15 | 16 | # Keep Glide definitions 17 | -keep public class * implements com.bumptech.glide.module.GlideModule 18 | -keep public class * extends com.bumptech.glide.module.AppGlideModule 19 | -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { 20 | **[] $VALUES; 21 | public *; 22 | } 23 | 24 | # Suppress warnings that occur during obfuscation. 25 | -dontwarn com.google.errorprone.annotations.* -------------------------------------------------------------------------------- /app/src/debug/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | @color/mtrl_orange_a200 20 | -------------------------------------------------------------------------------- /app/src/main/kotlin/fr/nihilus/music/OdeonApplication.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music 18 | 19 | import android.app.Application 20 | import android.os.StrictMode 21 | import com.google.android.material.color.DynamicColors 22 | import dagger.hilt.android.HiltAndroidApp 23 | import timber.log.Timber 24 | 25 | /** 26 | * An Android Application component that can inject dependencies into Activities and Services. 27 | * This class also performs general configuration tasks. 28 | */ 29 | @HiltAndroidApp 30 | class OdeonApplication : Application() { 31 | 32 | override fun onCreate() { 33 | super.onCreate() 34 | DynamicColors.applyToActivitiesIfAvailable(this) 35 | 36 | if (BuildConfig.DEBUG) { 37 | // Print logs to Logcat 38 | Timber.plant(Timber.DebugTree()) 39 | StrictMode.enableDefaults() 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/kotlin/fr/nihilus/music/dagger/AppModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.dagger 18 | 19 | import dagger.Module 20 | import dagger.Provides 21 | import dagger.hilt.InstallIn 22 | import dagger.hilt.components.SingletonComponent 23 | import fr.nihilus.music.BuildConfig 24 | import javax.inject.Named 25 | 26 | /** 27 | * The main module for this application. 28 | * It defines dependencies that cannot be instantiated with a constructor, 29 | * such as implementations for abstract types or calls to factory methods. 30 | * 31 | * All dependencies defined here can be used in both app modules: client and service. 32 | */ 33 | @Module 34 | @InstallIn(SingletonComponent::class) 35 | internal object AppModule { 36 | 37 | @Provides @Named("APP_VERSION_NAME") 38 | fun providesVersionName() = BuildConfig.VERSION_NAME 39 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/fr/nihilus/music/view/PlayPauseButton.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.view 18 | 19 | import android.content.Context 20 | import android.util.AttributeSet 21 | import androidx.appcompat.widget.AppCompatImageView 22 | 23 | /** 24 | * Extension of ImageView specialized for a play/pause button. 25 | * 26 | * The drawable displayed by this ImageView must be a LevelListDrawable with 2 levels: 27 | * - (0) is the drawable shown while playback is paused (play icon) 28 | * - (1) is the drawable shown while playing (pause icon) 29 | */ 30 | class PlayPauseButton 31 | @JvmOverloads constructor( 32 | context: Context, 33 | attrs: AttributeSet? = null, 34 | defStyleAttr: Int = 0 35 | ) : AppCompatImageView(context, attrs, defStyleAttr) { 36 | 37 | /** 38 | * Whether this button should display its "playing" state. 39 | */ 40 | var isPlaying: Boolean by PlayPauseDelegate() 41 | } -------------------------------------------------------------------------------- /app/src/main/res/anim/linear_out_slow_in.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/color/activation_state_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 21 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_chevron_down_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_chevron_up_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_level_repeat.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 22 | 25 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_repeat_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_skip_next_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_skip_previous_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/level_chevron_up_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 22 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/level_play_pause.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 22 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/navigation/main_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | Pochette d\'album 20 | Activer la lecture aléatoire 21 | Play ou pause 22 | Suivant 23 | Précédent 24 | Vous ne pourrez pas lire les morceaux stockées sur votre appareil à moins que vous n\'accordiez l\'autorisation d\'y accéder. 25 | Répéter la lecture 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/values-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 64dp 20 | -------------------------------------------------------------------------------- /app/src/main/res/values-pl/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Okładka albumu 19 | Włącz/wyłącz tryb losowy 20 | Odtwarzaj bądź pauzuj 21 | Przejdź do poprzedniego 22 | Przejdź do następnego 23 | Nie będziesz mógł odtwarzać utworów jeśli nie przyznasz aplikacji uprawnień do odczytu pamięci telefonu. 24 | Włącz/wyłącz tryb powtarzania 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | @color/mtrl_deep_purple_500 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 64dp 19 | 0dp 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Album art 19 | Toggle shuffle mode 20 | Play or pause 21 | Skip to previous 22 | Skip to next 23 | You won\'t be able to play tracks stored on the device\'s storage unless you grant permission to access it. 24 | Toggle repeat mode 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/web_hi_res_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/app/src/main/web_hi_res_512.png -------------------------------------------------------------------------------- /app/src/staging/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | @color/mtrl_orange_a200 20 | -------------------------------------------------------------------------------- /app/staging-rules.pro: -------------------------------------------------------------------------------- 1 | # Preserve the line number information for debugging stack traces. 2 | -keepattributes SourceFile,LineNumberTable 3 | 4 | # If you keep the line number information, uncomment this to 5 | # hide the original source file name. 6 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /build-logic/gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle properties are not passed to to included builds 2 | # See https://github.com/gradle/gradle/issues/2534 3 | org.gradle.caching=true 4 | org.gradle.configuration-cache=true 5 | -------------------------------------------------------------------------------- /build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:Suppress("UnstableApiUsage") 18 | 19 | rootProject.name = "build-logic" 20 | 21 | dependencyResolutionManagement { 22 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 23 | repositories { 24 | gradlePluginPortal() 25 | google() 26 | mavenCentral() 27 | } 28 | versionCatalogs { 29 | create("libs") { 30 | from(files("../gradle/libs.versions.toml")) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/AndroidVersion.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | internal object AndroidVersion { 18 | const val COMPILE = 34 19 | const val TARGET = 33 20 | const val MINIMUM = 24 21 | } 22 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/JvmToolchain.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.Project 2 | import org.gradle.api.plugins.JavaPluginExtension 3 | import org.gradle.jvm.toolchain.JavaLanguageVersion 4 | import org.gradle.kotlin.dsl.configure 5 | 6 | /* 7 | * Copyright 2023 Thibault Seisel 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | internal fun Project.configureJavaToolchain() { 23 | extensions.configure { 24 | toolchain.languageVersion.set(JavaLanguageVersion.of(11)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/common/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core/common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id("odeon.android.library") 19 | id("odeon.android.hilt") 20 | } 21 | 22 | android { 23 | namespace = "fr.nihilus.music.core" 24 | defaultConfig { 25 | testInstrumentationRunner = "fr.nihilus.music.core.instrumentation.runner.HiltJUnitRunner" 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation(libs.bundles.core) 31 | implementation(libs.androidx.lifecycle.process) 32 | 33 | testImplementation(libs.bundles.testing.unit) 34 | testImplementation(libs.androidx.lifecycle.runtime.testing) 35 | 36 | androidTestImplementation(projects.core.instrumentation) 37 | androidTestImplementation(libs.bundles.testing.instrumented) 38 | androidTestImplementation(libs.hilt.android.testing) 39 | kaptAndroidTest(libs.hilt.compiler) 40 | } 41 | -------------------------------------------------------------------------------- /core/common/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/core/common/consumer-rules.pro -------------------------------------------------------------------------------- /core/common/src/debug/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | Odéon Dev 20 | -------------------------------------------------------------------------------- /core/common/src/debug/res/values/flags.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | true 20 | -------------------------------------------------------------------------------- /core/common/src/debug/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | Odeon Dev 20 | fr.nihilus.music.debug.provider 21 | -------------------------------------------------------------------------------- /core/common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 21 | 25 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/fr/nihilus/music/core/collections/ListDiff.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.collections 18 | 19 | class ListDiff( 20 | val additions: List, 21 | val deletions: List 22 | ) { 23 | operator fun component1() = additions 24 | operator fun component2() = deletions 25 | } 26 | 27 | fun diffList( 28 | original: List, 29 | modified: List, 30 | equalizer: (a: E, b: E) -> Boolean = { a, b -> a == b } 31 | ): ListDiff { 32 | val additions = 33 | modified.filter { potentiallyAdded -> original.none { equalizer(it, potentiallyAdded) } } 34 | val removals = original.filter { potentiallyRemoved -> 35 | modified.none { 36 | equalizer( 37 | it, 38 | potentiallyRemoved 39 | ) 40 | } 41 | } 42 | return ListDiff(additions, removals) 43 | } 44 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/fr/nihilus/music/core/context/AppCoroutineScope.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.context 18 | 19 | import kotlinx.coroutines.CoroutineScope 20 | import javax.inject.Qualifier 21 | 22 | /** 23 | * Qualifier for a provided [CoroutineScope] that is active for the lifetime of the whole 24 | * application. 25 | */ 26 | @Qualifier 27 | @MustBeDocumented 28 | @Retention(AnnotationRetention.BINARY) 29 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD) 30 | annotation class AppCoroutineScope 31 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/fr/nihilus/music/core/flow/FlowMaterialize.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.flow 18 | 19 | import kotlinx.coroutines.flow.Flow 20 | import kotlinx.coroutines.flow.catch 21 | import kotlinx.coroutines.flow.map 22 | 23 | sealed class Materialized { 24 | class Value(val value: T) : Materialized() 25 | class Error(val error: Throwable) : Materialized() 26 | } 27 | 28 | fun Flow.materialize(): Flow> = this 29 | .map> { Materialized.Value(it) } 30 | .catch { emit(Materialized.Error(it)) } 31 | 32 | fun Flow>.dematerialize(): Flow = map { 33 | when (it) { 34 | is Materialized.Value -> it.value 35 | is Materialized.Error -> throw it.error 36 | } 37 | } -------------------------------------------------------------------------------- /core/common/src/main/kotlin/fr/nihilus/music/core/lifecycle/ApplicationLifecycle.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.lifecycle 18 | 19 | import javax.inject.Qualifier 20 | 21 | /** 22 | * Denotes that a provided value is tied to the lifecycle of the whole application. 23 | * This is currently only used to provide an instance of [androidx.lifecycle.LifecycleOwner]. 24 | */ 25 | @Qualifier 26 | @MustBeDocumented 27 | @Retention(AnnotationRetention.BINARY) 28 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD) 29 | annotation class ApplicationLifecycle 30 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/fr/nihilus/music/core/os/Clock.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.os 18 | 19 | import dagger.Reusable 20 | import javax.inject.Inject 21 | 22 | /** 23 | * A service that provides functions for reading and measuring time. 24 | */ 25 | interface Clock { 26 | 27 | /** 28 | * The current Epoch Time. 29 | * This is the number of seconds that have elapsed since `00:00:00 Thursday, 1 January 1970 UTC`. 30 | */ 31 | val currentEpochTime: Long 32 | } 33 | 34 | /** 35 | * Implementation of the [Clock] service that delegates to the device's system clock. 36 | */ 37 | @Reusable 38 | internal class DeviceClock @Inject constructor() : Clock { 39 | override val currentEpochTime: Long 40 | get() = System.currentTimeMillis() / 1000L 41 | } -------------------------------------------------------------------------------- /core/common/src/main/kotlin/fr/nihilus/music/core/provider/AndroidResourcesExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.provider 18 | 19 | import android.content.ContentResolver 20 | import android.content.res.Resources 21 | import android.net.Uri 22 | import androidx.annotation.DrawableRes 23 | 24 | /** 25 | * Retrieve the [Uri] of a given drawable resource. 26 | */ 27 | fun Resources.getResourceUri(@DrawableRes drawableResId: Int): Uri = Uri.Builder() 28 | .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) 29 | .authority(getResourcePackageName(drawableResId)) 30 | .appendPath(getResourceTypeName(drawableResId)) 31 | .appendPath(getResourceName(drawableResId)) 32 | .build() 33 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/fr/nihilus/music/core/settings/SettingsModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.settings 18 | 19 | import dagger.Binds 20 | import dagger.Module 21 | import dagger.hilt.InstallIn 22 | import dagger.hilt.components.SingletonComponent 23 | 24 | /** 25 | * Provides the implementation of the [Settings] store. 26 | */ 27 | @Module 28 | @InstallIn(SingletonComponent::class) 29 | @Suppress("unused") 30 | abstract class SettingsModule { 31 | 32 | @Binds 33 | internal abstract fun bindsSettings(settings: SharedPreferencesSettings): Settings 34 | } 35 | -------------------------------------------------------------------------------- /core/common/src/main/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | Odéon 20 | -------------------------------------------------------------------------------- /core/common/src/main/res/values-v29/preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | @string/pref_theme_system_value 21 | -------------------------------------------------------------------------------- /core/common/src/main/res/values/flags.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | false 20 | -------------------------------------------------------------------------------- /core/common/src/main/res/values/public.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /core/common/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Odeon 19 | fr.nihilus.music.provider 20 | 21 | -------------------------------------------------------------------------------- /core/compose/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/compose/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id("odeon.android.library") 19 | id("odeon.android.compose") 20 | } 21 | 22 | android { 23 | namespace = "fr.nihilus.music.core.compose" 24 | } 25 | 26 | dependencies { 27 | implementation(projects.core.ui) 28 | api(libs.compose.material3) 29 | } 30 | -------------------------------------------------------------------------------- /core/compose/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /core/compose/src/main/kotlin/fr/nihilus/music/core/compose/theme/Shapes.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.compose.theme 18 | 19 | import androidx.compose.foundation.shape.RoundedCornerShape 20 | import androidx.compose.material3.Shapes 21 | import androidx.compose.ui.unit.dp 22 | 23 | internal val OdeonShapes = Shapes( 24 | small = RoundedCornerShape(4.dp), 25 | medium = RoundedCornerShape(8.dp), 26 | large = RoundedCornerShape(16.dp) 27 | ) 28 | -------------------------------------------------------------------------------- /core/database/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /core/database/src/main/kotlin/fr/nihilus/music/core/database/playlists/PlaylistConverters.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.database.playlists 18 | 19 | import android.net.Uri 20 | import androidx.core.net.toUri 21 | import androidx.room.TypeConverter 22 | 23 | internal class PlaylistConverters { 24 | 25 | @TypeConverter 26 | fun fromString(str: String?): Uri? = str?.toUri() 27 | 28 | @TypeConverter 29 | fun toUriString(uri: Uri?): String? = uri?.toString() 30 | } 31 | -------------------------------------------------------------------------------- /core/instrumentation/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core/instrumentation/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id("odeon.android.library") 19 | } 20 | 21 | android { 22 | namespace = "fr.nihilus.music.core.instrumentation" 23 | useLibrary("android.test.mock") 24 | } 25 | 26 | dependencies { 27 | implementation(libs.androidx.test.runner) 28 | implementation(libs.androidx.test.rules) 29 | implementation(libs.hilt.android.testing) 30 | } 31 | -------------------------------------------------------------------------------- /core/instrumentation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /core/instrumentation/src/main/kotlin/fr/nihilus/music/core/instrumentation/runner/HiltJUnitRunner.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.instrumentation.runner 18 | 19 | import android.app.Application 20 | import android.content.Context 21 | import androidx.test.runner.AndroidJUnitRunner 22 | import dagger.hilt.android.testing.HiltTestApplication 23 | 24 | /** 25 | * A custom test runner that replaces the default test [Application] with [HiltTestApplication]. 26 | * This is necessary so that singleton-scoped dependencies could be injected. 27 | */ 28 | class HiltJUnitRunner : AndroidJUnitRunner() { 29 | override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application = 30 | super.newApplication(cl, HiltTestApplication::class.java.name, context) 31 | } 32 | -------------------------------------------------------------------------------- /core/testing/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core/testing/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id("odeon.android.library") 19 | } 20 | 21 | android { 22 | namespace = "fr.nihilus.music.core.test" 23 | } 24 | 25 | dependencies { 26 | implementation(projects.core.common) 27 | implementation(libs.bundles.testing.unit) 28 | } 29 | -------------------------------------------------------------------------------- /core/testing/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /core/testing/src/main/kotlin/fr/nihilus/music/core/test/Assertions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.test 18 | 19 | import org.junit.AssumptionViolatedException 20 | 21 | /** 22 | * Make the current test fail. 23 | * @param message Description of the assertion that failed. 24 | */ 25 | fun fail(message: String): Nothing { 26 | throw AssertionError(message) 27 | } 28 | 29 | /** 30 | * Denote that the current test is not relevant because an assumption is invalid. 31 | * @param message Description of the assumption that failed. 32 | */ 33 | fun failAssumption(message: String): Nothing { 34 | throw AssumptionViolatedException(message) 35 | } 36 | 37 | /** 38 | * Replaces the body of a function that should not be called from test code. 39 | */ 40 | fun stub(): Nothing = fail("Unexpected call to a stubbed method.") -------------------------------------------------------------------------------- /core/testing/src/main/kotlin/fr/nihilus/music/core/test/coroutines/flow/Flows.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.test.coroutines.flow 18 | 19 | import kotlinx.coroutines.awaitCancellation 20 | import kotlinx.coroutines.flow.Flow 21 | import kotlinx.coroutines.flow.flow 22 | 23 | /** 24 | * Creates a flow that produces values from the specified vararg-arguments, 25 | * then suspends indefinitely after emitting all elements. 26 | * 27 | * This is useful when simulating flows that occasionally emit elements but never terminates 28 | * such as a hot stream of events. 29 | * 30 | * @param elements The elements that are immediately emitted by the resulting flow. 31 | */ 32 | fun infiniteFlowOf(vararg elements: T): Flow = flow { 33 | for (element in elements) { 34 | emit(element) 35 | } 36 | 37 | awaitCancellation() 38 | } 39 | -------------------------------------------------------------------------------- /core/testing/src/main/kotlin/fr/nihilus/music/core/test/os/TestClock.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.test.os 18 | 19 | import fr.nihilus.music.core.os.Clock 20 | 21 | /** 22 | * A [Clock] implementation used for testing. 23 | * This clock is stopped: its [current time][currentEpochTime] is fixed and can only be updated manually. 24 | * For consistency reasons, you cannot go back in time. 25 | * 26 | * @param startTime The initial epoch time. Should be positive or zero. 27 | */ 28 | class TestClock(startTime: Long) : Clock { 29 | init { 30 | require(startTime >= 0L) { "Invalid epoch time: $startTime" } 31 | } 32 | 33 | override var currentEpochTime: Long = startTime 34 | set(value) { 35 | require(value >= field) { "Attempt to go back in time!" } 36 | field = value 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /core/ui/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core/ui/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id("odeon.android.library") 19 | id("odeon.android.hilt") 20 | alias(libs.plugins.ksp) 21 | } 22 | 23 | android { 24 | namespace = "fr.nihilus.music.core.ui" 25 | buildFeatures.buildConfig = true 26 | } 27 | 28 | dependencies { 29 | implementation(projects.core.common) 30 | implementation(projects.core.database) 31 | implementation(projects.media) 32 | 33 | implementation(libs.bundles.core) 34 | implementation(libs.bundles.android.ui) 35 | implementation(libs.bundles.androidx.lifecycle) 36 | implementation(libs.androidx.media) 37 | implementation(libs.identikon) 38 | 39 | ksp(libs.glide.ksp) 40 | 41 | testImplementation(projects.core.testing) 42 | testImplementation(libs.bundles.testing.unit) 43 | 44 | androidTestImplementation(libs.bundles.testing.instrumented) 45 | } 46 | -------------------------------------------------------------------------------- /core/ui/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/core/ui/consumer-rules.pro -------------------------------------------------------------------------------- /core/ui/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /core/ui/src/main/kotlin/fr/nihilus/music/core/ui/actions/ExcludeTrackAction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.ui.actions 18 | 19 | import fr.nihilus.music.core.media.MediaId 20 | import fr.nihilus.music.media.tracks.TrackRepository 21 | import javax.inject.Inject 22 | 23 | /** 24 | * An action to exclude tracks from the music library. 25 | * A track marked as excluded is no longer part of media collections. 26 | */ 27 | class ExcludeTrackAction @Inject constructor( 28 | private val repository: TrackRepository, 29 | ) { 30 | suspend operator fun invoke(trackMediaId: MediaId) { 31 | val trackId = requireNotNull(trackMediaId.track) { 32 | "Attempt to exclude a media that's not a track: $trackMediaId" 33 | } 34 | repository.excludeTrack(trackId) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/ui/src/main/kotlin/fr/nihilus/music/core/ui/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.ui.base 18 | 19 | import androidx.appcompat.app.AppCompatActivity 20 | 21 | /** 22 | * Base Activity class. 23 | * Makes it easier to add common behavior to all activities. 24 | */ 25 | abstract class BaseActivity : AppCompatActivity() -------------------------------------------------------------------------------- /core/ui/src/main/kotlin/fr/nihilus/music/core/ui/base/BaseDialogFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.ui.base 18 | 19 | import androidx.appcompat.app.AppCompatDialogFragment 20 | 21 | abstract class BaseDialogFragment : AppCompatDialogFragment() -------------------------------------------------------------------------------- /core/ui/src/main/kotlin/fr/nihilus/music/core/ui/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.ui.base 18 | 19 | import androidx.annotation.ContentView 20 | import androidx.annotation.LayoutRes 21 | import androidx.fragment.app.Fragment 22 | 23 | /** 24 | * Base Fragment class. 25 | * Makes it easier to add common behavior to all fragments. 26 | */ 27 | abstract class BaseFragment : Fragment { 28 | 29 | /** No-arg Fragment constructor. */ 30 | constructor() : super() 31 | 32 | /** 33 | * Alternate constructor that can be used to provide a default layout that will be inflated by [onCreateView]. 34 | * @param contentLayoutId identifier of a layout resource to be inflated as this fragment's view. 35 | */ 36 | @ContentView 37 | constructor(@LayoutRes contentLayoutId: Int) : super(contentLayoutId) 38 | } -------------------------------------------------------------------------------- /core/ui/src/main/kotlin/fr/nihilus/music/core/ui/dagger/CoreUiModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.ui.dagger 18 | 19 | import dagger.Binds 20 | import dagger.Module 21 | import dagger.hilt.InstallIn 22 | import dagger.hilt.components.SingletonComponent 23 | import fr.nihilus.music.core.ui.client.BrowserClient 24 | import fr.nihilus.music.core.ui.client.BrowserClientImpl 25 | 26 | /** 27 | * Provides an implementation of [BrowserClient] that connects to MusicService 28 | * to read media hierarchy and send playback commands. 29 | */ 30 | @Module 31 | @InstallIn(SingletonComponent::class) 32 | @Suppress("unused") 33 | abstract class CoreUiModule { 34 | 35 | @Binds 36 | internal abstract fun bindsBrowserClient(impl: BrowserClientImpl): BrowserClient 37 | } 38 | -------------------------------------------------------------------------------- /core/ui/src/main/kotlin/fr/nihilus/music/core/ui/glide/palette/AlbumArt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.core.ui.glide.palette 18 | 19 | import android.graphics.Bitmap 20 | 21 | data class AlbumArt(val bitmap: Bitmap, val palette: AlbumPalette) -------------------------------------------------------------------------------- /core/ui/src/main/res/animator/card_state_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ic_audiotrack_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ic_play_arrow_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 24 | 25 | 29 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ic_shuffle_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ui_ic_arrow_back_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ui_ic_clear_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ui_ic_delete_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /core/ui/src/main/res/font/quicksand.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/core/ui/src/main/res/font/quicksand.ttf -------------------------------------------------------------------------------- /core/ui/src/main/res/font/quicksand_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/core/ui/src/main/res/font/quicksand_bold.ttf -------------------------------------------------------------------------------- /core/ui/src/main/res/font/quicksand_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/core/ui/src/main/res/font/quicksand_light.ttf -------------------------------------------------------------------------------- /core/ui/src/main/res/font/quicksand_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/core/ui/src/main/res/font/quicksand_medium.ttf -------------------------------------------------------------------------------- /core/ui/src/main/res/font/quicksand_semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/core/ui/src/main/res/font/quicksand_semibold.ttf -------------------------------------------------------------------------------- /core/ui/src/main/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | OK 20 | Annuler 21 | Supprimer 22 | Paramètres 23 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values-night-v29/themes.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 23 | 24 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | #1DFFFFFF 20 | 21 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values-pl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | OK 20 | Anuluj 21 | Usuń 22 | Ustawienia 23 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values-v29/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | @android:color/transparent 20 | @android:color/transparent 21 | 22 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values-w600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/attrs_album_palette.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/attrs_ratioimageview.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 8dp 20 | 8dp 21 | @dimen/grid_2 22 | 23 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 23 | 8dp 24 | 16dp 25 | 24dp 26 | 32dp 27 | 28 | 29 | 4dp 30 | 12dp 31 | 32 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/motion.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 300 20 | 225 21 | 175 22 | 23 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | OK 20 | Cancel 21 | Delete 22 | Settings 23 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/200110.txt: -------------------------------------------------------------------------------- 1 | * Better user experience with tabs instead of Navigation Drawer menu. 2 | * It's now easier to find media with the new search bar. You could also ask the Google Assistant with voice commands such as "Play Daft Punk on Odeon". 3 | * Free some device space by listing and deleting songs that you may have not listened for some time. -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/200204.txt: -------------------------------------------------------------------------------- 1 | * Fix display of album artworks in the whole application on Android 10. -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/200205.txt: -------------------------------------------------------------------------------- 1 | * Polish translation (thanks to @Atrate). 2 | * Fix a bug where the shuffle mode toggle would not work. -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/200206.txt: -------------------------------------------------------------------------------- 1 | * Fix a crash when deleting tracks from search results 2 | * Fix a potential crash when updating notification with a track with no album art 3 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/201005.txt: -------------------------------------------------------------------------------- 1 | * Full supports for Android 10. 2 | * Odeon can now play M4A and FLAC files (the latter only on Android Oreo and newer). 3 | * App is now movable to SD card. 4 | * A more pleasant and ergonomic search screen. Result are now grouped by media category. 5 | * A lot of visual improvements for a more friendly look. 6 | * And of course a lot of bug fixes! 7 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | Odeon has all features you'd expect from a music player in a clean and simple interface: 2 |
    3 |
  • Browse your music by title, artists and albums.
  • 4 |
  • Control playback via the interface, the lock screen or headset buttons.
  • 5 |
  • Create and edit playlists.
  • 6 |
  • Play tracks that have been added recently or the ones you like the most.
  • 7 |
  • Delete tracks you like to free-up the device's storage.
  • 8 |
  • Control your music safely while driving with Android Auto.
  • 9 |
  • Tell the Google Assistant what to play.
  • 10 |
  • Enable Night Mode to reduce eye-strain and save your battery life.
  • 11 |
-------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Play and manage locally stored music files -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/title.txt: -------------------------------------------------------------------------------- 1 | Odeon -------------------------------------------------------------------------------- /fastlane/metadata/android/fr-FR/changelogs/200110.txt: -------------------------------------------------------------------------------- 1 | * Passez plus rapidement des chansons aux artistes grâce à la nouvelle présentation de la page d'accueil sous forme d'onglets. 2 | * Trouvez plus rapidement votre morceau préférée grâce à la barre de recherche. Vous pouvez même demander à Google Assistant de jouer un morceau pour vous ! Dites "Joue du Daft Punk sur Odéon" ! 3 | * Vous avez beaucoup de morceaux mais ne savez pas lesquels vous n'écoutez plus ? Vous pouvez désormais les trouver et les supprimer avec la nouvelle fonctionnalité "Nettoyage". -------------------------------------------------------------------------------- /fastlane/metadata/android/fr-FR/changelogs/200204.txt: -------------------------------------------------------------------------------- 1 | * Correction d'un bug d'affichage des pochettes d'album sur Android 10. -------------------------------------------------------------------------------- /fastlane/metadata/android/fr-FR/changelogs/200205.txt: -------------------------------------------------------------------------------- 1 | * Traduction de l'application en Polonais 2 | * On a réparé le bouton pour activer/désactiver la lecture aléatoire ! -------------------------------------------------------------------------------- /fastlane/metadata/android/fr-FR/changelogs/200206.txt: -------------------------------------------------------------------------------- 1 | * Correction d'un plantage après suppression d'un morceau depuis les résultats de recherche 2 | * Correction d'un plantage potentiel à la mise à jour de la notification avec un morceau sans pochette d'album 3 | -------------------------------------------------------------------------------- /fastlane/metadata/android/fr-FR/changelogs/201005.txt: -------------------------------------------------------------------------------- 1 | * L'application est maintenant adaptée pour Android 10. 2 | * Odeon peut maintenant lire les fichiers M4A et FLAC (sur Android Oreo et supérieur). 3 | * Possibilité de déplacer l'application sur la carte SD. 4 | * L'écran de recherche et maintenant bien plus agréable. Les résultats sont regroupés par catégorie. 5 | * De nombreuses améliorations graphiques pour un look plus décontracté. 6 | * Et bien sûr, des corrections de bug! 7 | -------------------------------------------------------------------------------- /fastlane/metadata/android/fr-FR/full_description.txt: -------------------------------------------------------------------------------- 1 | Odeon apporte toutes les fonctionnalités essentielles d'un lecteur musical dans une interface simple et épurée : 2 |
    3 |
  • Listez vos morceaux par titre, artiste et album.
  • 4 |
  • Contrôler la lecture via l'application, l'écran de verrouillage ou votre casque audio.
  • 5 |
  • Créez vos propres listes de lecture.
  • 6 |
  • Ecoutez les morceaux que vous avez ajouté recemment ou ceux que vous aimez le plus.
  • 7 |
  • Supprimez les morceaux que vous n'écoutez plus pour faire de la place sur votre appareil.
  • 8 |
  • Contrôlez votre musique en conduisant avec Android Auto.
  • 9 |
  • Dites à l'Assistant Google ce que vous voulez jouer.
  • 10 |
  • Utilisez le thème sombre pour reposer vos yeux et augmenter la durée de vie de la batterie.
  • 11 |
-------------------------------------------------------------------------------- /fastlane/metadata/android/fr-FR/short_description.txt: -------------------------------------------------------------------------------- 1 | Ecouter et gérer les morceaux de musique stockés sur l'appareil -------------------------------------------------------------------------------- /fastlane/metadata/android/fr-FR/title.txt: -------------------------------------------------------------------------------- 1 | Odéon -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /images/screenshot_album_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/images/screenshot_album_detail.png -------------------------------------------------------------------------------- /images/screenshot_albums.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/images/screenshot_albums.png -------------------------------------------------------------------------------- /images/screenshot_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/images/screenshot_home.png -------------------------------------------------------------------------------- /images/screenshot_player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/images/screenshot_player.png -------------------------------------------------------------------------------- /lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /media/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /media/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id("odeon.android.library") 19 | id("odeon.android.hilt") 20 | } 21 | 22 | android { 23 | namespace = "fr.nihilus.music.media" 24 | } 25 | 26 | dependencies { 27 | implementation(projects.core.common) 28 | implementation(projects.core.database) 29 | implementation(libs.bundles.core) 30 | implementation(libs.identikon) 31 | 32 | testImplementation(projects.core.testing) 33 | testImplementation(libs.bundles.testing.unit) 34 | } 35 | -------------------------------------------------------------------------------- /media/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | # Proguard rules to be included in the final application. -------------------------------------------------------------------------------- /media/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 21 | 24 | 28 | 29 | -------------------------------------------------------------------------------- /media/src/main/kotlin/fr/nihilus/music/media/artists/Artist.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.media.artists 18 | 19 | /** 20 | * The metadata of an artist that produced tracks that are saved on the device's storage. 21 | */ 22 | data class Artist( 23 | /** 24 | * The unique identifier of this artist on the device's media storage index. 25 | */ 26 | val id: Long, 27 | /** 28 | * The full name of this artist as it should be displayed to the user. 29 | */ 30 | val name: String, 31 | /** 32 | * The number of album this artist as recorded. 33 | */ 34 | val albumCount: Int, 35 | /** 36 | * The number of tracks that have been produced by this artist, all albums combined. 37 | */ 38 | val trackCount: Int, 39 | /** 40 | * An optional Uri-formatted String pointing to an artwork representing this artist. 41 | */ 42 | val iconUri: String? 43 | ) -------------------------------------------------------------------------------- /media/src/main/kotlin/fr/nihilus/music/media/dagger/MediaSourceModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.media.dagger 18 | 19 | import dagger.Binds 20 | import dagger.Module 21 | import dagger.hilt.InstallIn 22 | import dagger.hilt.components.SingletonComponent 23 | import fr.nihilus.music.media.browser.BrowserTree 24 | import fr.nihilus.music.media.browser.BrowserTreeImpl 25 | import fr.nihilus.music.media.usage.UsageManager 26 | import fr.nihilus.music.media.usage.UsageManagerImpl 27 | 28 | @Module 29 | @InstallIn(SingletonComponent::class) 30 | internal abstract class MediaSourceModule { 31 | 32 | @Binds 33 | abstract fun bindsBrowserTree(impl: BrowserTreeImpl): BrowserTree 34 | 35 | @Binds 36 | abstract fun bindsUsageManager(impl: UsageManagerImpl): UsageManager 37 | } 38 | -------------------------------------------------------------------------------- /media/src/main/kotlin/fr/nihilus/music/media/provider/MediaStoreModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.media.provider 18 | 19 | import android.content.ContentResolver 20 | import android.content.Context 21 | import dagger.Module 22 | import dagger.Provides 23 | import dagger.hilt.InstallIn 24 | import dagger.hilt.android.qualifiers.ApplicationContext 25 | import dagger.hilt.components.SingletonComponent 26 | 27 | /** 28 | * Provides an instance of [ContentResolver]. 29 | */ 30 | @Module 31 | @InstallIn(SingletonComponent::class) 32 | internal object MediaStoreModule { 33 | 34 | @Provides 35 | fun providesContentResolver( 36 | @ApplicationContext context: Context 37 | ): ContentResolver = context.contentResolver 38 | } 39 | -------------------------------------------------------------------------------- /media/src/main/kotlin/fr/nihilus/music/media/usage/DisposableTrack.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.media.usage 18 | 19 | import fr.nihilus.music.core.files.FileSize 20 | 21 | /** 22 | * Information on a track that could be deleted from the device's storage to free-up space. 23 | * 24 | * @property trackId Unique identifier of the related track. 25 | * @property title The display title of the related track. 26 | * @property fileSize The size of the file stored on the device's storage. 27 | * @property lastPlayedTime The epoch time at which that track has been played for the last time, 28 | * or `null` if it has never been played. 29 | */ 30 | data class DisposableTrack( 31 | val trackId: Long, 32 | val title: String, 33 | val fileSize: FileSize, 34 | val lastPlayedTime: Long? 35 | ) 36 | -------------------------------------------------------------------------------- /media/src/main/res/drawable/svc_ic_most_rated_128dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 23 | 26 | 27 | 32 | 33 | -------------------------------------------------------------------------------- /media/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | #9575cd 20 | #FFD54F 21 | -------------------------------------------------------------------------------- /media/src/test/kotlin/fr/nihilus/music/media/browser/SamplePlaylists.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.media.browser 18 | 19 | import fr.nihilus.music.core.database.playlists.Playlist 20 | import fr.nihilus.music.core.database.playlists.PlaylistTrack 21 | 22 | internal val SAMPLE_PLAYLISTS = listOf( 23 | Playlist(1, "Zen", 1551434321, null), 24 | Playlist(2, "Sport", 1551435123, null), 25 | Playlist(3, "Metal", 1551436125, null) 26 | ) 27 | 28 | internal val SAMPLE_MEMBERS = listOf( 29 | PlaylistTrack(1L, 309L), 30 | PlaylistTrack(2L, 477L), 31 | PlaylistTrack(2L, 48L), 32 | PlaylistTrack(2L, 125L), 33 | PlaylistTrack(3L, 75L), 34 | PlaylistTrack(3L, 161L) 35 | ) 36 | -------------------------------------------------------------------------------- /media/src/test/kotlin/fr/nihilus/music/media/playlists/PlaylistFixtures.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.media.playlists 18 | 19 | import androidx.core.net.toUri 20 | import fr.nihilus.music.core.database.playlists.Playlist 21 | 22 | internal val PLAYLIST_FAVORITES = Playlist( 23 | id = 1, 24 | title = "My Favorites", 25 | created = 1551434321, 26 | iconUri = "content://fr.nihilus.music.test.provider/icons/my_favorites.png".toUri() 27 | ) 28 | 29 | internal val PLAYLIST_ZEN = Playlist( 30 | id = 2, 31 | title = "Zen", 32 | created = 1551435123, 33 | iconUri = null 34 | ) 35 | 36 | internal val PLAYLIST_SPORT = Playlist( 37 | id = 3, 38 | title = "Sport", 39 | created = 1551436125, 40 | iconUri = null 41 | ) -------------------------------------------------------------------------------- /release/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/release/debug.keystore -------------------------------------------------------------------------------- /service/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /service/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id("odeon.android.library") 19 | id("odeon.android.hilt") 20 | } 21 | 22 | android { 23 | namespace = "fr.nihilus.music.service" 24 | buildFeatures.buildConfig = true 25 | } 26 | 27 | dependencies { 28 | implementation(projects.core.common) 29 | implementation(projects.core.database) 30 | implementation(projects.media) 31 | 32 | implementation(libs.bundles.core) 33 | implementation(libs.androidx.media) 34 | implementation(libs.androidx.media3.exoplayer) 35 | implementation(libs.glide) 36 | 37 | testImplementation(projects.core.testing) 38 | testImplementation(libs.bundles.testing.unit) 39 | 40 | androidTestImplementation(libs.bundles.testing.instrumented) 41 | } 42 | -------------------------------------------------------------------------------- /service/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/service/consumer-rules.pro -------------------------------------------------------------------------------- /service/src/main/kotlin/fr/nihilus/music/service/ServiceCoroutineScope.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.service 18 | 19 | import javax.inject.Qualifier 20 | 21 | @Qualifier 22 | @MustBeDocumented 23 | @Retention(AnnotationRetention.BINARY) 24 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD) 25 | annotation class ServiceCoroutineScope 26 | -------------------------------------------------------------------------------- /service/src/main/res/drawable/svc_ic_blank_24.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /service/src/main/res/drawable/svc_ic_pause_48dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /service/src/main/res/drawable/svc_ic_play_arrow_48dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /service/src/main/res/drawable/svc_ic_skip_next_36dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /service/src/main/res/drawable/svc_ic_skip_previous_36dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /service/src/main/res/drawable/svc_notif_pause.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 24 | 25 | 28 | 29 | -------------------------------------------------------------------------------- /service/src/main/res/drawable/svc_notif_play_arrow.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /service/src/main/res/values/untranslatable.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | Caller has a valid certificate, but its package doesn\'t match any expected package for the 22 | given certificate. To allow this caller, add the following to the allowed callers list:\n 23 | \n\t%3$s\n\n 25 | ]]> 26 | 27 | -------------------------------------------------------------------------------- /service/src/main/res/xml/svc_automotive_app_desc.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 18 | 19 | pluginManagement { 20 | repositories { 21 | gradlePluginPortal() 22 | google() 23 | mavenCentral() 24 | } 25 | } 26 | 27 | @Suppress("UnstableApiUsage") 28 | dependencyResolutionManagement { 29 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 30 | repositories { 31 | google() 32 | mavenCentral() 33 | } 34 | } 35 | 36 | rootProject.name = "android-odeon" 37 | includeBuild("build-logic") 38 | include(":app") 39 | include(":core:common", ":core:testing", ":core:database", ":core:ui", ":core:compose", ":core:instrumentation", ":media") 40 | include(":service") 41 | include(":ui:cleanup", ":ui:settings", ":ui:library") 42 | -------------------------------------------------------------------------------- /ui/cleanup/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ui/cleanup/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id("odeon.android.library") 19 | id("odeon.android.hilt") 20 | id("odeon.android.compose") 21 | } 22 | 23 | android { 24 | namespace = "fr.nihilus.music.ui.cleanup" 25 | } 26 | 27 | dependencies { 28 | implementation(projects.core.common) 29 | implementation(projects.core.ui) 30 | implementation(projects.core.compose) 31 | implementation(projects.media) 32 | 33 | implementation(libs.bundles.core) 34 | implementation(libs.bundles.android.ui) 35 | implementation(libs.bundles.androidx.lifecycle) 36 | 37 | implementation(libs.androidx.activity.compose) 38 | implementation(libs.androidx.lifecycle.runtime.compose) 39 | implementation(libs.compose.material3) 40 | implementation(libs.compose.material.icons.core) 41 | 42 | testImplementation(projects.core.testing) 43 | testImplementation(libs.bundles.testing.unit) 44 | } 45 | -------------------------------------------------------------------------------- /ui/cleanup/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/ui/cleanup/consumer-rules.pro -------------------------------------------------------------------------------- /ui/cleanup/sampledata/track_usage.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "usage_info": "Never played", 5 | "file_size": "12.76 Mo" 6 | }, 7 | { 8 | "usage_info": "Last played 3 months ago", 9 | "file_size": "9.87 Mo" 10 | }, 11 | { 12 | "usage_info": "Last played 2 weeks ago", 13 | "file_size": "16.34 Mo" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /ui/cleanup/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ui/cleanup/src/main/kotlin/fr/nihilus/music/ui/cleanup/CleanupState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.ui.cleanup 18 | 19 | import fr.nihilus.music.core.files.FileSize 20 | import fr.nihilus.music.core.media.MediaId 21 | import fr.nihilus.music.media.tracks.DeleteTracksResult 22 | 23 | internal data class CleanupState( 24 | val tracks: List, 25 | val selectedCount: Int, 26 | val freedStorage: FileSize, 27 | val result: DeleteTracksResult?, 28 | ) { 29 | data class Track( 30 | val id: MediaId, 31 | val title: String, 32 | val fileSize: FileSize, 33 | val lastPlayedTime: Long?, 34 | val selected: Boolean, 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /ui/cleanup/src/main/res/navigation/cleanup_graph.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 21 | 22 | 23 | 24 | 28 | 29 | -------------------------------------------------------------------------------- /ui/cleanup/src/main/res/values-fr/plurals.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | Supprimer %d morceau ? 21 | Supprimer %d de morceaux ? 22 | Supprimer %d morceaux ? 23 | 24 | 25 | 26 | %d morceau sélectionné 27 | %d de morceaux sélectionnés 28 | %d morceaux sélectionnés 29 | 30 | 31 | -------------------------------------------------------------------------------- /ui/cleanup/src/main/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | Jamais lu 20 | Dernière lecture %1$s 21 | Cette opération est irréversible. 22 | Libérer de l\'espace 23 | Supprimer les morceaux sélectionnés 24 | -------------------------------------------------------------------------------- /ui/cleanup/src/main/res/values-pl/plurals.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | Usunąć %d utwór? 21 | Usunąć %d utwory? 22 | Usunąć %d utworów? 23 | 24 | 25 | %d wybrany utwór 26 | %d wybrane utwory 27 | %d wybranych utworów 28 | 29 | 30 | -------------------------------------------------------------------------------- /ui/cleanup/src/main/res/values-pl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | Nigdy nie odtwarzane 20 | Ostatnio odtwarzane %1$s 21 | Tego działania nie można cofnąć. 22 | Zwolnij miejsce na dysku 23 | Delete selected tracks 24 | -------------------------------------------------------------------------------- /ui/cleanup/src/main/res/values/plurals.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | Delete %d track? 21 | Delete %d tracks? 22 | 23 | 24 | %d track selected 25 | %d tracks selected 26 | 27 | 28 | -------------------------------------------------------------------------------- /ui/cleanup/src/main/res/values/public.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ui/cleanup/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | Never played 20 | Last played %1$s 21 | This operation cannot be undone. 22 | Free storage space 23 | Delete selected tracks 24 | -------------------------------------------------------------------------------- /ui/library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | plugins { 17 | id("odeon.android.library") 18 | id("odeon.android.hilt") 19 | id("androidx.navigation.safeargs.kotlin") 20 | } 21 | 22 | android { 23 | namespace = "fr.nihilus.music.ui.library" 24 | buildFeatures { 25 | viewBinding = true 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation(projects.core.common) 31 | implementation(projects.core.ui) 32 | implementation(projects.media) 33 | 34 | implementation(libs.bundles.core) 35 | implementation(libs.bundles.android.ui) 36 | implementation(libs.bundles.androidx.lifecycle) 37 | implementation(libs.androidx.viewpager) 38 | implementation(libs.androidx.media) 39 | 40 | testImplementation(projects.core.testing) 41 | testImplementation(libs.bundles.testing.unit) 42 | } 43 | -------------------------------------------------------------------------------- /ui/library/sampledata/albums.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "title": "The 2nd Law", 5 | "artist": "Muse" 6 | }, 7 | { 8 | "title": "Sunset on the Golden Age", 9 | "artist": "Alestorm" 10 | }, 11 | { 12 | "title": "Concrete and Gold", 13 | "artist": "Foo Fighters" 14 | }, 15 | { 16 | "title": "Greatests Hits 30 Anniversary Edition", 17 | "artist": "AC/DC" 18 | }, 19 | { 20 | "title": "Black Holes and Revelations", 21 | "artist": "Muse" 22 | }, 23 | { 24 | "title": "Wasting Light", 25 | "artist": "Foo Fighters" 26 | }, 27 | { 28 | "title": "Nightmare", 29 | "artist": "Avenged Sevenfold" 30 | }, 31 | { 32 | "title": "Echoes, Silence, Patience & Grace", 33 | "artist": "Foo Fighters" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /ui/library/sampledata/artists.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "name": "AC/DC", 5 | "subtitle": "2 tracks" 6 | }, 7 | { 8 | "name": "Alestorm", 9 | "subtitle": "1 track" 10 | }, 11 | { 12 | "name": "Avenged Sevenfold", 13 | "subtitle": "1 track" 14 | }, 15 | { 16 | "name": "Foo Fighters", 17 | "subtitle": "4 tracks" 18 | }, 19 | { 20 | "name": "Muse", 21 | "subtitle": "2 tracks" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /ui/library/sampledata/playlists.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "title": "Last added", 5 | "description": "Always good to hear the news." 6 | }, 7 | { 8 | "title": "Favorites", 9 | "description": "You music library's Hall of Fame." 10 | }, 11 | { 12 | "title": "Zen", 13 | "description": " " 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /ui/library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ui/library/src/main/kotlin/fr/nihilus/music/ui/library/DeleteTracksConfirmation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.ui.library 18 | 19 | import fr.nihilus.music.core.media.MediaId 20 | import fr.nihilus.music.media.tracks.DeleteTracksResult 21 | 22 | /** 23 | * Wraps the result of deleting a single track. 24 | * Note that while the main outcome of a delete operation is that the track is deleted, 25 | * this is not necessarily the case. You may need to let user grant permissions to proceed. 26 | */ 27 | internal class DeleteTracksConfirmation( 28 | /** 29 | * identifier of the target audio track. 30 | */ 31 | val trackId: MediaId, 32 | /** 33 | * Result of deleting that track. 34 | */ 35 | val result: DeleteTracksResult, 36 | ) 37 | -------------------------------------------------------------------------------- /ui/library/src/main/kotlin/fr/nihilus/music/ui/library/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.ui.library 18 | 19 | import androidx.lifecycle.ViewModel 20 | import androidx.lifecycle.viewModelScope 21 | import dagger.hilt.android.lifecycle.HiltViewModel 22 | import fr.nihilus.music.core.media.MediaId 23 | import fr.nihilus.music.core.ui.client.BrowserClient 24 | import kotlinx.coroutines.launch 25 | import javax.inject.Inject 26 | 27 | @HiltViewModel 28 | internal class HomeViewModel @Inject constructor( 29 | private val client: BrowserClient, 30 | ) : ViewModel() { 31 | 32 | fun playAllShuffled() { 33 | viewModelScope.launch { 34 | client.setShuffleModeEnabled(true) 35 | client.playFromMediaId(MediaId.ALL_TRACKS) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ui/library/src/main/kotlin/fr/nihilus/music/ui/library/playlists/details/PlaylistDetailsUiState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.ui.library.playlists.details 18 | 19 | import android.net.Uri 20 | import fr.nihilus.music.core.media.MediaId 21 | import kotlin.time.Duration 22 | 23 | internal data class PlaylistDetailsUiState( 24 | val playlistTitle: String, 25 | val isUserDefined: Boolean, 26 | val tracks: List, 27 | val isLoadingTracks: Boolean, 28 | ) 29 | 30 | internal data class PlaylistTrackUiState( 31 | val id: MediaId, 32 | val title: String, 33 | val artistName: String, 34 | val duration: Duration, 35 | val artworkUri: Uri?, 36 | ) 37 | -------------------------------------------------------------------------------- /ui/library/src/main/kotlin/fr/nihilus/music/ui/library/search/SearchScreenUiState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.ui.library.search 18 | 19 | /** 20 | * State of the "search" screen. 21 | */ 22 | internal data class SearchScreenUiState( 23 | /** 24 | * User-defined text used to filter search results. 25 | */ 26 | val query: String, 27 | /** 28 | * List of media that matched the query. 29 | * Media are organized in sections of the same type and separated by section titles. 30 | */ 31 | val results: List 32 | ) 33 | -------------------------------------------------------------------------------- /ui/library/src/main/res/drawable/currently_playing_decoration.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /ui/library/src/main/res/drawable/ic_album_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /ui/library/src/main/res/drawable/ic_block_24.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /ui/library/src/main/res/drawable/ic_overflow_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /ui/library/src/main/res/drawable/ic_person_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /ui/library/src/main/res/drawable/ic_playlist_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /ui/library/src/main/res/drawable/ic_playlist_add_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /ui/library/src/main/res/drawable/ic_search_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | 28 | 29 | -------------------------------------------------------------------------------- /ui/library/src/main/res/layout/section_header_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | -------------------------------------------------------------------------------- /ui/library/src/main/res/menu/menu_playlist_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | 28 | 29 | -------------------------------------------------------------------------------- /ui/library/src/main/res/menu/menu_search.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 26 | -------------------------------------------------------------------------------- /ui/library/src/main/res/menu/track_popup_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | 26 | 27 | 32 | 33 | 38 | -------------------------------------------------------------------------------- /ui/library/src/main/res/values-fr/plurals.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | Ce morceau a été supprimé. 21 | Ces morceaux ont été supprimés. 22 | Ces morceaux ont été supprimés. 23 | 24 | 25 | %d morceau 26 | %d de morceaux 27 | %d morceaux 28 | 29 | 30 | -------------------------------------------------------------------------------- /ui/library/src/main/res/values-pl/plurals.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | Utwór został usunięty. 21 | Utwory zostały usunięte. 22 | 23 | 24 | 25 | %d utwór 26 | %d utwory 27 | %d utworów 28 | 29 | 30 | -------------------------------------------------------------------------------- /ui/library/src/main/res/values-w600dp/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 4 20 | 3 21 | 1 22 | 23 | -------------------------------------------------------------------------------- /ui/library/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 16dp 19 | 192dp 20 | 96dp 21 | 22 | -------------------------------------------------------------------------------- /ui/library/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ui/library/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 2 20 | 2 21 | 2 22 | 23 | -------------------------------------------------------------------------------- /ui/library/src/main/res/values/plurals.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | The track has been deleted. 21 | Those tracks have been deleted. 22 | 23 | 24 | %d track 25 | %d tracks 26 | 27 | 28 | -------------------------------------------------------------------------------- /ui/library/src/main/res/values/public.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ui/library/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 22 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /ui/settings/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ui/settings/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thibseisel/android-odeon/f097bcda052665dc791bd3c26880adb0545514dc/ui/settings/consumer-rules.pro -------------------------------------------------------------------------------- /ui/settings/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ui/settings/src/main/kotlin/fr/nihilus/music/ui/settings/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.ui.settings 18 | 19 | import android.os.Bundle 20 | import androidx.appcompat.widget.Toolbar 21 | import dagger.hilt.android.AndroidEntryPoint 22 | import fr.nihilus.music.core.ui.base.BaseActivity 23 | import fr.nihilus.music.core.ui.extensions.drawEdgeToEdge 24 | 25 | @AndroidEntryPoint 26 | class SettingsActivity : BaseActivity() { 27 | 28 | override fun onCreate(savedInstanceState: Bundle?) { 29 | super.onCreate(savedInstanceState) 30 | window.drawEdgeToEdge(true) 31 | setContentView(R.layout.activity_settings) 32 | 33 | val toolbar = findViewById(R.id.toolbar) 34 | setSupportActionBar(toolbar) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ui/settings/src/main/kotlin/fr/nihilus/music/ui/settings/exclusion/ExcludedTrackUiState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Thibault Seisel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fr.nihilus.music.ui.settings.exclusion 18 | 19 | internal data class ExcludedTrackUiState( 20 | val id: Long, 21 | val title: String, 22 | val artistName: String, 23 | val excludeDate: Long 24 | ) 25 | -------------------------------------------------------------------------------- /ui/settings/src/main/res/navigation/settings_graph.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 23 | 24 | 25 | 30 | 31 | -------------------------------------------------------------------------------- /ui/settings/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | @string/pref_theme_light 21 | @string/pref_theme_dark 22 | @string/pref_theme_battery 23 | @string/pref_theme_system 24 | 25 | 26 | @string/pref_reload_queue_start 27 | @string/pref_reload_queue_track 28 | @string/pref_reload_queue_position 29 | 30 | -------------------------------------------------------------------------------- /ui/settings/src/main/res/values/public.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------