diff --git a/app/build.gradle b/app/build.gradle index ffa8850e3..a1687f981 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,6 +4,7 @@ apply plugin: 'kotlin-kapt' apply plugin: "de.mannodermaus.android-junit5" android { + namespace 'com.github.anrimian.musicplayer' def config = rootProject.ext @@ -31,7 +32,6 @@ android { debug { minifyEnabled false } - } buildFeatures { @@ -41,9 +41,6 @@ android { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() - } packagingOptions { jniLibs { excludes += ['**/kotlin/**'] @@ -75,7 +72,6 @@ dependencies { implementation deps.appCompatResources implementation deps.material implementation deps.recyclerView - implementation deps.recyclerViewFastScroll implementation deps.constraintLayout implementation deps.supportMedia diff --git a/app/lite/build.gradle b/app/lite/build.gradle index 6fd5e6760..b7d9c1f22 100644 --- a/app/lite/build.gradle +++ b/app/lite/build.gradle @@ -15,6 +15,8 @@ android { } } + namespace 'com.github.anrimian.musicplayer.lite' + def config = rootProject.ext compileSdk config.androidCompileSdkVersion @@ -24,8 +26,8 @@ android { targetSdkVersion config.androidTargetSdkVersion applicationId 'com.github.anrimian.musicplayer' - versionCode 143 - versionName "0.9.6.1" + versionCode 159 + versionName "0.9.7" testInstrumentationRunner config.testInstrumentationRunner testApplicationId "${applicationId}.test" archivesBaseName = "$applicationId-v$versionName($versionCode)" @@ -56,9 +58,6 @@ android { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() - } packagingOptions { jniLibs { excludes += ['**/kotlin/**'] diff --git a/app/lite/src/main/AndroidManifest.xml b/app/lite/src/main/AndroidManifest.xml index 15d300a1b..b3ae6db94 100644 --- a/app/lite/src/main/AndroidManifest.xml +++ b/app/lite/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> diff --git a/app/lite/src/main/java/com/github/anrimian/musicplayer/lite/di/app/LiteAppModule.kt b/app/lite/src/main/java/com/github/anrimian/musicplayer/lite/di/app/LiteAppModule.kt index fb605c9ac..a92ff84fd 100644 --- a/app/lite/src/main/java/com/github/anrimian/musicplayer/lite/di/app/LiteAppModule.kt +++ b/app/lite/src/main/java/com/github/anrimian/musicplayer/lite/di/app/LiteAppModule.kt @@ -10,6 +10,7 @@ import com.github.anrimian.musicplayer.data.storage.providers.music.StorageMusic import com.github.anrimian.musicplayer.data.storage.source.ContentSourceHelper import com.github.anrimian.musicplayer.domain.interactors.analytics.Analytics import com.github.anrimian.musicplayer.domain.interactors.player.PlayerErrorParser +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.lite.ui.AboutTextBinderImpl import com.github.anrimian.musicplayer.lite.ui.SpecialNavigationImpl import com.github.anrimian.musicplayer.ui.about.AboutTextBinder @@ -29,7 +30,7 @@ class LiteAppModule { @Provides @Singleton - fun syncInteractor(): SyncInteractor<*, *, Long> = StubSyncInteractor() + fun syncInteractor(): SyncInteractor = StubSyncInteractor() @Provides @Singleton diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 761d753d3..b816f9909 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> + + + + + + + + - + + + + + + diff --git a/app/src/main/java/com/github/anrimian/musicplayer/Constants.java b/app/src/main/java/com/github/anrimian/musicplayer/Constants.java index a9f852739..9da85275d 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/Constants.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/Constants.java @@ -2,6 +2,8 @@ public interface Constants { + String PLAYLIST_MIME_TYPE = "audio/x-mpegurl"; + interface Actions { int PLAY = 1; int PAUSE = 2; @@ -39,6 +41,10 @@ interface Arguments { String IDS_ARG = "ids_arg"; String NAME_ARG = "name_arg"; String LAUNCH_PREPARE_ARG = "launch_prepare_arg"; + String INPUT_TYPE_ARG = "input_type_arg"; + String DIGITS_ARG = "digits_arg"; + String CLOSE_MULTISELECT_ARG = "close_multiselect_arg"; + String PLAYLIST_IMPORT_ARG = "playlist_import_arg"; } interface Tags { @@ -53,6 +59,9 @@ interface Tags { String ALBUM_TAG = "album_tag"; String ALBUM_ARTIST_TAG = "album_artist_tag"; String LYRICS = "lyrics_tag"; + String TRACK_NUMBER_TAG = "track_number_tag"; + String DISC_NUMBER_TAG = "disc_number_tag"; + String COMMENT_TAG = "comment_tag"; String ADD_GENRE_TAG = "add_genre_tag"; String EDIT_GENRE_TAG = "edit_genre_tag"; String NEW_FOLDER_NAME_TAG = "new_folder_name_tag"; diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/Components.java b/app/src/main/java/com/github/anrimian/musicplayer/di/Components.java index 2413f5dcc..2ca80fb81 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/Components.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/Components.java @@ -30,11 +30,14 @@ import com.github.anrimian.musicplayer.di.app.library.genres.GenresModule; import com.github.anrimian.musicplayer.di.app.library.genres.items.GenreItemsComponent; import com.github.anrimian.musicplayer.di.app.library.genres.items.GenreItemsModule; +import com.github.anrimian.musicplayer.di.app.order.OrderComponent; +import com.github.anrimian.musicplayer.di.app.order.OrderModule; import com.github.anrimian.musicplayer.di.app.play_list.PlayListComponent; import com.github.anrimian.musicplayer.di.app.play_list.PlayListModule; import com.github.anrimian.musicplayer.di.app.settings.SettingsComponent; import com.github.anrimian.musicplayer.di.app.share.ShareComponent; import com.github.anrimian.musicplayer.di.app.share.ShareModule; +import com.github.anrimian.musicplayer.domain.models.order.Order; import javax.annotation.Nullable; @@ -140,6 +143,10 @@ public static ShareComponent getShareComponent(long[] ids) { return getAppComponent().shareComponent(new ShareModule(ids)); } + public static OrderComponent getOrderComponent(Order order) { + return getAppComponent().orderComponent(new OrderModule(order)); + } + private LibraryComponent buildLibraryComponent() { if (libraryComponent == null) { libraryComponent = getAppComponent().libraryComponent(new LibraryModule()); diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/AppComponent.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/AppComponent.java index 67912fba8..da51b8b74 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/AppComponent.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/AppComponent.java @@ -16,6 +16,8 @@ import com.github.anrimian.musicplayer.di.app.external_player.ExternalPlayerModule; import com.github.anrimian.musicplayer.di.app.library.LibraryComponent; import com.github.anrimian.musicplayer.di.app.library.LibraryModule; +import com.github.anrimian.musicplayer.di.app.order.OrderComponent; +import com.github.anrimian.musicplayer.di.app.order.OrderModule; import com.github.anrimian.musicplayer.di.app.play_list.PlayListComponent; import com.github.anrimian.musicplayer.di.app.play_list.PlayListModule; import com.github.anrimian.musicplayer.di.app.settings.SettingsComponent; @@ -29,8 +31,10 @@ import com.github.anrimian.musicplayer.domain.interactors.player.PlayerInteractor; import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor; import com.github.anrimian.musicplayer.domain.interactors.settings.LibrarySettingsInteractor; +import com.github.anrimian.musicplayer.domain.models.sync.FileKey; import com.github.anrimian.musicplayer.domain.repositories.LoggerRepository; import com.github.anrimian.musicplayer.domain.repositories.MediaScannerRepository; +import com.github.anrimian.musicplayer.domain.repositories.SettingsRepository; import com.github.anrimian.musicplayer.domain.repositories.StorageSourceRepository; import com.github.anrimian.musicplayer.domain.repositories.UiStateRepository; import com.github.anrimian.musicplayer.infrastructure.MediaSessionHandler; @@ -80,6 +84,7 @@ public interface AppComponent { ArtistEditorComponent artistEditorComponent(ArtistEditorModule module); ExternalPlayerComponent externalPlayerComponent(ExternalPlayerModule module); ShareComponent shareComponent(ShareModule module); + OrderComponent orderComponent(OrderModule orderModule); LibraryPlayerInteractor libraryPlayerInteractor(); DisplaySettingsInteractor displaySettingsInteractor(); @@ -87,7 +92,7 @@ public interface AppComponent { MusicServiceInteractor musicServiceInteractor(); LibrarySettingsInteractor librarySettingsInteractor(); CompositionSourceInteractor sourceInteractor(); - SyncInteractor syncInteractor(); + SyncInteractor syncInteractor(); PlayListsPresenter playListsPresenter(); CreatePlayListPresenter createPlayListsPresenter(); @@ -97,6 +102,7 @@ public interface AppComponent { WidgetMenuPresenter widgetMenuPresenter(); UiStateRepository uiStateRepository(); + SettingsRepository settingsRepository(); MediaScannerRepository mediaScannerRepository(); StorageSourceRepository storageSourceRepository(); LoggerRepository loggerRepository(); diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/AppModule.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/AppModule.java index 265770b4f..ed259a298 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/AppModule.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/AppModule.java @@ -24,6 +24,8 @@ import com.github.anrimian.musicplayer.ui.common.locale.LocaleControllerImpl; import com.github.anrimian.musicplayer.ui.common.theme.ThemeController; import com.github.anrimian.musicplayer.ui.notifications.MediaNotificationsDisplayer; +import com.github.anrimian.musicplayer.ui.notifications.MediaNotificationsDisplayerApi33; +import com.github.anrimian.musicplayer.ui.notifications.MediaNotificationsDisplayerImpl; import com.github.anrimian.musicplayer.ui.notifications.NotificationDisplayerApi33; import com.github.anrimian.musicplayer.ui.notifications.NotificationsDisplayer; import com.github.anrimian.musicplayer.ui.notifications.NotificationsDisplayerImpl; @@ -65,9 +67,12 @@ Context appContext() { @Nonnull @Singleton MediaNotificationsDisplayer mediaNotificationsDisplayer(Context context, - AppNotificationBuilder notificationBuilder, - CoverImageLoader coverImageLoader) { - return new MediaNotificationsDisplayer(context, notificationBuilder, coverImageLoader); + AppNotificationBuilder notificationBuilder, + CoverImageLoader coverImageLoader) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return new MediaNotificationsDisplayerApi33(context, notificationBuilder); + } + return new MediaNotificationsDisplayerImpl(context, notificationBuilder, coverImageLoader); } @Provides @@ -90,8 +95,8 @@ AppNotificationBuilder appNotificationBuilder() { @Provides @Nonnull @Singleton - SystemServiceController systemServiceController(Context context) { - return new SystemServiceControllerImpl(context); + SystemServiceController systemServiceController(Context context, SettingsRepository settingsRepository) { + return new SystemServiceControllerImpl(context, settingsRepository); } @Provides diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/MusicModule.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/MusicModule.java index b8b332080..84e369301 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/MusicModule.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/MusicModule.java @@ -45,6 +45,7 @@ import com.github.anrimian.musicplayer.domain.interactors.player.PlayerErrorParser; import com.github.anrimian.musicplayer.domain.interactors.player.PlayerInteractor; import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInteractor; +import com.github.anrimian.musicplayer.domain.models.sync.FileKey; import com.github.anrimian.musicplayer.domain.repositories.EditorRepository; import com.github.anrimian.musicplayer.domain.repositories.EqualizerRepository; import com.github.anrimian.musicplayer.domain.repositories.LibraryRepository; @@ -98,7 +99,7 @@ PlayerInteractor playerInteractor(MusicPlayerController musicPlayerController, @NonNull @Singleton CompositionSourceInteractor compositionSourceInteractor(StorageSourceRepository storageSourceRepository, - SyncInteractor syncInteractor) { + SyncInteractor syncInteractor) { return new CompositionSourceInteractor(storageSourceRepository, syncInteractor); } @@ -122,7 +123,7 @@ ExternalPlayerInteractor externalPlayerInteractor(PlayerCoordinatorInteractor in @NonNull @Singleton LibraryPlayerInteractor libraryPlayerInteractor(PlayerCoordinatorInteractor playerCoordinatorInteractor, - SyncInteractor syncInteractor, + SyncInteractor syncInteractor, SettingsRepository settingsRepository, PlayQueueRepository playQueueRepository, LibraryRepository musicProviderRepository, @@ -314,7 +315,7 @@ LibraryFoldersInteractor libraryFilesInteractor(LibraryRepository musicProviderR EditorRepository editorRepository, LibraryPlayerInteractor musicPlayerInteractor, PlayListsInteractor playListsInteractor, - SyncInteractor syncInteractor, + SyncInteractor syncInteractor, SettingsRepository settingsRepository, UiStateRepository uiStateRepository, MediaScannerRepository mediaScannerRepository) { @@ -331,11 +332,13 @@ LibraryFoldersInteractor libraryFilesInteractor(LibraryRepository musicProviderR @Provides @Nonnull LibraryArtistsInteractor libraryArtistsInteractor(LibraryRepository repository, - EditorRepository editorRepository, + LibraryPlayerInteractor libraryPlayerInteractor, + PlayListsInteractor playListsInteractor, SettingsRepository settingsRepository, UiStateRepository uiStateRepository) { return new LibraryArtistsInteractor(repository, - editorRepository, + libraryPlayerInteractor, + playListsInteractor, settingsRepository, uiStateRepository); } @@ -343,11 +346,13 @@ LibraryArtistsInteractor libraryArtistsInteractor(LibraryRepository repository, @Provides @Nonnull LibraryAlbumsInteractor libraryAlbumsInteractor(LibraryRepository repository, - EditorRepository editorRepository, + LibraryPlayerInteractor libraryPlayerInteractor, + PlayListsInteractor playListsInteractor, SettingsRepository settingsRepository, UiStateRepository uiStateRepository) { return new LibraryAlbumsInteractor(repository, - editorRepository, + libraryPlayerInteractor, + playListsInteractor, settingsRepository, uiStateRepository); } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/PlayListsModule.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/PlayListsModule.java index 84c5657a5..e604cb26a 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/PlayListsModule.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/PlayListsModule.java @@ -6,8 +6,10 @@ import android.content.Context; +import com.github.anrimian.musicplayer.data.database.dao.compositions.CompositionsDaoWrapper; import com.github.anrimian.musicplayer.data.database.dao.play_list.PlayListsDaoWrapper; import com.github.anrimian.musicplayer.data.repositories.playlists.PlayListsRepositoryImpl; +import com.github.anrimian.musicplayer.data.repositories.scanner.storage.playlists.PlaylistFilesStorage; import com.github.anrimian.musicplayer.data.storage.providers.playlists.StoragePlayListsProvider; import com.github.anrimian.musicplayer.domain.interactors.analytics.Analytics; import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInteractor; @@ -65,14 +67,20 @@ PlayListsInteractor playListsInteractor(PlayListsRepository playListsRepository, @Provides @Nonnull @Singleton - PlayListsRepository storagePlayListDataSource(SettingsRepository settingsRepository, + PlayListsRepository storagePlayListDataSource(Context context, + SettingsRepository settingsRepository, StoragePlayListsProvider playListsProvider, + CompositionsDaoWrapper compositionsDaoWrapper, PlayListsDaoWrapper playListsDaoWrapper, + PlaylistFilesStorage playlistFilesStorage, @Named(DB_SCHEDULER) Scheduler dbScheduler, @Named(SLOW_BG_SCHEDULER) Scheduler slowBgScheduler) { - return new PlayListsRepositoryImpl(settingsRepository, + return new PlayListsRepositoryImpl(context, + settingsRepository, playListsProvider, + compositionsDaoWrapper, playListsDaoWrapper, + playlistFilesStorage, dbScheduler, slowBgScheduler); } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/SettingsModule.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/SettingsModule.java index 8f61444be..9593b67ea 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/SettingsModule.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/SettingsModule.java @@ -11,6 +11,7 @@ import com.github.anrimian.musicplayer.data.repositories.state.UiStateRepositoryImpl; import com.github.anrimian.musicplayer.domain.controllers.MusicPlayerController; import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor; +import com.github.anrimian.musicplayer.domain.interactors.settings.HeadsetSettingsInteractor; import com.github.anrimian.musicplayer.domain.interactors.settings.LibrarySettingsInteractor; import com.github.anrimian.musicplayer.domain.interactors.settings.PlayerSettingsInteractor; import com.github.anrimian.musicplayer.domain.repositories.MediaScannerRepository; @@ -19,6 +20,7 @@ import com.github.anrimian.musicplayer.domain.repositories.UiStateRepository; import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser; import com.github.anrimian.musicplayer.ui.settings.display.DisplaySettingsPresenter; +import com.github.anrimian.musicplayer.ui.settings.headset.HeadsetSettingsPresenter; import com.github.anrimian.musicplayer.ui.settings.library.LibrarySettingsPresenter; import com.github.anrimian.musicplayer.ui.settings.player.PlayerSettingsPresenter; import com.github.anrimian.musicplayer.ui.settings.player.impls.EnabledMediaPlayersPresenter; @@ -64,6 +66,12 @@ DisplaySettingsInteractor displaySettingsInteractor(SettingsRepository settingsR return new DisplaySettingsInteractor(settingsRepository); } + @Provides + @Nonnull + HeadsetSettingsInteractor headsetSettingsInteractor(SettingsRepository settingsRepository) { + return new HeadsetSettingsInteractor(settingsRepository); + } + @Provides @Nonnull DisplaySettingsPresenter displaySettingsPresenter(DisplaySettingsInteractor displaySettingsInteractor, @@ -72,6 +80,12 @@ DisplaySettingsPresenter displaySettingsPresenter(DisplaySettingsInteractor disp return new DisplaySettingsPresenter(displaySettingsInteractor, uiScheduler, errorParser); } + @Provides + @Nonnull + HeadsetSettingsPresenter headsetSettingsPresenter(HeadsetSettingsInteractor interactor) { + return new HeadsetSettingsPresenter(interactor); + } + @Provides @Nonnull PlayerSettingsInteractor playerSettingsInteractor( diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/StorageModule.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/StorageModule.java index 6744f00c4..2feca618c 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/StorageModule.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/StorageModule.java @@ -23,8 +23,9 @@ import com.github.anrimian.musicplayer.data.repositories.library.edit.EditorRepositoryImpl; import com.github.anrimian.musicplayer.data.repositories.scanner.MediaScannerRepositoryImpl; import com.github.anrimian.musicplayer.data.repositories.scanner.StorageCompositionAnalyzer; -import com.github.anrimian.musicplayer.data.repositories.scanner.StoragePlaylistAnalyzer; import com.github.anrimian.musicplayer.data.repositories.scanner.files.FileScanner; +import com.github.anrimian.musicplayer.data.repositories.scanner.storage.playlists.PlaylistFilesStorage; +import com.github.anrimian.musicplayer.data.repositories.scanner.storage.playlists.StoragePlaylistsAnalyzer; import com.github.anrimian.musicplayer.data.storage.files.StorageFilesDataSource; import com.github.anrimian.musicplayer.data.storage.files.StorageFilesDataSourceApi30; import com.github.anrimian.musicplayer.data.storage.files.StorageFilesDataSourceImpl; @@ -39,6 +40,7 @@ import com.github.anrimian.musicplayer.domain.interactors.analytics.Analytics; import com.github.anrimian.musicplayer.domain.interactors.editor.EditorInteractor; import com.github.anrimian.musicplayer.domain.interactors.player.CompositionSourceInteractor; +import com.github.anrimian.musicplayer.domain.models.sync.FileKey; import com.github.anrimian.musicplayer.domain.repositories.EditorRepository; import com.github.anrimian.musicplayer.domain.repositories.LibraryRepository; import com.github.anrimian.musicplayer.domain.repositories.LoggerRepository; @@ -136,7 +138,7 @@ StorageFilesDataSource storageFilesDataSource(StorageMusicProvider musicProvider @Provides @Nonnull EditorInteractor compositionEditorInteractor(CompositionSourceInteractor sourceInteractor, - SyncInteractor syncInteractor, + SyncInteractor syncInteractor, EditorRepository editorRepository, LibraryRepository musicProviderRepository, StorageSourceRepository storageSourceRepository) { @@ -174,9 +176,8 @@ MediaScannerRepository mediaScannerRepository(StorageMusicProvider musicProvider CompositionsDaoWrapper compositionsDao, GenresDaoWrapper genresDao, SettingsRepository settingsRepository, -// StorageCompositionAnalyzer compositionAnalyzer, StorageCompositionAnalyzer compositionAnalyzer, - StoragePlaylistAnalyzer storagePlaylistAnalyzer, + StoragePlaylistsAnalyzer storagePlaylistAnalyzer, FileScanner fileScanner, LoggerRepository loggerRepository, Analytics analytics, @@ -212,9 +213,17 @@ StorageCompositionAnalyzer compositionAnalyzer(CompositionsDaoWrapper compositio @Provides @Nonnull - StoragePlaylistAnalyzer storagePlaylistAnalyzer(PlayListsDaoWrapper playListsDao, - StoragePlayListsProvider playListsProvider) { - return new StoragePlaylistAnalyzer(playListsDao, playListsProvider); + StoragePlaylistsAnalyzer storagePlaylistsAnalyzer2(CompositionsDaoWrapper compositionsDao, + PlayListsDaoWrapper playListsDao, + StoragePlayListsProvider playListsProvider, + PlaylistFilesStorage playlistFilesStorage) { + return new StoragePlaylistsAnalyzer(compositionsDao, playListsDao, playListsProvider, playlistFilesStorage); + } + + @Provides + @Nonnull + PlaylistFilesStorage playlistFilesStorage(Context context) { + return new PlaylistFilesStorage(context); } @Provides diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/editor/album/AlbumEditorModule.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/editor/album/AlbumEditorModule.java index 2d1f3848b..e9dba2a1b 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/editor/album/AlbumEditorModule.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/editor/album/AlbumEditorModule.java @@ -4,6 +4,7 @@ import com.github.anrimian.filesync.SyncInteractor; import com.github.anrimian.musicplayer.domain.interactors.editor.EditorInteractor; +import com.github.anrimian.musicplayer.domain.models.sync.FileKey; import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser; import com.github.anrimian.musicplayer.ui.editor.album.AlbumEditorPresenter; @@ -26,7 +27,7 @@ public AlbumEditorModule(long albumId) { @Provides @Nonnull AlbumEditorPresenter compositionEditorPresenter(EditorInteractor interactor, - SyncInteractor syncInteractor, + SyncInteractor syncInteractor, @Named(UI_SCHEDULER) Scheduler uiScheduler, ErrorParser errorParser) { return new AlbumEditorPresenter(albumId, interactor, syncInteractor, uiScheduler, errorParser); diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/editor/artist/ArtistEditorModule.kt b/app/src/main/java/com/github/anrimian/musicplayer/di/app/editor/artist/ArtistEditorModule.kt index 2d9b449c2..c5654ae08 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/editor/artist/ArtistEditorModule.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/editor/artist/ArtistEditorModule.kt @@ -3,6 +3,7 @@ package com.github.anrimian.musicplayer.di.app.editor.artist import com.github.anrimian.filesync.SyncInteractor import com.github.anrimian.musicplayer.di.app.SchedulerModule import com.github.anrimian.musicplayer.domain.interactors.editor.EditorInteractor +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser import com.github.anrimian.musicplayer.ui.editor.artist.RenameArtistPresenter import dagger.Module @@ -16,7 +17,7 @@ class ArtistEditorModule(private val artistId: Long, private val name: String) { @Provides fun compositionEditorPresenter( editorInteractor: EditorInteractor, - syncInteractor: SyncInteractor<*, *, Long>, + syncInteractor: SyncInteractor, @Named(SchedulerModule.UI_SCHEDULER) uiScheduler: Scheduler, errorParser: ErrorParser ) = RenameArtistPresenter( diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/editor/composition/CompositionEditorModule.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/editor/composition/CompositionEditorModule.java index ab5b52def..494600b24 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/editor/composition/CompositionEditorModule.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/editor/composition/CompositionEditorModule.java @@ -4,6 +4,7 @@ import com.github.anrimian.filesync.SyncInteractor; import com.github.anrimian.musicplayer.domain.interactors.editor.EditorInteractor; +import com.github.anrimian.musicplayer.domain.models.sync.FileKey; import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser; import com.github.anrimian.musicplayer.ui.editor.composition.CompositionEditorPresenter; @@ -26,7 +27,7 @@ public CompositionEditorModule(long compositionId) { @Provides @Nonnull CompositionEditorPresenter compositionEditorPresenter(EditorInteractor interactor, - SyncInteractor syncInteractor, + SyncInteractor syncInteractor, @Named(UI_SCHEDULER) Scheduler uiScheduler, ErrorParser errorParser) { return new CompositionEditorPresenter(compositionId, interactor, syncInteractor, uiScheduler, errorParser); diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/LibraryComponent.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/LibraryComponent.java index 0231f5c8f..1dacbedda 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/LibraryComponent.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/LibraryComponent.java @@ -10,7 +10,6 @@ import com.github.anrimian.musicplayer.di.app.library.files.LibraryFilesModule; import com.github.anrimian.musicplayer.di.app.library.genres.GenresComponent; import com.github.anrimian.musicplayer.di.app.library.genres.GenresModule; -import com.github.anrimian.musicplayer.ui.library.common.order.SelectOrderPresenter; import com.github.anrimian.musicplayer.ui.player_screen.PlayerPresenter; import com.github.anrimian.musicplayer.ui.player_screen.lyrics.LyricsPresenter; import com.github.anrimian.musicplayer.ui.player_screen.queue.PlayQueuePresenter; @@ -35,6 +34,5 @@ public interface LibraryComponent { PlayerPresenter playerPresenter(); PlayQueuePresenter playQueuePresenter(); LyricsPresenter lyricsPresenter(); - SelectOrderPresenter selectOrderPresenter(); ExcludedFoldersPresenter excludedFoldersPresenter(); } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/LibraryModule.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/LibraryModule.java index 19edb84bb..d54f9419e 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/LibraryModule.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/LibraryModule.java @@ -10,14 +10,13 @@ import com.github.anrimian.musicplayer.domain.interactors.player.LibraryPlayerInteractor; import com.github.anrimian.musicplayer.domain.interactors.player.PlayerScreenInteractor; import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInteractor; -import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor; import com.github.anrimian.musicplayer.domain.interactors.sleep_timer.SleepTimerInteractor; +import com.github.anrimian.musicplayer.domain.models.sync.FileKey; import com.github.anrimian.musicplayer.domain.repositories.MediaScannerRepository; import com.github.anrimian.musicplayer.domain.repositories.PlayQueueRepository; import com.github.anrimian.musicplayer.domain.repositories.SettingsRepository; import com.github.anrimian.musicplayer.domain.repositories.UiStateRepository; import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser; -import com.github.anrimian.musicplayer.ui.library.common.order.SelectOrderPresenter; import com.github.anrimian.musicplayer.ui.player_screen.PlayerPresenter; import com.github.anrimian.musicplayer.ui.player_screen.lyrics.LyricsPresenter; import com.github.anrimian.musicplayer.ui.player_screen.queue.PlayQueuePresenter; @@ -55,7 +54,7 @@ PlayerPresenter playerPresenter(LibraryPlayerInteractor musicPlayerInteractor, PlayQueuePresenter playQueuePresenter(LibraryPlayerInteractor musicPlayerInteractor, PlayListsInteractor playListsInteractor, PlayerScreenInteractor playerScreenInteractor, - SyncInteractor syncInteractor, + SyncInteractor syncInteractor, ErrorParser errorParser, @Named(UI_SCHEDULER) Scheduler uiScheduler) { return new PlayQueuePresenter(musicPlayerInteractor, @@ -84,7 +83,7 @@ LyricsPresenter lyricsPresenter(LibraryPlayerInteractor libraryPlayerInteractor, @LibraryScope PlayerScreenInteractor playerScreenInteractor(SleepTimerInteractor sleepTimerInteractor, LibraryPlayerInteractor libraryPlayerInteractor, - SyncInteractor syncInteractor, + SyncInteractor syncInteractor, PlayQueueRepository playQueueRepository, UiStateRepository uiStateRepository, SettingsRepository settingsRepository, @@ -98,12 +97,6 @@ PlayerScreenInteractor playerScreenInteractor(SleepTimerInteractor sleepTimerInt mediaScannerRepository); } - @Provides - @Nonnull - SelectOrderPresenter selectOrderPresenter(DisplaySettingsInteractor displaySettingsInteractor) { - return new SelectOrderPresenter(displaySettingsInteractor); - } - @Provides @Nonnull ExcludedFoldersPresenter excludedFoldersPresenter(LibraryFoldersInteractor interactor, diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/albums/items/AlbumItemsModule.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/albums/items/AlbumItemsModule.java index b488dfb39..02046de11 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/albums/items/AlbumItemsModule.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/albums/items/AlbumItemsModule.java @@ -7,6 +7,7 @@ import com.github.anrimian.musicplayer.domain.interactors.player.LibraryPlayerInteractor; import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInteractor; import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor; +import com.github.anrimian.musicplayer.domain.models.sync.FileKey; import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser; import com.github.anrimian.musicplayer.ui.library.albums.items.AlbumItemsPresenter; @@ -32,7 +33,7 @@ AlbumItemsPresenter genreItemsPresenter(LibraryAlbumsInteractor interactor, PlayListsInteractor playListsInteractor, LibraryPlayerInteractor playerInteractor, DisplaySettingsInteractor displaySettingsInteractor, - SyncInteractor syncInteractor, + SyncInteractor syncInteractor, ErrorParser errorParser, @Named(UI_SCHEDULER) Scheduler uiScheduler) { return new AlbumItemsPresenter(id, diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/artists/items/ArtistItemsModule.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/artists/items/ArtistItemsModule.java index ac75c2e71..fc86d6a8b 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/artists/items/ArtistItemsModule.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/artists/items/ArtistItemsModule.java @@ -7,6 +7,7 @@ import com.github.anrimian.musicplayer.domain.interactors.player.LibraryPlayerInteractor; import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInteractor; import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor; +import com.github.anrimian.musicplayer.domain.models.sync.FileKey; import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser; import com.github.anrimian.musicplayer.ui.library.artists.items.ArtistItemsPresenter; @@ -32,7 +33,7 @@ ArtistItemsPresenter itemsPresenter(LibraryArtistsInteractor interactor, PlayListsInteractor playListsInteractor, LibraryPlayerInteractor playerInteractor, DisplaySettingsInteractor displaySettingsInteractor, - SyncInteractor syncInteractor, + SyncInteractor syncInteractor, ErrorParser errorParser, @Named(UI_SCHEDULER) Scheduler uiScheduler) { return new ArtistItemsPresenter(id, diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/compositions/LibraryCompositionsModule.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/compositions/LibraryCompositionsModule.java index c19a2f641..030713202 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/compositions/LibraryCompositionsModule.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/compositions/LibraryCompositionsModule.java @@ -7,6 +7,7 @@ import com.github.anrimian.musicplayer.domain.interactors.player.LibraryPlayerInteractor; import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInteractor; import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor; +import com.github.anrimian.musicplayer.domain.models.sync.FileKey; import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser; import com.github.anrimian.musicplayer.ui.library.compositions.LibraryCompositionsPresenter; @@ -30,7 +31,7 @@ LibraryCompositionsPresenter libraryCompositionsPresenter(LibraryCompositionsInt PlayListsInteractor playListsInteractor, LibraryPlayerInteractor playerInteractor, DisplaySettingsInteractor displaySettingsInteractor, - SyncInteractor syncInteractor, + SyncInteractor syncInteractor, ErrorParser errorParser, @Named(UI_SCHEDULER) Scheduler uiScheduler) { return new LibraryCompositionsPresenter(interactor, diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/files/folder/FolderModule.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/files/folder/FolderModule.java index 72df46906..3a9c98e92 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/files/folder/FolderModule.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/files/folder/FolderModule.java @@ -6,6 +6,7 @@ import com.github.anrimian.musicplayer.domain.interactors.library.LibraryFoldersScreenInteractor; import com.github.anrimian.musicplayer.domain.interactors.player.LibraryPlayerInteractor; import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor; +import com.github.anrimian.musicplayer.domain.models.sync.FileKey; import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser; import com.github.anrimian.musicplayer.ui.library.folders.LibraryFoldersPresenter; @@ -36,7 +37,7 @@ public FolderModule(@Nullable Long folderId) { LibraryFoldersPresenter libraryFoldersPresenter(LibraryFoldersScreenInteractor interactor, LibraryPlayerInteractor playerInteractor, DisplaySettingsInteractor displaySettingsInteractor, - SyncInteractor syncInteractor, + SyncInteractor syncInteractor, ErrorParser errorParser, @Named(UI_SCHEDULER) Scheduler uiScheduler) { return new LibraryFoldersPresenter(folderId, diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/genres/items/GenreItemsModule.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/genres/items/GenreItemsModule.java index 5ea1723e4..e3d5ad8bc 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/genres/items/GenreItemsModule.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/library/genres/items/GenreItemsModule.java @@ -7,6 +7,7 @@ import com.github.anrimian.musicplayer.domain.interactors.player.LibraryPlayerInteractor; import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInteractor; import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor; +import com.github.anrimian.musicplayer.domain.models.sync.FileKey; import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser; import com.github.anrimian.musicplayer.ui.library.genres.items.GenreItemsPresenter; @@ -32,7 +33,7 @@ GenreItemsPresenter genreItemsPresenter(LibraryGenresInteractor interactor, PlayListsInteractor playListsInteractor, LibraryPlayerInteractor playerInteractor, DisplaySettingsInteractor displaySettingsInteractor, - SyncInteractor syncInteractor, + SyncInteractor syncInteractor, ErrorParser errorParser, @Named(UI_SCHEDULER) Scheduler uiScheduler) { return new GenreItemsPresenter(id, diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/play_list/PlayListModule.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/play_list/PlayListModule.java index e2392d2d6..bf9d078fe 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/play_list/PlayListModule.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/play_list/PlayListModule.java @@ -6,6 +6,7 @@ import com.github.anrimian.musicplayer.domain.interactors.player.LibraryPlayerInteractor; import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInteractor; import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor; +import com.github.anrimian.musicplayer.domain.models.sync.FileKey; import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser; import com.github.anrimian.musicplayer.ui.playlist_screens.playlist.PlayListPresenter; import com.github.anrimian.musicplayer.ui.playlist_screens.rename.RenamePlayListPresenter; @@ -31,7 +32,7 @@ public PlayListModule(long playListId) { PlayListPresenter playListsPresenter(LibraryPlayerInteractor musicPlayerInteractor, PlayListsInteractor playListsInteractor, DisplaySettingsInteractor displaySettingsInteractor, - SyncInteractor syncInteractor, + SyncInteractor syncInteractor, @Named(UI_SCHEDULER) Scheduler uiSchedule, ErrorParser errorParser) { return new PlayListPresenter(playListId, diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/settings/SettingsComponent.java b/app/src/main/java/com/github/anrimian/musicplayer/di/app/settings/SettingsComponent.java index bf1894ffc..ac9cca5bb 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/settings/SettingsComponent.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/settings/SettingsComponent.java @@ -1,6 +1,7 @@ package com.github.anrimian.musicplayer.di.app.settings; import com.github.anrimian.musicplayer.ui.settings.display.DisplaySettingsPresenter; +import com.github.anrimian.musicplayer.ui.settings.headset.HeadsetSettingsPresenter; import com.github.anrimian.musicplayer.ui.settings.library.LibrarySettingsPresenter; import com.github.anrimian.musicplayer.ui.settings.player.PlayerSettingsPresenter; import com.github.anrimian.musicplayer.ui.settings.player.impls.EnabledMediaPlayersPresenter; @@ -13,6 +14,7 @@ public interface SettingsComponent { DisplaySettingsPresenter displaySettingsPresenter(); PlayerSettingsPresenter playerSettingsPresenter(); LibrarySettingsPresenter librarySettingsPresenter(); + HeadsetSettingsPresenter headsetSettingsPresenter(); EnabledMediaPlayersPresenter enabledMediaPlayersPresenter(); } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/di/app/share/ShareModule.kt b/app/src/main/java/com/github/anrimian/musicplayer/di/app/share/ShareModule.kt index ea95ba5b2..f802b750a 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/di/app/share/ShareModule.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/di/app/share/ShareModule.kt @@ -3,6 +3,7 @@ package com.github.anrimian.musicplayer.di.app.share import com.github.anrimian.filesync.SyncInteractor import com.github.anrimian.musicplayer.di.app.SchedulerModule.UI_SCHEDULER import com.github.anrimian.musicplayer.domain.interactors.player.CompositionSourceInteractor +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.ui.common.dialogs.share.ShareCompositionsPresenter import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser import dagger.Module @@ -16,7 +17,7 @@ class ShareModule(private val ids: LongArray) { @Provides fun shareCompositionsPresenter( sourceInteractor: CompositionSourceInteractor, - syncInteractor: SyncInteractor<*, *, Long>, + syncInteractor: SyncInteractor, @Named(UI_SCHEDULER) uiScheduler: Scheduler, errorParser: ErrorParser ) = ShareCompositionsPresenter(ids, sourceInteractor, syncInteractor, uiScheduler, errorParser) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/MediaSessionHandler.kt b/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/MediaSessionHandler.kt index 8b7bdfa4e..949ac2ffb 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/MediaSessionHandler.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/MediaSessionHandler.kt @@ -268,7 +268,7 @@ class MediaSessionHandler( currentSource, metadataBuilder, getMediaSession(), - state.settings.isCoversOnLockScreen + state.settings ) } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/receivers/BluetoothConnectionReceiver.java b/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/receivers/BluetoothConnectionReceiver.java index ee2bad05b..ea04a369a 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/receivers/BluetoothConnectionReceiver.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/receivers/BluetoothConnectionReceiver.java @@ -1,27 +1,32 @@ package com.github.anrimian.musicplayer.infrastructure.receivers; +import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE; +import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES; +import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO; +import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_UNCATEGORIZED; +import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; +import static android.content.pm.PackageManager.DONT_KILL_APP; +import static com.github.anrimian.musicplayer.domain.utils.ListUtils.asList; + +import android.Manifest; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; + +import androidx.core.app.ActivityCompat; +import com.github.anrimian.musicplayer.di.Components; import com.github.anrimian.musicplayer.infrastructure.service.SystemServiceControllerImpl; import java.util.List; import java.util.Objects; -import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE; -import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES; -import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO; -import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_UNCATEGORIZED; -import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET; -import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; -import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; -import static android.content.pm.PackageManager.DONT_KILL_APP; -import static com.github.anrimian.musicplayer.domain.utils.ListUtils.asList; - public class BluetoothConnectionReceiver extends BroadcastReceiver { private static final List ALLOWED_DEVICES_TO_START = asList( @@ -32,12 +37,10 @@ public class BluetoothConnectionReceiver extends BroadcastReceiver { AUDIO_VIDEO_PORTABLE_AUDIO ); - private static final int PLAY_DELAY_MILLIS = 1500; - public static void setEnabled(Context context, boolean enabled) { context.getPackageManager().setComponentEnabledSetting( new ComponentName(context, BluetoothConnectionReceiver.class), - enabled? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED, + enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP ); } @@ -54,6 +57,9 @@ public void onReceive(Context context, Intent intent) { if (Objects.equals(intent.getAction(), BluetoothDevice.ACTION_ACL_CONNECTED)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device != null) { + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { + return; + } BluetoothClass bluetoothClass = device.getBluetoothClass(); if (bluetoothClass == null) { return; @@ -64,7 +70,10 @@ public void onReceive(Context context, Intent intent) { return; } } - SystemServiceControllerImpl.startPlayForegroundService(context, PLAY_DELAY_MILLIS); + long delay = Components.getAppComponent() + .settingsRepository() + .getBluetoothConnectAutoPlayDelay(); + SystemServiceControllerImpl.startPlayForegroundService(context, delay); } } } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/service/SystemServiceControllerImpl.java b/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/service/SystemServiceControllerImpl.java index 2525f00d5..36d76001a 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/service/SystemServiceControllerImpl.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/service/SystemServiceControllerImpl.java @@ -21,6 +21,7 @@ import com.github.anrimian.musicplayer.di.Components; import com.github.anrimian.musicplayer.di.app.AppComponent; import com.github.anrimian.musicplayer.domain.controllers.SystemServiceController; +import com.github.anrimian.musicplayer.domain.repositories.SettingsRepository; import com.github.anrimian.musicplayer.infrastructure.service.music.MusicService; import io.reactivex.rxjava3.core.Observable; @@ -29,16 +30,20 @@ public class SystemServiceControllerImpl implements SystemServiceController { private final Context context; + private final SettingsRepository settingsRepository; private final PublishSubject stopForegroundSubject = PublishSubject.create(); private static final Handler handler = new Handler(Looper.getMainLooper()); + private static final Handler stopHandler = new Handler(Looper.getMainLooper()); public static void startPlayForegroundService(Context context) { startPlayForegroundService(context, 0); } - public static void startPlayForegroundService(Context context, int playDelay) { + public static void startPlayForegroundService(Context context, long playDelay) { + stopHandler.removeCallbacksAndMessages(null); + Intent intent = new Intent(context, MusicService.class); intent.putExtra(MusicService.START_FOREGROUND_SIGNAL, true); intent.putExtra(MusicService.REQUEST_CODE, Constants.Actions.PLAY); @@ -46,12 +51,15 @@ public static void startPlayForegroundService(Context context, int playDelay) { checkPermissionsAndStartServiceFromBg(context, intent); } - public SystemServiceControllerImpl(Context context) { + public SystemServiceControllerImpl(Context context, + SettingsRepository settingsRepository) { this.context = context; + this.settingsRepository = settingsRepository; } @Override public void startMusicService() { + stopHandler.removeCallbacksAndMessages(null); handler.post(() -> { Intent intent = new Intent(context, MusicService.class); checkPermissionsAndStartServiceSafe(context, intent); @@ -59,9 +67,13 @@ public void startMusicService() { } @Override - public void stopMusicService() { - handler.removeCallbacksAndMessages(null); - stopForegroundSubject.onNext(TRIGGER); + public void stopMusicService(boolean forceStop) { + long stopDelayMillis = settingsRepository.getKeepNotificationTime(); + if (forceStop || stopDelayMillis == 0L) { + stopForegroundService(); + return; + } + stopHandler.postDelayed(this::stopForegroundService, stopDelayMillis); } @Override @@ -122,6 +134,11 @@ private static void startServiceFromBg(Context context, Intent intent) { } } + private void stopForegroundService() { + handler.removeCallbacksAndMessages(null); + stopForegroundSubject.onNext(TRIGGER); + } + private static class ForegroundServiceStarterConnection implements ServiceConnection { private final Context context; diff --git a/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/service/music/CompositionSourceModelHelper.java b/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/service/music/CompositionSourceModelHelper.java index 7c0c1a6cd..ecd78c6ed 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/service/music/CompositionSourceModelHelper.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/service/music/CompositionSourceModelHelper.java @@ -5,7 +5,6 @@ import static com.github.anrimian.musicplayer.ui.common.format.FormatUtils.formatCompositionAuthor; import android.content.Context; -import android.graphics.Bitmap; import android.media.MediaMetadata; import android.os.Build; import android.support.v4.media.MediaMetadataCompat; @@ -16,11 +15,9 @@ import com.github.anrimian.musicplayer.domain.models.composition.Composition; import com.github.anrimian.musicplayer.domain.models.composition.source.CompositionSource; import com.github.anrimian.musicplayer.domain.models.composition.source.LibraryCompositionSource; +import com.github.anrimian.musicplayer.domain.models.player.service.MusicNotificationSetting; import com.github.anrimian.musicplayer.domain.models.utils.CompositionHelper; -import com.github.anrimian.musicplayer.domain.utils.functions.Callback; -import com.github.anrimian.musicplayer.ui.common.images.CoverImageLoader; -import javax.annotation.Nonnull; import javax.annotation.Nullable; public class CompositionSourceModelHelper { @@ -46,47 +43,57 @@ public static boolean areSourcesTheSame(@Nullable CompositionSource first, @Null public static void updateMediaSessionAlbumArt(@Nullable CompositionSource source, MediaMetadataCompat.Builder metadataBuilder, MediaSessionCompat mediaSession, - boolean isEnabled) { - if (isEnabled && source != null) { - if (source instanceof LibraryCompositionSource) { - Composition composition = ((LibraryCompositionSource) source).getComposition(); + MusicNotificationSetting setting) { + boolean useAlbumArt = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + && setting.isShowCovers()) || setting.isCoversOnLockScreen(); + if (!useAlbumArt || source == null) { + metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, null); + metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART, null); + mediaSession.setMetadata(metadataBuilder.build()); + return; + } + + if (source instanceof LibraryCompositionSource) { + Composition composition = ((LibraryCompositionSource) source).getComposition(); + Components.getAppComponent() + .imageLoader() + .loadImageUri(composition, uri -> { + String uriStr = null; + if (uri != null) { + uriStr = uri.toString(); + } + metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, uriStr); + mediaSession.setMetadata(metadataBuilder.build()); + }); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { Components.getAppComponent() .imageLoader() - .loadImageUri(composition, uri -> { - String uriStr = null; - if (uri != null) { - uriStr = uri.toString(); - } - metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, uriStr); + .loadMediaSessionImage(composition, bitmap -> { + metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmap); mediaSession.setMetadata(metadataBuilder.build()); }); - + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, null); + mediaSession.setMetadata(metadataBuilder.build()); + } else { //uri doesn't work for lock screen background, so put it here - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - Components.getAppComponent() - .imageLoader() - .loadImage(composition, bitmap -> { - metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmap); - mediaSession.setMetadata(metadataBuilder.build()); - }); - } else { - metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, null); - mediaSession.setMetadata(metadataBuilder.build()); - } - } - if (source instanceof ExternalCompositionSource) { Components.getAppComponent() .imageLoader() - .loadImage((ExternalCompositionSource) source, bitmap -> { - metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, null); + .loadImage(composition, bitmap -> { metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmap); mediaSession.setMetadata(metadataBuilder.build()); }); } - } else { - metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, null); - metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART, null); - mediaSession.setMetadata(metadataBuilder.build()); + } + if (source instanceof ExternalCompositionSource) { + Components.getAppComponent() + .imageLoader() + .loadImage((ExternalCompositionSource) source, bitmap -> { + metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, null); + metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmap); + mediaSession.setMetadata(metadataBuilder.build()); + }); } } @@ -123,26 +130,4 @@ public static void updateMediaSessionMetadata(@Nullable CompositionSource source mediaSession.setMetadata(builder.build()); } - public static long getTrackPosition(@Nullable CompositionSource source) { - if (source instanceof LibraryCompositionSource) { - return ((LibraryCompositionSource) source).getTrackPosition(); - } - if (source instanceof ExternalCompositionSource) { - return 0; - } - return 0; - } - - public static Runnable getCompositionSourceCover(@Nonnull CompositionSource source, - Callback onCompleted, - CoverImageLoader coverImageLoader) { - if (source instanceof LibraryCompositionSource) { - Composition composition = ((LibraryCompositionSource) source).getComposition(); - return coverImageLoader.loadNotificationImage(composition, onCompleted); - } - if (source instanceof ExternalCompositionSource) { - return coverImageLoader.loadNotificationImage((ExternalCompositionSource) source, onCompleted); - } - throw new IllegalStateException(); - } } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/service/music/MusicService.java b/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/service/music/MusicService.java index 7592eea4e..2252ee195 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/service/music/MusicService.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/infrastructure/service/music/MusicService.java @@ -126,7 +126,7 @@ public void startForeground() { private void handleNotificationAction(int requestCode, Intent intent) { switch (requestCode) { case PLAY: { - int playDelay = intent.getIntExtra(PLAY_DELAY_MILLIS, 0); + long playDelay = intent.getLongExtra(PLAY_DELAY_MILLIS, 0); playerInteractor().play(playDelay); break; } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/about/AboutAppFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/about/AboutAppFragment.kt index 35f0eb346..673426a15 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/about/AboutAppFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/about/AboutAppFragment.kt @@ -11,14 +11,15 @@ import com.github.anrimian.musicplayer.databinding.FragmentAboutBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.ui.common.toolbar.AdvancedToolbar import com.github.anrimian.musicplayer.ui.utils.ViewUtils -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener +import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigationListener import com.github.anrimian.musicplayer.ui.utils.getAppInfo import com.github.anrimian.musicplayer.ui.utils.slidr.SlidrPanel import com.github.anrimian.musicplayer.utils.logger.FileLog -class AboutAppFragment : Fragment(), FragmentLayerListener { +class AboutAppFragment : Fragment(), + FragmentNavigationListener { - private lateinit var viewBinding: FragmentAboutBinding + private lateinit var binding: FragmentAboutBinding private lateinit var fileLog: FileLog @@ -27,8 +28,8 @@ class AboutAppFragment : Fragment(), FragmentLayerListener { container: ViewGroup?, savedInstanceState: Bundle? ): View { - viewBinding = FragmentAboutBinding.inflate(inflater, container, false) - return viewBinding.root + binding = FragmentAboutBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -44,24 +45,24 @@ class AboutAppFragment : Fragment(), FragmentLayerListener { val isLogExists = fileLog.isFileExists setLogActionsVisibility(isLogExists) if (isLogExists) { - viewBinding.tvLogInfo.text = getString( + binding.tvLogInfo.text = getString( R.string.log_info_text, fileLog.fileSize / 1024 ) } - appComponent.aboutTextBinder().bind(this, viewBinding.tvAbout) + appComponent.aboutTextBinder().bind(this, binding.tvAbout) - viewBinding.btnDelete.setOnClickListener { deleteLogFile() } - viewBinding.btnView.setOnClickListener { appLogger.startViewLogScreen(requireActivity()) } - viewBinding.btnSend.setOnClickListener { appLogger.startSendLogScreen(requireActivity()) } + binding.btnDelete.setOnClickListener { deleteLogFile() } + binding.btnView.setOnClickListener { appLogger.startViewLogScreen(requireActivity()) } + binding.btnSend.setOnClickListener { appLogger.startSendLogScreen(requireActivity()) } - viewBinding.cbShowReportDialogOnStart.isChecked = loggerRepository.isReportDialogOnStartEnabled - ViewUtils.onCheckChanged(viewBinding.cbShowReportDialogOnStart, loggerRepository::showReportDialogOnStart) + binding.cbShowReportDialogOnStart.isChecked = loggerRepository.isReportDialogOnStartEnabled + ViewUtils.onCheckChanged(binding.cbShowReportDialogOnStart, loggerRepository::showReportDialogOnStart) - SlidrPanel.simpleSwipeBack(viewBinding.containerView, this, toolbar::onStackFragmentSlided) + SlidrPanel.simpleSwipeBack(binding.containerView, this, toolbar::onStackFragmentSlided) } - override fun onFragmentMovedOnTop() { + override fun onFragmentResumed() { requireActivity().findViewById(R.id.toolbar).setup { config -> config.setTitle(R.string.app_name) val appInfo = requireContext().getAppInfo() @@ -81,7 +82,7 @@ class AboutAppFragment : Fragment(), FragmentLayerListener { private fun setLogActionsVisibility(isLogExists: Boolean) { val logActionsVisibility = if (isLogExists) View.VISIBLE else View.GONE - viewBinding.logActionsContainer.visibility = logActionsVisibility - viewBinding.tvLogInfo.visibility = logActionsVisibility + binding.logActionsContainer.visibility = logActionsVisibility + binding.tvLogInfo.visibility = logActionsVisibility } } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/DialogUtils.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/DialogUtils.java index af68648f7..0aaab28bf 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/DialogUtils.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/DialogUtils.java @@ -7,7 +7,6 @@ import com.github.anrimian.musicplayer.R; import com.github.anrimian.musicplayer.databinding.DialogSpeedSelectorBinding; -import com.github.anrimian.musicplayer.databinding.PartialNumberPickerDialogBinding; import com.github.anrimian.musicplayer.domain.utils.functions.Callback; import com.github.anrimian.musicplayer.ui.utils.views.seek_bar.SeekBarViewWrapper; @@ -54,25 +53,4 @@ public static void showSpeedSelectorDialog(Context context, dialog.show(); } - public static void showNumberPickerDialog(Context context, - int minValue, - int maxValue, - int currentValue, - Callback pickCallback) { - PartialNumberPickerDialogBinding binding = PartialNumberPickerDialogBinding.inflate( - LayoutInflater.from(context) - ); - - binding.numberPicker.setMinValue(minValue); - binding.numberPicker.setMaxValue(maxValue); - binding.numberPicker.setValue(currentValue); - - new AlertDialog.Builder(context) - .setView(binding.getRoot()) - .setPositiveButton( - android.R.string.ok, - (dialog, which) -> pickCallback.call(binding.numberPicker.getValue()) - ).setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()) - .show(); - } } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/DialogUtils.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/DialogUtils.kt index 94cc86a1f..2ef7eddd3 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/DialogUtils.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/DialogUtils.kt @@ -13,6 +13,7 @@ import androidx.fragment.app.FragmentManager import com.github.anrimian.musicplayer.R import com.github.anrimian.musicplayer.databinding.DialogSoundBalanceBinding import com.github.anrimian.musicplayer.databinding.PartialDeleteDialogBinding +import com.github.anrimian.musicplayer.databinding.PartialNumberPickerDialogBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.composition.InitialSource @@ -82,7 +83,7 @@ fun shareCompositions(fragment: Fragment, compositions: Collection) fun shareComposition( ctx: Context, fragmentManager: FragmentManager, - fullComposition: Composition + fullComposition: Composition, ) { shareCompositions( ctx, @@ -96,7 +97,7 @@ fun shareCompositions( ctx: Context, fragmentManager: FragmentManager, ids: List, - hasNonExistComposition: Boolean + hasNonExistComposition: Boolean, ) { if (hasNonExistComposition) { newShareCompositionsDialogFragment(ids.toLongArray()).safeShow(fragmentManager) @@ -195,7 +196,7 @@ fun showConfirmDeleteFileDialog( context: Context, message: String, deleteCallback: () -> Unit, - hasStorageFiles: Boolean + hasStorageFiles: Boolean, ) { var view: View? = null if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && hasStorageFiles) { @@ -221,7 +222,7 @@ fun showConfirmDeleteDialog( context: Context, message: String, deleteCallback: () -> Unit, - view: View? = null + view: View? = null, ) { AlertDialog.Builder(context) .setTitle(R.string.deleting) @@ -232,6 +233,50 @@ fun showConfirmDeleteDialog( .show() } +fun showNumberPickerDialog( + context: Context, + minValue: Long, + maxValue: Long, + currentValue: Long, + stepValue: Long = 1, + valueFormatter: ((Long) -> String)? = null, + pickCallback: (Long) -> Unit, +) { + val binding = PartialNumberPickerDialogBinding.inflate(LayoutInflater.from(context)) + + val pickAction: () -> Unit + if (stepValue != 1L && valueFormatter != null) { + binding.numberPicker.minValue = 0 + val values = ArrayList() + var v = minValue + var index = 0 + var currentIndex = 0 + while (v <= maxValue) { + if (v == currentValue) { + currentIndex = index + } + values.add(v) + v += stepValue + index++ + } + binding.numberPicker.maxValue = index - 1 + binding.numberPicker.value = currentIndex + binding.numberPicker.displayedValues = Array(values.size) { i -> valueFormatter(values[i])} + pickAction = { pickCallback(values[binding.numberPicker.value]) } + } else { + binding.numberPicker.minValue = minValue.toInt() + binding.numberPicker.maxValue = maxValue.toInt() + binding.numberPicker.value = currentValue.toInt() + pickAction = { pickCallback(binding.numberPicker.value.toLong()) } + } + + AlertDialog.Builder(context) + .setView(binding.root) + .setPositiveButton(android.R.string.ok) { _, _ -> pickAction() } + .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } + .show() +} + private fun getDeleteCompositionsMessage(context: Context, count: Int): String { return context.resources.getQuantityString(R.plurals.delete_compositions_template, count, count) } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/input/InputTextDialogFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/input/InputTextDialogFragment.kt index eb9677af1..11e1426fb 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/input/InputTextDialogFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/input/InputTextDialogFragment.kt @@ -5,6 +5,7 @@ import android.app.Dialog import android.os.Bundle import android.text.InputType import android.text.TextUtils +import android.text.method.DigitsKeyListener import android.view.LayoutInflater import android.view.inputmethod.EditorInfo import android.widget.ArrayAdapter @@ -26,6 +27,8 @@ fun newInputTextDialogFragment( editTextValue: String?, canBeEmpty: Boolean = true, completeOnEnterButton: Boolean = true, + inputType: Int = InputType.TYPE_CLASS_TEXT, + digits: String? = null, hints: Array? = null, extra: Bundle? = null ) = InputTextDialogFragment().apply { @@ -37,6 +40,8 @@ fun newInputTextDialogFragment( putString(EDIT_TEXT_VALUE, editTextValue) putBoolean(CAN_BE_EMPTY_ARG, canBeEmpty) putBoolean(COMPLETE_ON_ENTER_ARG, completeOnEnterButton) + putInt(INPUT_TYPE_ARG, inputType) + putString(DIGITS_ARG, digits) putBundle(EXTRA_DATA_ARG, extra) putStringArray(HINTS_ARG, hints) } @@ -68,7 +73,11 @@ class InputTextDialogFragment : DialogFragment() { val completeOnEnterButton = args.getBoolean(COMPLETE_ON_ENTER_ARG) editText.setHint(args.getInt(EDIT_TEXT_HINT)) editText.imeOptions = if (completeOnEnterButton) EditorInfo.IME_ACTION_DONE else EditorInfo.IME_ACTION_UNSPECIFIED - editText.setRawInputType(InputType.TYPE_CLASS_TEXT) + editText.setRawInputType(args.getInt(INPUT_TYPE_ARG)) + val digits = args.getString(DIGITS_ARG) + if (digits != null) { + editText.keyListener = DigitsKeyListener.getInstance(digits) + } editText.setOnEditorActionListener { _, actionId: Int, _ -> if (!canBeEmpty && !isEnterButtonEnabled(editText.text.toString().trim())) { return@setOnEditorActionListener true diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/share/ShareCompositionsPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/share/ShareCompositionsPresenter.kt index 1e3bb6180..9c900728d 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/share/ShareCompositionsPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/dialogs/share/ShareCompositionsPresenter.kt @@ -1,8 +1,9 @@ package com.github.anrimian.musicplayer.ui.common.dialogs.share import com.github.anrimian.filesync.SyncInteractor -import com.github.anrimian.filesync.models.state.file.Downloading +import com.github.anrimian.filesync.models.state.file.FileSyncState import com.github.anrimian.musicplayer.domain.interactors.player.CompositionSourceInteractor +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser import com.github.anrimian.musicplayer.ui.common.mvp.AppPresenter import io.reactivex.rxjava3.core.Scheduler @@ -11,7 +12,7 @@ import io.reactivex.rxjava3.subjects.BehaviorSubject class ShareCompositionsPresenter( private val ids: LongArray, private val sourceInteractor: CompositionSourceInteractor, - private val syncInteractor: SyncInteractor<*, *, Long>, + private val syncInteractor: SyncInteractor, uiScheduler: Scheduler, errorParser: ErrorParser, ): AppPresenter(uiScheduler, errorParser) { @@ -37,7 +38,7 @@ class ShareCompositionsPresenter( .doOnSubscribe { viewState.showProcessedFileCount(++preparedCount, ids.size) } } .subscribe { fileSyncState -> - if (fileSyncState is Downloading) { + if (fileSyncState is FileSyncState.Downloading) { viewState.showDownloadingFileInfo(fileSyncState.getProgress()) } } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/error/parser/DefaultErrorParser.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/error/parser/DefaultErrorParser.java index df7280a21..0209d3b29 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/error/parser/DefaultErrorParser.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/error/parser/DefaultErrorParser.java @@ -6,6 +6,7 @@ import com.github.anrimian.musicplayer.R; import com.github.anrimian.musicplayer.data.controllers.music.equalizer.internal.EqInitializationException; +import com.github.anrimian.musicplayer.data.models.exceptions.NoPlaylistItemsException; import com.github.anrimian.musicplayer.data.models.exceptions.PlayListAlreadyDeletedException; import com.github.anrimian.musicplayer.data.models.exceptions.PlayListAlreadyExistsException; import com.github.anrimian.musicplayer.data.models.exceptions.PlayListNotCreatedException; @@ -14,14 +15,15 @@ import com.github.anrimian.musicplayer.data.repositories.library.edit.exceptions.FileExistsException; import com.github.anrimian.musicplayer.data.repositories.library.edit.exceptions.MoveFolderToItselfException; import com.github.anrimian.musicplayer.data.repositories.library.edit.exceptions.MoveInTheSameFolderException; +import com.github.anrimian.musicplayer.data.repositories.scanner.storage.playlists.m3uparser.M3UEditorException; import com.github.anrimian.musicplayer.data.storage.exceptions.NotAllowedPathException; +import com.github.anrimian.musicplayer.data.storage.exceptions.TagReaderException; import com.github.anrimian.musicplayer.data.storage.exceptions.UnavailableMediaStoreException; import com.github.anrimian.musicplayer.data.storage.providers.music.RecoverableSecurityExceptionExt; import com.github.anrimian.musicplayer.domain.interactors.analytics.Analytics; import com.github.anrimian.musicplayer.domain.models.composition.content.CorruptedMediaFileException; import com.github.anrimian.musicplayer.domain.models.composition.content.LocalSourceNotFoundException; import com.github.anrimian.musicplayer.domain.models.composition.content.UnsupportedSourceException; -import com.github.anrimian.musicplayer.domain.models.exceptions.EditorReadException; import com.github.anrimian.musicplayer.domain.models.exceptions.FileWriteNotAllowedException; import com.github.anrimian.musicplayer.domain.models.exceptions.FolderAlreadyIgnoredException; import com.github.anrimian.musicplayer.domain.models.exceptions.StorageTimeoutException; @@ -56,6 +58,9 @@ public ErrorCommand parseError(Throwable throwable) { case EMPTY_NAME: { return error(R.string.name_can_not_be_empty); } + case TOO_LONG_NAME: { + return error(R.string.name_is_too_long); + } } } } @@ -66,6 +71,9 @@ public ErrorCommand parseError(Throwable throwable) { if (throwable instanceof PlayListAlreadyDeletedException) { return error(R.string.play_not_exists); } + if (throwable instanceof NoPlaylistItemsException) { + return error(R.string.playlist_has_no_records); + } if (throwable instanceof FileNotFoundException || throwable instanceof LocalSourceNotFoundException) { return error(R.string.file_not_found); } @@ -83,7 +91,7 @@ public ErrorCommand parseError(Throwable throwable) { } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && (throwable instanceof RecoverableSecurityException || throwable instanceof RecoverableSecurityExceptionExt)) { - return new EditorErrorCommand(throwable); + return new EditorErrorCommand(getString(R.string.no_file_modify_permission), throwable); } if (throwable instanceof EqInitializationException) { return error(R.string.equalizer_initialization_error); @@ -91,8 +99,8 @@ public ErrorCommand parseError(Throwable throwable) { if (throwable instanceof EditorTimeoutException) { return error(R.string.editor_timeout_error); } - if (throwable instanceof EditorReadException) { - return new ErrorCommand(throwable.getMessage()); + if (throwable instanceof TagReaderException) { + return new ErrorCommand(getString(R.string.tag_reading_error, throwable.getMessage())); } if (throwable instanceof NullPointerException) { logException(throwable); @@ -108,7 +116,7 @@ public ErrorCommand parseError(Throwable throwable) { return error(R.string.unsupported_format_hint); } if (throwable instanceof CorruptedMediaFileException) { - return error(R.string.unsupported_format_hint); + return error(R.string.file_is_corrupted); } if (throwable instanceof FolderAlreadyIgnoredException) { return error(R.string.folder_already_excluded_from_scanning); @@ -118,6 +126,9 @@ public ErrorCommand parseError(Throwable throwable) { getString(R.string.android_r_editor_restriction_error, throwable.getMessage()) ); } + if (throwable instanceof M3UEditorException) { + return new ErrorCommand(getString(R.string.unexpected_error, throwable.getMessage())); + } if (throwable instanceof FileWriteNotAllowedException) { logException(throwable); return error(R.string.write_to_this_is_not_allowed); diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/format/FormatUtils.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/format/FormatUtils.kt index 471345ae0..fa697662e 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/format/FormatUtils.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/format/FormatUtils.kt @@ -8,9 +8,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.graphics.ColorUtils import com.github.anrimian.filesync.models.ProgressInfo -import com.github.anrimian.filesync.models.state.file.Downloading import com.github.anrimian.filesync.models.state.file.FileSyncState -import com.github.anrimian.filesync.models.state.file.Uploading import com.github.anrimian.musicplayer.Constants import com.github.anrimian.musicplayer.R import com.github.anrimian.musicplayer.domain.models.player.MediaPlayers @@ -33,11 +31,11 @@ fun showFileSyncState( progressView: ProgressView, ) { when(fileSyncState) { - is Uploading -> { + is FileSyncState.Uploading -> { progressView.setProgressInfo(fileSyncState.getProgress()) progressView.setIconResource(R.drawable.ic_upload) } - is Downloading -> { + is FileSyncState.Downloading -> { progressView.setProgressInfo(fileSyncState.getProgress()) progressView.setIconResource(R.drawable.ic_download) } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/format/MessagesUtils.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/format/MessagesUtils.java index 745c7a030..5c00cb63b 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/format/MessagesUtils.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/format/MessagesUtils.java @@ -1,27 +1,23 @@ package com.github.anrimian.musicplayer.ui.common.format; -import android.annotation.SuppressLint; +import static com.github.anrimian.musicplayer.domain.models.utils.CompositionHelper.formatCompositionName; + import android.content.Context; -import android.view.View; import android.view.ViewGroup; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.StringRes; import com.github.anrimian.musicplayer.R; import com.github.anrimian.musicplayer.domain.models.composition.Composition; +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition; import com.github.anrimian.musicplayer.domain.models.playlist.PlayList; import com.github.anrimian.musicplayer.domain.models.playlist.PlayListItem; import com.github.anrimian.musicplayer.ui.common.snackbars.AppSnackbar; import com.google.android.material.snackbar.Snackbar; -import com.google.android.material.snackbar.SnackbarContentLayout; import java.util.List; -import static com.github.anrimian.musicplayer.domain.models.utils.CompositionHelper.formatCompositionName; -import static com.github.anrimian.musicplayer.ui.utils.AndroidUtils.getColorFromAttr; - public class MessagesUtils { public static String getAddToPlayListCompleteMessage(Context context, @@ -59,7 +55,7 @@ public static String getDeletePlayListItemCompleteMessage(Context context, } } - public static String getDeleteCompleteMessage(Context context, List compositions) { + public static String getDeleteCompleteMessage(Context context, List compositions) { int size = compositions.size(); if (size == 1) { return context.getString(R.string.delete_composition_success, diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/format/wrappers/CompositionItemWrapper.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/format/wrappers/CompositionItemWrapper.kt index f810cd045..39d916433 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/format/wrappers/CompositionItemWrapper.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/format/wrappers/CompositionItemWrapper.kt @@ -1,5 +1,6 @@ package com.github.anrimian.musicplayer.ui.common.format.wrappers +import android.content.Context import android.content.res.ColorStateList import android.graphics.Color import android.graphics.drawable.RippleDrawable @@ -31,10 +32,10 @@ import com.github.anrimian.musicplayer.ui.utils.getHighlightAnimator import com.github.anrimian.musicplayer.ui.utils.views.progress_bar.ProgressView import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.ItemDrawable -class CompositionItemWrapper( +open class CompositionItemWrapper( itemView: View, - onIconClickListener: (Composition) -> Unit, - onClickListener: (Composition) -> Unit + onIconClickListener: (T) -> Unit, + onClickListener: (T) -> Unit ) { private val tvMusicName: TextView = itemView.findViewById(R.id.tv_composition_name) private val tvAdditionalInfo: TextView = itemView.findViewById(R.id.tv_additional_info) @@ -50,7 +51,7 @@ class CompositionItemWrapper( private val stateDrawable = ItemDrawable() private val rippleMaskDrawable = ItemDrawable() - private lateinit var composition: Composition + protected lateinit var composition: T private var showCovers = false private var isCurrent = false @@ -63,18 +64,18 @@ class CompositionItemWrapper( iconClickableArea.setOnClickListener { onIconClickListener(composition) } clickableItem.setOnClickListener { onClickListener(composition) } - backgroundDrawable.setColor(getContext().colorFromAttr(R.attr.listItemBackground)) + backgroundDrawable.setColor(this.getContext().colorFromAttr(R.attr.listItemBackground)) itemView.background = backgroundDrawable stateDrawable.setColor(Color.TRANSPARENT) clickableItem.background = stateDrawable clickableItem.foreground = RippleDrawable( - ColorStateList.valueOf(getContext().colorFromAttr(android.R.attr.colorControlHighlight)), + ColorStateList.valueOf(this.getContext().colorFromAttr(android.R.attr.colorControlHighlight)), null, rippleMaskDrawable ) } - fun bind(composition: Composition, showCovers: Boolean) { + fun bind(composition: T, showCovers: Boolean) { this.composition = composition this.showCovers = showCovers @@ -86,31 +87,32 @@ class CompositionItemWrapper( updateFileSyncState() } - fun update(composition: Composition, payloads: List<*>) { + fun update(composition: T, payloads: List<*>) { this.composition = composition for (payload in payloads) { if (payload is List<*>) { update(composition, payload) } - if (payload === Payloads.FILE_NAME || payload === Payloads.TITLE) { + if (payload == Payloads.FILE_NAME || payload == Payloads.TITLE) { showCompositionName() } - if (payload === Payloads.ARTIST || payload === Payloads.DURATION) { + if (payload == Payloads.ARTIST || payload == Payloads.DURATION) { showAdditionalInfo() } - if (payload === Payloads.DATE_MODIFIED - || payload === Payloads.SIZE - || payload === Payloads.FILE_EXISTS - || payload === Payloads.COVER_MODIFY_TIME) { + if (payload == Payloads.DATE_MODIFIED + || payload == Payloads.SIZE + || payload == Payloads.FILE_EXISTS + || payload == Payloads.COVER_MODIFY_TIME) { showCompositionImage(showCovers) } - if (payload === Payloads.CORRUPTED) { + if (payload == Payloads.CORRUPTED) { showCorrupted() showAdditionalInfo() } - if (payload === Payloads.FILE_EXISTS) { + if (payload == Payloads.FILE_EXISTS) { updateFileSyncState() } + onUpdate(payload) } } @@ -184,14 +186,11 @@ class CompositionItemWrapper( } fun runHighlightAnimation() { - val colorAnimator = getHighlightAnimator( + getHighlightAnimator( getContext().colorFromAttr(R.attr.listItemBackground), - getContext().getHighlightColor() - ) - colorAnimator.addUpdateListener { animator -> - backgroundDrawable.setColor(animator.animatedValue as Int) - } - colorAnimator.start() + getContext().getHighlightColor(), + backgroundDrawable::setColor + ).start() } fun showFileSyncState(fileSyncState: FileSyncState?) { @@ -199,6 +198,29 @@ class CompositionItemWrapper( updateFileSyncState() } + protected fun showAdditionalInfo() { + val sb: SpannableStringBuilder = DescriptionSpannableStringBuilder(getContext()) + getAdditionalInfo(sb) + tvAdditionalInfo.text = sb + } + + protected open fun getAdditionalInfo(sb: SpannableStringBuilder) { + sb.append(FormatUtils.formatCompositionAuthor(composition, getContext())) + sb.append(FormatUtils.formatMilliseconds(composition.duration)) + val corruptionHint = getCorruptionTypeHint(composition) + if (corruptionHint != null) { + sb.append(corruptionHint) + val start = sb.length - corruptionHint.length + val end = sb.length + val fcs = ForegroundColorSpan(getContext().colorFromAttr(R.attr.colorError)) + sb.setSpan(fcs, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE) + } + } + + protected open fun getContext(): Context = clickableItem.context + + protected open fun onUpdate(payload: Any?) {} + private fun onCoverImageLoadFinished(loaded: Boolean) { var tint = Color.TRANSPARENT if (loaded) { @@ -241,21 +263,6 @@ class CompositionItemWrapper( ViewUtils.animateItemDrawableColor(stateDrawable, endColor) } - private fun showAdditionalInfo() { - val sb: SpannableStringBuilder = DescriptionSpannableStringBuilder(getContext()) - sb.append(FormatUtils.formatCompositionAuthor(composition, getContext())) - sb.append(FormatUtils.formatMilliseconds(composition.duration)) - val corruptionHint = getCorruptionTypeHint(composition) - if (corruptionHint != null) { - sb.append(corruptionHint) - val start = sb.length - corruptionHint.length - val end = sb.length - val fcs = ForegroundColorSpan(getContext().colorFromAttr(R.attr.colorError)) - sb.setSpan(fcs, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE) - } - tvAdditionalInfo.text = sb - } - private fun getCorruptionTypeHint(composition: Composition): String? { val corruptionType = composition.corruptionType ?: return null return when (corruptionType) { @@ -263,8 +270,9 @@ class CompositionItemWrapper( CorruptionType.NOT_FOUND -> getContext().getString(R.string.file_not_found) CorruptionType.SOURCE_NOT_FOUND -> getContext().getString(R.string.file_source_not_found) CorruptionType.TOO_LARGE_SOURCE -> getContext().getString(R.string.file_is_too_large) - CorruptionType.FILE_IS_CORRUPTED -> getContext().getString(R.string.unsupported_format_hint) - else -> null + CorruptionType.FILE_IS_CORRUPTED -> getContext().getString(R.string.file_is_corrupted) + CorruptionType.FILE_READ_TIMEOUT -> getContext().getString(R.string.file_read_timeout) + else -> getContext().getString(R.string.unknown_play_error) } } @@ -277,7 +285,5 @@ class CompositionItemWrapper( ) } - private fun getContext() = clickableItem.context - private fun getResources() = getContext().resources } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/images/CoverImageLoader.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/images/CoverImageLoader.java index eeca3b597..f121aeb66 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/images/CoverImageLoader.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/images/CoverImageLoader.java @@ -156,18 +156,13 @@ public void displayImage(@NonNull ImageView imageView, .into(imageView); } - public Runnable loadNotificationImage(@Nonnull Composition data, - Callback onCompleted) { - return loadNotificationImage( - toImageRequest(data), - onCompleted); + public Runnable loadNotificationImage(@Nonnull Composition data, Callback onCompleted) { + return loadNotificationImage(toImageRequest(data), onCompleted); } public Runnable loadNotificationImage(@Nonnull ExternalCompositionSource source, Callback onCompleted) { - return loadNotificationImage( - new UriCompositionImage(source), - onCompleted); + return loadNotificationImage(new UriCompositionImage(source), onCompleted); } public Bitmap getDefaultNotificationBitmap() { @@ -195,6 +190,15 @@ public void loadImage(@Nonnull Composition data, Callback onCompleted) { loadImage(toImageRequest(data), onCompleted); } + public void loadMediaSessionImage(@Nonnull Composition data, Callback onCompleted) { + GlideApp.with(context) + .asBitmap() + .load(toImageRequest(data)) + .override(getCoverMediaSessionSize()) + .timeout(TIMEOUT_MILLIS) + .into(simpleTarget(onCompleted)); + } + public Single> loadImageUri(@Nonnull Composition data) { return Single.create(emitter -> loadImageUri(data, uri -> emitter.onSuccess(new Optional<>(uri))) @@ -310,7 +314,7 @@ private Runnable loadNotificationImage(Object compositionImage, GlideApp.with(context) .asBitmap() .load(compositionImage) - .override(getCoverSize()) + .override(getCoverMediaSessionSize()) .timeout(NOTIFICATION_IMAGE_TIMEOUT_MILLIS) .into(target); @@ -346,6 +350,10 @@ private int getCoverSize() { return context.getResources().getInteger(R.integer.icon_image_size); } + private int getCoverMediaSessionSize() { + return context.getResources().getInteger(R.integer.icon_media_session_image_size); + } + private CustomTarget simpleTarget(Callback callback) { return new CustomTarget<>() { @Override diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/mvp/AppPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/mvp/AppPresenter.kt index f87abbb05..d293c72e9 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/mvp/AppPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/mvp/AppPresenter.kt @@ -72,6 +72,11 @@ abstract class AppPresenter( .subscribe(onNext, { t -> onError(errorParser.parseError(t)) }, presenterDisposable) } + protected fun Single.justSubscribeOnUi(onNext: (K) -> Unit, onError: (Throwable) -> Unit) { + this.observeOn(uiScheduler) + .subscribe(onNext, onError, presenterDisposable) + } + protected fun Single.launchOnUi(onNext: (K) -> Unit, onError: (ErrorCommand) -> Unit) { this.subscribeOnUi(onNext) { t -> onError(errorParser.parseError(t)) } } @@ -109,7 +114,8 @@ abstract class AppPresenter( onComplete: () -> Unit, onError: (ErrorCommand) -> Unit ): Disposable { - return subscribe(onComplete, { t -> onError(errorParser.parseError(t)) }, presenterDisposable) + return observeOn(uiScheduler) + .subscribe(onComplete, { t -> onError(errorParser.parseError(t)) }, presenterDisposable) } protected fun Completable.subscribe(onError: (ErrorCommand) -> Unit): Disposable { diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/theme/AppTheme.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/theme/AppTheme.java index e980625d5..618ae41aa 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/theme/AppTheme.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/theme/AppTheme.java @@ -8,10 +8,10 @@ import com.github.anrimian.musicplayer.R; public class AppTheme { - public static final AppTheme WHITE_PURPLE_DEFAULT = new AppTheme(0, + public static final AppTheme WHITE_PURPLE_TEAL = new AppTheme(0, R.style.PrimaryPurpleTheme, 0, - R.color.colorPrimary, + R.color.color_purple_primary, R.color.light_background_level_0, R.color.colorAccent, R.color.color_control_highlight, @@ -24,10 +24,10 @@ public class AppTheme { R.color.colorAccentDark, R.color.color_control_highlight_dark, true); - public static final AppTheme WHITE_INDIGO = new AppTheme(2, - R.style.PrimaryBlueTheme, + public static final AppTheme WHITE_INDIGO_GREEN = new AppTheme(2, + R.style.PrimaryIndigoTheme, 0, - R.color.colorBluePrimary, + R.color.color_indigo_primary, R.color.light_background_level_0, R.color.colorGreenAccent, R.color.color_control_highlight, @@ -37,10 +37,10 @@ public class AppTheme { 0, R.color.darkColorPrimary, R.color.dark_background_level_1, - R.color.colorOrangeAccent, + R.color.color_orange_dark_accent, R.color.color_control_highlight_dark, true); - public static final AppTheme WHITE_TEAL = new AppTheme(3, + public static final AppTheme WHITE_TEAL_PINK = new AppTheme(3, R.style.PrimaryTealTheme, 0, R.color.colorTealPrimary, @@ -80,6 +80,30 @@ public class AppTheme { R.color.colorRedPrimary, R.color.color_control_highlight, false); + public static final AppTheme WHITE_ORANGE = new AppTheme(9, + R.style.PrimaryOrangeTheme, + 0, + R.color.colorOrangePrimary, + R.color.light_background_level_0, + R.color.colorOrangePrimary, + R.color.color_control_highlight, + false); + public static final AppTheme WHITE_PURPLE_PINK = new AppTheme(10, + R.style.PrimaryPurplePinkTheme, + 0, + R.color.color_purple_primary, + R.color.light_background_level_0, + R.color.colorPinkAccent, + R.color.color_control_highlight, + false); + public static final AppTheme WHITE_BLUE_ORANGE = new AppTheme(11, + R.style.PrimaryBlueOrangeTheme, + 0, + R.color.color_blue_primary, + R.color.light_background_level_0, + R.color.color_orange_accent, + R.color.color_control_highlight, + false); @RequiresApi(Build.VERSION_CODES.S) public static AppTheme getSystemWhiteTheme() { @@ -112,20 +136,23 @@ public static AppTheme getTheme(int id) { return type; } } - return WHITE_PURPLE_DEFAULT; + return WHITE_PURPLE_TEAL; } public static AppTheme[] appThemes() { return new AppTheme[] { - WHITE_PURPLE_DEFAULT, + WHITE_PURPLE_TEAL, DARK, - WHITE_INDIGO, + WHITE_INDIGO_GREEN, DARK_ORANGE, - WHITE_TEAL, + WHITE_TEAL_PINK, DARK_GREEN, COMPLETELY_WHITE, COMPLETELY_BLACK, - WHITE_RED + WHITE_RED, + WHITE_ORANGE, + WHITE_PURPLE_PINK, + WHITE_BLUE_ORANGE }; } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/theme/ThemeController.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/theme/ThemeController.java index 935151ef0..3bfc52f37 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/theme/ThemeController.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/theme/ThemeController.java @@ -69,7 +69,7 @@ public void setTheme(Activity activity, AppTheme appTheme) { } public AppTheme getCurrentTheme() { - return AppTheme.getTheme(preferences.getInt(THEME_ID, AppTheme.WHITE_PURPLE_DEFAULT.getId())); + return AppTheme.getTheme(preferences.getInt(THEME_ID, AppTheme.WHITE_PURPLE_TEAL.getId())); } @ColorInt @@ -123,7 +123,7 @@ private void processThemeChange(Activity activity, AppTheme appTheme) { } private AppTheme getCurrentUsedTheme() { - AppTheme theme = AppTheme.getTheme(preferences.getInt(THEME_ID, AppTheme.WHITE_PURPLE_DEFAULT.getId())); + AppTheme theme = AppTheme.getTheme(preferences.getInt(THEME_ID, AppTheme.WHITE_PURPLE_TEAL.getId())); return applyThemeRules(theme); } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/view/AppViewUtils.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/view/AppViewUtils.kt index fa54393e8..f641d4490 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/common/view/AppViewUtils.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/common/view/AppViewUtils.kt @@ -8,3 +8,7 @@ fun View.runHighlightAnimation() { val color = context.getHighlightColor() runHighlightAnimation(color) } + +fun View.setOnHoldListener(action: () -> Unit) { + ViewUtils.setOnHoldListener(this, action) +} diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/album/AlbumEditorActivity.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/album/AlbumEditorActivity.kt index e172d2168..37a6c10c6 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/album/AlbumEditorActivity.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/album/AlbumEditorActivity.kt @@ -42,61 +42,55 @@ class AlbumEditorActivity : MvpAppCompatActivity(), AlbumEditorView { val albumId = intent.getLongExtra(Constants.Arguments.ALBUM_ID_ARG, 0) Components.getAlbumEditorComponent(albumId).albumEditorPresenter() } - - private lateinit var viewBinding: ActivityAlbumEditBinding - + + private lateinit var binding: ActivityAlbumEditBinding + private lateinit var authorDialogFragmentRunner: DialogFragmentRunner private lateinit var nameDialogFragmentRunner: DialogFragmentRunner private lateinit var progressDialogRunner: DialogFragmentDelayRunner - + private lateinit var errorHandler: ErrorHandler override fun onCreate(savedInstanceState: Bundle?) { Components.getAppComponent().themeController().applyCurrentSlidrTheme(this) super.onCreate(savedInstanceState) - viewBinding = ActivityAlbumEditBinding.inflate(layoutInflater) - setContentView(viewBinding.root) + binding = ActivityAlbumEditBinding.inflate(layoutInflater) + setContentView(binding.root) AndroidUtils.setNavigationBarColorAttr(this, android.R.attr.colorBackground) - setToolbar(viewBinding.toolbar, R.string.edit_album_tags) - - viewBinding.changeAuthorClickableArea.setOnClickListener { presenter.onChangeAuthorClicked() } - viewBinding.changeNameClickableArea.setOnClickListener { presenter.onChangeNameClicked() } - - ViewUtils.onLongClick(viewBinding.changeAuthorClickableArea) { - copyText(viewBinding.tvAuthor, viewBinding.tvAuthorHint) + setToolbar(binding.toolbar, R.string.edit_album_tags) + + binding.changeAuthorClickableArea.setOnClickListener { presenter.onChangeAuthorClicked() } + binding.changeNameClickableArea.setOnClickListener { presenter.onChangeNameClicked() } + + ViewUtils.onLongClick(binding.changeAuthorClickableArea) { + copyText(binding.tvAuthor, binding.tvAuthorHint) } - ViewUtils.onLongClick(viewBinding.changeNameClickableArea) { - copyText(viewBinding.tvName, viewBinding.tvNameHint) + ViewUtils.onLongClick(binding.changeNameClickableArea) { + copyText(binding.tvName, binding.tvNameHint) } - + SlidrPanel.attachWithNavBarChange( this, R.attr.playerPanelBackground, android.R.attr.colorBackground ) - - val fm = supportFragmentManager + errorHandler = ErrorHandler( this, presenter::onRetryFailedEditActionClicked, this::showEditorRequestDeniedMessage ) - authorDialogFragmentRunner = DialogFragmentRunner( - fm, - Tags.AUTHOR_TAG - ) { fragment -> fragment.setOnCompleteListener(presenter::onNewAuthorEntered) } - nameDialogFragmentRunner = DialogFragmentRunner( - fm, - Tags.NAME_TAG - ) { fragment -> fragment.setOnCompleteListener(presenter::onNewNameEntered) } - progressDialogRunner = DialogFragmentDelayRunner( - fm, - Tags.PROGRESS_DIALOG_TAG, - fragmentInitializer = { fragment -> fragment.setCancellationListener { - presenter.onEditActionCancelled() - } } - ) + val fm = supportFragmentManager + authorDialogFragmentRunner = DialogFragmentRunner(fm, Tags.AUTHOR_TAG) { fragment -> + fragment.setOnCompleteListener(presenter::onNewAuthorEntered) + } + nameDialogFragmentRunner = DialogFragmentRunner(fm, Tags.NAME_TAG) { fragment -> + fragment.setOnCompleteListener(presenter::onNewNameEntered) + } + progressDialogRunner = DialogFragmentDelayRunner(fm, Tags.PROGRESS_DIALOG_TAG, { fragment -> + fragment.setCancellationListener { presenter.onEditActionCancelled() } + }) } override fun attachBaseContext(base: Context) { @@ -110,18 +104,18 @@ class AlbumEditorActivity : MvpAppCompatActivity(), AlbumEditorView { } override fun showAlbumLoadingError(errorCommand: ErrorCommand) { - viewBinding.tvAuthor.text = errorCommand.message + binding.tvAuthor.text = errorCommand.message } override fun showAlbum(album: Album) { - viewBinding.tvName.text = album.name - viewBinding.tvAuthor.text = FormatUtils.formatAuthor(album.artist, this) + binding.tvName.text = album.name + binding.tvAuthor.text = FormatUtils.formatAuthor(album.artist, this) } override fun showErrorMessage(errorCommand: ErrorCommand) { errorHandler.handleError(errorCommand) { MessagesUtils.makeSnackbar( - viewBinding.root, errorCommand.message, Snackbar.LENGTH_LONG + binding.root, errorCommand.message, Snackbar.LENGTH_LONG ).show() } } @@ -188,7 +182,7 @@ class AlbumEditorActivity : MvpAppCompatActivity(), AlbumEditorView { private fun onTextCopied() { MessagesUtils.makeSnackbar( - viewBinding.root, + binding.root, R.string.copied_message, Snackbar.LENGTH_SHORT ).show() @@ -196,7 +190,7 @@ class AlbumEditorActivity : MvpAppCompatActivity(), AlbumEditorView { private fun showEditorRequestDeniedMessage() { MessagesUtils.makeSnackbar( - viewBinding.root, + binding.root, R.string.android_r_edit_file_permission_denied, Snackbar.LENGTH_LONG ).show() diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/album/AlbumEditorPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/album/AlbumEditorPresenter.kt index e26bc96d8..bd23998a2 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/album/AlbumEditorPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/album/AlbumEditorPresenter.kt @@ -3,6 +3,7 @@ package com.github.anrimian.musicplayer.ui.editor.album import com.github.anrimian.filesync.SyncInteractor import com.github.anrimian.musicplayer.domain.interactors.editor.EditorInteractor import com.github.anrimian.musicplayer.domain.models.albums.Album +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.domain.utils.rx.RxUtils import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser import com.github.anrimian.musicplayer.ui.common.mvp.AppPresenter @@ -15,7 +16,7 @@ import io.reactivex.rxjava3.subjects.BehaviorSubject class AlbumEditorPresenter( private val albumId: Long, private val editorInteractor: EditorInteractor, - private val syncInteractor: SyncInteractor<*, *, Long>, + private val syncInteractor: SyncInteractor, uiScheduler: Scheduler, errorParser: ErrorParser ) : AppPresenter(uiScheduler, errorParser) { diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/artist/RenameArtistDialogFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/artist/RenameArtistDialogFragment.kt index cccc24d58..9688b7ba3 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/artist/RenameArtistDialogFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/artist/RenameArtistDialogFragment.kt @@ -39,34 +39,34 @@ class RenameArtistDialogFragment: MvpAppCompatDialogFragment(), RenameArtistView Components.getArtistEditorComponent(id, name).renameArtistPresenter() } - private lateinit var viewBinding: DialogCommonInputBinding + private lateinit var binding: DialogCommonInputBinding private lateinit var btnChange: Button private lateinit var errorHandler: ErrorHandler override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - viewBinding = DialogCommonInputBinding.inflate(LayoutInflater.from(context)) + binding = DialogCommonInputBinding.inflate(LayoutInflater.from(context)) val dialog = AlertDialog.Builder(activity) .setTitle(R.string.edit_name) .setPositiveButton(R.string.change, null) .setNegativeButton(R.string.cancel) { _, _ -> } - .setView(viewBinding.root) + .setView(binding.root) .create() AndroidUtils.setSoftInputVisible(dialog.window) dialog.show() if (savedInstanceState == null) { - ViewUtils.setEditableText(viewBinding.etInput, requireArguments().getString(NAME_ARG)) + ViewUtils.setEditableText(binding.etInput, requireArguments().getString(NAME_ARG)) } - viewBinding.etInput.imeOptions = EditorInfo.IME_ACTION_DONE - viewBinding.etInput.setRawInputType(InputType.TYPE_CLASS_TEXT) - viewBinding.etInput.setOnEditorActionListener { _, _, _ -> + binding.etInput.imeOptions = EditorInfo.IME_ACTION_DONE + binding.etInput.setRawInputType(InputType.TYPE_CLASS_TEXT) + binding.etInput.setOnEditorActionListener { _, _, _ -> presenter.onChangeButtonClicked() true } - SimpleTextWatcher.onTextChanged(viewBinding.etInput, presenter::onInputTextChanged) - viewBinding.etInput.requestFocus() + SimpleTextWatcher.onTextChanged(binding.etInput, presenter::onInputTextChanged) + binding.etInput.requestFocus() btnChange = dialog.getButton(AlertDialog.BUTTON_POSITIVE) btnChange.setOnClickListener { presenter.onChangeButtonClicked() } @@ -81,19 +81,19 @@ class RenameArtistDialogFragment: MvpAppCompatDialogFragment(), RenameArtistView override fun showProgress() { btnChange.isEnabled = false - viewBinding.etInput.isEnabled = false - viewBinding.tvError.visibility = View.GONE - viewBinding.tvProgress.visibility = View.VISIBLE - viewBinding.tvProgress.setText(R.string.rename_progress) - viewBinding.progressBar.visibility = View.VISIBLE + binding.etInput.isEnabled = false + binding.tvError.visibility = View.GONE + binding.tvProgress.visibility = View.VISIBLE + binding.tvProgress.setText(R.string.rename_progress) + binding.progressBar.visibility = View.VISIBLE } override fun showInputState() { btnChange.isEnabled = true - viewBinding.etInput.isEnabled = true - viewBinding.tvError.visibility = View.GONE - viewBinding.tvProgress.visibility = View.GONE - viewBinding.progressBar.visibility = View.GONE + binding.etInput.isEnabled = true + binding.tvError.visibility = View.GONE + binding.tvProgress.visibility = View.GONE + binding.progressBar.visibility = View.GONE } override fun showError(errorCommand: ErrorCommand) { @@ -107,11 +107,11 @@ class RenameArtistDialogFragment: MvpAppCompatDialogFragment(), RenameArtistView } override fun showPreparedFilesCount(processed: Int, total: Int) { - viewBinding.tvProgress.text = getString(R.string.downloading, processed, total) + binding.tvProgress.text = getString(R.string.downloading, processed, total) } override fun showDownloadingFileInfo(progressInfo: ProgressInfo) { - viewBinding.progressBar.setExtProgress(progressInfo.asInt()) + binding.progressBar.setExtProgress(progressInfo.asInt()) } override fun showEditedFilesCount(processed: Int, total: Int) { @@ -120,7 +120,7 @@ class RenameArtistDialogFragment: MvpAppCompatDialogFragment(), RenameArtistView } else { getString(R.string.rename_progress) } - viewBinding.tvProgress.text = message + binding.tvProgress.text = message } override fun showChangeAllowed(enabled: Boolean) { @@ -129,11 +129,11 @@ class RenameArtistDialogFragment: MvpAppCompatDialogFragment(), RenameArtistView private fun showError(message: String) { btnChange.isEnabled = true - viewBinding.etInput.isEnabled = true - viewBinding.tvProgress.visibility = View.GONE - viewBinding.progressBar.visibility = View.GONE - viewBinding.tvError.visibility = View.VISIBLE - viewBinding.tvError.text = message + binding.etInput.isEnabled = true + binding.tvProgress.visibility = View.GONE + binding.progressBar.visibility = View.GONE + binding.tvError.visibility = View.VISIBLE + binding.tvError.text = message } } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/artist/RenameArtistPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/artist/RenameArtistPresenter.kt index 8d406b31e..84a06cced 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/artist/RenameArtistPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/artist/RenameArtistPresenter.kt @@ -2,6 +2,7 @@ package com.github.anrimian.musicplayer.ui.editor.artist import com.github.anrimian.filesync.SyncInteractor import com.github.anrimian.musicplayer.domain.interactors.editor.EditorInteractor +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.domain.utils.rx.RxUtils import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser import com.github.anrimian.musicplayer.ui.common.mvp.AppPresenter @@ -14,7 +15,7 @@ class RenameArtistPresenter( private val artistId: Long, private val initialName: String, private val editorInteractor: EditorInteractor, - private val syncInteractor: SyncInteractor<*, *, Long>, + private val syncInteractor: SyncInteractor, uiScheduler: Scheduler, errorParser: ErrorParser ): AppPresenter(uiScheduler, errorParser) { diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/common/EditorErrorCommand.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/common/EditorErrorCommand.java index be3a7238c..8fb0c40b6 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/common/EditorErrorCommand.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/common/EditorErrorCommand.java @@ -16,8 +16,8 @@ public class EditorErrorCommand extends ErrorCommand { @Nonnull private final IntentSender intentSender; - public EditorErrorCommand(Throwable throwable) { - super(throwable.getMessage() == null? "" : throwable.getMessage()); + public EditorErrorCommand(String message, Throwable throwable) { + super(message); if (throwable instanceof RecoverableSecurityException) { RecoverableSecurityException recoverableSecurityException = (RecoverableSecurityException) throwable; diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/common/FileEditorUtils.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/common/FileEditorUtils.kt index 2b263a732..8b6a2a48b 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/common/FileEditorUtils.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/common/FileEditorUtils.kt @@ -2,7 +2,8 @@ package com.github.anrimian.musicplayer.ui.editor.common import com.github.anrimian.filesync.SyncInteractor import com.github.anrimian.filesync.models.ProgressInfo -import com.github.anrimian.filesync.models.state.file.Downloading +import com.github.anrimian.filesync.models.state.file.FileSyncState +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.domain.utils.rx.doOnFirst import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Scheduler @@ -11,7 +12,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.subjects.BehaviorSubject fun performFilesChangeAction( - syncInteractor: SyncInteractor<*, *, Long>, + syncInteractor: SyncInteractor, uiScheduler: Scheduler, onFilesPrepared: (Int) -> Unit, onFileDownloading: (ProgressInfo) -> Unit, @@ -21,7 +22,7 @@ fun performFilesChangeAction( editingSubject: BehaviorSubject ) -> Completable ): Completable { - return Single.create { emitter -> + return Single.create { emitter -> val downloadingSubject = BehaviorSubject.create() val editingSubject = BehaviorSubject.create() var preparedFilesCount = 0 @@ -40,11 +41,11 @@ fun performFilesChangeAction( .switchMap { id -> syncInteractor.getFileSyncStateObservable(id) .observeOn(uiScheduler) - .filter { state -> state is Downloading } + .filter { state -> state is FileSyncState.Downloading } .doOnFirst { onFilesPrepared(++preparedFilesCount) } } .subscribe { fileSyncState -> - if (fileSyncState is Downloading) { + if (fileSyncState is FileSyncState.Downloading) { onFileDownloading(fileSyncState.getProgress()) } } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/composition/CompositionEditorActivity.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/composition/CompositionEditorActivity.kt index 8fccd6a1a..e5c433efa 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/composition/CompositionEditorActivity.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/composition/CompositionEditorActivity.kt @@ -3,11 +3,11 @@ package com.github.anrimian.musicplayer.ui.editor.composition import android.content.Context import android.content.Intent import android.os.Bundle +import android.text.InputType import android.view.MenuItem import android.view.View import android.widget.TextView import com.beloo.widget.chipslayoutmanager.ChipsLayoutManager -import com.github.anrimian.filesync.models.state.file.Downloading import com.github.anrimian.filesync.models.state.file.FileSyncState import com.github.anrimian.musicplayer.Constants import com.github.anrimian.musicplayer.Constants.Tags @@ -19,6 +19,7 @@ import com.github.anrimian.musicplayer.domain.models.composition.FullComposition import com.github.anrimian.musicplayer.domain.models.composition.InitialSource import com.github.anrimian.musicplayer.domain.models.genres.ShortGenre import com.github.anrimian.musicplayer.domain.utils.FileUtils +import com.github.anrimian.musicplayer.domain.utils.TextUtils import com.github.anrimian.musicplayer.ui.common.activity.PickImageContract import com.github.anrimian.musicplayer.ui.common.dialogs.input.InputTextDialogFragment import com.github.anrimian.musicplayer.ui.common.dialogs.input.newInputTextDialogFragment @@ -68,6 +69,9 @@ class CompositionEditorActivity : MvpAppCompatActivity(), CompositionEditorView private lateinit var addGenreDialogFragmentRunner: DialogFragmentRunner private lateinit var editGenreDialogFragmentRunner: DialogFragmentRunner private lateinit var lyricsDialogFragmentRunner: DialogFragmentRunner + private lateinit var trackNumberDialogFragmentRunner: DialogFragmentRunner + private lateinit var discNumberDialogFragmentRunner: DialogFragmentRunner + private lateinit var commentDialogFragmentRunner: DialogFragmentRunner private lateinit var coverMenuDialogRunner: DialogFragmentRunner private lateinit var progressDialogRunner: DialogFragmentDelayRunner @@ -103,24 +107,27 @@ class CompositionEditorActivity : MvpAppCompatActivity(), CompositionEditorView viewBinding.changeAlbumArtistClickableArea.setOnClickListener { presenter.onChangeAlbumArtistClicked() } viewBinding.changeGenreClickableArea.setOnClickListener { presenter.onAddGenreItemClicked() } viewBinding.changeCoverClickableArea.setOnClickListener { presenter.onChangeCoverClicked() } + viewBinding.changeTrackNumberClickableArea.setOnClickListener { presenter.onChangeTrackNumberClicked() } + viewBinding.changeDiscNumberClickableArea.setOnClickListener { presenter.onChangeDiscNumberClicked() } + viewBinding.changeCommentClickableArea.setOnClickListener { presenter.onChangeCommentClicked() } viewBinding.changeLyricsClickableArea.setOnClickListener { presenter.onChangeLyricsClicked() } - ViewUtils.onLongClick(viewBinding.changeAuthorClickableArea) { + viewBinding.changeAuthorClickableArea.setOnLongClickListener { copyText(viewBinding.tvAuthor, viewBinding.tvAuthorHint) } - ViewUtils.onLongClick(viewBinding.changeTitleClickableArea) { + viewBinding.changeTitleClickableArea.setOnLongClickListener { copyText(viewBinding.tvTitle, viewBinding.tvTitleHint) } ViewUtils.onLongClick(viewBinding.changeFilenameClickableArea) { presenter.onCopyFileNameClicked() } - ViewUtils.onLongClick(viewBinding.changeAlbumClickableArea) { + viewBinding.changeAlbumClickableArea.setOnLongClickListener { copyText(viewBinding.tvAlbum, viewBinding.tvAlbumHint) } - ViewUtils.onLongClick(viewBinding.changeAlbumArtistClickableArea) { + viewBinding.changeAlbumArtistClickableArea.setOnLongClickListener { copyText(viewBinding.tvAlbumArtist, viewBinding.tvAlbumAuthorHint) } - ViewUtils.onLongClick(viewBinding.changeLyricsClickableArea) { + viewBinding.changeLyricsClickableArea.setOnLongClickListener { copyText(viewBinding.tvLyrics, viewBinding.tvLyricsHint) } @@ -176,6 +183,20 @@ class CompositionEditorActivity : MvpAppCompatActivity(), CompositionEditorView presenter.onNewGenreNameEntered(name, GenreSerializer.deserializeShort(extra)) } } + trackNumberDialogFragmentRunner = DialogFragmentRunner(fm, Tags.TRACK_NUMBER_TAG) { fragment -> + fragment.setOnCompleteListener { text -> + presenter.onNewTrackNumberEntered(text.toLongOrNull()) + } + } + discNumberDialogFragmentRunner = DialogFragmentRunner(fm, Tags.DISC_NUMBER_TAG) { fragment -> + fragment.setOnCompleteListener { text -> + presenter.onNewDiscNumberEntered(text.toLongOrNull()) + } + } + commentDialogFragmentRunner = DialogFragmentRunner(fm, Tags.DISC_NUMBER_TAG) { fragment -> + fragment.setOnCompleteListener(presenter::onNewCommentEntered) + } + coverMenuDialogRunner = DialogFragmentRunner( fm, Tags.EDIT_COVER_TAG @@ -216,19 +237,31 @@ class CompositionEditorActivity : MvpAppCompatActivity(), CompositionEditorView val album = composition.album viewBinding.tvAlbum.text = album - val albumArtistVisibility = if (album == null) View.GONE else View.VISIBLE - viewBinding.tvAlbumArtist.visibility = albumArtistVisibility - viewBinding.tvAlbumAuthorHint.visibility = albumArtistVisibility - viewBinding.ivAlbumArtist.visibility = albumArtistVisibility - viewBinding.dividerAlbumArtist.visibility = albumArtistVisibility + val albumFieldsVisibility = if (album == null) View.GONE else View.VISIBLE + viewBinding.tvAlbumArtist.visibility = albumFieldsVisibility + viewBinding.tvAlbumAuthorHint.visibility = albumFieldsVisibility + viewBinding.ivAlbumArtist.visibility = albumFieldsVisibility + viewBinding.dividerAlbumArtist.visibility = albumFieldsVisibility + + viewBinding.tvTrackNumber.visibility = albumFieldsVisibility + viewBinding.tvTrackNumberHint.visibility = albumFieldsVisibility + viewBinding.ivTrackNumber.visibility = albumFieldsVisibility + viewBinding.dividerTrackNumber.visibility = albumFieldsVisibility + + viewBinding.tvDiscNumber.visibility = albumFieldsVisibility + viewBinding.tvDiscNumberHint.visibility = albumFieldsVisibility + viewBinding.dividerTrackNumberVertical.visibility = albumFieldsVisibility // -// dividerLyrics.setVisibility(albumArtistVisibility); +// dividerLyrics.setVisibility(albumFieldsVisibility); viewBinding.tvAlbumArtist.text = composition.albumArtist viewBinding.tvLyrics.text = composition.lyrics viewBinding.tvAuthor.text = FormatUtils.formatAuthor(composition.artist, this) viewBinding.tvFilename.text = FileUtils.formatFileName(composition.fileName, true) + viewBinding.tvTrackNumber.text = TextUtils.toString(composition.trackNumber) + viewBinding.tvDiscNumber.text = TextUtils.toString(composition.discNumber) + viewBinding.tvComment.text = composition.comment } override fun showCompositionCover(composition: FullComposition) { @@ -343,6 +376,44 @@ class CompositionEditorActivity : MvpAppCompatActivity(), CompositionEditorView albumDialogFragmentRunner.show(fragment) } + override fun showEnterTrackNumberDialog(composition: FullComposition) { + val fragment = newInputTextDialogFragment( + R.string.change_track_number, + R.string.change, + R.string.cancel, + R.string.track_number, + composition.trackNumber?.toString(), + inputType = InputType.TYPE_CLASS_NUMBER, + digits = "123456789" + ) + trackNumberDialogFragmentRunner.show(fragment) + } + + override fun showEnterDiscNumberDialog(composition: FullComposition) { + val fragment = newInputTextDialogFragment( + R.string.change_disc_number, + R.string.change, + R.string.cancel, + R.string.disc_number, + composition.discNumber?.toString(), + inputType = InputType.TYPE_CLASS_NUMBER, + digits = "123456789" + ) + discNumberDialogFragmentRunner.show(fragment) + } + + override fun showEnterCommentDialog(composition: FullComposition) { + val fragment = newInputTextDialogFragment( + R.string.change_comment, + R.string.change, + R.string.cancel, + R.string.comment, + composition.comment, + completeOnEnterButton = false + ) + commentDialogFragmentRunner.show(fragment) + } + override fun showErrorMessage(errorCommand: ErrorCommand) { errorHandler.handleError(errorCommand) { MessagesUtils.makeSnackbar( @@ -390,7 +461,7 @@ class CompositionEditorActivity : MvpAppCompatActivity(), CompositionEditorView val isFileRemote = composition.storageId == null && composition.initialSource == InitialSource.REMOTE showFileSyncState(fileSyncState, isFileRemote, viewBinding.pvFileState) progressDialogRunner.runAction { dialog -> - val message = if (fileSyncState is Downloading) { + val message = if (fileSyncState is FileSyncState.Downloading) { val progress = fileSyncState.getProgress() val progressPercentage = progress.asInt() dialog.setProgress(progressPercentage) @@ -415,9 +486,14 @@ class CompositionEditorActivity : MvpAppCompatActivity(), CompositionEditorView onTextCopied() } - private fun copyText(textView: TextView, tvLabel: TextView) { - AndroidUtils.copyText(this, textView.text.toString(), tvLabel.text.toString()) + private fun copyText(textView: TextView, tvLabel: TextView): Boolean { + val text = textView.text.toString() + if (text.isEmpty()) { + return false + } + AndroidUtils.copyText(this, text, tvLabel.text.toString()) onTextCopied() + return true } private fun onTextCopied() { diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/composition/CompositionEditorPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/composition/CompositionEditorPresenter.kt index 2c48a315f..d6e8bb74b 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/composition/CompositionEditorPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/composition/CompositionEditorPresenter.kt @@ -6,6 +6,7 @@ import com.github.anrimian.musicplayer.domain.interactors.editor.EditorInteracto import com.github.anrimian.musicplayer.domain.models.composition.FullComposition import com.github.anrimian.musicplayer.domain.models.genres.ShortGenre import com.github.anrimian.musicplayer.domain.models.image.ImageSource +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.domain.models.utils.isFileExists import com.github.anrimian.musicplayer.domain.utils.rx.RxUtils import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser @@ -17,7 +18,7 @@ import io.reactivex.rxjava3.disposables.Disposable class CompositionEditorPresenter( private val compositionId: Long, private val editorInteractor: EditorInteractor, - private val syncInteractor: SyncInteractor<*, *, Long>, + private val syncInteractor: SyncInteractor, uiScheduler: Scheduler, errorParser: ErrorParser ) : AppPresenter(uiScheduler, errorParser) { @@ -211,6 +212,27 @@ class CompositionEditorPresenter( viewState.showCoverActionsDialog() } + fun onChangeTrackNumberClicked() { + if (!::composition.isInitialized) { + return + } + viewState.showEnterTrackNumberDialog(composition) + } + + fun onChangeDiscNumberClicked() { + if (!::composition.isInitialized) { + return + } + viewState.showEnterDiscNumberDialog(composition) + } + + fun onChangeCommentClicked() { + if (!::composition.isInitialized) { + return + } + viewState.showEnterCommentDialog(composition) + } + fun onClearCoverClicked() { performChangeAction(editorInteractor.removeCompositionAlbumArt(compositionId)) } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/composition/CompositionEditorView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/composition/CompositionEditorView.kt index 291035952..1b6dcbb57 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/composition/CompositionEditorView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/editor/composition/CompositionEditorView.kt @@ -82,4 +82,13 @@ interface CompositionEditorView : MvpView { @Skip fun showSelectImageFromGalleryScreen() + @Skip + fun showEnterTrackNumberDialog(composition: FullComposition) + + @Skip + fun showEnterDiscNumberDialog(composition: FullComposition) + + @Skip + fun showEnterCommentDialog(composition: FullComposition) + } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/equalizer/EqualizerDialogFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/equalizer/EqualizerDialogFragment.kt index cb4441cc1..70ba88a19 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/equalizer/EqualizerDialogFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/equalizer/EqualizerDialogFragment.kt @@ -42,7 +42,7 @@ class EqualizerDialogFragment : MvpBottomSheetDialogFragment(), EqualizerView { private val presenter by moxyPresenter { Components.getAppComponent().equalizerPresenter() } - private lateinit var viewBinding: DialogEqualizerBinding + private lateinit var binding: DialogEqualizerBinding private lateinit var equalizerController: EqualizerController @@ -56,8 +56,8 @@ class EqualizerDialogFragment : MvpBottomSheetDialogFragment(), EqualizerView { @SuppressLint("RestrictedApi") override fun setupDialog(dialog: Dialog, style: Int) { super.setupDialog(dialog, style) - viewBinding = DialogEqualizerBinding.inflate(LayoutInflater.from(context)) - val view = viewBinding.root + binding = DialogEqualizerBinding.inflate(LayoutInflater.from(context)) + val view = binding.root dialog.setContentView(view) view.measure( @@ -77,22 +77,22 @@ class EqualizerDialogFragment : MvpBottomSheetDialogFragment(), EqualizerView { AndroidUtils.setDialogNavigationBarColorAttr(dialog, R.attr.dialogBackground) - RecyclerViewUtils.attachDynamicShadow(viewBinding.nestedScrollView, viewBinding.titleShadow) + RecyclerViewUtils.attachDynamicShadow(binding.nestedScrollView, binding.titleShadow) equalizerController = Components.getAppComponent().equalizerController() - viewBinding.rbUseSystemEqualizer.setOnClickListener { enableSystemEqualizer() } - viewBinding.btnOpenSystemEqualizer.setOnClickListener { openSystemEqualizer() } - viewBinding.rbUseAppEqualizer.setOnClickListener { enableAppEqualizer() } - viewBinding.rbDisableEqualizer.setOnClickListener { disableEqualizer() } - viewBinding.ivClose.setOnClickListener { dismissAllowingStateLoss() } - viewBinding.btnRestartSystemEqualizer.setOnClickListener { presenter.onRestartAppEqClicked() } + binding.rbUseSystemEqualizer.setOnClickListener { enableSystemEqualizer() } + binding.btnOpenSystemEqualizer.setOnClickListener { openSystemEqualizer() } + binding.rbUseAppEqualizer.setOnClickListener { enableAppEqualizer() } + binding.rbDisableEqualizer.setOnClickListener { disableEqualizer() } + binding.ivClose.setOnClickListener { dismissAllowingStateLoss() } + binding.btnRestartSystemEqualizer.setOnClickListener { presenter.onRestartAppEqClicked() } showActiveEqualizer(equalizerController.selectedEqualizerType) - CompatUtils.setOutlineButtonStyle(viewBinding.btnOpenSystemEqualizer) - CompatUtils.setOutlineButtonStyle(viewBinding.tvPresets) - CompatUtils.setOutlineButtonStyle(viewBinding.btnRestartSystemEqualizer) + CompatUtils.setOutlineButtonStyle(binding.btnOpenSystemEqualizer) + CompatUtils.setOutlineButtonStyle(binding.tvPresets) + CompatUtils.setOutlineButtonStyle(binding.btnRestartSystemEqualizer) } override fun onResume() { @@ -106,11 +106,11 @@ class EqualizerDialogFragment : MvpBottomSheetDialogFragment(), EqualizerView { textResId = R.string.external_equalizer_not_found enabled = false } - viewBinding.btnOpenSystemEqualizer.isEnabled = enabled - viewBinding.rbUseSystemEqualizer.isEnabled = enabled - viewBinding.tvSystemEqualizerText.isEnabled = enabled - viewBinding.tvSystemEqualizerDescription.isEnabled = enabled - viewBinding.tvSystemEqualizerDescription.text = getString(textResId) + binding.btnOpenSystemEqualizer.isEnabled = enabled + binding.rbUseSystemEqualizer.isEnabled = enabled + binding.tvSystemEqualizerText.isEnabled = enabled + binding.tvSystemEqualizerDescription.isEnabled = enabled + binding.tvSystemEqualizerDescription.text = getString(textResId) } override fun showErrorMessage(errorCommand: ErrorCommand) { @@ -126,7 +126,7 @@ class EqualizerDialogFragment : MvpBottomSheetDialogFragment(), EqualizerView { ViewGroup.LayoutParams.WRAP_CONTENT ) lp.weight = 1f - viewBinding.llBands.addView(binding.root, lp) //recalculate dialog height? + this.binding.llBands.addView(binding.root, lp) //recalculate dialog height? val lowestRange = config.lowestBandRange val highestRange = config.highestBandRange @@ -147,7 +147,7 @@ class EqualizerDialogFragment : MvpBottomSheetDialogFragment(), EqualizerView { val preset = presets[i] menuBuilder.add(i, preset.presetName) } - viewBinding.tvPresets.setOnClickListener { view -> + binding.tvPresets.setOnClickListener { view -> PopupMenuWindow.showActionBarPopup( view, menuBuilder.items, @@ -170,7 +170,7 @@ class EqualizerDialogFragment : MvpBottomSheetDialogFragment(), EqualizerView { } override fun showEqualizerRestartButton(show: Boolean) { - viewBinding.btnRestartSystemEqualizer.visibility = if (show) View.VISIBLE else View.GONE + binding.btnRestartSystemEqualizer.visibility = if (show) View.VISIBLE else View.GONE } private fun enableSystemEqualizer() { @@ -194,9 +194,9 @@ class EqualizerDialogFragment : MvpBottomSheetDialogFragment(), EqualizerView { } private fun showActiveEqualizer(type: Int) { - viewBinding.rbUseSystemEqualizer.isChecked = type == EqualizerType.EXTERNAL - viewBinding.rbUseAppEqualizer.isChecked = type == EqualizerType.APP - viewBinding.rbDisableEqualizer.isChecked = type == EqualizerType.NONE + binding.rbUseSystemEqualizer.isChecked = type == EqualizerType.EXTERNAL + binding.rbUseAppEqualizer.isChecked = type == EqualizerType.APP + binding.rbDisableEqualizer.isChecked = type == EqualizerType.NONE setInAppEqualizerSettingsEnabled(type == EqualizerType.APP) } @@ -206,6 +206,6 @@ class EqualizerDialogFragment : MvpBottomSheetDialogFragment(), EqualizerView { val binding = pair.first binding.sbLevel.isEnabled = enabled } - viewBinding.tvPresets.isEnabled = enabled + binding.tvPresets.isEnabled = enabled } } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/LibraryFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/LibraryFragment.kt index e7a00a3dc..4b3cfd564 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/LibraryFragment.kt @@ -13,11 +13,12 @@ import com.github.anrimian.musicplayer.ui.library.albums.list.AlbumsListFragment import com.github.anrimian.musicplayer.ui.library.artists.list.ArtistsListFragment import com.github.anrimian.musicplayer.ui.library.compositions.LibraryCompositionsFragment import com.github.anrimian.musicplayer.ui.library.folders.root.newLibraryFoldersRootFragment -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigation +import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigationListener import moxy.MvpAppCompatFragment -open class LibraryFragment : MvpAppCompatFragment(), FragmentLayerListener { +open class LibraryFragment : MvpAppCompatFragment(), + FragmentNavigationListener { private lateinit var uiStateRepository: UiStateRepository @@ -26,7 +27,7 @@ open class LibraryFragment : MvpAppCompatFragment(), FragmentLayerListener { uiStateRepository = Components.getAppComponent().uiStateRepository() } - override fun onFragmentMovedOnTop() { + override fun onFragmentResumed() { //we can't use Toolbar.setup here. Because different fields are changed in different places // A: we can wrap setup into setupLibraryToolbar(parentFragmentManager, callback) // and setup common part there and remove common method diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/items/AlbumItemsFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/items/AlbumItemsFragment.kt index e315802c9..aa179d9e4 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/items/AlbumItemsFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/items/AlbumItemsFragment.kt @@ -14,8 +14,10 @@ import com.github.anrimian.musicplayer.R import com.github.anrimian.musicplayer.databinding.FragmentBaseFabListBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.models.albums.Album +import com.github.anrimian.musicplayer.domain.models.albums.AlbumComposition import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.composition.CurrentComposition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.ui.common.dialogs.shareCompositions @@ -28,15 +30,14 @@ import com.github.anrimian.musicplayer.ui.common.view.ViewUtils import com.github.anrimian.musicplayer.ui.editor.album.newAlbumEditorIntent import com.github.anrimian.musicplayer.ui.editor.common.DeleteErrorHandler import com.github.anrimian.musicplayer.ui.editor.common.ErrorHandler +import com.github.anrimian.musicplayer.ui.library.albums.items.adaptar.AlbumCompositionsAdapter import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsFragment -import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsPresenter -import com.github.anrimian.musicplayer.ui.library.compositions.adapter.CompositionsAdapter import com.github.anrimian.musicplayer.ui.playlist_screens.choose.ChoosePlayListDialogFragment import com.github.anrimian.musicplayer.ui.playlist_screens.choose.newChoosePlayListDialogFragment import com.github.anrimian.musicplayer.ui.utils.fragments.BackButtonListener import com.github.anrimian.musicplayer.ui.utils.fragments.DialogFragmentRunner -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigation +import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigationListener import com.github.anrimian.musicplayer.ui.utils.slidr.SlidrPanel import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.RecyclerViewUtils import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.touch_helper.short_swipe.ShortSwipeCallback @@ -51,68 +52,67 @@ fun newAlbumItemsFragment(albumId: Long): AlbumItemsFragment { return fragment } -class AlbumItemsFragment : BaseLibraryCompositionsFragment(), AlbumItemsView, FragmentLayerListener, +class AlbumItemsFragment : BaseLibraryCompositionsFragment(), AlbumItemsView, + FragmentNavigationListener, BackButtonListener { private val presenter by moxyPresenter { Components.albumItemsComponent(getAlbumId()).albumItemsPresenter() } - private lateinit var viewBinding: FragmentBaseFabListBinding + private lateinit var binding: FragmentBaseFabListBinding private lateinit var toolbar: AdvancedToolbar - private lateinit var adapter: CompositionsAdapter + private lateinit var adapter: AlbumCompositionsAdapter private lateinit var layoutManager: LinearLayoutManager private lateinit var choosePlayListDialogRunner: DialogFragmentRunner private lateinit var deletingErrorHandler: ErrorHandler - override fun getLibraryPresenter(): BaseLibraryCompositionsPresenter { - return presenter - } + override fun getLibraryPresenter(): AlbumItemsPresenter = presenter override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - viewBinding = FragmentBaseFabListBinding.inflate(inflater, container, false) - return viewBinding.root + binding = FragmentBaseFabListBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) toolbar = requireActivity().findViewById(R.id.toolbar) - viewBinding.progressStateView.onTryAgainClick { presenter.onTryAgainLoadCompositionsClicked() } + binding.progressStateView.onTryAgainClick { presenter.onTryAgainLoadCompositionsClicked() } - RecyclerViewUtils.attachFastScroller(viewBinding.recyclerView, true) - adapter = CompositionsAdapter( + RecyclerViewUtils.attachFastScroller(binding.recyclerView, true) + adapter = AlbumCompositionsAdapter( this, - viewBinding.recyclerView, + binding.recyclerView, presenter.getSelectedCompositions(), presenter::onCompositionClicked, presenter::onCompositionLongClick, presenter::onCompositionIconClicked, this::onCompositionMenuClicked ) - viewBinding.recyclerView.adapter = adapter - viewBinding.fab.setOnClickListener { presenter.onPlayAllButtonClicked() } - ViewUtils.onLongVibrationClick(viewBinding.fab, presenter::onChangeRandomModePressed) + binding.recyclerView.adapter = adapter + binding.fab.setOnClickListener { presenter.onPlayAllButtonClicked() } + ViewUtils.onLongVibrationClick(binding.fab, presenter::onChangeRandomModePressed) layoutManager = LinearLayoutManager(context) - viewBinding.recyclerView.layoutManager = layoutManager + binding.recyclerView.layoutManager = layoutManager val callback = ShortSwipeCallback(requireContext(), R.drawable.ic_play_next, R.string.play_next, swipeCallback = presenter::onPlayNextCompositionClicked ) val itemTouchHelper = ItemTouchHelper(callback) - itemTouchHelper.attachToRecyclerView(viewBinding.recyclerView) + itemTouchHelper.attachToRecyclerView(binding.recyclerView) SlidrPanel.simpleSwipeBack( - viewBinding.listContainer, + binding.listContainer, this, toolbar::onStackFragmentSlided ) @@ -128,14 +128,14 @@ class AlbumItemsFragment : BaseLibraryCompositionsFragment(), AlbumItemsView, Fr } } - override fun onFragmentMovedOnTop() { + override fun onFragmentResumed() { // super.onFragmentMovedOnTop(); presenter.onFragmentMovedToTop() val toolbar: AdvancedToolbar = requireActivity().findViewById(R.id.toolbar) toolbar.setupSearch(null, null) toolbar.setTitleClickListener(null) toolbar.setupSelectionModeMenu(R.menu.library_compositions_selection_menu, this::onActionModeItemClicked) - toolbar.setupOptionsMenu(R.menu.album_menu, this::onOptionsItemClicked) + toolbar.setupOptionsMenu(R.menu.library_album_items_menu, this::onOptionsItemClicked) } override fun onStop() { @@ -165,41 +165,41 @@ class AlbumItemsFragment : BaseLibraryCompositionsFragment(), AlbumItemsView, Fr } override fun showEmptyList() { - viewBinding.fab.visibility = View.GONE - viewBinding.progressStateView.showMessage(R.string.no_compositions) + binding.fab.visibility = View.GONE + binding.progressStateView.showMessage(R.string.no_compositions) } override fun showEmptySearchResult() { - viewBinding.fab.visibility = View.GONE - viewBinding.progressStateView.showMessage(R.string.compositions_for_search_not_found) + binding.fab.visibility = View.GONE + binding.progressStateView.showMessage(R.string.compositions_for_search_not_found) } override fun showList() { - viewBinding.fab.visibility = View.VISIBLE - viewBinding.progressStateView.hideAll() + binding.fab.visibility = View.VISIBLE + binding.progressStateView.hideAll() } override fun showLoading() { - viewBinding.progressStateView.showProgress() + binding.progressStateView.showProgress() } override fun showLoadingError(errorCommand: ErrorCommand) { - viewBinding.progressStateView.showMessage(errorCommand.message, true) + binding.progressStateView.showMessage(errorCommand.message, true) } - override fun updateList(genres: List) { - adapter.submitList(genres) + override fun updateList(compositions: List) { + adapter.submitList(compositions) } override fun restoreListPosition(listPosition: ListPosition) { ViewUtils.scrollToPosition(layoutManager, listPosition) } - override fun onCompositionSelected(composition: Composition, position: Int) { + override fun onCompositionSelected(composition: AlbumComposition, position: Int) { adapter.setItemSelected(position) } - override fun onCompositionUnselected(composition: Composition, position: Int) { + override fun onCompositionUnselected(composition: AlbumComposition, position: Int) { adapter.setItemUnselected(position) } @@ -213,7 +213,7 @@ class AlbumItemsFragment : BaseLibraryCompositionsFragment(), AlbumItemsView, Fr override fun showAddingToPlayListError(errorCommand: ErrorCommand) { MessagesUtils.makeSnackbar( - viewBinding.listContainer, + binding.listContainer, getString(R.string.add_to_playlist_error_template, errorCommand.message), Snackbar.LENGTH_SHORT ) @@ -221,9 +221,12 @@ class AlbumItemsFragment : BaseLibraryCompositionsFragment(), AlbumItemsView, Fr } override fun showAddingToPlayListComplete(playList: PlayList, compositions: List) { - val text = - MessagesUtils.getAddToPlayListCompleteMessage(requireActivity(), playList, compositions) - MessagesUtils.makeSnackbar(viewBinding.listContainer, text, Snackbar.LENGTH_SHORT).show() + val text = MessagesUtils.getAddToPlayListCompleteMessage( + requireActivity(), + playList, + compositions + ) + MessagesUtils.makeSnackbar(binding.listContainer, text, Snackbar.LENGTH_SHORT).show() } override fun showSelectPlayListDialog() { @@ -246,16 +249,16 @@ class AlbumItemsFragment : BaseLibraryCompositionsFragment(), AlbumItemsView, Fr override fun showDeleteCompositionError(errorCommand: ErrorCommand) { deletingErrorHandler.handleError(errorCommand) { MessagesUtils.makeSnackbar( - viewBinding.listContainer, + binding.listContainer, getString(R.string.delete_composition_error_template, errorCommand.message), Snackbar.LENGTH_SHORT ).show() } } - override fun showDeleteCompositionMessage(compositionsToDelete: List) { + override fun showDeleteCompositionMessage(compositionsToDelete: List) { val text = MessagesUtils.getDeleteCompleteMessage(requireActivity(), compositionsToDelete) - MessagesUtils.makeSnackbar(viewBinding.listContainer, text, Snackbar.LENGTH_SHORT).show() + MessagesUtils.makeSnackbar(binding.listContainer, text, Snackbar.LENGTH_SHORT).show() } override fun shareCompositions(selectedCompositions: Collection) { @@ -271,22 +274,22 @@ class AlbumItemsFragment : BaseLibraryCompositionsFragment(), AlbumItemsView, Fr } override fun showRandomMode(isRandomModeEnabled: Boolean) { - FormatUtils.formatPlayAllButton(viewBinding.fab, isRandomModeEnabled) + FormatUtils.formatPlayAllButton(binding.fab, isRandomModeEnabled) } override fun showErrorMessage(errorCommand: ErrorCommand) { - MessagesUtils.makeSnackbar(viewBinding.listContainer, errorCommand.message, Snackbar.LENGTH_SHORT) + MessagesUtils.makeSnackbar(binding.listContainer, errorCommand.message, Snackbar.LENGTH_SHORT) .show() } override fun onCompositionsAddedToPlayNext(compositions: List) { val message = MessagesUtils.getPlayNextMessage(requireContext(), compositions) - MessagesUtils.makeSnackbar(viewBinding.listContainer, message, Snackbar.LENGTH_SHORT).show() + MessagesUtils.makeSnackbar(binding.listContainer, message, Snackbar.LENGTH_SHORT).show() } override fun onCompositionsAddedToQueue(compositions: List) { val message = MessagesUtils.getAddedToQueueMessage(requireContext(), compositions) - MessagesUtils.makeSnackbar(viewBinding.listContainer, message, Snackbar.LENGTH_SHORT).show() + MessagesUtils.makeSnackbar(binding.listContainer, message, Snackbar.LENGTH_SHORT).show() } override fun showEditAlbumScreen(album: Album) { @@ -311,7 +314,7 @@ class AlbumItemsFragment : BaseLibraryCompositionsFragment(), AlbumItemsView, Fr private fun showEditorRequestDeniedMessage() { MessagesUtils.makeSnackbar( - viewBinding.listContainer, + binding.listContainer, R.string.android_r_edit_file_permission_denied, Snackbar.LENGTH_LONG ).show() diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/items/AlbumItemsPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/items/AlbumItemsPresenter.kt index c3efa3d94..1d1bb30aa 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/items/AlbumItemsPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/items/AlbumItemsPresenter.kt @@ -6,7 +6,8 @@ import com.github.anrimian.musicplayer.domain.interactors.player.LibraryPlayerIn import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInteractor import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor import com.github.anrimian.musicplayer.domain.models.albums.Album -import com.github.anrimian.musicplayer.domain.models.composition.Composition +import com.github.anrimian.musicplayer.domain.models.albums.AlbumComposition +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsPresenter @@ -19,10 +20,10 @@ class AlbumItemsPresenter( playListsInteractor: PlayListsInteractor, playerInteractor: LibraryPlayerInteractor, displaySettingsInteractor: DisplaySettingsInteractor, - syncInteractor: SyncInteractor<*, *, Long>, + syncInteractor: SyncInteractor, errorParser: ErrorParser, uiScheduler: Scheduler -) : BaseLibraryCompositionsPresenter( +) : BaseLibraryCompositionsPresenter( playerInteractor, playListsInteractor, displaySettingsInteractor, @@ -38,7 +39,7 @@ class AlbumItemsPresenter( subscribeOnAlbumInfo() } - override fun getCompositionsObservable(searchText: String?): Observable> { + override fun getCompositionsObservable(searchText: String?): Observable> { return interactor.getAlbumItemsObservable(albumId) } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/items/AlbumItemsView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/items/AlbumItemsView.kt index 075de4df2..f0e7e0959 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/items/AlbumItemsView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/items/AlbumItemsView.kt @@ -1,12 +1,13 @@ package com.github.anrimian.musicplayer.ui.library.albums.items import com.github.anrimian.musicplayer.domain.models.albums.Album +import com.github.anrimian.musicplayer.domain.models.albums.AlbumComposition import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsView import moxy.viewstate.strategy.alias.AddToEndSingle import moxy.viewstate.strategy.alias.OneExecution import moxy.viewstate.strategy.alias.Skip -interface AlbumItemsView : BaseLibraryCompositionsView { +interface AlbumItemsView : BaseLibraryCompositionsView { @AddToEndSingle fun showAlbumInfo(album: Album) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/AlbumsListFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/AlbumsListFragment.kt index 2ed9c898c..ccbfe08b5 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/AlbumsListFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/AlbumsListFragment.kt @@ -5,15 +5,21 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager +import com.github.anrimian.musicplayer.Constants import com.github.anrimian.musicplayer.Constants.Tags import com.github.anrimian.musicplayer.R import com.github.anrimian.musicplayer.databinding.FragmentLibraryAlbumsBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.models.albums.Album +import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.order.Order import com.github.anrimian.musicplayer.domain.models.order.OrderType +import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.utils.ListPosition +import com.github.anrimian.musicplayer.domain.utils.toLongArray +import com.github.anrimian.musicplayer.ui.common.dialogs.shareCompositions import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand import com.github.anrimian.musicplayer.ui.common.format.MessagesUtils import com.github.anrimian.musicplayer.ui.common.menu.PopupMenuWindow @@ -25,24 +31,28 @@ import com.github.anrimian.musicplayer.ui.library.LibraryFragment import com.github.anrimian.musicplayer.ui.library.albums.items.newAlbumItemsFragment import com.github.anrimian.musicplayer.ui.library.albums.list.adapter.AlbumsAdapter import com.github.anrimian.musicplayer.ui.library.common.order.SelectOrderDialogFragment +import com.github.anrimian.musicplayer.ui.playlist_screens.choose.ChoosePlayListDialogFragment +import com.github.anrimian.musicplayer.ui.playlist_screens.choose.newChoosePlayListDialogFragment import com.github.anrimian.musicplayer.ui.sleep_timer.SleepTimerDialogFragment import com.github.anrimian.musicplayer.ui.utils.fragments.BackButtonListener import com.github.anrimian.musicplayer.ui.utils.fragments.DialogFragmentRunner -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigation +import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigationListener import com.github.anrimian.musicplayer.ui.utils.fragments.safeShow import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.RecyclerViewUtils +import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.touch_helper.short_swipe.ShortSwipeCallback import com.google.android.material.snackbar.Snackbar import moxy.ktx.moxyPresenter -class AlbumsListFragment : LibraryFragment(), AlbumsListView, FragmentLayerListener, +class AlbumsListFragment : LibraryFragment(), AlbumsListView, + FragmentNavigationListener, BackButtonListener { private val presenter by moxyPresenter { Components.albumsComponent().albumsListPresenter() } - private lateinit var viewBinding: FragmentLibraryAlbumsBinding + private lateinit var binding: FragmentLibraryAlbumsBinding private lateinit var toolbar: AdvancedToolbar @@ -50,46 +60,65 @@ class AlbumsListFragment : LibraryFragment(), AlbumsListView, FragmentLayerListe private lateinit var layoutManager: LinearLayoutManager private lateinit var selectOrderDialogRunner: DialogFragmentRunner + private lateinit var choosePlayListDialogRunner: DialogFragmentRunner override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - viewBinding = FragmentLibraryAlbumsBinding.inflate(inflater, container, false) - return viewBinding.root + binding = FragmentLibraryAlbumsBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) toolbar = requireActivity().findViewById(R.id.toolbar) - viewBinding.progressStateView.onTryAgainClick(presenter::onTryAgainLoadCompositionsClicked) + binding.progressStateView.onTryAgainClick(presenter::onTryAgainLoadCompositionsClicked) adapter = AlbumsAdapter( - viewBinding.rvAlbums, - this::goToAlbumScreen, + this, + binding.rvAlbums, + presenter.getSelectedAlbums(), + presenter::onAlbumClicked, + presenter::onAlbumLongClicked, this::onAlbumMenuClicked ) - viewBinding.rvAlbums.adapter = adapter + binding.rvAlbums.adapter = adapter layoutManager = LinearLayoutManager(context) - viewBinding.rvAlbums.layoutManager = layoutManager - RecyclerViewUtils.attachFastScroller(viewBinding.rvAlbums) + binding.rvAlbums.layoutManager = layoutManager + RecyclerViewUtils.attachFastScroller(binding.rvAlbums) + val callback = ShortSwipeCallback( + requireContext(), + R.drawable.ic_play_next, + R.string.play_next, + swipeCallback = presenter::onPlayNextAlbumClicked + ) + val itemTouchHelper = ItemTouchHelper(callback) + itemTouchHelper.attachToRecyclerView(binding.rvAlbums) val fm = childFragmentManager selectOrderDialogRunner = DialogFragmentRunner(fm, Tags.ORDER_TAG) { f -> f.setOnCompleteListener(presenter::onOrderSelected) } + choosePlayListDialogRunner = DialogFragmentRunner(fm, Tags.SELECT_PLAYLIST_TAG) { fragment -> + fragment.setComplexCompleteListener { playlist, extra -> + presenter.onPlayListToAddingSelected( + playlist, + extra.getLongArray(Constants.Arguments.IDS_ARG)!!, + extra.getBoolean(Constants.Arguments.CLOSE_MULTISELECT_ARG) + ) + } + } } - override fun onFragmentMovedOnTop() { - super.onFragmentMovedOnTop() + override fun onFragmentResumed() { + super.onFragmentResumed() val toolbar: AdvancedToolbar = requireActivity().findViewById(R.id.toolbar) toolbar.setSubtitle(R.string.albums) - toolbar.setupSearch( - presenter::onSearchTextChanged, - presenter.getSearchText() - ) + toolbar.setupSearch(presenter::onSearchTextChanged, presenter.getSearchText()) + toolbar.setupSelectionModeMenu(R.menu.library_albums_selection_menu, this::onActionModeItemClicked) toolbar.setupOptionsMenu(R.menu.library_albums_menu, this::onOptionsItemClicked) } @@ -99,6 +128,10 @@ class AlbumsListFragment : LibraryFragment(), AlbumsListView, FragmentLayerListe } override fun onBackPressed(): Boolean { + if (toolbar.isInActionMode) { + presenter.onSelectionModeBackPressed() + return true + } if (toolbar.isInSearchMode) { toolbar.setSearchModeEnabled(false) return true @@ -107,23 +140,23 @@ class AlbumsListFragment : LibraryFragment(), AlbumsListView, FragmentLayerListe } override fun showEmptyList() { - viewBinding.progressStateView.showMessage(R.string.no_albums_in_library) + binding.progressStateView.showMessage(R.string.no_albums_in_library) } override fun showEmptySearchResult() { - viewBinding.progressStateView.showMessage(R.string.compositions_for_search_not_found) + binding.progressStateView.showMessage(R.string.compositions_for_search_not_found) } override fun showList() { - viewBinding.progressStateView.hideAll() + binding.progressStateView.hideAll() } override fun showLoading() { - viewBinding.progressStateView.showProgress() + binding.progressStateView.showProgress() } override fun showLoadingError(errorCommand: ErrorCommand?) { - viewBinding.progressStateView.showMessage(errorCommand!!.message, true) + binding.progressStateView.showMessage(errorCommand!!.message, true) } override fun submitList(albums: List) { @@ -132,7 +165,7 @@ class AlbumsListFragment : LibraryFragment(), AlbumsListView, FragmentLayerListe override fun showErrorMessage(errorCommand: ErrorCommand) { MessagesUtils.makeSnackbar( - viewBinding.listContainer, + binding.listContainer, errorCommand.message, Snackbar.LENGTH_SHORT ).show() @@ -151,15 +184,79 @@ class AlbumsListFragment : LibraryFragment(), AlbumsListView, FragmentLayerListe ViewUtils.scrollToPosition(layoutManager, listPosition) } - private fun goToAlbumScreen(album: Album) { + override fun goToAlbumScreen(album: Album) { FragmentNavigation.from(parentFragmentManager) .addNewFragment(newAlbumItemsFragment(album.id)) } + override fun onAlbumSelected(album: Album, position: Int) { + adapter.setItemSelected(position) + } + + override fun onAlbumUnselected(album: Album, position: Int) { + adapter.setItemUnselected(position) + } + + override fun setItemsSelected(selected: Boolean) { + adapter.setItemsSelected(selected) + } + + override fun showSelectionMode(count: Int) { + toolbar.showSelectionMode(count) + } + + override fun onCompositionsAddedToPlayNext(compositions: List) { + val message = MessagesUtils.getPlayNextMessage(requireContext(), compositions) + MessagesUtils.makeSnackbar(binding.listContainer, message, Snackbar.LENGTH_SHORT).show() + } + + override fun onCompositionsAddedToQueue(compositions: List) { + val message = MessagesUtils.getAddedToQueueMessage(requireContext(), compositions) + MessagesUtils.makeSnackbar(binding.listContainer, message, Snackbar.LENGTH_SHORT).show() + } + + override fun showSelectPlayListDialog(albums: Collection, closeMultiselect: Boolean) { + val args = Bundle().apply { + putLongArray(Constants.Arguments.IDS_ARG, albums.toLongArray(Album::getId)) + putBoolean(Constants.Arguments.CLOSE_MULTISELECT_ARG, closeMultiselect) + } + choosePlayListDialogRunner.show(newChoosePlayListDialogFragment(args)) + } + + override fun showAddingToPlayListComplete(playList: PlayList, compositions: List) { + val text = MessagesUtils.getAddToPlayListCompleteMessage(requireActivity(), playList, compositions) + MessagesUtils.makeSnackbar(binding.listContainer, text, Snackbar.LENGTH_SHORT).show() + } + + override fun showAddingToPlayListError(errorCommand: ErrorCommand) { + MessagesUtils.makeSnackbar( + binding.listContainer, + getString(R.string.add_to_playlist_error_template, errorCommand.message), + Snackbar.LENGTH_SHORT + ).show() + } + + override fun sendCompositions(compositions: List) { + shareCompositions(this, compositions) + } + + override fun showReceiveCompositionsForSendError(errorCommand: ErrorCommand) { + MessagesUtils.makeSnackbar( + binding.listContainer, + getString(R.string.can_not_receive_file_for_send, errorCommand.message), + Snackbar.LENGTH_SHORT + ).show() + } + private fun onAlbumMenuClicked(view: View, album: Album) { PopupMenuWindow.showPopup(view, R.menu.album_menu) { menuItem -> when (menuItem.itemId) { + R.id.menu_play -> presenter.onPlayAlbumClicked(album) + R.id.menu_play_next -> presenter.onPlayNextAlbumClicked(album) + R.id.menu_add_to_queue -> presenter.onAddToQueueAlbumClicked(album) + R.id.menu_add_to_playlist -> presenter.onAddAlbumToPlayListClicked(album) R.id.menu_edit -> startActivity(newAlbumEditorIntent(requireContext(), album.id)) + R.id.menu_share -> presenter.onShareAlbumClicked(album) } } } @@ -172,4 +269,15 @@ class AlbumsListFragment : LibraryFragment(), AlbumsListView, FragmentLayerListe R.id.menu_equalizer -> EqualizerDialogFragment().safeShow(childFragmentManager) } } + + private fun onActionModeItemClicked(menuItem: MenuItem) { + when (menuItem.itemId) { + R.id.menu_play -> presenter.onPlayAllSelectedClicked() + R.id.menu_select_all -> presenter.onSelectAllButtonClicked() + R.id.menu_play_next -> presenter.onPlayNextSelectedAlbumsClicked() + R.id.menu_add_to_queue -> presenter.onAddToQueueSelectedAlbumsClicked() + R.id.menu_add_to_playlist -> presenter.onAddSelectedAlbumsToPlayListClicked() + R.id.menu_share -> presenter.onShareSelectedAlbumsClicked() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/AlbumsListPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/AlbumsListPresenter.kt index ee2a72336..1b0a7b0be 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/AlbumsListPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/AlbumsListPresenter.kt @@ -2,7 +2,9 @@ package com.github.anrimian.musicplayer.ui.library.albums.list import com.github.anrimian.musicplayer.domain.interactors.library.LibraryAlbumsInteractor import com.github.anrimian.musicplayer.domain.models.albums.Album +import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.order.Order +import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.domain.utils.TextUtils import com.github.anrimian.musicplayer.domain.utils.rx.RxUtils @@ -11,15 +13,17 @@ import com.github.anrimian.musicplayer.ui.common.mvp.AppPresenter import io.reactivex.rxjava3.core.Scheduler import io.reactivex.rxjava3.disposables.Disposable -class AlbumsListPresenter(private val interactor: LibraryAlbumsInteractor, - errorParser: ErrorParser, - uiScheduler: Scheduler) - : AppPresenter(uiScheduler, errorParser) { - +class AlbumsListPresenter( + private val interactor: LibraryAlbumsInteractor, + errorParser: ErrorParser, + uiScheduler: Scheduler +) : AppPresenter(uiScheduler, errorParser) { + private var albumsDisposable: Disposable? = null - + private var albums: List = ArrayList() - + private val selectedAlbums = LinkedHashSet() + private var searchText: String? = null override fun onFirstViewAttach() { @@ -35,12 +39,98 @@ class AlbumsListPresenter(private val interactor: LibraryAlbumsInteractor, subscribeOnAlbumsList() } + fun onAlbumClicked(position: Int, album: Album) { + if (selectedAlbums.isEmpty()) { + viewState.goToAlbumScreen(album) + return + } + if (selectedAlbums.contains(album)) { + selectedAlbums.remove(album) + viewState.onAlbumUnselected(album, position) + } else { + selectedAlbums.add(album) + viewState.onAlbumSelected(album, position) + } + viewState.showSelectionMode(selectedAlbums.size) + } + + fun onAlbumLongClicked(position: Int, album: Album) { + selectedAlbums.add(album) + viewState.showSelectionMode(selectedAlbums.size) + viewState.onAlbumSelected(album, position) + } + + fun onPlayAllSelectedClicked() { + playSelectedAlbums() + } + + fun onSelectAllButtonClicked() { + selectedAlbums.clear() + selectedAlbums.addAll(albums) + viewState.showSelectionMode(albums.size) + viewState.setItemsSelected(true) + } + + fun onPlayNextSelectedAlbumsClicked() { + addAlbumsToPlayNext(ArrayList(selectedAlbums)) + closeSelectionMode() + } + + fun onAddToQueueSelectedAlbumsClicked() { + addAlbumsToPlayQueue(ArrayList(selectedAlbums)) + closeSelectionMode() + } + + fun onAddSelectedAlbumsToPlayListClicked() { + viewState.showSelectPlayListDialog(selectedAlbums, true) + } + + fun onPlayListToAddingSelected(playList: PlayList, albums: LongArray, closeMultiselect: Boolean) { + interactor.addAlbumsToPlayList(albums, playList) + .subscribeOnUi( + { compositions -> onAddingToPlayListCompleted(compositions, playList, closeMultiselect) }, + this::onAddingToPlayListError + ) + } + + fun onShareSelectedAlbumsClicked() { + shareAlbumsCompositions(ArrayList(selectedAlbums)) + } + + fun onSelectionModeBackPressed() { + closeSelectionMode() + } + + fun onPlayAlbumClicked(album: Album) { + interactor.startPlaying(listOf(album)) + } + + fun onPlayNextAlbumClicked(position: Int) { + addAlbumsToPlayNext(listOf(albums[position])) + } + + fun onPlayNextAlbumClicked(album: Album) { + addAlbumsToPlayNext(listOf(album)) + } + + fun onAddToQueueAlbumClicked(album: Album) { + addAlbumsToPlayQueue(listOf(album)) + } + + fun onAddAlbumToPlayListClicked(album: Album) { + viewState.showSelectPlayListDialog(listOf(album), false) + } + + fun onShareAlbumClicked(album: Album) { + shareAlbumsCompositions(listOf(album)) + } + fun onOrderMenuItemClicked() { - viewState.showSelectOrderScreen(interactor.order) + viewState.showSelectOrderScreen(interactor.getOrder()) } - fun onOrderSelected(order: Order?) { - interactor.order = order + fun onOrderSelected(order: Order) { + interactor.setOrder(order) } fun onSearchTextChanged(text: String?) { @@ -50,16 +140,65 @@ class AlbumsListPresenter(private val interactor: LibraryAlbumsInteractor, } } + fun getSelectedAlbums(): HashSet = selectedAlbums + fun getSearchText() = searchText + private fun shareAlbumsCompositions(albums: List) { + interactor.getCompositionsInAlbums(albums) + .subscribeOnUi(viewState::sendCompositions, this::onReceiveCompositionsError) + } + + private fun onReceiveCompositionsError(throwable: Throwable) { + val errorCommand = errorParser.parseError(throwable) + viewState.showReceiveCompositionsForSendError(errorCommand) + } + + private fun onAddingToPlayListCompleted( + compositions: List, + playList: PlayList, + closeMultiselect: Boolean + ) { + viewState.showAddingToPlayListComplete(playList, compositions) + if (closeMultiselect && selectedAlbums.isNotEmpty()) { + closeSelectionMode() + } + } + + private fun onAddingToPlayListError(throwable: Throwable) { + val errorCommand = errorParser.parseError(throwable) + viewState.showAddingToPlayListError(errorCommand) + } + + private fun playSelectedAlbums() { + interactor.startPlaying(ArrayList(selectedAlbums)) + closeSelectionMode() + } + + private fun addAlbumsToPlayNext(albums: List) { + interactor.addAlbumsToPlayNext(albums) + .launchOnUi(viewState::onCompositionsAddedToPlayNext, viewState::showErrorMessage) + } + + private fun addAlbumsToPlayQueue(albums: List) { + interactor.addAlbumsToQueue(albums) + .launchOnUi(viewState::onCompositionsAddedToQueue, viewState::showErrorMessage) + } + + private fun closeSelectionMode() { + selectedAlbums.clear() + viewState.showSelectionMode(0) + viewState.setItemsSelected(false) + } + private fun subscribeOnAlbumsList() { if (albums.isEmpty()) { viewState.showLoading() } RxUtils.dispose(albumsDisposable, presenterDisposable) albumsDisposable = interactor.getAlbumsObservable(searchText) - .observeOn(uiScheduler) - .subscribe(this::onAlbumsReceived, this::onAlbumsReceivingError) + .observeOn(uiScheduler) + .subscribe(this::onAlbumsReceived, this::onAlbumsReceivingError) presenterDisposable.add(albumsDisposable!!) } @@ -82,7 +221,7 @@ class AlbumsListPresenter(private val interactor: LibraryAlbumsInteractor, } else { viewState.showList() if (firstReceive) { - val listPosition = interactor.savedListPosition + val listPosition = interactor.getSavedListPosition() if (listPosition != null) { viewState.restoreListPosition(listPosition) } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/AlbumsListView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/AlbumsListView.kt index c9e250b03..58f81c047 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/AlbumsListView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/AlbumsListView.kt @@ -1,7 +1,9 @@ package com.github.anrimian.musicplayer.ui.library.albums.list import com.github.anrimian.musicplayer.domain.models.albums.Album +import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.order.Order +import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand import moxy.MvpView @@ -9,6 +11,7 @@ import moxy.viewstate.strategy.AddToEndSingleTagStrategy import moxy.viewstate.strategy.StateStrategyType import moxy.viewstate.strategy.alias.AddToEndSingle import moxy.viewstate.strategy.alias.OneExecution +import moxy.viewstate.strategy.alias.Skip private const val LIST_STATE = "list_state" @@ -41,4 +44,39 @@ interface AlbumsListView : MvpView { @OneExecution fun restoreListPosition(listPosition: ListPosition) + @Skip + fun goToAlbumScreen(album: Album) + + @Skip + fun onAlbumSelected(album: Album, position: Int) + + @Skip + fun onAlbumUnselected(album: Album, position: Int) + + @Skip + fun setItemsSelected(selected: Boolean) + + @AddToEndSingle + fun showSelectionMode(count: Int) + + @OneExecution + fun onCompositionsAddedToPlayNext(compositions: List) + + @OneExecution + fun onCompositionsAddedToQueue(compositions: List) + + @OneExecution + fun showSelectPlayListDialog(albums: Collection, closeMultiselect: Boolean) + + @OneExecution + fun showAddingToPlayListComplete(playList: PlayList, compositions: List) + + @OneExecution + fun showAddingToPlayListError(errorCommand: ErrorCommand) + + @OneExecution + fun sendCompositions(compositions: List) + + @OneExecution + fun showReceiveCompositionsForSendError(errorCommand: ErrorCommand) } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/adapter/AlbumViewHolder.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/adapter/AlbumViewHolder.kt index 24eec2e3d..705098ccf 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/adapter/AlbumViewHolder.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/adapter/AlbumViewHolder.kt @@ -1,28 +1,63 @@ package com.github.anrimian.musicplayer.ui.library.albums.list.adapter +import android.content.res.ColorStateList +import android.graphics.Color +import android.graphics.drawable.RippleDrawable import android.view.View import android.view.ViewGroup +import androidx.annotation.ColorInt import com.github.anrimian.musicplayer.R import com.github.anrimian.musicplayer.databinding.ItemAlbumBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.Payloads import com.github.anrimian.musicplayer.domain.models.albums.Album import com.github.anrimian.musicplayer.ui.common.format.FormatUtils -import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.BaseViewHolder +import com.github.anrimian.musicplayer.ui.utils.AndroidUtils +import com.github.anrimian.musicplayer.ui.utils.ViewUtils +import com.github.anrimian.musicplayer.ui.utils.colorFromAttr +import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.ItemDrawable +import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.SelectableViewHolder +import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.short_swipe.SwipeListener class AlbumViewHolder( parent: ViewGroup, - itemClickListener: (Album) -> Unit, + itemClickListener: (Int, Album) -> Unit, + itemLongClickListener: (Int, Album) -> Unit, onItemMenuClickListener: (View, Album) -> Unit -) : BaseViewHolder(parent, R.layout.item_album) { +) : SelectableViewHolder(parent, R.layout.item_album), SwipeListener { private val viewBinding = ItemAlbumBinding.bind(itemView) private lateinit var album: Album + private val backgroundDrawable = ItemDrawable() + private val stateDrawable = ItemDrawable() + private val rippleMaskDrawable = ItemDrawable() + + private var selected = false + private var isSwiping = false + init { - viewBinding.clickableItem.setOnClickListener { itemClickListener(album) } + viewBinding.clickableItem.setOnClickListener { itemClickListener(bindingAdapterPosition, album) } viewBinding.btnActionsMenu.setOnClickListener { v -> onItemMenuClickListener(v, album) } + viewBinding.clickableItem.setOnLongClickListener { + if (selected) { + return@setOnLongClickListener false + } + selectImmediate() + itemLongClickListener(bindingAdapterPosition, album) + true + } + + backgroundDrawable.setColor(context.colorFromAttr(R.attr.listItemBackground)) + itemView.background = backgroundDrawable + stateDrawable.setColor(Color.TRANSPARENT) + viewBinding.clickableItem.background = stateDrawable + viewBinding.clickableItem.foreground = RippleDrawable( + ColorStateList.valueOf(context.colorFromAttr(android.R.attr.colorControlHighlight)), + null, + rippleMaskDrawable + ) } override fun release() { @@ -56,6 +91,48 @@ class AlbumViewHolder( } } + override fun setSelected(selected: Boolean) { + if (this.selected != selected) { + this.selected = selected + val unselectedColor = Color.TRANSPARENT + val selectedColor = selectionColor + val endColor = if (selected) selectedColor else unselectedColor + showStateColor(endColor, true) + } + } + + override fun onSwipeStateChanged(swipeOffset: Float) { + val swiping = swipeOffset > 0.0f + if (isSwiping != swiping) { + isSwiping = swiping + val swipedCorners = context.resources.getDimension(R.dimen.swiped_item_corners) + val from: Float = if (swiping) 0f else swipedCorners + val to: Float = if (swiping) swipedCorners else 0f + val duration = context.resources.getInteger(R.integer.swiped_item_animation_time) + AndroidUtils.animateItemDrawableCorners( + from, + to, + duration, + backgroundDrawable, + stateDrawable, + rippleMaskDrawable + ) + } + } + + private fun selectImmediate() { + showStateColor(selectionColor, false) + selected = true + } + + private fun showStateColor(@ColorInt color: Int, animate: Boolean) { + if (animate) { + stateDrawable.setColor(color) + } else { + ViewUtils.animateItemDrawableColor(stateDrawable, color) + } + } + private fun showAlbumName() { val name = album.name viewBinding.tvAlbumName.text = name diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/adapter/AlbumsAdapter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/adapter/AlbumsAdapter.kt index 9cddb52bb..684c1b829 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/adapter/AlbumsAdapter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/albums/list/adapter/AlbumsAdapter.kt @@ -2,27 +2,44 @@ package com.github.anrimian.musicplayer.ui.library.albums.list.adapter import android.view.View import android.view.ViewGroup +import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView +import com.github.anrimian.musicplayer.domain.Payloads import com.github.anrimian.musicplayer.domain.models.albums.Album import com.github.anrimian.musicplayer.domain.models.utils.AlbumHelper import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.diff_utils.SimpleDiffItemCallback -import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.diff_utils.adapter.DiffListAdapter +import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.mvp.MvpDiffAdapter class AlbumsAdapter( + lifecycleOwner: LifecycleOwner, recyclerView: RecyclerView, - private val itemClickListener: (Album) -> Unit, + private val selectedAlbums: Set, + private val itemClickListener: (Int, Album) -> Unit, + private val itemLongClickListener: (Int, Album) -> Unit, private val onItemMenuClickListener: (View, Album) -> Unit -) : DiffListAdapter( +) : MvpDiffAdapter( + lifecycleOwner, recyclerView, SimpleDiffItemCallback(AlbumHelper::areSourcesTheSame, AlbumHelper::getChangePayload) ) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AlbumViewHolder { - return AlbumViewHolder(parent, itemClickListener, onItemMenuClickListener) + return AlbumViewHolder( + parent, + itemClickListener, + itemLongClickListener, + onItemMenuClickListener + ) } override fun onBindViewHolder(holder: AlbumViewHolder, position: Int) { - holder.bind(getItem(position)) + super.onBindViewHolder(holder, position) + + val album = getItem(position) + holder.bind(album) + + val selected = selectedAlbums.contains(album) + holder.setSelected(selected) } override fun onBindViewHolder( @@ -34,11 +51,29 @@ class AlbumsAdapter( super.onBindViewHolder(holder, position, payloads) return } + for (payload in payloads) { + if (payload === Payloads.ITEM_SELECTED) { + holder.setSelected(true) + return + } + if (payload === Payloads.ITEM_UNSELECTED) { + holder.setSelected(false) + return + } + } holder.update(getItem(position), payloads) } - override fun onViewRecycled(holder: AlbumViewHolder) { - super.onViewRecycled(holder) - holder.release() + fun setItemSelected(position: Int) { + notifyItemChanged(position, Payloads.ITEM_SELECTED) + } + + fun setItemUnselected(position: Int) { + notifyItemChanged(position, Payloads.ITEM_UNSELECTED) + } + + fun setItemsSelected(selected: Boolean) { + forEachHolder { holder -> holder.setSelected(selected) } } + } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/items/ArtistItemsFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/items/ArtistItemsFragment.kt index 6ad209f9e..c0d596589 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/items/ArtistItemsFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/items/ArtistItemsFragment.kt @@ -20,6 +20,7 @@ import com.github.anrimian.musicplayer.domain.models.albums.Album import com.github.anrimian.musicplayer.domain.models.artist.Artist import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.composition.CurrentComposition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.ui.common.dialogs.shareCompositions @@ -35,14 +36,13 @@ import com.github.anrimian.musicplayer.ui.editor.common.ErrorHandler import com.github.anrimian.musicplayer.ui.library.albums.items.newAlbumItemsFragment import com.github.anrimian.musicplayer.ui.library.artists.items.adapter.AlbumsAdapter import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsFragment -import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsPresenter import com.github.anrimian.musicplayer.ui.library.compositions.adapter.CompositionsAdapter import com.github.anrimian.musicplayer.ui.playlist_screens.choose.ChoosePlayListDialogFragment import com.github.anrimian.musicplayer.ui.playlist_screens.choose.newChoosePlayListDialogFragment import com.github.anrimian.musicplayer.ui.utils.fragments.BackButtonListener import com.github.anrimian.musicplayer.ui.utils.fragments.DialogFragmentRunner -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigation +import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigationListener import com.github.anrimian.musicplayer.ui.utils.fragments.safeShow import com.github.anrimian.musicplayer.ui.utils.slidr.SlidrPanel import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.RecyclerViewUtils @@ -52,25 +52,24 @@ import com.google.android.material.snackbar.Snackbar import com.r0adkll.slidr.model.SlidrInterface import moxy.ktx.moxyPresenter -fun newInstance(artistId: Long): ArtistItemsFragment { - val args = Bundle() - args.putLong(Constants.Arguments.ID_ARG, artistId) - val fragment = ArtistItemsFragment() - fragment.arguments = args - return fragment +fun newInstance(artistId: Long) = ArtistItemsFragment().apply { + arguments = Bundle().apply { + putLong(Constants.Arguments.ID_ARG, artistId) + } } class ArtistItemsFragment : BaseLibraryCompositionsFragment(), - ArtistItemsView, FragmentLayerListener, BackButtonListener { + ArtistItemsView, + FragmentNavigationListener, BackButtonListener { private val presenter by moxyPresenter { Components.artistItemsComponent(getAlbumId()).artistItemsPresenter() } - private lateinit var viewBinding: FragmentBaseFabListBinding + private lateinit var binding: FragmentBaseFabListBinding private lateinit var toolbar: AdvancedToolbar private lateinit var albumsAdapter: AlbumsAdapter private lateinit var albumsHeaderWrapper: SingleItemAdapter - private lateinit var adapter: CompositionsAdapter + private lateinit var adapter: CompositionsAdapter private lateinit var layoutManager: LinearLayoutManager private lateinit var choosePlayListDialogRunner: DialogFragmentRunner @@ -81,25 +80,23 @@ class ArtistItemsFragment : BaseLibraryCompositionsFragment(), private var isCompositionsEmpty: Boolean = true private var isAlbumsEmpty: Boolean = true - override fun getLibraryPresenter(): BaseLibraryCompositionsPresenter { - return presenter - } + override fun getLibraryPresenter(): ArtistItemsPresenter = presenter override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View { - viewBinding = FragmentBaseFabListBinding.inflate(inflater, container, false) - return viewBinding.root + binding = FragmentBaseFabListBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) toolbar = requireActivity().findViewById(R.id.toolbar) - viewBinding.progressStateView.onTryAgainClick { presenter.onTryAgainLoadCompositionsClicked() } + binding.progressStateView.onTryAgainClick { presenter.onTryAgainLoadCompositionsClicked() } - RecyclerViewUtils.attachFastScroller(viewBinding.recyclerView, true) + RecyclerViewUtils.attachFastScroller(binding.recyclerView, true) albumsHeaderWrapper = SingleItemAdapter { inflater, parent -> val binding = ItemAlbumsHorizontalBinding.inflate(inflater, parent, false) @@ -119,16 +116,16 @@ class ArtistItemsFragment : BaseLibraryCompositionsFragment(), } adapter = CompositionsAdapter( this, - viewBinding.recyclerView, + binding.recyclerView, presenter.getSelectedCompositions(), presenter::onCompositionClicked, presenter::onCompositionLongClick, presenter::onCompositionIconClicked, this::onCompositionMenuClicked ) - viewBinding.recyclerView.adapter = ConcatAdapter(albumsHeaderWrapper, adapter) + binding.recyclerView.adapter = ConcatAdapter(albumsHeaderWrapper, adapter) layoutManager = LinearLayoutManager(context) - viewBinding.recyclerView.layoutManager = layoutManager + binding.recyclerView.layoutManager = layoutManager val callback = ShortSwipeCallback( requireContext(), R.drawable.ic_play_next, @@ -139,13 +136,13 @@ class ArtistItemsFragment : BaseLibraryCompositionsFragment(), swipeCallback = presenter::onPlayNextCompositionClicked ) val itemTouchHelper = ItemTouchHelper(callback) - itemTouchHelper.attachToRecyclerView(viewBinding.recyclerView) + itemTouchHelper.attachToRecyclerView(binding.recyclerView) - viewBinding.fab.setOnClickListener { presenter.onPlayAllButtonClicked() } - ViewUtils.onLongVibrationClick(viewBinding.fab, presenter::onChangeRandomModePressed) + binding.fab.setOnClickListener { presenter.onPlayAllButtonClicked() } + ViewUtils.onLongVibrationClick(binding.fab, presenter::onChangeRandomModePressed) slidrInterface = SlidrPanel.simpleSwipeBack( - viewBinding.listContainer, + binding.listContainer, this, toolbar::onStackFragmentSlided ) @@ -164,14 +161,14 @@ class ArtistItemsFragment : BaseLibraryCompositionsFragment(), ) { f -> f.setOnCompleteListener(presenter::onPlayListToAddingSelected) } } - override fun onFragmentMovedOnTop() { + override fun onFragmentResumed() { // super.onFragmentMovedOnTop(); presenter.onFragmentMovedToTop() val toolbar: AdvancedToolbar = requireActivity().findViewById(R.id.toolbar) toolbar.setupSearch(null, null) toolbar.setTitleClickListener(null) toolbar.setupSelectionModeMenu(R.menu.library_compositions_selection_menu, this::onActionModeItemClicked) - toolbar.setupOptionsMenu(R.menu.artist_menu, this::onOptionsItemClicked) + toolbar.setupOptionsMenu(R.menu.library_artist_items_menu, this::onOptionsItemClicked) } override fun onStop() { @@ -201,26 +198,26 @@ class ArtistItemsFragment : BaseLibraryCompositionsFragment(), } override fun showEmptyList() { - viewBinding.fab.visibility = View.GONE - viewBinding.progressStateView.hideAll() + binding.fab.visibility = View.GONE + binding.progressStateView.hideAll() } override fun showEmptySearchResult() { - viewBinding.fab.visibility = View.GONE - viewBinding.progressStateView.showMessage(R.string.compositions_for_search_not_found) + binding.fab.visibility = View.GONE + binding.progressStateView.showMessage(R.string.compositions_for_search_not_found) } override fun showList() { - viewBinding.fab.visibility = View.VISIBLE - viewBinding.progressStateView.hideAll() + binding.fab.visibility = View.VISIBLE + binding.progressStateView.hideAll() } override fun showLoading() { - viewBinding.progressStateView.showProgress() + binding.progressStateView.showProgress() } override fun showLoadingError(errorCommand: ErrorCommand) { - viewBinding.progressStateView.showMessage(errorCommand.message, true) + binding.progressStateView.showMessage(errorCommand.message, true) } override fun updateList(compositions: List) { @@ -277,7 +274,7 @@ class ArtistItemsFragment : BaseLibraryCompositionsFragment(), override fun showAddingToPlayListError(errorCommand: ErrorCommand) { MessagesUtils.makeSnackbar( - viewBinding.listContainer, + binding.listContainer, getString(R.string.add_to_playlist_error_template, errorCommand.message), Snackbar.LENGTH_SHORT ).show() @@ -285,7 +282,7 @@ class ArtistItemsFragment : BaseLibraryCompositionsFragment(), override fun showAddingToPlayListComplete(playList: PlayList, compositions: List) { val text = MessagesUtils.getAddToPlayListCompleteMessage(requireActivity(), playList, compositions) - MessagesUtils.makeSnackbar(viewBinding.listContainer, text, Snackbar.LENGTH_SHORT).show() + MessagesUtils.makeSnackbar(binding.listContainer, text, Snackbar.LENGTH_SHORT).show() } override fun showSelectPlayListDialog() { @@ -308,16 +305,16 @@ class ArtistItemsFragment : BaseLibraryCompositionsFragment(), override fun showDeleteCompositionError(errorCommand: ErrorCommand) { deletingErrorHandler.handleError(errorCommand) { MessagesUtils.makeSnackbar( - viewBinding.listContainer, + binding.listContainer, getString(R.string.delete_composition_error_template, errorCommand.message), Snackbar.LENGTH_SHORT ).show() } } - override fun showDeleteCompositionMessage(compositionsToDelete: List) { + override fun showDeleteCompositionMessage(compositionsToDelete: List) { val text = MessagesUtils.getDeleteCompleteMessage(requireActivity(), compositionsToDelete) - MessagesUtils.makeSnackbar(viewBinding.listContainer, text, Snackbar.LENGTH_SHORT).show() + MessagesUtils.makeSnackbar(binding.listContainer, text, Snackbar.LENGTH_SHORT).show() } override fun shareCompositions(selectedCompositions: Collection) { @@ -333,21 +330,21 @@ class ArtistItemsFragment : BaseLibraryCompositionsFragment(), } override fun showRandomMode(isRandomModeEnabled: Boolean) { - FormatUtils.formatPlayAllButton(viewBinding.fab, isRandomModeEnabled) + FormatUtils.formatPlayAllButton(binding.fab, isRandomModeEnabled) } override fun showErrorMessage(errorCommand: ErrorCommand) { - MessagesUtils.makeSnackbar(viewBinding.listContainer, errorCommand.message, Snackbar.LENGTH_LONG).show() + MessagesUtils.makeSnackbar(binding.listContainer, errorCommand.message, Snackbar.LENGTH_LONG).show() } override fun onCompositionsAddedToPlayNext(compositions: List) { val message = MessagesUtils.getPlayNextMessage(requireContext(), compositions) - MessagesUtils.makeSnackbar(viewBinding.listContainer, message, Snackbar.LENGTH_SHORT).show() + MessagesUtils.makeSnackbar(binding.listContainer, message, Snackbar.LENGTH_SHORT).show() } override fun onCompositionsAddedToQueue(compositions: List) { val message = MessagesUtils.getAddedToQueueMessage(requireContext(), compositions) - MessagesUtils.makeSnackbar(viewBinding.listContainer, message, Snackbar.LENGTH_SHORT).show() + MessagesUtils.makeSnackbar(binding.listContainer, message, Snackbar.LENGTH_SHORT).show() } override fun closeScreen() { @@ -385,12 +382,12 @@ class ArtistItemsFragment : BaseLibraryCompositionsFragment(), } private fun showEditorRequestDeniedMessage() { - MessagesUtils.makeSnackbar(viewBinding.listContainer, R.string.android_r_edit_file_permission_denied, Snackbar.LENGTH_LONG).show() + MessagesUtils.makeSnackbar(binding.listContainer, R.string.android_r_edit_file_permission_denied, Snackbar.LENGTH_LONG).show() } private fun updateEmptyMessage() { if (isCompositionsEmpty && isAlbumsEmpty) { - viewBinding.progressStateView.showMessage(R.string.no_compositions) + binding.progressStateView.showMessage(R.string.no_compositions) } } } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/items/ArtistItemsPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/items/ArtistItemsPresenter.kt index 24bdc6a32..b441ea755 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/items/ArtistItemsPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/items/ArtistItemsPresenter.kt @@ -7,6 +7,7 @@ import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInt import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor import com.github.anrimian.musicplayer.domain.models.artist.Artist import com.github.anrimian.musicplayer.domain.models.composition.Composition +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsPresenter @@ -19,10 +20,10 @@ class ArtistItemsPresenter( playListsInteractor: PlayListsInteractor, playerInteractor: LibraryPlayerInteractor, displaySettingsInteractor: DisplaySettingsInteractor, - syncInteractor: SyncInteractor<*, *, Long>, + syncInteractor: SyncInteractor, errorParser: ErrorParser, uiScheduler: Scheduler -) : BaseLibraryCompositionsPresenter( +) : BaseLibraryCompositionsPresenter( playerInteractor, playListsInteractor, displaySettingsInteractor, diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/items/ArtistItemsView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/items/ArtistItemsView.kt index c94ba0c10..21dcd692c 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/items/ArtistItemsView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/items/ArtistItemsView.kt @@ -2,12 +2,13 @@ package com.github.anrimian.musicplayer.ui.library.artists.items import com.github.anrimian.musicplayer.domain.models.albums.Album import com.github.anrimian.musicplayer.domain.models.artist.Artist +import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsView import moxy.viewstate.strategy.alias.AddToEndSingle import moxy.viewstate.strategy.alias.OneExecution import moxy.viewstate.strategy.alias.Skip -interface ArtistItemsView : BaseLibraryCompositionsView { +interface ArtistItemsView : BaseLibraryCompositionsView { @AddToEndSingle fun showArtistInfo(artist: Artist) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/ArtistsListFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/ArtistsListFragment.kt index 85ac727bd..5af9e9581 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/ArtistsListFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/ArtistsListFragment.kt @@ -5,16 +5,23 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager +import com.github.anrimian.musicplayer.Constants.Arguments import com.github.anrimian.musicplayer.Constants.Tags import com.github.anrimian.musicplayer.R import com.github.anrimian.musicplayer.databinding.FragmentLibraryArtistsBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.models.artist.Artist +import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.order.Order import com.github.anrimian.musicplayer.domain.models.order.OrderType +import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.utils.ListPosition +import com.github.anrimian.musicplayer.domain.utils.toLongArray +import com.github.anrimian.musicplayer.ui.common.dialogs.shareCompositions import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand +import com.github.anrimian.musicplayer.ui.common.format.MessagesUtils import com.github.anrimian.musicplayer.ui.common.menu.PopupMenuWindow import com.github.anrimian.musicplayer.ui.common.toolbar.AdvancedToolbar import com.github.anrimian.musicplayer.ui.common.view.ViewUtils @@ -24,21 +31,24 @@ import com.github.anrimian.musicplayer.ui.library.LibraryFragment import com.github.anrimian.musicplayer.ui.library.artists.items.newInstance import com.github.anrimian.musicplayer.ui.library.artists.list.adapter.ArtistsAdapter import com.github.anrimian.musicplayer.ui.library.common.order.SelectOrderDialogFragment +import com.github.anrimian.musicplayer.ui.playlist_screens.choose.ChoosePlayListDialogFragment +import com.github.anrimian.musicplayer.ui.playlist_screens.choose.newChoosePlayListDialogFragment import com.github.anrimian.musicplayer.ui.sleep_timer.SleepTimerDialogFragment import com.github.anrimian.musicplayer.ui.utils.fragments.BackButtonListener import com.github.anrimian.musicplayer.ui.utils.fragments.DialogFragmentRunner -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigation +import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigationListener import com.github.anrimian.musicplayer.ui.utils.fragments.safeShow import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.RecyclerViewUtils +import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.touch_helper.short_swipe.ShortSwipeCallback +import com.google.android.material.snackbar.Snackbar import moxy.ktx.moxyPresenter -class ArtistsListFragment : LibraryFragment(), ArtistsListView, FragmentLayerListener, +class ArtistsListFragment : LibraryFragment(), ArtistsListView, + FragmentNavigationListener, BackButtonListener { - private val presenter by moxyPresenter { - Components.artistsComponent().artistsListPresenter() - } + private val presenter by moxyPresenter { Components.artistsComponent().artistsListPresenter() } private lateinit var binding: FragmentLibraryArtistsBinding @@ -47,6 +57,7 @@ class ArtistsListFragment : LibraryFragment(), ArtistsListView, FragmentLayerLis private lateinit var layoutManager: LinearLayoutManager private lateinit var selectOrderDialogRunner: DialogFragmentRunner + private lateinit var choosePlayListDialogRunner: DialogFragmentRunner override fun onCreateView( inflater: LayoutInflater, @@ -64,26 +75,47 @@ class ArtistsListFragment : LibraryFragment(), ArtistsListView, FragmentLayerLis binding.progressStateView.onTryAgainClick(presenter::onTryAgainLoadCompositionsClicked) adapter = ArtistsAdapter( + this, binding.rvArtists, - this::goToArtistScreen, + presenter.getSelectedArtists(), + presenter::onArtistClicked, + presenter::onArtistLongClicked, this::onArtistMenuClicked ) binding.rvArtists.adapter = adapter layoutManager = LinearLayoutManager(context) binding.rvArtists.layoutManager = layoutManager RecyclerViewUtils.attachFastScroller(binding.rvArtists) + val callback = ShortSwipeCallback( + requireContext(), + R.drawable.ic_play_next, + R.string.play_next, + swipeCallback = presenter::onPlayNextArtistClicked + ) + val itemTouchHelper = ItemTouchHelper(callback) + itemTouchHelper.attachToRecyclerView(binding.rvArtists) val fm = childFragmentManager selectOrderDialogRunner = DialogFragmentRunner(fm, Tags.ORDER_TAG) { fragment -> fragment.setOnCompleteListener(presenter::onOrderSelected) } + choosePlayListDialogRunner = DialogFragmentRunner(fm, Tags.SELECT_PLAYLIST_TAG) { fragment -> + fragment.setComplexCompleteListener { playlist, extra -> + presenter.onPlayListToAddingSelected( + playlist, + extra.getLongArray(Arguments.IDS_ARG)!!, + extra.getBoolean(Arguments.CLOSE_MULTISELECT_ARG) + ) + } + } } - override fun onFragmentMovedOnTop() { - super.onFragmentMovedOnTop() + override fun onFragmentResumed() { + super.onFragmentResumed() val toolbar: AdvancedToolbar = requireActivity().findViewById(R.id.toolbar) toolbar.setSubtitle(R.string.artists) toolbar.setupSearch(presenter::onSearchTextChanged, presenter.getSearchText()) + toolbar.setupSelectionModeMenu(R.menu.library_artists_selection_menu, this::onActionModeItemClicked) toolbar.setupOptionsMenu(R.menu.library_artists_menu, this::onOptionsItemClicked) } @@ -93,6 +125,10 @@ class ArtistsListFragment : LibraryFragment(), ArtistsListView, FragmentLayerLis } override fun onBackPressed(): Boolean { + if (toolbar.isInActionMode) { + presenter.onSelectionModeBackPressed() + return true + } if (toolbar.isInSearchMode) { toolbar.setSearchModeEnabled(false) return true @@ -137,18 +173,88 @@ class ArtistsListFragment : LibraryFragment(), ArtistsListView, FragmentLayerLis selectOrderDialogRunner.show(fragment) } - private fun showEditArtistNameDialog(artist: Artist) { - newRenameArtistDialog(artist.id, artist.name).safeShow(childFragmentManager) + override fun goToArtistScreen(artist: Artist) { + FragmentNavigation.from(parentFragmentManager).addNewFragment(newInstance(artist.id)) } - private fun goToArtistScreen(artist: Artist) { - FragmentNavigation.from(parentFragmentManager).addNewFragment(newInstance(artist.id)) + override fun onArtistSelected(artist: Artist, position: Int) { + adapter.setItemSelected(position) + } + + override fun onArtistUnselected(artist: Artist, position: Int) { + adapter.setItemUnselected(position) + } + + override fun setItemsSelected(selected: Boolean) { + adapter.setItemsSelected(selected) + } + + override fun showSelectionMode(count: Int) { + toolbar.showSelectionMode(count) + } + + override fun onCompositionsAddedToPlayNext(compositions: List) { + val message = MessagesUtils.getPlayNextMessage(requireContext(), compositions) + MessagesUtils.makeSnackbar(binding.listContainer, message, Snackbar.LENGTH_SHORT).show() + } + + override fun onCompositionsAddedToQueue(compositions: List) { + val message = MessagesUtils.getAddedToQueueMessage(requireContext(), compositions) + MessagesUtils.makeSnackbar(binding.listContainer, message, Snackbar.LENGTH_SHORT).show() + } + + override fun showSelectPlayListDialog(artists: Collection, closeMultiselect: Boolean) { + val args = Bundle().apply { + putLongArray(Arguments.IDS_ARG, artists.toLongArray(Artist::getId)) + putBoolean(Arguments.CLOSE_MULTISELECT_ARG, closeMultiselect) + } + choosePlayListDialogRunner.show(newChoosePlayListDialogFragment(args)) + } + + override fun showAddingToPlayListComplete(playList: PlayList, compositions: List) { + val text = MessagesUtils.getAddToPlayListCompleteMessage(requireActivity(), playList, compositions) + MessagesUtils.makeSnackbar(binding.listContainer, text, Snackbar.LENGTH_SHORT).show() + } + + override fun showAddingToPlayListError(errorCommand: ErrorCommand) { + MessagesUtils.makeSnackbar( + binding.listContainer, + getString(R.string.add_to_playlist_error_template, errorCommand.message), + Snackbar.LENGTH_SHORT + ).show() + } + + override fun showErrorMessage(errorCommand: ErrorCommand) { + MessagesUtils.makeSnackbar( + binding.listContainer, errorCommand.message, Snackbar.LENGTH_LONG + ).show() + } + + override fun sendCompositions(compositions: List) { + shareCompositions(this, compositions) + } + + override fun showReceiveCompositionsForSendError(errorCommand: ErrorCommand) { + MessagesUtils.makeSnackbar( + binding.listContainer, + getString(R.string.can_not_receive_file_for_send, errorCommand.message), + Snackbar.LENGTH_SHORT + ).show() + } + + private fun showEditArtistNameDialog(artist: Artist) { + newRenameArtistDialog(artist.id, artist.name).safeShow(childFragmentManager) } private fun onArtistMenuClicked(view: View, artist: Artist) { PopupMenuWindow.showPopup(view, R.menu.artist_menu) { menuItem -> when (menuItem.itemId) { + R.id.menu_play -> presenter.onPlayArtistClicked(artist) + R.id.menu_play_next -> presenter.onPlayNextArtistClicked(artist) + R.id.menu_add_to_queue -> presenter.onAddToQueueArtistClicked(artist) + R.id.menu_add_to_playlist -> presenter.onAddArtistToPlayListClicked(artist) R.id.menu_rename -> showEditArtistNameDialog(artist) + R.id.menu_share -> presenter.onShareArtistClicked(artist) } } } @@ -161,4 +267,15 @@ class ArtistsListFragment : LibraryFragment(), ArtistsListView, FragmentLayerLis R.id.menu_equalizer -> EqualizerDialogFragment().safeShow(childFragmentManager) } } + + private fun onActionModeItemClicked(menuItem: MenuItem) { + when (menuItem.itemId) { + R.id.menu_play -> presenter.onPlayAllSelectedClicked() + R.id.menu_select_all -> presenter.onSelectAllButtonClicked() + R.id.menu_play_next -> presenter.onPlayNextSelectedArtistsClicked() + R.id.menu_add_to_queue -> presenter.onAddToQueueSelectedArtistsClicked() + R.id.menu_add_to_playlist -> presenter.onAddSelectedArtistsToPlayListClicked() + R.id.menu_share -> presenter.onShareSelectedArtistsClicked() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/ArtistsListPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/ArtistsListPresenter.kt index ef3e63815..1379cad4a 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/ArtistsListPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/ArtistsListPresenter.kt @@ -2,7 +2,9 @@ package com.github.anrimian.musicplayer.ui.library.artists.list import com.github.anrimian.musicplayer.domain.interactors.library.LibraryArtistsInteractor import com.github.anrimian.musicplayer.domain.models.artist.Artist +import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.order.Order +import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.domain.utils.TextUtils import com.github.anrimian.musicplayer.domain.utils.rx.RxUtils @@ -20,6 +22,7 @@ class ArtistsListPresenter( private var artistsDisposable: Disposable? = null private var artists: List = ArrayList() + private val selectedArtists = LinkedHashSet() private var searchText: String? = null @@ -28,7 +31,7 @@ class ArtistsListPresenter( subscribeOnArtistsList() } - fun onStop(listPosition: ListPosition) { + fun onStop(listPosition: ListPosition?) { interactor.saveListPosition(listPosition) } @@ -36,12 +39,98 @@ class ArtistsListPresenter( subscribeOnArtistsList() } + fun onArtistClicked(position: Int, artist: Artist) { + if (selectedArtists.isEmpty()) { + viewState.goToArtistScreen(artist) + return + } + if (selectedArtists.contains(artist)) { + selectedArtists.remove(artist) + viewState.onArtistUnselected(artist, position) + } else { + selectedArtists.add(artist) + viewState.onArtistSelected(artist, position) + } + viewState.showSelectionMode(selectedArtists.size) + } + + fun onArtistLongClicked(position: Int, artist: Artist) { + selectedArtists.add(artist) + viewState.showSelectionMode(selectedArtists.size) + viewState.onArtistSelected(artist, position) + } + + fun onPlayAllSelectedClicked() { + playSelectedArtists() + } + + fun onSelectAllButtonClicked() { + selectedArtists.clear() + selectedArtists.addAll(artists) + viewState.showSelectionMode(artists.size) + viewState.setItemsSelected(true) + } + + fun onPlayNextSelectedArtistsClicked() { + addArtistsToPlayNext(ArrayList(selectedArtists)) + closeSelectionMode() + } + + fun onAddToQueueSelectedArtistsClicked() { + addArtistsToPlayQueue(ArrayList(selectedArtists)) + closeSelectionMode() + } + + fun onAddSelectedArtistsToPlayListClicked() { + viewState.showSelectPlayListDialog(selectedArtists, true) + } + + fun onPlayListToAddingSelected(playList: PlayList, artists: LongArray, closeMultiselect: Boolean) { + interactor.addArtistsToPlayList(artists, playList) + .subscribeOnUi( + { compositions -> onAddingToPlayListCompleted(compositions, playList, closeMultiselect) }, + this::onAddingToPlayListError + ) + } + + fun onShareSelectedArtistsClicked() { + shareArtistCompositions(ArrayList(selectedArtists)) + } + + fun onSelectionModeBackPressed() { + closeSelectionMode() + } + + fun onPlayArtistClicked(artist: Artist) { + interactor.startPlaying(listOf(artist)) + } + + fun onPlayNextArtistClicked(position: Int) { + addArtistsToPlayNext(listOf(artists[position])) + } + + fun onPlayNextArtistClicked(artist: Artist) { + addArtistsToPlayNext(listOf(artist)) + } + + fun onAddToQueueArtistClicked(artist: Artist) { + addArtistsToPlayQueue(listOf(artist)) + } + + fun onAddArtistToPlayListClicked(artist: Artist) { + viewState.showSelectPlayListDialog(listOf(artist), false) + } + + fun onShareArtistClicked(artist: Artist) { + shareArtistCompositions(listOf(artist)) + } + fun onOrderMenuItemClicked() { - viewState.showSelectOrderScreen(interactor.order) + viewState.showSelectOrderScreen(interactor.getOrder()) } - fun onOrderSelected(order: Order?) { - interactor.order = order + fun onOrderSelected(order: Order) { + interactor.setOrder(order) } fun onSearchTextChanged(text: String?) { @@ -51,8 +140,57 @@ class ArtistsListPresenter( } } + fun getSelectedArtists(): HashSet = selectedArtists + fun getSearchText() = searchText + private fun shareArtistCompositions(artists: List) { + interactor.getAllCompositionsForArtists(artists) + .subscribeOnUi(viewState::sendCompositions, this::onReceiveCompositionsError) + } + + private fun onReceiveCompositionsError(throwable: Throwable) { + val errorCommand = errorParser.parseError(throwable) + viewState.showReceiveCompositionsForSendError(errorCommand) + } + + private fun onAddingToPlayListCompleted( + compositions: List, + playList: PlayList, + closeMultiselect: Boolean + ) { + viewState.showAddingToPlayListComplete(playList, compositions) + if (closeMultiselect && selectedArtists.isNotEmpty()) { + closeSelectionMode() + } + } + + private fun onAddingToPlayListError(throwable: Throwable) { + val errorCommand = errorParser.parseError(throwable) + viewState.showAddingToPlayListError(errorCommand) + } + + private fun playSelectedArtists() { + interactor.startPlaying(ArrayList(selectedArtists)) + closeSelectionMode() + } + + private fun addArtistsToPlayNext(artists: List) { + interactor.addArtistsToPlayNext(artists) + .launchOnUi(viewState::onCompositionsAddedToPlayNext, viewState::showErrorMessage) + } + + private fun addArtistsToPlayQueue(artists: List) { + interactor.addArtistsToQueue(artists) + .launchOnUi(viewState::onCompositionsAddedToQueue, viewState::showErrorMessage) + } + + private fun closeSelectionMode() { + selectedArtists.clear() + viewState.showSelectionMode(0) + viewState.setItemsSelected(false) + } + private fun subscribeOnArtistsList() { if (artists.isEmpty()) { viewState.showLoading() @@ -83,7 +221,7 @@ class ArtistsListPresenter( } else { viewState.showList() if (firstReceive) { - val listPosition = interactor.savedListPosition + val listPosition = interactor.getSavedListPosition() if (listPosition != null) { viewState.restoreListPosition(listPosition) } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/ArtistsListView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/ArtistsListView.kt index be9a2450c..3600f3f26 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/ArtistsListView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/ArtistsListView.kt @@ -1,7 +1,9 @@ package com.github.anrimian.musicplayer.ui.library.artists.list import com.github.anrimian.musicplayer.domain.models.artist.Artist +import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.order.Order +import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand import moxy.MvpView @@ -9,6 +11,7 @@ import moxy.viewstate.strategy.AddToEndSingleTagStrategy import moxy.viewstate.strategy.StateStrategyType import moxy.viewstate.strategy.alias.AddToEndSingle import moxy.viewstate.strategy.alias.OneExecution +import moxy.viewstate.strategy.alias.Skip private const val LIST_STATE = "list_state" @@ -38,4 +41,42 @@ interface ArtistsListView : MvpView { @OneExecution fun restoreListPosition(listPosition: ListPosition) + @Skip + fun goToArtistScreen(artist: Artist) + + @Skip + fun onArtistSelected(artist: Artist, position: Int) + + @Skip + fun onArtistUnselected(artist: Artist, position: Int) + + @Skip + fun setItemsSelected(selected: Boolean) + + @AddToEndSingle + fun showSelectionMode(count: Int) + + @OneExecution + fun onCompositionsAddedToPlayNext(compositions: List) + + @OneExecution + fun onCompositionsAddedToQueue(compositions: List) + + @OneExecution + fun showSelectPlayListDialog(artists: Collection, closeMultiselect: Boolean) + + @OneExecution + fun showAddingToPlayListComplete(playList: PlayList, compositions: List) + + @OneExecution + fun showAddingToPlayListError(errorCommand: ErrorCommand) + + @OneExecution + fun showErrorMessage(errorCommand: ErrorCommand) + + @OneExecution + fun sendCompositions(compositions: List) + + @OneExecution + fun showReceiveCompositionsForSendError(errorCommand: ErrorCommand) } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/adapter/ArtistViewHolder.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/adapter/ArtistViewHolder.kt index ef47d624f..a4a228d88 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/adapter/ArtistViewHolder.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/adapter/ArtistViewHolder.kt @@ -1,27 +1,62 @@ package com.github.anrimian.musicplayer.ui.library.artists.list.adapter +import android.content.res.ColorStateList +import android.graphics.Color +import android.graphics.drawable.RippleDrawable import android.view.View import android.view.ViewGroup +import androidx.annotation.ColorInt import com.github.anrimian.musicplayer.R import com.github.anrimian.musicplayer.databinding.ItemArtistBinding import com.github.anrimian.musicplayer.domain.Payloads import com.github.anrimian.musicplayer.domain.models.artist.Artist import com.github.anrimian.musicplayer.ui.common.format.FormatUtils -import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.BaseViewHolder +import com.github.anrimian.musicplayer.ui.utils.AndroidUtils +import com.github.anrimian.musicplayer.ui.utils.ViewUtils +import com.github.anrimian.musicplayer.ui.utils.colorFromAttr +import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.ItemDrawable +import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.SelectableViewHolder +import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.short_swipe.SwipeListener class ArtistViewHolder( parent: ViewGroup, - itemClickListener: (Artist) -> Unit, - onItemMenuClickListener: (View, Artist) -> Unit -) : BaseViewHolder(parent, R.layout.item_artist) { + itemClickListener: (Int, Artist) -> Unit, + itemLongClickListener: (Int, Artist) -> Unit, + onItemMenuClickListener: (View, Artist) -> Unit, +) : SelectableViewHolder(parent, R.layout.item_artist), SwipeListener { private val viewBinding = ItemArtistBinding.bind(itemView) private lateinit var artist: Artist + private val backgroundDrawable = ItemDrawable() + private val stateDrawable = ItemDrawable() + private val rippleMaskDrawable = ItemDrawable() + + private var selected = false + private var isSwiping = false + init { - viewBinding.clickableItem.setOnClickListener { itemClickListener(artist) } + viewBinding.clickableItem.setOnClickListener { itemClickListener(bindingAdapterPosition, artist) } viewBinding.btnActionsMenu.setOnClickListener { v -> onItemMenuClickListener(v, artist) } + viewBinding.clickableItem.setOnLongClickListener { + if (selected) { + return@setOnLongClickListener false + } + selectImmediate() + itemLongClickListener(bindingAdapterPosition, artist) + true + } + + backgroundDrawable.setColor(context.colorFromAttr(R.attr.listItemBackground)) + itemView.background = backgroundDrawable + stateDrawable.setColor(Color.TRANSPARENT) + viewBinding.clickableItem.background = stateDrawable + viewBinding.clickableItem.foreground = RippleDrawable( + ColorStateList.valueOf(context.colorFromAttr(android.R.attr.colorControlHighlight)), + null, + rippleMaskDrawable + ) } fun bind(artist: Artist) { @@ -46,6 +81,48 @@ class ArtistViewHolder( } } + override fun setSelected(selected: Boolean) { + if (this.selected != selected) { + this.selected = selected + val unselectedColor = Color.TRANSPARENT + val selectedColor = selectionColor + val endColor = if (selected) selectedColor else unselectedColor + showStateColor(endColor, true) + } + } + + override fun onSwipeStateChanged(swipeOffset: Float) { + val swiping = swipeOffset > 0.0f + if (isSwiping != swiping) { + isSwiping = swiping + val swipedCorners = context.resources.getDimension(R.dimen.swiped_item_corners) + val from: Float = if (swiping) 0f else swipedCorners + val to: Float = if (swiping) swipedCorners else 0f + val duration = context.resources.getInteger(R.integer.swiped_item_animation_time) + AndroidUtils.animateItemDrawableCorners( + from, + to, + duration, + backgroundDrawable, + stateDrawable, + rippleMaskDrawable + ) + } + } + + private fun selectImmediate() { + showStateColor(selectionColor, false) + selected = true + } + + private fun showStateColor(@ColorInt color: Int, animate: Boolean) { + if (animate) { + stateDrawable.setColor(color) + } else { + ViewUtils.animateItemDrawableColor(stateDrawable, color) + } + } + private fun showAuthorName() { val name = artist.name viewBinding.tvArtistName.text = name diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/adapter/ArtistsAdapter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/adapter/ArtistsAdapter.kt index 8aaab5b1b..73c493c12 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/adapter/ArtistsAdapter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/artists/list/adapter/ArtistsAdapter.kt @@ -2,27 +2,44 @@ package com.github.anrimian.musicplayer.ui.library.artists.list.adapter import android.view.View import android.view.ViewGroup +import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView +import com.github.anrimian.musicplayer.domain.Payloads import com.github.anrimian.musicplayer.domain.models.artist.Artist import com.github.anrimian.musicplayer.domain.models.utils.ArtistHelper import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.diff_utils.SimpleDiffItemCallback -import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.diff_utils.adapter.DiffListAdapter +import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.mvp.MvpDiffAdapter class ArtistsAdapter( + lifecycleOwner: LifecycleOwner, recyclerView: RecyclerView, - private val itemClickListener: (Artist) -> Unit, + private val selectedArtists: Set, + private val itemClickListener: (Int, Artist) -> Unit, + private val itemLongClickListener: (Int, Artist) -> Unit, private val onItemMenuClickListener: (View, Artist) -> Unit -) : DiffListAdapter( +) : MvpDiffAdapter( + lifecycleOwner, recyclerView, SimpleDiffItemCallback(ArtistHelper::areSourcesTheSame, ArtistHelper::getChangePayload) ) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArtistViewHolder { - return ArtistViewHolder(parent, itemClickListener, onItemMenuClickListener) + return ArtistViewHolder( + parent, + itemClickListener, + itemLongClickListener, + onItemMenuClickListener + ) } override fun onBindViewHolder(holder: ArtistViewHolder, position: Int) { - holder.bind(getItem(position)) + super.onBindViewHolder(holder, position) + + val artist = getItem(position) + holder.bind(artist) + + val selected = selectedArtists.contains(artist) + holder.setSelected(selected) } override fun onBindViewHolder( @@ -34,6 +51,28 @@ class ArtistsAdapter( super.onBindViewHolder(holder, position, payloads) return } - holder.update(getItem(position)!!, payloads) + for (payload in payloads) { + if (payload === Payloads.ITEM_SELECTED) { + holder.setSelected(true) + return + } + if (payload === Payloads.ITEM_UNSELECTED) { + holder.setSelected(false) + return + } + } + holder.update(getItem(position), payloads) + } + + fun setItemSelected(position: Int) { + notifyItemChanged(position, Payloads.ITEM_SELECTED) + } + + fun setItemUnselected(position: Int) { + notifyItemChanged(position, Payloads.ITEM_UNSELECTED) + } + + fun setItemsSelected(selected: Boolean) { + forEachHolder { holder -> holder.setSelected(selected) } } } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/compositions/BaseLibraryCompositionsFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/compositions/BaseLibraryCompositionsFragment.kt index cfd81b74a..8bfe41b98 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/compositions/BaseLibraryCompositionsFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/compositions/BaseLibraryCompositionsFragment.kt @@ -13,7 +13,7 @@ import com.github.anrimian.musicplayer.ui.main.MainActivity abstract class BaseLibraryCompositionsFragment : LibraryFragment() { - protected abstract fun getLibraryPresenter(): BaseLibraryCompositionsPresenter<*> + protected abstract fun getLibraryPresenter(): BaseLibraryCompositionsPresenter<*, *> protected fun onActionModeItemClicked(menuItem: MenuItem) { when (menuItem.itemId) { diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/compositions/BaseLibraryCompositionsPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/compositions/BaseLibraryCompositionsPresenter.kt index b470f21e8..4de76c8e5 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/compositions/BaseLibraryCompositionsPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/compositions/BaseLibraryCompositionsPresenter.kt @@ -6,7 +6,9 @@ import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInt import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.composition.CurrentComposition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.playlist.PlayList +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.domain.utils.ListUtils import com.github.anrimian.musicplayer.domain.utils.TextUtils @@ -17,22 +19,22 @@ import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Scheduler import io.reactivex.rxjava3.disposables.Disposable -import java.util.* +import java.util.LinkedList -abstract class BaseLibraryCompositionsPresenter( +abstract class BaseLibraryCompositionsPresenter>( private val playerInteractor: LibraryPlayerInteractor, private val playListsInteractor: PlayListsInteractor, private val displaySettingsInteractor: DisplaySettingsInteractor, - private val syncInteractor: SyncInteractor<*, *, Long>, + private val syncInteractor: SyncInteractor, errorParser: ErrorParser, uiScheduler: Scheduler, -) : AppPresenter(uiScheduler, errorParser) { +) : AppPresenter(uiScheduler, errorParser) { private var currentCompositionDisposable: Disposable? = null private var compositionsDisposable: Disposable? = null - private var compositions: List = ArrayList() - private val selectedCompositions = LinkedHashSet() + private var compositions: List = ArrayList() + private val selectedCompositions = LinkedHashSet() private val compositionsForPlayList: MutableList = LinkedList() private val compositionsToDelete: MutableList = LinkedList() @@ -61,12 +63,12 @@ abstract class BaseLibraryCompositionsPresenter subscribeOnCompositions() } - fun onCompositionClicked(position: Int, composition: Composition) { + fun onCompositionClicked(position: Int, composition: C) { if (selectedCompositions.isEmpty()) { if (composition == currentComposition) { playerInteractor.playOrPause() } else { - playerInteractor.startPlaying(compositions, position) + playerInteractor.startPlayingCompositions(compositions, position) viewState.showCurrentComposition(CurrentComposition(composition, true)) } return @@ -81,16 +83,16 @@ abstract class BaseLibraryCompositionsPresenter viewState.showSelectionMode(selectedCompositions.size) } - fun onCompositionIconClicked(position: Int, composition: Composition) { + fun onCompositionIconClicked(position: Int, composition: C) { if (composition == currentComposition) { playerInteractor.playOrPause() } else { - playerInteractor.startPlaying(compositions, position) + playerInteractor.startPlayingCompositions(compositions, position) viewState.showCurrentComposition(CurrentComposition(composition, true)) } } - fun onCompositionLongClick(position: Int, composition: Composition) { + fun onCompositionLongClick(position: Int, composition: C) { selectedCompositions.add(composition) viewState.showSelectionMode(selectedCompositions.size) viewState.onCompositionSelected(composition, position) @@ -98,7 +100,7 @@ abstract class BaseLibraryCompositionsPresenter fun onPlayAllButtonClicked() { if (selectedCompositions.isEmpty()) { - playerInteractor.startPlaying(compositions) + playerInteractor.startPlayingCompositions(compositions) } else { playSelectedCompositions() } @@ -185,7 +187,7 @@ abstract class BaseLibraryCompositionsPresenter } fun onPlayActionSelected(position: Int) { - playerInteractor.startPlaying(compositions, position) + playerInteractor.startPlayingCompositions(compositions, position) } fun onSearchTextChanged(text: String?) { @@ -207,7 +209,7 @@ abstract class BaseLibraryCompositionsPresenter playerInteractor.setRandomPlayingEnabled(!playerInteractor.isRandomPlayingEnabled()) } - fun getSelectedCompositions(): HashSet = selectedCompositions + fun getSelectedCompositions(): HashSet = selectedCompositions fun getSearchText() = searchText @@ -256,7 +258,7 @@ abstract class BaseLibraryCompositionsPresenter } private fun playSelectedCompositions() { - playerInteractor.startPlaying(ArrayList(selectedCompositions)) + playerInteractor.startPlayingCompositions(ArrayList(selectedCompositions)) closeSelectionMode() } @@ -274,7 +276,7 @@ abstract class BaseLibraryCompositionsPresenter lastDeleteAction!!.justSubscribe(this::onDeleteCompositionError) } - private fun onDeleteCompositionsSuccess(compositions: List) { + private fun onDeleteCompositionsSuccess(compositions: List) { viewState.showDeleteCompositionMessage(compositions) compositionsToDelete.clear() if (selectedCompositions.isNotEmpty()) { @@ -292,7 +294,7 @@ abstract class BaseLibraryCompositionsPresenter viewState.showLoadingError(errorCommand) } - private fun onCompositionsReceived(compositions: List) { + private fun onCompositionsReceived(compositions: List) { val firstReceive = this.compositions.isEmpty() this.compositions = compositions @@ -333,7 +335,7 @@ abstract class BaseLibraryCompositionsPresenter .subscribeOnUi(viewState::showRandomMode, errorParser::logError) } - protected abstract fun getCompositionsObservable(searchText: String?): Observable> + protected abstract fun getCompositionsObservable(searchText: String?): Observable> protected abstract fun getSavedListPosition(): ListPosition? protected abstract fun saveListPosition(listPosition: ListPosition) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/compositions/BaseLibraryCompositionsView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/compositions/BaseLibraryCompositionsView.kt index cf6c26975..6ea9ef4fa 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/compositions/BaseLibraryCompositionsView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/compositions/BaseLibraryCompositionsView.kt @@ -3,6 +3,7 @@ package com.github.anrimian.musicplayer.ui.library.common.compositions import com.github.anrimian.filesync.models.state.file.FileSyncState import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.composition.CurrentComposition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand @@ -11,7 +12,7 @@ import moxy.viewstate.strategy.alias.AddToEndSingle import moxy.viewstate.strategy.alias.OneExecution import moxy.viewstate.strategy.alias.Skip -interface BaseLibraryCompositionsView : ListMvpView { +interface BaseLibraryCompositionsView : ListMvpView { @OneExecution fun showSelectPlayListDialog() @@ -29,13 +30,13 @@ interface BaseLibraryCompositionsView : ListMvpView { fun showDeleteCompositionError(errorCommand: ErrorCommand) @OneExecution - fun showDeleteCompositionMessage(compositionsToDelete: List) + fun showDeleteCompositionMessage(compositionsToDelete: List) @Skip - fun onCompositionSelected(composition: Composition, position: Int) + fun onCompositionSelected(composition: C, position: Int) @Skip - fun onCompositionUnselected(composition: Composition, position: Int) + fun onCompositionUnselected(composition: C, position: Int) @Skip fun setItemsSelected(selected: Boolean) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/order/SelectOrderDialogFragment.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/order/SelectOrderDialogFragment.java index 51d66b55c..0e99c3222 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/order/SelectOrderDialogFragment.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/order/SelectOrderDialogFragment.java @@ -36,7 +36,7 @@ public class SelectOrderDialogFragment extends MvpAppCompatDialogFragment implem @InjectPresenter SelectOrderPresenter presenter; - private DialogOrderBinding viewBinding; + private DialogOrderBinding binding; private OrderAdapter orderAdapter; @@ -45,7 +45,7 @@ public class SelectOrderDialogFragment extends MvpAppCompatDialogFragment implem @ProvidePresenter SelectOrderPresenter providePresenter() { - return Components.getLibraryComponent().selectOrderPresenter(); + return Components.getOrderComponent(getOrder()).selectOrderPresenter(); } public static SelectOrderDialogFragment newInstance(Order selectedOrder, @@ -68,13 +68,9 @@ public static SelectOrderDialogFragment newInstance(Order selectedOrder, @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - if (savedInstanceState == null) { - presenter.setOrder(getOrder()); - } - - viewBinding = DialogOrderBinding.inflate(LayoutInflater.from(requireContext())); - RecyclerView rvOrder = viewBinding.rvOrder; - View view = viewBinding.getRoot(); + binding = DialogOrderBinding.inflate(LayoutInflater.from(requireContext())); + RecyclerView rvOrder = binding.rvOrder; + View view = binding.getRoot(); LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); rvOrder.setLayoutManager(layoutManager); @@ -96,13 +92,13 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { Button btnOk = dialog.getButton(AlertDialog.BUTTON_POSITIVE); btnOk.setOnClickListener(v -> presenter.onCompleteButtonClicked()); - viewBinding.cbDesc.setOnCheckedChangeListener((buttonView, isChecked) -> + binding.cbDesc.setOnCheckedChangeListener((buttonView, isChecked) -> presenter.onReverseTypeSelected(isChecked) ); if (requireArguments().getBoolean(FILE_NAME_SETTING_ARG)) { - onCheckChanged(viewBinding.cbUseFileName, presenter::onFileNameChecked); + onCheckChanged(binding.cbUseFileName, presenter::onFileNameChecked); } else { - viewBinding.cbUseFileName.setVisibility(View.GONE); + binding.cbUseFileName.setVisibility(View.GONE); } return dialog; @@ -111,17 +107,17 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { @Override public void showSelectedOrder(OrderType orderType) { orderAdapter.setCheckedItem(orderType); - viewBinding.cbDesc.setText(getReversedOrderText(orderType)); + binding.cbDesc.setText(getReversedOrderText(orderType)); } @Override public void showReverse(boolean selected) { - viewBinding.cbDesc.setChecked(selected); + binding.cbDesc.setChecked(selected); } @Override public void showFileNameEnabled(boolean checked) { - setChecked(viewBinding.cbUseFileName, checked); + setChecked(binding.cbUseFileName, checked); } @Override diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/order/SelectOrderPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/order/SelectOrderPresenter.kt index bdd179655..cda69ee53 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/order/SelectOrderPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/common/order/SelectOrderPresenter.kt @@ -6,20 +6,14 @@ import com.github.anrimian.musicplayer.domain.models.order.OrderType import moxy.MvpPresenter class SelectOrderPresenter( + private var orderType: OrderType, + private var reverse: Boolean, private val settingsInteractor: DisplaySettingsInteractor ) : MvpPresenter() { - private lateinit var orderType: OrderType - private var reverse = false - override fun onFirstViewAttach() { super.onFirstViewAttach() viewState.showFileNameEnabled(settingsInteractor.isDisplayFileNameEnabled()) - } - - fun setOrder(order: Order) { - orderType = order.orderType - reverse = order.isReversed viewState.showReverse(reverse) viewState.showSelectedOrder(orderType) } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/LibraryCompositionsFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/LibraryCompositionsFragment.kt index 298369117..2f85c5a9d 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/LibraryCompositionsFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/LibraryCompositionsFragment.kt @@ -14,6 +14,7 @@ import com.github.anrimian.musicplayer.databinding.FragmentLibraryCompositionsBi import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.composition.CurrentComposition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.order.Order import com.github.anrimian.musicplayer.domain.models.order.OrderType import com.github.anrimian.musicplayer.domain.models.playlist.PlayList @@ -30,7 +31,6 @@ import com.github.anrimian.musicplayer.ui.editor.common.DeleteErrorHandler import com.github.anrimian.musicplayer.ui.editor.common.ErrorHandler import com.github.anrimian.musicplayer.ui.equalizer.EqualizerDialogFragment import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsFragment -import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsPresenter import com.github.anrimian.musicplayer.ui.library.common.order.SelectOrderDialogFragment import com.github.anrimian.musicplayer.ui.library.compositions.adapter.CompositionsAdapter import com.github.anrimian.musicplayer.ui.playlist_screens.choose.ChoosePlayListDialogFragment @@ -38,7 +38,7 @@ import com.github.anrimian.musicplayer.ui.playlist_screens.choose.newChoosePlayL import com.github.anrimian.musicplayer.ui.sleep_timer.SleepTimerDialogFragment import com.github.anrimian.musicplayer.ui.utils.fragments.BackButtonListener import com.github.anrimian.musicplayer.ui.utils.fragments.DialogFragmentRunner -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener +import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigationListener import com.github.anrimian.musicplayer.ui.utils.fragments.safeShow import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.RecyclerViewUtils import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.touch_helper.short_swipe.ShortSwipeCallback @@ -46,7 +46,8 @@ import com.google.android.material.snackbar.Snackbar import moxy.ktx.moxyPresenter class LibraryCompositionsFragment : BaseLibraryCompositionsFragment(), LibraryCompositionsView, - BackButtonListener, FragmentLayerListener { + BackButtonListener, + FragmentNavigationListener { private val presenter by moxyPresenter { Components.getLibraryCompositionsComponent().libraryCompositionsPresenter() @@ -56,13 +57,13 @@ class LibraryCompositionsFragment : BaseLibraryCompositionsFragment(), LibraryCo private lateinit var layoutManager: LinearLayoutManager private lateinit var toolbar: AdvancedToolbar - private lateinit var adapter: CompositionsAdapter + private lateinit var adapter: CompositionsAdapter private lateinit var choosePlayListDialogRunner: DialogFragmentRunner private lateinit var selectOrderDialogRunner: DialogFragmentRunner private lateinit var deletingErrorHandler: ErrorHandler - override fun getLibraryPresenter(): BaseLibraryCompositionsPresenter = presenter + override fun getLibraryPresenter(): LibraryCompositionsPresenter = presenter override fun onCreateView( inflater: LayoutInflater, @@ -118,8 +119,8 @@ class LibraryCompositionsFragment : BaseLibraryCompositionsFragment(), LibraryCo } } - override fun onFragmentMovedOnTop() { - super.onFragmentMovedOnTop() + override fun onFragmentResumed() { + super.onFragmentResumed() val toolbar: AdvancedToolbar = requireActivity().findViewById(R.id.toolbar) toolbar.setSubtitle(R.string.compositions) toolbar.setupSearch(presenter::onSearchTextChanged, presenter.getSearchText()) @@ -242,7 +243,7 @@ class LibraryCompositionsFragment : BaseLibraryCompositionsFragment(), LibraryCo } } - override fun showDeleteCompositionMessage(compositionsToDelete: List) { + override fun showDeleteCompositionMessage(compositionsToDelete: List) { val text = MessagesUtils.getDeleteCompleteMessage(requireActivity(), compositionsToDelete) MessagesUtils.makeSnackbar(viewBinding.listContainer, text, Snackbar.LENGTH_SHORT).show() } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/LibraryCompositionsPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/LibraryCompositionsPresenter.kt index 1bb70a96b..f85d674a2 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/LibraryCompositionsPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/LibraryCompositionsPresenter.kt @@ -7,6 +7,7 @@ import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInt import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.order.Order +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsPresenter @@ -18,10 +19,10 @@ class LibraryCompositionsPresenter( playListsInteractor: PlayListsInteractor, playerInteractor: LibraryPlayerInteractor, displaySettingsInteractor: DisplaySettingsInteractor, - syncInteractor: SyncInteractor<*, *, Long>, + syncInteractor: SyncInteractor, errorParser: ErrorParser, uiScheduler: Scheduler -) : BaseLibraryCompositionsPresenter( +) : BaseLibraryCompositionsPresenter( playerInteractor, playListsInteractor, displaySettingsInteractor, @@ -47,6 +48,5 @@ class LibraryCompositionsPresenter( fun onOrderSelected(order: Order) { interactor.order = order - subscribeOnCompositions() } } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/LibraryCompositionsView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/LibraryCompositionsView.kt index bb3aa2ae6..c0c31d252 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/LibraryCompositionsView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/LibraryCompositionsView.kt @@ -1,10 +1,11 @@ package com.github.anrimian.musicplayer.ui.library.compositions +import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.order.Order import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsView import moxy.viewstate.strategy.alias.OneExecution -interface LibraryCompositionsView : BaseLibraryCompositionsView { +interface LibraryCompositionsView : BaseLibraryCompositionsView { @OneExecution fun showSelectOrderScreen(order: Order) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/adapter/CompositionsAdapter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/adapter/CompositionsAdapter.kt index 1770f7b10..e9d309637 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/adapter/CompositionsAdapter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/adapter/CompositionsAdapter.kt @@ -3,6 +3,7 @@ package com.github.anrimian.musicplayer.ui.library.compositions.adapter import android.view.View import android.view.ViewGroup import androidx.lifecycle.LifecycleOwner +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.github.anrimian.filesync.models.state.file.FileSyncState import com.github.anrimian.musicplayer.domain.Payloads @@ -15,26 +16,30 @@ import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.mvp.MvpDiffA /** * Created on 31.10.2017. */ -class CompositionsAdapter( +open class CompositionsAdapter( lifecycleOwner: LifecycleOwner, recyclerView: RecyclerView, - private val selectedCompositions: Set, - private val onCompositionClickListener: (Int, Composition) -> Unit, - private val onLongClickListener: (Int, Composition) -> Unit, - private val iconClickListener: (Int, Composition) -> Unit, - private val menuClickListener: (View, Int, Composition) -> Unit -) : MvpDiffAdapter( + private val selectedCompositions: Set, + private val onCompositionClickListener: (Int, T) -> Unit, + private val onLongClickListener: (Int, T) -> Unit, + private val iconClickListener: (Int, T) -> Unit, + private val menuClickListener: (View, Int, T) -> Unit, + diffCallback: DiffUtil.ItemCallback = SimpleDiffItemCallback( + CompositionHelper::areSourcesTheSame, + CompositionHelper::getChangePayload + ) +) : MvpDiffAdapter>( lifecycleOwner, recyclerView, - SimpleDiffItemCallback(CompositionHelper::areSourcesTheSame, CompositionHelper::getChangePayload) + diffCallback ) { private var currentComposition: CurrentComposition? = null private var isCoversEnabled = false private var syncStates = emptyMap() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MusicViewHolder { - return MusicViewHolder( + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CompositionViewHolder { + return CompositionViewHolder( parent, onCompositionClickListener, onLongClickListener, @@ -43,7 +48,7 @@ class CompositionsAdapter( ) } - override fun onBindViewHolder(holder: MusicViewHolder, position: Int) { + override fun onBindViewHolder(holder: CompositionViewHolder, position: Int) { super.onBindViewHolder(holder, position) val composition = getItem(position) @@ -56,7 +61,7 @@ class CompositionsAdapter( } override fun onBindViewHolder( - holder: MusicViewHolder, + holder: CompositionViewHolder, position: Int, payloads: List ) { diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/adapter/MusicViewHolder.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/adapter/MusicViewHolder.kt deleted file mode 100644 index 14aecd781..000000000 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/compositions/adapter/MusicViewHolder.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.github.anrimian.musicplayer.ui.library.compositions.adapter - -import android.graphics.Color -import android.view.View -import android.view.ViewGroup -import androidx.annotation.ColorInt -import com.github.anrimian.filesync.models.state.file.FileSyncState -import com.github.anrimian.musicplayer.R -import com.github.anrimian.musicplayer.databinding.ItemStorageMusicBinding -import com.github.anrimian.musicplayer.domain.models.composition.Composition -import com.github.anrimian.musicplayer.domain.models.composition.CurrentComposition -import com.github.anrimian.musicplayer.ui.common.format.ColorFormatUtils -import com.github.anrimian.musicplayer.ui.common.format.wrappers.CompositionItemWrapper -import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.SelectableViewHolder -import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.short_swipe.SwipeListener - -/** - * Created on 31.10.2017. - */ -class MusicViewHolder( - parent: ViewGroup, - onCompositionClickListener: (Int, Composition) -> Unit, - onLongClickListener: (Int, Composition) -> Unit, - iconClickListener: (Int, Composition) -> Unit, - menuClickListener: (View, Int, Composition) -> Unit -) : SelectableViewHolder(parent, R.layout.item_storage_music), SwipeListener { - - private val compositionItemWrapper: CompositionItemWrapper - - private lateinit var composition: Composition - - private var selected = false - private var isCurrent = false - - init { - val binding = ItemStorageMusicBinding.bind(itemView) - compositionItemWrapper = CompositionItemWrapper( - itemView, - { composition -> iconClickListener(bindingAdapterPosition, composition) } - ) { composition -> onCompositionClickListener(bindingAdapterPosition, composition) } - - binding.btnActionsMenu.setOnClickListener { v -> - menuClickListener(v, bindingAdapterPosition, composition) - } - - binding.clickableItem.setOnLongClickListener { - if (selected) { - return@setOnLongClickListener false - } - selectImmediate() - onLongClickListener(bindingAdapterPosition, composition) - true - } - } - - override fun release() { - compositionItemWrapper.release() - } - - fun bind(composition: Composition, isCoversEnabled: Boolean) { - this.composition = composition - compositionItemWrapper.bind(composition, isCoversEnabled) - } - - fun update(composition: Composition, payloads: List<*>) { - this.composition = composition - compositionItemWrapper.update(composition, payloads) - } - - fun setCoversVisible(isCoversEnabled: Boolean) { - compositionItemWrapper.showCompositionImage(isCoversEnabled) - } - - fun setFileSyncStates(fileSyncStates: Map) { - compositionItemWrapper.showFileSyncState(fileSyncStates[composition.id]) - } - - override fun setSelected(selected: Boolean) { - if (this.selected != selected) { - this.selected = selected - val unselectedColor = - if (!selected && isCurrent) getPlaySelectionColor() else Color.TRANSPARENT - val selectedColor = selectionColor - val endColor = if (selected) selectedColor else unselectedColor - compositionItemWrapper.showStateColor(endColor, true) - } - } - - override fun onSwipeStateChanged(swipeOffset: Float) { - compositionItemWrapper.showAsSwipingItem(swipeOffset) - } - - fun showCurrentComposition( - currentComposition: CurrentComposition?, - animate: Boolean - ) { - var isCurrent = false - var isPlaying = false - if (currentComposition != null) { - isCurrent = composition == currentComposition.composition - isPlaying = isCurrent && currentComposition.isPlaying - } - showAsCurrentComposition(isCurrent) - compositionItemWrapper.showAsPlaying(isPlaying, animate) - } - - private fun showAsCurrentComposition(isCurrent: Boolean) { - if (this.isCurrent != isCurrent) { - this.isCurrent = isCurrent - if (!selected) { - compositionItemWrapper.showAsCurrentComposition(isCurrent) - } - } - } - - private fun selectImmediate() { - compositionItemWrapper.showStateColor(selectionColor, false) - selected = true - } - - @ColorInt - private fun getPlaySelectionColor() = ColorFormatUtils.getPlayingCompositionColor(context, 25) - -} \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/LibraryFoldersFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/LibraryFoldersFragment.kt index aff9f16cd..1bfd87d55 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/LibraryFoldersFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/LibraryFoldersFragment.kt @@ -24,6 +24,7 @@ import com.github.anrimian.musicplayer.databinding.FragmentLibraryFoldersBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.composition.CurrentComposition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.folders.CompositionFileSource import com.github.anrimian.musicplayer.domain.models.folders.FileSource import com.github.anrimian.musicplayer.domain.models.folders.FolderFileSource @@ -59,8 +60,8 @@ import com.github.anrimian.musicplayer.ui.utils.dialogs.newProgressDialogFragmen import com.github.anrimian.musicplayer.ui.utils.fragments.BackButtonListener import com.github.anrimian.musicplayer.ui.utils.fragments.DialogFragmentDelayRunner import com.github.anrimian.musicplayer.ui.utils.fragments.DialogFragmentRunner -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigation +import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigationListener import com.github.anrimian.musicplayer.ui.utils.fragments.safeShow import com.github.anrimian.musicplayer.ui.utils.slidr.SlidrPanel import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.RecyclerViewUtils @@ -94,7 +95,7 @@ fun newFolderFragment( } class LibraryFoldersFragment : MvpAppCompatFragment(), LibraryFoldersView, BackButtonListener, - FragmentLayerListener { + FragmentNavigationListener { private val presenter by moxyPresenter { Components.getLibraryFolderComponent(getFolderId()).storageLibraryPresenter() @@ -258,7 +259,7 @@ class LibraryFoldersFragment : MvpAppCompatFragment(), LibraryFoldersView, BackB fragmentDisposable.clear() } - override fun onFragmentMovedOnTop() { + override fun onFragmentResumed() { val inLockedSearchMode = requireArguments().getBoolean(LOCKED_SEARCH_MODE) presenter.onFragmentDisplayed(inLockedSearchMode) val act = requireActivity() @@ -401,7 +402,7 @@ class LibraryFoldersFragment : MvpAppCompatFragment(), LibraryFoldersView, BackB } } - override fun showDeleteCompositionMessage(compositionsToDelete: List) { + override fun showDeleteCompositionMessage(compositionsToDelete: List) { val text = MessagesUtils.getDeleteCompleteMessage(requireActivity(), compositionsToDelete) MessagesUtils.makeSnackbar(viewBinding.listContainer, text, Snackbar.LENGTH_SHORT).show() } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/LibraryFoldersPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/LibraryFoldersPresenter.kt index 168849e92..d0dd515bc 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/LibraryFoldersPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/LibraryFoldersPresenter.kt @@ -6,12 +6,14 @@ import com.github.anrimian.musicplayer.domain.interactors.player.LibraryPlayerIn import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.composition.CurrentComposition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.folders.CompositionFileSource import com.github.anrimian.musicplayer.domain.models.folders.FileSource import com.github.anrimian.musicplayer.domain.models.folders.FolderFileSource import com.github.anrimian.musicplayer.domain.models.folders.IgnoredFolder import com.github.anrimian.musicplayer.domain.models.order.Order import com.github.anrimian.musicplayer.domain.models.playlist.PlayList +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.domain.utils.ListUtils import com.github.anrimian.musicplayer.domain.utils.TextUtils @@ -32,7 +34,7 @@ class LibraryFoldersPresenter( private val interactor: LibraryFoldersScreenInteractor, private val playerInteractor: LibraryPlayerInteractor, private val displaySettingsInteractor: DisplaySettingsInteractor, - private val syncInteractor: SyncInteractor<*, *, Long>, + private val syncInteractor: SyncInteractor, errorParser: ErrorParser, uiScheduler: Scheduler ) : AppPresenter(uiScheduler, errorParser) { @@ -66,11 +68,6 @@ class LibraryFoldersPresenter( .unsafeSubscribeOnUi(viewState::showFilesSyncState) } - override fun onDestroy() { - super.onDestroy() - presenterDisposable.dispose() - } - fun onStop(listPosition: ListPosition) { interactor.saveListPosition(folderId, listPosition) } @@ -446,11 +443,11 @@ class LibraryFoldersPresenter( lastDeleteAction!!.justSubscribe(this::onDeleteCompositionsError) } - private fun onDeleteFolderSuccess(deletedCompositions: List) { + private fun onDeleteFolderSuccess(deletedCompositions: List) { viewState.showDeleteCompositionMessage(deletedCompositions) } - private fun onDeleteCompositionsSuccess(compositions: List) { + private fun onDeleteCompositionsSuccess(compositions: List) { viewState.showDeleteCompositionMessage(compositions) filesToDelete.clear() if (!selectedFiles.isEmpty()) { diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/LibraryFoldersView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/LibraryFoldersView.kt index 7b1464502..bbc7cb1a9 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/LibraryFoldersView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/LibraryFoldersView.kt @@ -3,6 +3,7 @@ package com.github.anrimian.musicplayer.ui.library.folders import com.github.anrimian.filesync.models.state.file.FileSyncState import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.composition.CurrentComposition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.folders.FileSource import com.github.anrimian.musicplayer.domain.models.folders.FolderFileSource import com.github.anrimian.musicplayer.domain.models.folders.IgnoredFolder @@ -84,7 +85,7 @@ interface LibraryFoldersView : MvpView { fun showDeleteCompositionError(errorCommand: ErrorCommand) @OneExecution - fun showDeleteCompositionMessage(compositionsToDelete: List) + fun showDeleteCompositionMessage(compositionsToDelete: List) @OneExecution fun showConfirmDeleteDialog(folder: FolderFileSource) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/adapter/FolderViewHolder.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/adapter/FolderViewHolder.kt index 4128d6d7a..9ce89c2b5 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/adapter/FolderViewHolder.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/adapter/FolderViewHolder.kt @@ -13,6 +13,7 @@ import com.github.anrimian.musicplayer.domain.models.folders.FolderFileSource import com.github.anrimian.musicplayer.ui.common.format.FormatUtils import com.github.anrimian.musicplayer.ui.utils.AndroidUtils import com.github.anrimian.musicplayer.ui.utils.ViewUtils +import com.github.anrimian.musicplayer.ui.utils.colorFromAttr import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.ItemDrawable import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.short_swipe.SwipeListener @@ -51,16 +52,12 @@ internal class FolderViewHolder( } viewBinding.btnActionsMenu.setOnClickListener { v -> onMenuClickListener(v, folder) } - backgroundDrawable.setColor( - AndroidUtils.getColorFromAttr(context, R.attr.listItemBackground) - ) + backgroundDrawable.setColor(context.colorFromAttr(R.attr.listItemBackground)) itemView.background = backgroundDrawable stateDrawable.setColor(Color.TRANSPARENT) viewBinding.clickableItem.background = stateDrawable viewBinding.clickableItem.foreground = RippleDrawable( - ColorStateList.valueOf( - AndroidUtils.getColorFromAttr(context, android.R.attr.colorControlHighlight) - ), + ColorStateList.valueOf(context.colorFromAttr(android.R.attr.colorControlHighlight)), null, rippleMaskDrawable ) @@ -86,9 +83,6 @@ internal class FolderViewHolder( override fun onSwipeStateChanged(swipeOffset: Float) { val swiping = swipeOffset > 0.0f - - viewBinding.divider.translationX = swipeOffset - if (isSwiping != swiping) { isSwiping = swiping val swipedCorners = context.resources.getDimension(R.dimen.swiped_item_corners) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/adapter/MusicFileViewHolder.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/adapter/MusicFileViewHolder.kt index 665ed1c92..797d35062 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/adapter/MusicFileViewHolder.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/adapter/MusicFileViewHolder.kt @@ -27,7 +27,7 @@ class MusicFileViewHolder( menuClickListener: (View, Int, CompositionFileSource) -> Unit ) : FileViewHolder(parent, R.layout.item_storage_music), SwipeListener { - private val compositionItemWrapper: CompositionItemWrapper + private val compositionItemWrapper: CompositionItemWrapper private lateinit var fileSource: CompositionFileSource diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/root/LibraryFoldersRootFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/root/LibraryFoldersRootFragment.kt index b2b286289..563726932 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/root/LibraryFoldersRootFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/folders/root/LibraryFoldersRootFragment.kt @@ -66,8 +66,8 @@ class LibraryFoldersRootFragment : LibraryFragment(), FolderRootView, BackButton setupFolderTree() } - override fun onFragmentMovedOnTop() { - super.onFragmentMovedOnTop() + override fun onFragmentResumed() { + super.onFragmentResumed() val toolbar = requireActivity().findViewById(R.id.toolbar) toolbar.setSubtitle(R.string.folders) val folderNavigation = FragmentNavigation.from(childFragmentManager) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/items/GenreItemsFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/items/GenreItemsFragment.kt index 717ed3e67..cec114757 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/items/GenreItemsFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/items/GenreItemsFragment.kt @@ -15,6 +15,7 @@ import com.github.anrimian.musicplayer.databinding.FragmentBaseFabListBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.composition.CurrentComposition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.genres.Genre import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.utils.ListPosition @@ -30,7 +31,6 @@ import com.github.anrimian.musicplayer.ui.common.view.ViewUtils import com.github.anrimian.musicplayer.ui.editor.common.DeleteErrorHandler import com.github.anrimian.musicplayer.ui.editor.common.ErrorHandler import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsFragment -import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsPresenter import com.github.anrimian.musicplayer.ui.library.compositions.adapter.CompositionsAdapter import com.github.anrimian.musicplayer.ui.playlist_screens.choose.ChoosePlayListDialogFragment import com.github.anrimian.musicplayer.ui.playlist_screens.choose.newChoosePlayListDialogFragment @@ -39,8 +39,8 @@ import com.github.anrimian.musicplayer.ui.utils.dialogs.newProgressDialogFragmen import com.github.anrimian.musicplayer.ui.utils.fragments.BackButtonListener import com.github.anrimian.musicplayer.ui.utils.fragments.DialogFragmentDelayRunner import com.github.anrimian.musicplayer.ui.utils.fragments.DialogFragmentRunner -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigation +import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigationListener import com.github.anrimian.musicplayer.ui.utils.slidr.SlidrPanel import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.RecyclerViewUtils import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.touch_helper.short_swipe.ShortSwipeCallback @@ -55,7 +55,8 @@ fun newGenreItemsFragment(genreId: Long): GenreItemsFragment { return fragment } -class GenreItemsFragment : BaseLibraryCompositionsFragment(), GenreItemsView, FragmentLayerListener, +class GenreItemsFragment : BaseLibraryCompositionsFragment(), GenreItemsView, + FragmentNavigationListener, BackButtonListener { private val presenter by moxyPresenter { @@ -65,7 +66,7 @@ class GenreItemsFragment : BaseLibraryCompositionsFragment(), GenreItemsView, Fr private lateinit var toolbar: AdvancedToolbar - private lateinit var adapter: CompositionsAdapter + private lateinit var adapter: CompositionsAdapter private lateinit var choosePlayListDialogRunner: DialogFragmentRunner private lateinit var editGenreNameDialogRunner: DialogFragmentRunner @@ -73,9 +74,7 @@ class GenreItemsFragment : BaseLibraryCompositionsFragment(), GenreItemsView, Fr private lateinit var progressDialogRunner: DialogFragmentDelayRunner private lateinit var deletingErrorHandler: ErrorHandler - override fun getLibraryPresenter(): BaseLibraryCompositionsPresenter { - return presenter - } + override fun getLibraryPresenter(): GenreItemsPresenter = presenter override fun onCreateView( inflater: LayoutInflater, @@ -141,7 +140,7 @@ class GenreItemsFragment : BaseLibraryCompositionsFragment(), GenreItemsView, Fr progressDialogRunner = DialogFragmentDelayRunner(fm, Tags.PROGRESS_DIALOG_TAG) } - override fun onFragmentMovedOnTop() { + override fun onFragmentResumed() { // super.onFragmentMovedOnTop(); presenter.onFragmentMovedToTop() val toolbar: AdvancedToolbar = requireActivity().findViewById(R.id.toolbar) @@ -255,7 +254,7 @@ class GenreItemsFragment : BaseLibraryCompositionsFragment(), GenreItemsView, Fr } } - override fun showDeleteCompositionMessage(compositionsToDelete: List) { + override fun showDeleteCompositionMessage(compositionsToDelete: List) { val text = MessagesUtils.getDeleteCompleteMessage(requireActivity(), compositionsToDelete) MessagesUtils.makeSnackbar(viewBinding.listContainer, text, Snackbar.LENGTH_SHORT).show() } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/items/GenreItemsPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/items/GenreItemsPresenter.kt index a93c88f68..508a9c843 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/items/GenreItemsPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/items/GenreItemsPresenter.kt @@ -7,6 +7,7 @@ import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInt import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.genres.Genre +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.domain.utils.rx.RxUtils import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser @@ -21,10 +22,10 @@ class GenreItemsPresenter( playListsInteractor: PlayListsInteractor, playerInteractor: LibraryPlayerInteractor, displaySettingsInteractor: DisplaySettingsInteractor, - syncInteractor: SyncInteractor<*, *, Long>, + syncInteractor: SyncInteractor, errorParser: ErrorParser, uiScheduler: Scheduler -) : BaseLibraryCompositionsPresenter( +) : BaseLibraryCompositionsPresenter( playerInteractor, playListsInteractor, displaySettingsInteractor, diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/items/GenreItemsView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/items/GenreItemsView.kt index d2230e9b3..bcef7b1c0 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/items/GenreItemsView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/items/GenreItemsView.kt @@ -1,5 +1,6 @@ package com.github.anrimian.musicplayer.ui.library.genres.items +import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.genres.Genre import com.github.anrimian.musicplayer.ui.library.common.compositions.BaseLibraryCompositionsView import moxy.viewstate.strategy.AddToEndSingleTagStrategy @@ -10,7 +11,7 @@ import moxy.viewstate.strategy.alias.Skip private const val RENAME_STATE = "rename_state" -interface GenreItemsView : BaseLibraryCompositionsView { +interface GenreItemsView : BaseLibraryCompositionsView { @AddToEndSingle fun showGenreInfo(genre: Genre) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/list/GenresListFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/list/GenresListFragment.kt index 6b6e7d1ce..71a3079a8 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/list/GenresListFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/library/genres/list/GenresListFragment.kt @@ -32,14 +32,15 @@ import com.github.anrimian.musicplayer.ui.utils.dialogs.newProgressDialogFragmen import com.github.anrimian.musicplayer.ui.utils.fragments.BackButtonListener import com.github.anrimian.musicplayer.ui.utils.fragments.DialogFragmentDelayRunner import com.github.anrimian.musicplayer.ui.utils.fragments.DialogFragmentRunner -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigation +import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigationListener import com.github.anrimian.musicplayer.ui.utils.fragments.safeShow import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.RecyclerViewUtils import com.google.android.material.snackbar.Snackbar import moxy.ktx.moxyPresenter -class GenresListFragment : LibraryFragment(), GenresListView, FragmentLayerListener, +class GenresListFragment : LibraryFragment(), GenresListView, + FragmentNavigationListener, BackButtonListener { private val presenter by moxyPresenter { @@ -97,8 +98,8 @@ class GenresListFragment : LibraryFragment(), GenresListView, FragmentLayerListe progressDialogRunner = DialogFragmentDelayRunner(fm, Tags.PROGRESS_DIALOG_TAG) } - override fun onFragmentMovedOnTop() { - super.onFragmentMovedOnTop() + override fun onFragmentResumed() { + super.onFragmentResumed() val toolbar: AdvancedToolbar = requireActivity().findViewById(R.id.toolbar) toolbar.setSubtitle(R.string.genres) toolbar.setupSearch(presenter::onSearchTextChanged, presenter.getSearchText()) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/main/MainActivity.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/main/MainActivity.java index 090796f30..32ca1c637 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/main/MainActivity.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/main/MainActivity.java @@ -11,6 +11,7 @@ import android.view.View; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.view.ViewCompat; import androidx.fragment.app.DialogFragment; @@ -69,7 +70,6 @@ protected void onCreate(Bundle savedInstanceState) { return; } } - startScreens(); } } @@ -82,10 +82,15 @@ protected void attachBaseContext(Context base) { @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - if (getOpenPlayerPanelArg(intent)) { - Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_activity_container); - if (fragment instanceof PlayerFragment) { - ((PlayerFragment) fragment).openPlayerPanel();//non-smooth update, why... + Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_activity_container); + if (fragment instanceof PlayerFragment) { + PlayerFragment playerFragment = (PlayerFragment) fragment; + if (getOpenPlayerPanelArg(intent)) { + playerFragment.openPlayerPanel();//non-smooth update, why... + } + String playlistUri = getPlaylistArg(intent); + if (playlistUri != null) { + playerFragment.openImportPlaylistScreen(playlistUri); } } } @@ -118,8 +123,15 @@ private void goToSetupScreen() { } private void goToMainScreen() { - boolean openPlayQueue = getOpenPlayerPanelArg(getIntent()); - startFragment(PlayerFragmentKt.newPlayerFragment(openPlayQueue)); + Intent intent = getIntent(); + + boolean openPlayQueue = getOpenPlayerPanelArg(intent); + + String playlistUri = null; + if (!AndroidUtils.isLaunchedFromHistory(this)) { + playlistUri = getPlaylistArg(intent); + } + startFragment(PlayerFragmentKt.newPlayerFragment(openPlayQueue, playlistUri)); } private void startFragment(Fragment fragment) { @@ -138,6 +150,15 @@ private boolean getOpenPlayerPanelArg(Intent intent) { return openPlayerPanel; } + @Nullable + private String getPlaylistArg(Intent intent) { + String type = intent.getType(); + if ("audio/x-mpegurl".equals(type) || "audio/mpegurl".equals(type)) { + return intent.getData().toString(); + } + return null; + } + public static class ErrorReportDialogFragment extends DialogFragment { @NonNull diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/main/external_player/ExternalPlayerActivity.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/main/external_player/ExternalPlayerActivity.java deleted file mode 100644 index 4d8ac090b..000000000 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/main/external_player/ExternalPlayerActivity.java +++ /dev/null @@ -1,267 +0,0 @@ -package com.github.anrimian.musicplayer.ui.main.external_player; - -import static android.view.View.VISIBLE; -import static com.github.anrimian.musicplayer.Constants.Arguments.LAUNCH_PREPARE_ARG; -import static com.github.anrimian.musicplayer.domain.models.utils.CompositionHelper.formatCompositionName; -import static com.github.anrimian.musicplayer.ui.common.format.FormatUtils.formatMilliseconds; -import static com.github.anrimian.musicplayer.ui.common.format.FormatUtils.getRepeatModeIcon; -import static com.github.anrimian.musicplayer.ui.common.format.FormatUtils.getRepeatModeText; -import static com.github.anrimian.musicplayer.ui.common.view.ViewUtils.setOnHoldListener; -import static com.github.anrimian.musicplayer.ui.utils.ViewUtils.onCheckChanged; -import static com.github.anrimian.musicplayer.ui.utils.ViewUtils.setChecked; - -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.media.MediaMetadataRetriever; -import android.net.Uri; -import android.os.Bundle; -import android.provider.MediaStore; -import android.view.View; -import android.widget.Toast; - -import androidx.annotation.DrawableRes; -import androidx.annotation.Nullable; - -import com.github.anrimian.musicplayer.R; -import com.github.anrimian.musicplayer.data.models.composition.source.ExternalCompositionSource; -import com.github.anrimian.musicplayer.data.utils.db.CursorWrapper; -import com.github.anrimian.musicplayer.databinding.ActivityExternalPlayerBinding; -import com.github.anrimian.musicplayer.di.Components; -import com.github.anrimian.musicplayer.ui.common.compat.CompatUtils; -import com.github.anrimian.musicplayer.ui.common.dialogs.DialogUtils; -import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand; -import com.github.anrimian.musicplayer.ui.common.format.FormatUtils; -import com.github.anrimian.musicplayer.ui.utils.AndroidUtils; -import com.github.anrimian.musicplayer.ui.utils.ImageUtils; -import com.github.anrimian.musicplayer.ui.utils.views.seek_bar.SeekBarViewWrapper; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.disposables.Disposable; -import io.reactivex.rxjava3.schedulers.Schedulers; -import moxy.MvpAppCompatActivity; -import moxy.presenter.InjectPresenter; -import moxy.presenter.ProvidePresenter; - -public class ExternalPlayerActivity extends MvpAppCompatActivity implements ExternalPlayerView { - - @InjectPresenter - ExternalPlayerPresenter presenter; - - private ActivityExternalPlayerBinding viewBinding; - - private SeekBarViewWrapper seekBarViewWrapper; - - @Nullable - private Disposable sourceCreationDisposable; - - @ProvidePresenter - ExternalPlayerPresenter providePresenter() { - return Components.getExternalPlayerComponent().externalPlayerPresenter(); - } - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - Components.getAppComponent().themeController().applyCurrentTheme(this); - getTheme().applyStyle(R.style.DialogActivityTheme, true); - super.onCreate(savedInstanceState); - viewBinding = ActivityExternalPlayerBinding.inflate(getLayoutInflater()); - setContentView(viewBinding.getRoot()); - - CompatUtils.setOutlineTextButtonStyle(viewBinding.tvPlaybackSpeed); - - seekBarViewWrapper = new SeekBarViewWrapper(viewBinding.sbTrackState); - seekBarViewWrapper.setProgressChangeListener(presenter::onTrackRewoundTo); - seekBarViewWrapper.setOnSeekStartListener(presenter::onSeekStart); - seekBarViewWrapper.setOnSeekStopListener(presenter::onSeekStop); - - viewBinding.ivPlayPause.setOnClickListener(v -> presenter.onPlayPauseClicked()); - viewBinding.ivRepeatMode.setOnClickListener(v -> presenter.onRepeatModeButtonClicked()); - onCheckChanged(viewBinding.cbKeepPlayingAfterClose, presenter::onKeepPlayerInBackgroundChecked); - - viewBinding.ivFastForward.setOnClickListener(v -> presenter.onFastSeekForwardCalled()); - setOnHoldListener(viewBinding.ivFastForward, presenter::onFastSeekForwardCalled); - viewBinding.ivRewind.setOnClickListener(v -> presenter.onFastSeekBackwardCalled()); - setOnHoldListener(viewBinding.ivRewind, presenter::onFastSeekBackwardCalled); - - if (savedInstanceState == null && getIntent().getBooleanExtra(LAUNCH_PREPARE_ARG, true)) { - Uri uriToPlay = getIntent().getData(); - createCompositionSource(uriToPlay); - } - } - - @Override - protected void attachBaseContext(Context base) { - super.attachBaseContext(Components.getAppComponent().localeController().dispatchAttachBaseContext(base)); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - - Uri uriToPlay = intent.getData(); - createCompositionSource(uriToPlay); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (sourceCreationDisposable != null) { - sourceCreationDisposable.dispose(); - } - } - - @Override - public void displayComposition(ExternalCompositionSource source) { - viewBinding.tvComposition.setText(formatCompositionName(source.getTitle(), source.getDisplayName())); - viewBinding.tvCompositionAuthor.setText(FormatUtils.formatAuthor(source.getArtist(), this)); - seekBarViewWrapper.setMax(source.getDuration()); - viewBinding.tvTotalTime.setText(formatMilliseconds(source.getDuration())); - Components.getAppComponent() - .imageLoader() - .displayImageInReusableTarget(viewBinding.ivMusicIcon, source, R.drawable.ic_music_placeholder); - } - - @Override - public void showPlayerState(boolean isPlaying) { - if (isPlaying) { - AndroidUtils.setAnimatedVectorDrawable(viewBinding.ivPlayPause, R.drawable.anim_play_to_pause); - viewBinding.ivPlayPause.setContentDescription(getString(R.string.pause)); - } else { - AndroidUtils.setAnimatedVectorDrawable(viewBinding.ivPlayPause, R.drawable.anim_pause_to_play); - viewBinding.ivPlayPause.setContentDescription(getString(R.string.play)); - } - } - - @Override - public void showTrackState(long currentPosition, long duration) { - seekBarViewWrapper.setProgress(currentPosition); - String formattedTime = formatMilliseconds(currentPosition); - viewBinding.sbTrackState.setContentDescription(getString(R.string.position_template, formattedTime)); - viewBinding.tvPlayedTime.setText(formattedTime); - } - - @Override - public void showRepeatMode(int mode) { - @DrawableRes int iconRes = getRepeatModeIcon(mode); - viewBinding.ivRepeatMode.setImageResource(iconRes); - String description = getString(getRepeatModeText(mode)); - viewBinding.ivRepeatMode.setContentDescription(description); - } - - @Override - public void showPlayErrorState(@Nullable ErrorCommand errorCommand) { - if (errorCommand == null) { - viewBinding.tvError.setVisibility(View.GONE); - return; - } - viewBinding.tvError.setVisibility(VISIBLE); - viewBinding.tvError.setText(errorCommand.getMessage()); - } - - @Override - public void showKeepPlayerInBackground(boolean externalPlayerKeepInBackground) { - setChecked(viewBinding.cbKeepPlayingAfterClose, externalPlayerKeepInBackground); - } - - @Override - public void displayPlaybackSpeed(float speed) { - viewBinding.tvPlaybackSpeed.setText(getString(R.string.playback_speed_template, speed)); - viewBinding.tvPlaybackSpeed.setOnClickListener(v -> - DialogUtils.showSpeedSelectorDialog(this, - speed, - presenter::onPlaybackSpeedSelected) - ); - } - - @Override - public void showSpeedChangeFeatureVisible(boolean visible) { - viewBinding.tvPlaybackSpeed.setVisibility(visible? VISIBLE: View.GONE); - } - - private void createCompositionSource(@Nullable Uri uri) { - if (uri == null) { - Toast.makeText(this, "Not enough data to play composition", Toast.LENGTH_LONG).show(); - finish(); - return; - } - ExternalCompositionSource.Builder builder = new ExternalCompositionSource.Builder(uri); - sourceCreationDisposable = Single.fromCallable(() -> builder) - .map(this::readDataFromContentResolver) - .timeout(2, TimeUnit.SECONDS) - .map(this::readDataFromFile) - .timeout(2, TimeUnit.SECONDS) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .onErrorReturnItem(builder) - .subscribe( - createdBuilder -> presenter.onSourceForPlayingReceived(createdBuilder.build()) - ); - } - - private ExternalCompositionSource.Builder readDataFromContentResolver(ExternalCompositionSource.Builder builder) { - String displayName = null; - long size = 0; - - try (Cursor cursor = getContentResolver().query( - builder.getUri(), - new String[] { - MediaStore.Audio.Media.DISPLAY_NAME, - MediaStore.Audio.Media.SIZE - }, - null, - null, - null)) { - CursorWrapper cursorWrapper = new CursorWrapper(cursor); - if (cursor != null && cursor.moveToFirst()) { - displayName = cursorWrapper.getString(MediaStore.Audio.Media.DISPLAY_NAME); - size = cursorWrapper.getLong(MediaStore.Audio.Media.SIZE); - } - } catch (Exception ignored) {} - - return builder.setDisplayName(displayName == null? "unknown name" : displayName) - .setSize(size); - } - - private ExternalCompositionSource.Builder readDataFromFile(ExternalCompositionSource.Builder builder) { - String title = null; - String artist = null; - String album = null; - long duration = 0; - byte[] imageBytes = null; - MediaMetadataRetriever mmr = null; - try { - mmr = new MediaMetadataRetriever(); - mmr.setDataSource(this, builder.getUri()); - - artist = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST); - title = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); - album = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM); - String durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); - try { - duration = Long.parseLong(durationStr); - } catch (NumberFormatException ignored) {} - - int coverSize = getResources().getInteger(R.integer.icon_image_size); - imageBytes = ImageUtils.downscaleImageBytes(mmr.getEmbeddedPicture(), coverSize); - } catch (Exception ignored) { - - } finally { - if (mmr != null) { - try { - mmr.release(); - } catch (IOException ignored) {} - } - } - - return builder.setTitle(title) - .setArtist(artist) - .setAlbum(album) - .setDuration(duration) - .setImageBytes(imageBytes); - } -} diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/main/external_player/ExternalPlayerView.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/main/external_player/ExternalPlayerView.java deleted file mode 100644 index 3a8d767fd..000000000 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/main/external_player/ExternalPlayerView.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.github.anrimian.musicplayer.ui.main.external_player; - -import com.github.anrimian.musicplayer.data.models.composition.source.ExternalCompositionSource; -import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand; - -import javax.annotation.Nullable; - -import moxy.MvpView; -import moxy.viewstate.strategy.alias.AddToEndSingle; -public interface ExternalPlayerView extends MvpView { - - @AddToEndSingle - void showPlayerState(boolean isPlaying); - - @AddToEndSingle - void displayComposition(ExternalCompositionSource source); - - @AddToEndSingle - void showTrackState(long currentPosition, long duration); - - @AddToEndSingle - void showRepeatMode(int mode); - - @AddToEndSingle - void showPlayErrorState(@Nullable ErrorCommand errorCommand); - - @AddToEndSingle - void showKeepPlayerInBackground(boolean externalPlayerKeepInBackground); - - @AddToEndSingle - void displayPlaybackSpeed(float speed); - - @AddToEndSingle - void showSpeedChangeFeatureVisible(boolean visible); -} diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/notifications/MediaNotificationsDisplayer.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/notifications/MediaNotificationsDisplayer.java index 1bc7fbe00..d33dcef12 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/notifications/MediaNotificationsDisplayer.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/notifications/MediaNotificationsDisplayer.java @@ -1,402 +1,35 @@ package com.github.anrimian.musicplayer.ui.notifications; -import static com.github.anrimian.musicplayer.Constants.Actions.CHANGE_REPEAT_MODE; -import static com.github.anrimian.musicplayer.Constants.Actions.PAUSE; -import static com.github.anrimian.musicplayer.Constants.Actions.PLAY; -import static com.github.anrimian.musicplayer.Constants.Actions.SKIP_TO_NEXT; -import static com.github.anrimian.musicplayer.Constants.Actions.SKIP_TO_PREVIOUS; -import static com.github.anrimian.musicplayer.Constants.Arguments.LAUNCH_PREPARE_ARG; -import static com.github.anrimian.musicplayer.Constants.Arguments.OPEN_PLAYER_PANEL_ARG; -import static com.github.anrimian.musicplayer.domain.models.utils.CompositionHelper.formatCompositionName; -import static com.github.anrimian.musicplayer.infrastructure.service.music.MusicService.REQUEST_CODE; -import static com.github.anrimian.musicplayer.ui.common.format.FormatUtils.formatAuthor; -import static com.github.anrimian.musicplayer.ui.common.format.FormatUtils.formatCompositionAuthor; -import static com.github.anrimian.musicplayer.ui.common.format.FormatUtils.getRepeatModeIcon; -import static com.github.anrimian.musicplayer.ui.common.format.FormatUtils.getRepeatModeText; - import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.os.Build; -import android.os.DeadSystemException; -import android.service.notification.StatusBarNotification; import android.support.v4.media.session.MediaSessionCompat; import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.core.app.NotificationCompat; -import com.github.anrimian.musicplayer.Constants; -import com.github.anrimian.musicplayer.R; -import com.github.anrimian.musicplayer.data.models.composition.source.ExternalCompositionSource; -import com.github.anrimian.musicplayer.domain.models.composition.Composition; import com.github.anrimian.musicplayer.domain.models.composition.source.CompositionSource; -import com.github.anrimian.musicplayer.domain.models.composition.source.LibraryCompositionSource; import com.github.anrimian.musicplayer.domain.models.player.service.MusicNotificationSetting; -import com.github.anrimian.musicplayer.infrastructure.service.music.CompositionSourceModelHelper; -import com.github.anrimian.musicplayer.infrastructure.service.music.MusicService; -import com.github.anrimian.musicplayer.ui.common.format.FormatUtilsKt; -import com.github.anrimian.musicplayer.ui.common.images.CoverImageLoader; -import com.github.anrimian.musicplayer.ui.main.MainActivity; -import com.github.anrimian.musicplayer.ui.main.external_player.ExternalPlayerActivity; -import com.github.anrimian.musicplayer.ui.notifications.builder.AppNotificationBuilder; -import com.github.anrimian.musicplayer.ui.utils.AndroidUtilsKt; - -import javax.annotation.Nonnull; - -//for api 33 can be used without actions -public class MediaNotificationsDisplayer { - - private static final int FOREGROUND_NOTIFICATION_ID = 1; - public static final String FOREGROUND_CHANNEL_ID = "0"; - - private final Context context; - private final NotificationManager notificationManager; - private final AppNotificationBuilder notificationBuilder; - private final CoverImageLoader coverImageLoader; - - private NotificationInfoState notificationInfoState; - private Bitmap currentNotificationBitmap; - private Runnable cancellationRunnable; - - public MediaNotificationsDisplayer(Context context, - AppNotificationBuilder notificationBuilder, - CoverImageLoader coverImageLoader) { - this.context = context; - this.notificationBuilder = notificationBuilder; - this.coverImageLoader = coverImageLoader; - - notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel channel = new NotificationChannel(FOREGROUND_CHANNEL_ID, - context.getString(R.string.foreground_channel_description), - NotificationManager.IMPORTANCE_LOW); - notificationManager.createNotificationChannel(channel); - } - } - - public void startStubForegroundNotification(Service service, MediaSessionCompat mediaSession) { - service.startForeground(FOREGROUND_NOTIFICATION_ID, getStubNotification(mediaSession)); - } - - public Notification getStubNotification(MediaSessionCompat mediaSession) { - androidx.media.app.NotificationCompat.MediaStyle style = new androidx.media.app.NotificationCompat.MediaStyle(); - style.setMediaSession(mediaSession.getSessionToken()); - return new NotificationCompat.Builder(context, FOREGROUND_CHANNEL_ID) - .setContentTitle("") - .setContentText("") - .setSmallIcon(R.drawable.ic_music_box) - .setShowWhen(false) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setCategory(Notification.CATEGORY_SERVICE) - .setStyle(style) - .build(); - } - - public void startForegroundNotification(Service service, - int isPlayingState, - @Nullable CompositionSource source, - MediaSessionCompat mediaSession, - int repeatMode, - @Nullable MusicNotificationSetting notificationSetting, - boolean reloadCover) { - notificationInfoState = new NotificationInfoState( - isPlayingState, - source, - mediaSession, - repeatMode, - notificationSetting - ); - - Notification notification = getDefaultMusicNotification(isPlayingState, - source, - mediaSession, - repeatMode, - notificationSetting) - .build(); - service.startForeground(FOREGROUND_NOTIFICATION_ID, notification); - - if (reloadCover) { - showMusicNotificationWithCover(source, notificationSetting); - } - } - - public void updateForegroundNotification(int isPlayingState, - @Nullable CompositionSource source, - MediaSessionCompat mediaSession, - int repeatMode, - MusicNotificationSetting notificationSetting, - boolean reloadCover) { - if (!isNotificationVisible(notificationManager, FOREGROUND_NOTIFICATION_ID)) { - return; - } - - notificationInfoState = new NotificationInfoState( - isPlayingState, - source, - mediaSession, - repeatMode, - notificationSetting - ); - - Notification notification = getDefaultMusicNotification(isPlayingState, - source, - mediaSession, - repeatMode, - notificationSetting) - .build(); - safeNotify(notificationManager, FOREGROUND_NOTIFICATION_ID, notification); - - if (reloadCover) { - showMusicNotificationWithCover(source, notificationSetting); - } - } - - public void cancelCoverLoadingForForegroundNotification() { - if (cancellationRunnable != null) { - cancellationRunnable.run(); - } - } - - private void showMusicNotificationWithCover(@Nullable CompositionSource source, - @Nullable MusicNotificationSetting notificationSetting) { - cancelCoverLoadingForForegroundNotification(); - - if (source == null) { - return; - } - - boolean showCovers = false; - if (notificationSetting != null) { - showCovers = notificationSetting.isShowCovers(); - } - if (!showCovers) { - return; - } - - //keep in mind, we cancel and get short update with an old data - cancellationRunnable = CompositionSourceModelHelper.getCompositionSourceCover( - source, - bitmap -> { - if (notificationInfoState == null) { - return; - } - - boolean showNotificationCoverStub = true; - MusicNotificationSetting setting = notificationInfoState.notificationSetting; - if (setting != null) { - showNotificationCoverStub = setting.isShowNotificationCoverStub(); - } - if (bitmap == null && showNotificationCoverStub) { - bitmap = coverImageLoader.getDefaultNotificationBitmap(); - } - - NotificationCompat.Builder builder = getDefaultMusicNotification( - notificationInfoState.isPlayingState, - notificationInfoState.source, - notificationInfoState.mediaSession, - notificationInfoState.repeatMode, - notificationInfoState.notificationSetting - ); - - builder.setLargeIcon(bitmap); - currentNotificationBitmap = bitmap; - safeNotify(notificationManager, FOREGROUND_NOTIFICATION_ID, builder.build()); - }, - coverImageLoader); - } - private NotificationCompat.Builder getDefaultMusicNotification(int isPlayingState, - @Nullable CompositionSource source, - MediaSessionCompat mediaSession, - int repeatMode, - @Nullable MusicNotificationSetting notificationSetting) { - Intent intent; - if (source instanceof ExternalCompositionSource) { - intent = new Intent(context, ExternalPlayerActivity.class); - intent.putExtra(LAUNCH_PREPARE_ARG, false); - } else { - intent = new Intent(context, MainActivity.class); - intent.putExtra(OPEN_PLAYER_PANEL_ARG, true); - } - PendingIntent pIntent = PendingIntent.getActivity(context, 0, intent, AndroidUtilsKt.pIntentFlag(PendingIntent.FLAG_UPDATE_CURRENT)); +public interface MediaNotificationsDisplayer { - boolean coloredNotification = false; - boolean showNotificationCoverStub = true; - boolean showCovers = false; - if (notificationSetting != null) { - coloredNotification = notificationSetting.isColoredNotification(); - showNotificationCoverStub = notificationSetting.isShowNotificationCoverStub(); - showCovers = notificationSetting.isShowCovers(); - } + void startStubForegroundNotification(Service service, MediaSessionCompat mediaSession); - NotificationCompat.Builder builder = notificationBuilder.buildMusicNotification(context) - .setColorized(coloredNotification) - .setSmallIcon(R.drawable.ic_music_box) - .setContentIntent(pIntent) - .setShowWhen(false) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setCategory(Notification.CATEGORY_SERVICE); + Notification getStubNotification(MediaSessionCompat mediaSession); - if (source != null) { - formatCompositionSource(source, builder); - setActionsToNotification(isPlayingState, source, mediaSession, repeatMode, builder); - } - - if (showCovers) { - Bitmap bitmap = currentNotificationBitmap; - if (!showNotificationCoverStub && bitmap == coverImageLoader.getDefaultNotificationBitmap()) { - bitmap = null; - } - if ((bitmap == null || bitmap.isRecycled()) && showNotificationCoverStub) { - bitmap = coverImageLoader.getDefaultNotificationBitmap(); - } - builder.setLargeIcon(bitmap); - } - - return builder; - } - - private void setActionsToNotification(int isPlayingState, - @Nonnull CompositionSource source, - MediaSessionCompat mediaSession, - int repeatMode, - NotificationCompat.Builder builder) { - int requestCode = isPlayingState == Constants.RemoteViewPlayerState.PAUSE? PLAY: PAUSE; - Intent intentPlayPause = new Intent(context, MusicService.class); - intentPlayPause.putExtra(REQUEST_CODE, requestCode); - PendingIntent pIntentPlayPause = PendingIntent.getService(context, - requestCode, - intentPlayPause, - AndroidUtilsKt.pIntentFlag(PendingIntent.FLAG_UPDATE_CURRENT)); - - NotificationCompat.Action playPauseAction = new NotificationCompat.Action( - FormatUtilsKt.getRemoteViewPlayerStateIcon(isPlayingState), - getString(isPlayingState == Constants.RemoteViewPlayerState.PAUSE? R.string.play: R.string.pause), - pIntentPlayPause); - - androidx.media.app.NotificationCompat.MediaStyle style = new androidx.media.app.NotificationCompat.MediaStyle(); - style.setMediaSession(mediaSession.getSessionToken()); - - if (source instanceof LibraryCompositionSource) { - Intent intentSkipToPrevious = new Intent(context, MusicService.class); - intentSkipToPrevious.putExtra(REQUEST_CODE, SKIP_TO_PREVIOUS); - PendingIntent pIntentSkipToPrevious = PendingIntent.getService(context, - SKIP_TO_PREVIOUS, - intentSkipToPrevious, - AndroidUtilsKt.pIntentFlag(PendingIntent.FLAG_UPDATE_CURRENT)); - - Intent intentSkipToNext = new Intent(context, MusicService.class); - intentSkipToNext.putExtra(REQUEST_CODE, SKIP_TO_NEXT); - PendingIntent pIntentSkipToNext = PendingIntent.getService(context, - SKIP_TO_NEXT, - intentSkipToNext, - AndroidUtilsKt.pIntentFlag(PendingIntent.FLAG_UPDATE_CURRENT)); - - style.setShowActionsInCompactView(0, 1, 2); - - builder.addAction(R.drawable.ic_skip_previous, getString(R.string.previous_track), pIntentSkipToPrevious) - .addAction(playPauseAction) - .addAction(R.drawable.ic_skip_next, getString(R.string.next_track), pIntentSkipToNext); - } - if (source instanceof ExternalCompositionSource) { - Intent intentChangeRepeatMode = new Intent(context, MusicService.class); - intentChangeRepeatMode.putExtra(REQUEST_CODE, CHANGE_REPEAT_MODE); - - PendingIntent pIntentChangeRepeatMode = PendingIntent.getService(context, - CHANGE_REPEAT_MODE, - intentChangeRepeatMode, - AndroidUtilsKt.pIntentFlag(PendingIntent.FLAG_UPDATE_CURRENT)); - - NotificationCompat.Action changeRepeatModeAction = new NotificationCompat.Action( - getRepeatModeIcon(repeatMode), - getString(getRepeatModeText(repeatMode)), - pIntentChangeRepeatMode); - - - style.setShowActionsInCompactView(0, 1); - - builder.addAction(changeRepeatModeAction) - .addAction(playPauseAction); - } - - builder.setStyle(style); - } - - private void formatCompositionSource(@Nonnull CompositionSource source, - NotificationCompat.Builder builder) { - if (source instanceof LibraryCompositionSource) { - Composition composition = ((LibraryCompositionSource) source).getComposition(); - builder.setContentTitle(formatCompositionName(composition)) - .setContentText(formatCompositionAuthor(composition, context)); - } - if (source instanceof ExternalCompositionSource) { - ExternalCompositionSource eSource = (ExternalCompositionSource) source; - builder.setContentTitle(formatCompositionName(eSource.getTitle(), eSource.getDisplayName())) - .setContentText(formatAuthor(eSource.getArtist(), context)); - } - } - - private boolean isNotificationVisible(NotificationManager notificationManager, - int notificationId) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { - try { - StatusBarNotification[] notifications = notificationManager.getActiveNotifications(); - for (StatusBarNotification notification : notifications) { - if (notification.getId() == notificationId) { - return true; - } - } - return false; - } catch (Exception ignored) {} //getActiveNotifications() can throw exception on android 6 - } - return true; - } - - - private void safeNotify(NotificationManager notificationManager, - int id, - Notification notification) { - try { - notificationManager.notify(id, notification); - } catch (RuntimeException e) { - Throwable cause = e.getCause(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && cause instanceof DeadSystemException) { - return; - } - throw e; - } - } - - private String getString(@StringRes int resId) { - return context.getString(resId); - } - - private static class NotificationInfoState { - final int isPlayingState; - final @Nullable CompositionSource source; - final MediaSessionCompat mediaSession; - final int repeatMode; - final MusicNotificationSetting notificationSetting; - - public NotificationInfoState(int isPlayingState, + void startForegroundNotification(Service service, + int isPlayingState, @Nullable CompositionSource source, MediaSessionCompat mediaSession, int repeatMode, - MusicNotificationSetting notificationSetting) { - this.isPlayingState = isPlayingState; - this.source = source; - this.mediaSession = mediaSession; - this.repeatMode = repeatMode; - this.notificationSetting = notificationSetting; - } - } + @Nullable MusicNotificationSetting notificationSetting, + boolean reloadCover); + + void updateForegroundNotification(int isPlayingState, + @Nullable CompositionSource source, + MediaSessionCompat mediaSession, + int repeatMode, + MusicNotificationSetting notificationSetting, + boolean reloadCover); + + void cancelCoverLoadingForForegroundNotification(); + } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/notifications/builder/AppNotificationBuilder.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/notifications/builder/AppNotificationBuilder.java index bbf1fbf8c..fed10569b 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/notifications/builder/AppNotificationBuilder.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/notifications/builder/AppNotificationBuilder.java @@ -1,7 +1,7 @@ package com.github.anrimian.musicplayer.ui.notifications.builder; import static androidx.core.content.ContextCompat.getColor; -import static com.github.anrimian.musicplayer.ui.notifications.MediaNotificationsDisplayer.FOREGROUND_CHANNEL_ID; +import static com.github.anrimian.musicplayer.ui.notifications.MediaNotificationsDisplayerImpl.FOREGROUND_CHANNEL_ID; import android.content.Context; import android.os.Build; diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/PlayerFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/PlayerFragment.kt index 9f60c15f8..48d2e98cf 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/PlayerFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/PlayerFragment.kt @@ -2,6 +2,7 @@ package com.github.anrimian.musicplayer.ui.player_screen import android.os.Bundle import android.view.* +import android.widget.LinearLayout import androidx.annotation.DrawableRes import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.AppCompatActivity @@ -9,10 +10,10 @@ import androidx.appcompat.graphics.drawable.DrawerArrowDrawable import androidx.constraintlayout.motion.widget.MotionLayout import androidx.core.content.ContextCompat import androidx.core.view.GravityCompat +import androidx.core.view.updateLayoutParams import androidx.drawerlayout.widget.DrawerLayout import androidx.fragment.app.Fragment import com.github.anrimian.filesync.models.state.file.FileSyncState -import com.github.anrimian.filesync.models.state.file.NotActive import com.github.anrimian.musicplayer.Constants import com.github.anrimian.musicplayer.Constants.Tags import com.github.anrimian.musicplayer.R @@ -25,6 +26,7 @@ import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.interactors.sleep_timer.NO_TIMER import com.github.anrimian.musicplayer.domain.models.Screens import com.github.anrimian.musicplayer.domain.models.composition.Composition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.play_queue.PlayQueueItem import com.github.anrimian.musicplayer.domain.models.player.modes.RepeatMode import com.github.anrimian.musicplayer.domain.models.playlist.PlayList @@ -65,6 +67,7 @@ import com.github.anrimian.musicplayer.ui.player_screen.view.wrappers.attachPlay import com.github.anrimian.musicplayer.ui.playlist_screens.choose.ChoosePlayListDialogFragment import com.github.anrimian.musicplayer.ui.playlist_screens.playlist.newPlayListFragment import com.github.anrimian.musicplayer.ui.playlist_screens.playlists.PlayListsFragment +import com.github.anrimian.musicplayer.ui.playlist_screens.playlists.newPlaylistsFragment import com.github.anrimian.musicplayer.ui.settings.SettingsFragment import com.github.anrimian.musicplayer.ui.sleep_timer.SleepTimerDialogFragment import com.github.anrimian.musicplayer.ui.utils.AndroidUtils @@ -91,12 +94,14 @@ import moxy.ktx.moxyPresenter private const val NO_ITEM = -1 private const val SELECTED_DRAWER_ITEM = "selected_drawer_item" -fun newPlayerFragment(openPlayQueue: Boolean = false): PlayerFragment { - val args = Bundle() - args.putBoolean(Constants.Arguments.OPEN_PLAYER_PANEL_ARG, openPlayQueue) - val fragment = PlayerFragment() - fragment.arguments = args - return fragment +fun newPlayerFragment( + openPlayQueue: Boolean = false, + playlistUriStr: String? = null +) = PlayerFragment().apply { + arguments = Bundle().apply { + putBoolean(Constants.Arguments.OPEN_PLAYER_PANEL_ARG, openPlayQueue) + putString(Constants.Arguments.PLAYLIST_IMPORT_ARG, playlistUriStr) + } } class PlayerFragment : MvpAppCompatFragment(), BackButtonListener, PlayerView { @@ -187,10 +192,15 @@ class PlayerFragment : MvpAppCompatFragment(), BackButtonListener, PlayerView { drawerLockStateProcessor::onBottomSheetOpened ) } + viewBinding.navigationView.setNavigationItemSelectedListener(this::onNavigationItemSelected) val headerView = viewBinding.navigationView.inflateHeaderView(R.layout.partial_drawer_header) headerView.setBackgroundColor(requireContext().getNavigationViewPrimaryColorLight()) + headerView.updateLayoutParams { + height += AndroidUtils.getStatusBarHeight(requireContext()) + } drawerHeaderBinding = PartialDrawerHeaderBinding.bind(headerView) + val drawerToggle = ActionBarDrawerToggle( requireActivity(), viewBinding.drawer, @@ -261,7 +271,15 @@ class PlayerFragment : MvpAppCompatFragment(), BackButtonListener, PlayerView { toolbarPlayQueueBinding.flTitleArea.setOnClickListener(this::onPlayerTitleClicked) if (savedInstanceState == null) { - presenter.onSetupScreenStateRequested() + val playlistImportUri = requireArguments().getString(Constants.Arguments.PLAYLIST_IMPORT_ARG) + if (playlistImportUri == null) { + presenter.onSetupScreenStateRequested() + } else { + requireArguments().remove(Constants.Arguments.PLAYLIST_IMPORT_ARG) + selectedDrawerItemId = R.id.menu_play_lists + viewBinding.navigationView.setCheckedItem(R.id.menu_play_lists) + startFragment(newPlaylistsFragment(playlistImportUri)) + } } else { selectedDrawerItemId = savedInstanceState.getInt(SELECTED_DRAWER_ITEM, NO_ITEM) } @@ -355,7 +373,7 @@ class PlayerFragment : MvpAppCompatFragment(), BackButtonListener, PlayerView { Screens.LIBRARY -> presenter.onLibraryScreenSelected() Screens.PLAY_LISTS -> { val fragments: MutableList = ArrayList() - fragments.add(PlayListsFragment()) + fragments.add(newPlaylistsFragment()) if (selectedPlayListScreenId != 0L) { fragments.add(newPlayListFragment(selectedPlayListScreenId)) } @@ -522,7 +540,7 @@ class PlayerFragment : MvpAppCompatFragment(), BackButtonListener, PlayerView { } } - override fun showDeleteCompositionMessage(compositionsToDelete: List) { + override fun showDeleteCompositionMessage(compositionsToDelete: List) { val text = MessagesUtils.getDeleteCompleteMessage(requireActivity(), compositionsToDelete) MessagesUtils.makeSnackbar(viewBinding.clPlayQueueContainer!!, text, Snackbar.LENGTH_SHORT).show() } @@ -588,7 +606,7 @@ class PlayerFragment : MvpAppCompatFragment(), BackButtonListener, PlayerView { val isFileRemote: Boolean val formattedState: FileSyncState if (item == null) { - formattedState = NotActive + formattedState = FileSyncState.NotActive isFileRemote = false } else { formattedState = syncState @@ -608,18 +626,41 @@ class PlayerFragment : MvpAppCompatFragment(), BackButtonListener, PlayerView { if (currentFragment is LibraryFoldersRootFragment) { currentFragment.revealComposition(id) } else { - viewBinding.navigationView.setCheckedItem(R.id.menu_library) + if (selectedDrawerItemId != R.id.menu_library) { + selectedDrawerItemId = R.id.menu_library + viewBinding.navigationView.setCheckedItem(R.id.menu_library) + } presenter.onLibraryScreenSelected(Screens.LIBRARY_FOLDERS) startFragment(newLibraryFoldersRootFragment(id)) } } } + override fun showScreensSwipeEnabled(enabled: Boolean) { + viewBinding.vpPlayContent!!.isUserInputEnabled = enabled + } + fun openPlayerPanel() { presenter.onOpenPlayerPanelClicked() playerPanelWrapper.openPlayerPanel() } + fun openImportPlaylistScreen(uriStr: String) { + playerPanelWrapper.collapseBottomPanelSmoothly { + val currentFragment = navigation.fragmentOnTop + if (currentFragment is PlayListsFragment) { + currentFragment.importPlaylist(uriStr) + } else { + if (selectedDrawerItemId != R.id.menu_play_lists) { + selectedDrawerItemId = R.id.menu_play_lists + viewBinding.navigationView.setCheckedItem(R.id.menu_play_lists) + presenter.onDrawerScreenSelected(Screens.PLAY_LISTS) + } + navigation.newRootFragment(newPlaylistsFragment(uriStr), 0, 0) + } + } + } + private fun setMusicControlsEnabled(show: Boolean) { panelBinding.btnActionsMenu.isEnabled = show panelBinding.ivSkipToNext.isEnabled = show diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/PlayerPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/PlayerPresenter.kt index c0862c471..033a23974 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/PlayerPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/PlayerPresenter.kt @@ -51,6 +51,8 @@ class PlayerPresenter( subscribeOnTrackPositionChanging() subscribeOnSleepTimerTime() subscribeOnFileScannerState() + playerScreenInteractor.playerScreensSwipeObservable + .unsafeSubscribeOnUi(viewState::showScreensSwipeEnabled) } fun onSetupScreenStateRequested() { diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/PlayerView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/PlayerView.kt index bc8ad13ad..874ea134b 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/PlayerView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/PlayerView.kt @@ -2,6 +2,7 @@ package com.github.anrimian.musicplayer.ui.player_screen import com.github.anrimian.filesync.models.state.file.FileSyncState import com.github.anrimian.musicplayer.domain.models.composition.Composition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.play_queue.PlayQueueItem import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.scanner.FileScannerState @@ -62,7 +63,7 @@ interface PlayerView : MvpView { fun showDeleteCompositionError(errorCommand: ErrorCommand) @OneExecution - fun showDeleteCompositionMessage(compositionsToDelete: List) + fun showDeleteCompositionMessage(compositionsToDelete: List) @OneExecution fun showDrawerScreen(selectedDrawerScreenId: Int, selectedPlayListScreenId: Long) @@ -97,4 +98,7 @@ interface PlayerView : MvpView { @AddToEndSingle fun showCurrentCompositionSyncState(syncState: FileSyncState, item: PlayQueueItem?) + @AddToEndSingle + fun showScreensSwipeEnabled(enabled: Boolean) + } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/lyrics/LyricsFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/lyrics/LyricsFragment.kt index 3c6f8c56b..aacfc73ca 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/lyrics/LyricsFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/lyrics/LyricsFragment.kt @@ -32,7 +32,7 @@ class LyricsFragment: MvpAppCompatFragment(), LyricsView { private val presenter by moxyPresenter { Components.getLibraryComponent().lyricsPresenter() } - private lateinit var viewBinding: FragmentLyricsBinding + private lateinit var binding: FragmentLyricsBinding private lateinit var clPlayQueueContainer: CoordinatorLayout private lateinit var acvToolbar: ActionMenuView @@ -49,8 +49,8 @@ class LyricsFragment: MvpAppCompatFragment(), LyricsView { container: ViewGroup?, savedInstanceState: Bundle? ): View { - viewBinding = FragmentLyricsBinding.inflate(inflater, container, false) - return viewBinding.root + binding = FragmentLyricsBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -59,7 +59,7 @@ class LyricsFragment: MvpAppCompatFragment(), LyricsView { clPlayQueueContainer = requireActivity().findViewById(R.id.cl_play_queue_container) acvToolbar = requireActivity().findViewById(R.id.acvPlayQueue) - viewBinding.progressStateView.onTryAgainClick(presenter::onEditLyricsClicked) + binding.progressStateView.onTryAgainClick(presenter::onEditLyricsClicked) errorHandler = ErrorHandler( this, @@ -68,10 +68,9 @@ class LyricsFragment: MvpAppCompatFragment(), LyricsView { ) val fm = childFragmentManager - lyricsDialogFragmentRunner = DialogFragmentRunner( - fm, - Constants.Tags.LYRICS - ) { fragment -> fragment.setOnCompleteListener(presenter::onNewLyricsEntered) } + lyricsDialogFragmentRunner = DialogFragmentRunner(fm, Constants.Tags.LYRICS) { fragment -> + fragment.setOnCompleteListener(presenter::onNewLyricsEntered) + } progressDialogRunner = DialogFragmentDelayRunner(fm, Constants.Tags.PROGRESS_DIALOG_TAG) } @@ -85,8 +84,8 @@ class LyricsFragment: MvpAppCompatFragment(), LyricsView { override fun showLyrics(text: String?) { if (text == null) { - viewBinding.progressStateView.showMessage(R.string.no_current_composition) - viewBinding.tvLyrics.visibility = View.GONE + binding.progressStateView.showMessage(R.string.no_current_composition) + binding.tvLyrics.visibility = View.GONE isActionMenuEnabled = false showMenuState() return @@ -96,15 +95,15 @@ class LyricsFragment: MvpAppCompatFragment(), LyricsView { showMenuState() if (text.isEmpty()) { - viewBinding.progressStateView.showMessage( + binding.progressStateView.showMessage( R.string.no_lyrics_for_current_composition, R.string.edit_lyrics ) - viewBinding.tvLyrics.visibility = View.GONE + binding.tvLyrics.visibility = View.GONE } else { - viewBinding.progressStateView.hideAll() - viewBinding.tvLyrics.visibility = View.VISIBLE - viewBinding.tvLyrics.text = text + binding.progressStateView.hideAll() + binding.tvLyrics.visibility = View.VISIBLE + binding.tvLyrics.text = text } } @@ -138,7 +137,7 @@ class LyricsFragment: MvpAppCompatFragment(), LyricsView { } override fun resetTextPosition() { - viewBinding.nsvContainer.scrollTo(0, 0) + binding.nsvContainer.scrollTo(0, 0) } private fun onLyricsMenuItemClicked(menuItem: MenuItem) { diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/PlayQueueFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/PlayQueueFragment.kt index e07a43b44..4f8ded55c 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/PlayQueueFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/PlayQueueFragment.kt @@ -16,6 +16,7 @@ import com.github.anrimian.musicplayer.R import com.github.anrimian.musicplayer.databinding.FragmentPlayQueueBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.models.composition.Composition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.play_queue.PlayQueueItem import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.ui.common.dialogs.shareComposition @@ -172,7 +173,7 @@ class PlayQueueFragment: MvpAppCompatFragment(), PlayQueueView { } } - override fun showDeleteCompositionMessage(compositionsToDelete: List) { + override fun showDeleteCompositionMessage(compositionsToDelete: List) { val text = MessagesUtils.getDeleteCompleteMessage(requireActivity(), compositionsToDelete) MessagesUtils.makeSnackbar(clPlayQueueContainer, text, Snackbar.LENGTH_SHORT).show() } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/PlayQueuePresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/PlayQueuePresenter.kt index 3c4247867..1f816a3e8 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/PlayQueuePresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/PlayQueuePresenter.kt @@ -10,6 +10,7 @@ import com.github.anrimian.musicplayer.domain.models.play_queue.PlayQueueData import com.github.anrimian.musicplayer.domain.models.play_queue.PlayQueueEvent import com.github.anrimian.musicplayer.domain.models.play_queue.PlayQueueItem import com.github.anrimian.musicplayer.domain.models.playlist.PlayList +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.domain.utils.ListUtils import com.github.anrimian.musicplayer.domain.utils.rx.RxUtils import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser @@ -18,13 +19,14 @@ import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.ListDragFilt import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Scheduler import io.reactivex.rxjava3.disposables.Disposable -import java.util.* +import java.util.Collections +import java.util.LinkedList class PlayQueuePresenter( private val playerInteractor: LibraryPlayerInteractor, private val playListsInteractor: PlayListsInteractor, private val playerScreenInteractor: PlayerScreenInteractor, - private val syncInteractor: SyncInteractor<*, *, Long>, + private val syncInteractor: SyncInteractor, errorParser: ErrorParser, uiScheduler: Scheduler ): AppPresenter(uiScheduler, errorParser) { @@ -130,7 +132,7 @@ class PlayQueuePresenter( if (lastDeleteAction != null) { lastDeleteAction!! .doFinally { lastDeleteAction = null } - .subscribe({}, this::onDeleteCompositionError) + .justSubscribe(this::onDeleteCompositionError) } } @@ -138,7 +140,7 @@ class PlayQueuePresenter( playerInteractor.restoreDeletedItem().justSubscribe(this::onDefaultError) } - fun onPlayListForAddingCreated(playList: PlayList?) { + fun onPlayListForAddingCreated(playList: PlayList) { val compositionsToAdd = playQueue.map(PlayQueueItem::getComposition) playListsInteractor.addCompositionsToPlayList(compositionsToAdd, playList) .subscribeOnUi( diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/PlayQueueView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/PlayQueueView.kt index 859207baf..1bf41cc34 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/PlayQueueView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/PlayQueueView.kt @@ -2,6 +2,7 @@ package com.github.anrimian.musicplayer.ui.player_screen.queue import com.github.anrimian.filesync.models.state.file.FileSyncState import com.github.anrimian.musicplayer.domain.models.composition.Composition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.play_queue.PlayQueueItem import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand @@ -30,7 +31,7 @@ interface PlayQueueView : MvpView { fun showConfirmDeleteDialog(compositionsToDelete: List) @OneExecution - fun showDeleteCompositionMessage(compositionsToDelete: List) + fun showDeleteCompositionMessage(compositionsToDelete: List) @Skip fun notifyItemMoved(from: Int, to: Int) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/adapter/PlayQueueViewHolder.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/adapter/PlayQueueViewHolder.kt index 7df987289..c13000c29 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/adapter/PlayQueueViewHolder.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/player_screen/queue/adapter/PlayQueueViewHolder.kt @@ -5,6 +5,7 @@ import android.view.View import android.view.ViewGroup import com.github.anrimian.filesync.models.state.file.FileSyncState import com.github.anrimian.musicplayer.R +import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.play_queue.PlayQueueItem import com.github.anrimian.musicplayer.ui.common.format.wrappers.CompositionItemWrapper import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.mvp.MvpDiffAdapter @@ -23,7 +24,7 @@ class PlayQueueViewHolder( ) : MvpDiffAdapter.MvpViewHolder(inflater.inflate(R.layout.item_play_queue, parent, false)), DragListener, SwipeListener { - private val compositionItemWrapper: CompositionItemWrapper + private val compositionItemWrapper: CompositionItemWrapper private lateinit var playQueueItem: PlayQueueItem diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/choose/ChoosePlayListDialogFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/choose/ChoosePlayListDialogFragment.kt index d3ea71b05..b5072b9c6 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/choose/ChoosePlayListDialogFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/choose/ChoosePlayListDialogFragment.kt @@ -13,7 +13,6 @@ import com.github.anrimian.musicplayer.R import com.github.anrimian.musicplayer.databinding.DialogSelectPlayListBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.models.playlist.PlayList -import com.github.anrimian.musicplayer.domain.utils.functions.BiCallback import com.github.anrimian.musicplayer.ui.common.dialogs.AppBottomSheetDialog import com.github.anrimian.musicplayer.ui.common.dialogs.showConfirmDeleteDialog import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand @@ -23,7 +22,6 @@ import com.github.anrimian.musicplayer.ui.playlist_screens.create.CreatePlayList import com.github.anrimian.musicplayer.ui.playlist_screens.playlists.adapter.PlayListsAdapter import com.github.anrimian.musicplayer.ui.playlist_screens.rename.RenamePlayListDialogFragment import com.github.anrimian.musicplayer.ui.utils.AndroidUtils -import com.github.anrimian.musicplayer.ui.utils.OnCompleteListener import com.github.anrimian.musicplayer.ui.utils.ViewUtils import com.github.anrimian.musicplayer.ui.utils.fragments.safeShow import com.github.anrimian.musicplayer.ui.utils.views.bottom_sheet.SimpleBottomSheetCallback @@ -59,8 +57,8 @@ class ChoosePlayListDialogFragment : MvpBottomSheetDialogFragment(), ChoosePlayL private lateinit var adapter: PlayListsAdapter private lateinit var slideDelegate: SlideDelegate - private var onCompleteListener: OnCompleteListener? = null - private var complexCompleteListener: BiCallback? = null + private var onCompleteListener: ((PlayList) -> Unit)? = null + private var complexCompleteListener: ((PlayList, Bundle) -> Unit)? = null override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return AppBottomSheetDialog(requireContext(), theme) @@ -170,7 +168,7 @@ class ChoosePlayListDialogFragment : MvpBottomSheetDialogFragment(), ChoosePlayL } private fun onPlaylistMenuClicked(playList: PlayList, view: View) { - PopupMenuWindow.showPopup(view, R.menu.play_list_menu) { menuItem -> + PopupMenuWindow.showPopup(view, R.menu.play_list_short_menu) { menuItem -> when (menuItem.itemId) { R.id.menu_change_play_list_name -> { presenter.onChangePlayListNameButtonClicked(playList) @@ -182,28 +180,27 @@ class ChoosePlayListDialogFragment : MvpBottomSheetDialogFragment(), ChoosePlayL } } - fun setOnCompleteListener(onCompleteListener: OnCompleteListener) { + fun setOnCompleteListener(onCompleteListener: (PlayList) -> Unit) { this.onCompleteListener = onCompleteListener } - fun setComplexCompleteListener(complexCompleteListener: BiCallback) { + fun setComplexCompleteListener(complexCompleteListener: (PlayList, Bundle) -> Unit) { this.complexCompleteListener = complexCompleteListener } private fun onPlayListSelected(playList: PlayList) { - onCompleteListener?.onComplete(playList) + onCompleteListener?.invoke(playList) - complexCompleteListener?.call( + complexCompleteListener?.invoke( playList, - requireArguments().getBundle(Constants.Arguments.EXTRA_DATA_ARG) + requireArguments().getBundle(Constants.Arguments.EXTRA_DATA_ARG)!! ) dismissAllowingStateLoss() } private fun onCreatePlayListButtonClicked() { - val fragment = CreatePlayListDialogFragment() - fragment.safeShow(childFragmentManager) + CreatePlayListDialogFragment().safeShow(childFragmentManager) } private fun buildSlideDelegate(): SlideDelegate { diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/choose/ChoosePlayListPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/choose/ChoosePlayListPresenter.kt index bb82132dc..485f2afcd 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/choose/ChoosePlayListPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/choose/ChoosePlayListPresenter.kt @@ -7,11 +7,11 @@ import com.github.anrimian.musicplayer.ui.common.mvp.AppPresenter import io.reactivex.rxjava3.core.Scheduler class ChoosePlayListPresenter( - private val playListsInteractor: PlayListsInteractor, - uiScheduler: Scheduler, - errorParser: ErrorParser + private val playListsInteractor: PlayListsInteractor, + uiScheduler: Scheduler, + errorParser: ErrorParser ) : AppPresenter(uiScheduler, errorParser) { - + private var slideOffset = 0f override fun onFirstViewAttach() { @@ -36,7 +36,7 @@ class ChoosePlayListPresenter( fun onDeletePlayListDialogConfirmed(playList: PlayList) { playListsInteractor.deletePlayList(playList.id) - .subscribeOnUi({ onPlayListDeleted(playList) }, this::onPlayListDeletingError) + .subscribeOnUi({ onPlayListDeleted(playList) }, this::onPlayListDeletingError) } fun onChangePlayListNameButtonClicked(playList: PlayList) { @@ -54,8 +54,7 @@ class ChoosePlayListPresenter( private fun subscribeOnPlayLists() { viewState.showLoading() - playListsInteractor.playListsObservable - .unsafeSubscribeOnUi(this::onPlayListsReceived) + playListsInteractor.getPlayListsObservable().unsafeSubscribeOnUi(this::onPlayListsReceived) } private fun onPlayListsReceived(list: List) { diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/PlayListFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/PlayListFragment.kt index d12e3ac81..a7af2d705 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/PlayListFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/PlayListFragment.kt @@ -5,6 +5,7 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.MenuRes import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager @@ -12,9 +13,11 @@ import com.github.anrimian.filesync.models.state.file.FileSyncState import com.github.anrimian.musicplayer.Constants import com.github.anrimian.musicplayer.Constants.Tags import com.github.anrimian.musicplayer.R +import com.github.anrimian.musicplayer.data.models.folders.UriFileReference import com.github.anrimian.musicplayer.databinding.FragmentBaseFabListBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.models.composition.Composition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.playlist.PlayListItem import com.github.anrimian.musicplayer.domain.models.utils.ListPosition @@ -24,6 +27,8 @@ import com.github.anrimian.musicplayer.ui.common.dialogs.showConfirmDeleteDialog import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand import com.github.anrimian.musicplayer.ui.common.format.FormatUtils import com.github.anrimian.musicplayer.ui.common.format.MessagesUtils +import com.github.anrimian.musicplayer.ui.common.format.getExportedPlaylistsMessage +import com.github.anrimian.musicplayer.ui.common.format.showSnackbar import com.github.anrimian.musicplayer.ui.common.toolbar.AdvancedToolbar import com.github.anrimian.musicplayer.ui.common.view.ViewUtils import com.github.anrimian.musicplayer.ui.editor.common.DeleteErrorHandler @@ -36,8 +41,8 @@ import com.github.anrimian.musicplayer.ui.playlist_screens.rename.RenamePlayList import com.github.anrimian.musicplayer.ui.utils.AndroidUtils import com.github.anrimian.musicplayer.ui.utils.fragments.BackButtonListener import com.github.anrimian.musicplayer.ui.utils.fragments.DialogFragmentRunner -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigation +import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigationListener import com.github.anrimian.musicplayer.ui.utils.fragments.safeShow import com.github.anrimian.musicplayer.ui.utils.slidr.SlidrPanel import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.RecyclerViewUtils @@ -54,7 +59,8 @@ fun newPlayListFragment(playListId: Long) = PlayListFragment().apply { } -class PlayListFragment : MvpAppCompatFragment(), PlayListView, BackButtonListener, FragmentLayerListener { +class PlayListFragment : MvpAppCompatFragment(), PlayListView, BackButtonListener, + FragmentNavigationListener { private val presenter by moxyPresenter { Components.getPlayListComponent(getPlayListId()).playListPresenter() @@ -72,6 +78,14 @@ class PlayListFragment : MvpAppCompatFragment(), PlayListView, BackButtonListene private lateinit var touchHelperCallback: DragAndSwipeTouchHelperCallback + private val pickFolderLauncher = registerForActivityResult( + ActivityResultContracts.OpenDocumentTree() + ) { uri -> + if (uri != null) { + presenter.onFolderForExportSelected(UriFileReference(uri)) + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -133,7 +147,7 @@ class PlayListFragment : MvpAppCompatFragment(), PlayListView, BackButtonListene ) { fragment -> fragment.setOnCompleteListener(presenter::onPlayListToAddingSelected) } } - override fun onFragmentMovedOnTop() { + override fun onFragmentResumed() { requireActivity().findViewById(R.id.toolbar).setup { config -> config.setupSearch(presenter::onSearchTextChanged, presenter.getSearchText()) config.setupOptionsMenu(R.menu.play_list_toolbar_menu, this::onOptionsItemClicked) @@ -216,7 +230,7 @@ class PlayListFragment : MvpAppCompatFragment(), PlayListView, BackButtonListene } } - override fun showDeletedCompositionMessage(compositionsToDelete: List) { + override fun showDeletedCompositionMessage(compositionsToDelete: List) { val text = MessagesUtils.getDeleteCompleteMessage(requireActivity(), compositionsToDelete) MessagesUtils.makeSnackbar(viewBinding.listContainer, text, Snackbar.LENGTH_SHORT).show() } @@ -312,6 +326,12 @@ class PlayListFragment : MvpAppCompatFragment(), PlayListView, BackButtonListene adapter.showFileSyncStates(states) } + override fun showPlaylistExportSuccess(playlist: PlayList) { + viewBinding.listContainer.showSnackbar( + getExportedPlaylistsMessage(requireContext(), listOf(playlist)) + ) + } + private fun onItemMenuClicked(view: View, position: Int, playListItem: PlayListItem) { val composition = playListItem.composition showCompositionPopupMenu(view, R.menu.play_list_item_menu, composition) { item -> @@ -345,6 +365,7 @@ class PlayListFragment : MvpAppCompatFragment(), PlayListView, BackButtonListene when (item.itemId) { R.id.menu_search -> toolbar.setSearchModeEnabled(true) R.id.menu_change_play_list_name -> presenter.onChangePlayListNameButtonClicked() + R.id.menu_export_playlist -> pickFolderLauncher.launch(null) R.id.menu_delete_play_list -> presenter.onDeletePlayListButtonClicked() } } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/PlayListPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/PlayListPresenter.kt index 1dd4bdd82..260c8e99c 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/PlayListPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/PlayListPresenter.kt @@ -1,14 +1,17 @@ package com.github.anrimian.musicplayer.ui.playlist_screens.playlist import com.github.anrimian.filesync.SyncInteractor +import com.github.anrimian.musicplayer.domain.Constants import com.github.anrimian.musicplayer.domain.interactors.player.LibraryPlayerInteractor import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInteractor import com.github.anrimian.musicplayer.domain.interactors.settings.DisplaySettingsInteractor import com.github.anrimian.musicplayer.domain.models.composition.Composition +import com.github.anrimian.musicplayer.domain.models.folders.FileReference import com.github.anrimian.musicplayer.domain.models.play_queue.PlayQueueEvent import com.github.anrimian.musicplayer.domain.models.play_queue.PlayQueueItem import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.playlist.PlayListItem +import com.github.anrimian.musicplayer.domain.models.sync.FileKey import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.domain.utils.ListUtils import com.github.anrimian.musicplayer.domain.utils.TextUtils @@ -27,7 +30,7 @@ class PlayListPresenter( private val playerInteractor: LibraryPlayerInteractor, private val playListsInteractor: PlayListsInteractor, private val displaySettingsInteractor: DisplaySettingsInteractor, - private val syncInteractor: SyncInteractor<*, *, Long>, + private val syncInteractor: SyncInteractor, errorParser: ErrorParser, uiScheduler: Scheduler ) : AppPresenter(uiScheduler, errorParser) { @@ -65,15 +68,15 @@ class PlayListPresenter( } fun onStop(listPosition: ListPosition) { - playListsInteractor.saveListPosition(listPosition) + playListsInteractor.saveItemsListPosition(playListId, listPosition) } fun onItemIconClicked(position: Int) { - playerInteractor.startPlaying(items.map(PlayListItem::getComposition), position) + startPlaying(position) } fun onPlayAllButtonClicked() { - playerInteractor.startPlaying(items.map(PlayListItem::getComposition)) + startPlaying() } fun onDeleteCompositionButtonClicked(composition: Composition) { @@ -153,7 +156,7 @@ class PlayListPresenter( } fun onPlayActionSelected(position: Int) { - playerInteractor.startPlaying(items.map(PlayListItem::getComposition), position) + startPlaying(position) } fun onRestoreRemovedItemClicked() { @@ -184,6 +187,16 @@ class PlayListPresenter( playerInteractor.setRandomPlayingEnabled(!playerInteractor.isRandomPlayingEnabled()) } + fun onFolderForExportSelected(folder: FileReference) { + playList.call { playList -> + playListsInteractor.exportPlaylistsToFolder(listOf(playList), folder) + .subscribe( + { viewState.showPlaylistExportSuccess(playList) }, + viewState::showErrorMessage + ) + } + } + fun isCoversEnabled() = displaySettingsInteractor.isCoversEnabled() fun getSearchText() = searchText @@ -307,7 +320,7 @@ class PlayListPresenter( } else { viewState.showList() if (firstReceive) { - val listPosition = playListsInteractor.savedListPosition + val listPosition = playListsInteractor.getSavedItemsListPosition(playListId) if (listPosition != null) { viewState.restoreListPosition(listPosition) } @@ -329,4 +342,8 @@ class PlayListPresenter( .subscribeOnUi(viewState::showRandomMode, errorParser::logError) } + private fun startPlaying(position: Int = Constants.NO_POSITION) { + playerInteractor.startPlaying(items.map { item -> item.composition.id }, position) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/PlayListView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/PlayListView.kt index 258f39835..f485813fd 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/PlayListView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/PlayListView.kt @@ -2,6 +2,7 @@ package com.github.anrimian.musicplayer.ui.playlist_screens.playlist import com.github.anrimian.filesync.models.state.file.FileSyncState import com.github.anrimian.musicplayer.domain.models.composition.Composition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.playlist.PlayListItem import com.github.anrimian.musicplayer.domain.models.utils.ListPosition @@ -45,7 +46,7 @@ interface PlayListView : MvpView { fun showDeleteCompositionError(errorCommand: ErrorCommand) @OneExecution - fun showDeletedCompositionMessage(compositionsToDelete: List) + fun showDeletedCompositionMessage(compositionsToDelete: List) @OneExecution fun showSelectPlayListDialog() @@ -101,4 +102,7 @@ interface PlayListView : MvpView { @AddToEndSingle fun showFilesSyncState(states: Map) + @OneExecution + fun showPlaylistExportSuccess(playlist: PlayList) + } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/adapter/PlayListItemViewHolder.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/adapter/PlayListItemViewHolder.kt index dac9d43a7..38499d0b3 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/adapter/PlayListItemViewHolder.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlist/adapter/PlayListItemViewHolder.kt @@ -5,6 +5,7 @@ import android.view.View import android.view.ViewGroup import com.github.anrimian.filesync.models.state.file.FileSyncState import com.github.anrimian.musicplayer.R +import com.github.anrimian.musicplayer.domain.models.composition.Composition import com.github.anrimian.musicplayer.domain.models.playlist.PlayListItem import com.github.anrimian.musicplayer.ui.common.format.wrappers.CompositionItemWrapper import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.mvp.MvpDiffAdapter @@ -22,7 +23,7 @@ class PlayListItemViewHolder( ) : MvpDiffAdapter.MvpViewHolder(inflater.inflate(R.layout.item_storage_music, parent, false)), DragListener, SwipeListener { - private val compositionItemWrapper: CompositionItemWrapper + private val compositionItemWrapper: CompositionItemWrapper private lateinit var item: PlayListItem diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlists/PlayListsFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlists/PlayListsFragment.kt index 5328956c0..21c7fed65 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlists/PlayListsFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlists/PlayListsFragment.kt @@ -1,18 +1,26 @@ package com.github.anrimian.musicplayer.ui.playlist_screens.playlists +import android.app.AlertDialog +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater +import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts import androidx.recyclerview.widget.LinearLayoutManager +import com.github.anrimian.musicplayer.Constants +import com.github.anrimian.musicplayer.Constants.PLAYLIST_MIME_TYPE import com.github.anrimian.musicplayer.R +import com.github.anrimian.musicplayer.data.models.folders.UriFileReference import com.github.anrimian.musicplayer.databinding.FragmentPlayListsBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.ui.common.dialogs.showConfirmDeleteDialog import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand -import com.github.anrimian.musicplayer.ui.common.format.MessagesUtils +import com.github.anrimian.musicplayer.ui.common.format.getExportedPlaylistsMessage +import com.github.anrimian.musicplayer.ui.common.format.showSnackbar import com.github.anrimian.musicplayer.ui.common.menu.PopupMenuWindow import com.github.anrimian.musicplayer.ui.common.toolbar.AdvancedToolbar import com.github.anrimian.musicplayer.ui.common.view.ViewUtils @@ -20,54 +28,83 @@ import com.github.anrimian.musicplayer.ui.playlist_screens.create.CreatePlayList import com.github.anrimian.musicplayer.ui.playlist_screens.playlist.newPlayListFragment import com.github.anrimian.musicplayer.ui.playlist_screens.playlists.adapter.PlayListsAdapter import com.github.anrimian.musicplayer.ui.playlist_screens.rename.RenamePlayListDialogFragment -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigation +import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigationListener import com.github.anrimian.musicplayer.ui.utils.fragments.safeShow import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.RecyclerViewUtils -import com.google.android.material.snackbar.Snackbar import moxy.MvpAppCompatFragment import moxy.ktx.moxyPresenter -class PlayListsFragment : MvpAppCompatFragment(), PlayListsView, FragmentLayerListener { +fun newPlaylistsFragment(playlistUri: String? = null) = PlayListsFragment().apply { + arguments = Bundle().apply { + putString(Constants.Arguments.PLAYLIST_IMPORT_ARG, playlistUri) + } +} + +class PlayListsFragment : MvpAppCompatFragment(), PlayListsView, + FragmentNavigationListener { private val presenter by moxyPresenter { Components.getAppComponent().playListsPresenter() } - private lateinit var viewBinding: FragmentPlayListsBinding + private lateinit var binding: FragmentPlayListsBinding private lateinit var adapter: PlayListsAdapter private lateinit var layoutManager: LinearLayoutManager + private val pickFolderLauncher = registerForActivityResult( + ActivityResultContracts.OpenDocumentTree() + ) { uri -> + if (uri != null) { + presenter.onFolderForExportSelected(UriFileReference(uri)) + } + } + + private val pickPlaylistFileLauncher = registerForActivityResult( + ActivityResultContracts.GetContent() + ) { uri -> + if (uri != null) { + presenter.onPlaylistFileReceived(UriFileReference(uri)) + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ): View { - viewBinding = FragmentPlayListsBinding.inflate(inflater, container, false) - return viewBinding.root + binding = FragmentPlayListsBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) layoutManager = LinearLayoutManager(context) - viewBinding.rvPlayLists.layoutManager = layoutManager + binding.rvPlayLists.layoutManager = layoutManager - RecyclerViewUtils.attachFastScroller(viewBinding.rvPlayLists) + RecyclerViewUtils.attachFastScroller(binding.rvPlayLists) adapter = PlayListsAdapter( - viewBinding.rvPlayLists, + binding.rvPlayLists, this::goToPlayListScreen, this::onPlaylistMenuClicked ) - viewBinding.rvPlayLists.adapter = adapter - viewBinding.fab.setOnClickListener { onCreatePlayListButtonClicked() } + binding.rvPlayLists.adapter = adapter + binding.fab.setOnClickListener { onCreatePlayListButtonClicked() } + + val playlistImportUri = requireArguments().getString(Constants.Arguments.PLAYLIST_IMPORT_ARG) + if (playlistImportUri != null) { + requireArguments().remove(Constants.Arguments.PLAYLIST_IMPORT_ARG) + importPlaylist(playlistImportUri) + } } - override fun onFragmentMovedOnTop() { + override fun onFragmentResumed() { requireActivity().findViewById(R.id.toolbar).setup { config -> config.setTitle(R.string.play_lists) config.setSubtitle(null) + config.setupOptionsMenu(R.menu.play_lists_toolbar_menu, this::onOptionsItemClicked) } presenter.onFragmentMovedToTop() } @@ -78,15 +115,15 @@ class PlayListsFragment : MvpAppCompatFragment(), PlayListsView, FragmentLayerLi } override fun showEmptyList() { - viewBinding.progressStateView.showMessage(R.string.play_lists_on_device_not_found, false) + binding.progressStateView.showMessage(R.string.play_lists_on_device_not_found, false) } override fun showList() { - viewBinding.progressStateView.hideAll() + binding.progressStateView.hideAll() } override fun showLoading() { - viewBinding.progressStateView.showProgress() + binding.progressStateView.showProgress() } override fun updateList(lists: List) { @@ -104,31 +141,69 @@ class PlayListsFragment : MvpAppCompatFragment(), PlayListsView, FragmentLayerLi } override fun showPlayListDeleteSuccess(playList: PlayList) { - MessagesUtils.makeSnackbar( - viewBinding.listContainer, - getString(R.string.play_list_deleted, playList.name), - Snackbar.LENGTH_SHORT - ).show() + binding.listContainer.showSnackbar(getString(R.string.play_list_deleted, playList.name)) } override fun showDeletePlayListError(errorCommand: ErrorCommand) { - MessagesUtils.makeSnackbar( - viewBinding.listContainer, - getString(R.string.play_list_delete_error, errorCommand.message), - Snackbar.LENGTH_SHORT - ).show() + binding.listContainer.showSnackbar(getString(R.string.play_list_delete_error, errorCommand.message)) } override fun showEditPlayListNameDialog(playList: PlayList) { RenamePlayListDialogFragment.newInstance(playList.id).safeShow(childFragmentManager) } + override fun launchPickFolderScreen() { + pickFolderLauncher.launch(null) + } + + override fun showErrorMessage(errorCommand: ErrorCommand) { + binding.listContainer.showSnackbar(errorCommand.message) + } + + override fun showPlaylistExportSuccess(playlists: List) { + binding.listContainer.showSnackbar(getExportedPlaylistsMessage(requireContext(), playlists)) + } + + override fun launchPlayListScreen(playlistId: Long) { + FragmentNavigation.from(parentFragmentManager).addNewFragment(newPlayListFragment(playlistId)) + } + + override fun showOverwritePlaylistDialog() { + AlertDialog.Builder(requireContext()) + .setMessage(R.string.overwrite_playlist) + .setNegativeButton(android.R.string.cancel) { _, _ -> } + .setPositiveButton(android.R.string.ok) { _, _ -> presenter.onOverwritePlaylistConfirmed()} + .show() + } + + override fun showNotCompletelyImportedPlaylistDialog( + playlistId: Long, + notFoundFilesCount: Int, + ) { + val message = resources.getQuantityString( + R.plurals.playlist_import_partial_success, + notFoundFilesCount, + notFoundFilesCount + ) + AlertDialog.Builder(requireContext()) + .setMessage(message) + .setPositiveButton(android.R.string.ok) { _, _ -> launchPlayListScreen(playlistId) } + .show() + } + + fun importPlaylist(uriStr: String) { + presenter.onPlaylistFileReceived(UriFileReference(Uri.parse(uriStr))) + } + private fun onPlaylistMenuClicked(playList: PlayList, view: View) { PopupMenuWindow.showPopup(view, R.menu.play_list_menu) { menuItem -> when (menuItem.itemId) { R.id.menu_change_play_list_name -> { presenter.onChangePlayListNameButtonClicked(playList) } + R.id.menu_export_playlist -> { + presenter.onExportPlaylistClicked(playList) + } R.id.menu_delete_play_list -> { presenter.onDeletePlayListButtonClicked(playList) } @@ -136,13 +211,17 @@ class PlayListsFragment : MvpAppCompatFragment(), PlayListsView, FragmentLayerLi } } + private fun onOptionsItemClicked(item: MenuItem) { + when (item.itemId) { + R.id.menu_import_playlist -> pickPlaylistFileLauncher.launch(PLAYLIST_MIME_TYPE) + } + } + private fun onCreatePlayListButtonClicked() { - val fragment = CreatePlayListDialogFragment() - fragment.safeShow(childFragmentManager) + CreatePlayListDialogFragment().safeShow(childFragmentManager) } private fun goToPlayListScreen(playList: PlayList) { - FragmentNavigation.from(parentFragmentManager) - .addNewFragment(newPlayListFragment(playList.id)) + launchPlayListScreen(playList.id) } } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlists/PlayListsPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlists/PlayListsPresenter.kt index 01c96067d..d858b79ce 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlists/PlayListsPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlists/PlayListsPresenter.kt @@ -1,21 +1,27 @@ package com.github.anrimian.musicplayer.ui.playlist_screens.playlists +import com.github.anrimian.musicplayer.data.models.exceptions.PlayListAlreadyExistsException +import com.github.anrimian.musicplayer.data.models.exceptions.PlaylistNotCompletelyImportedException import com.github.anrimian.musicplayer.domain.interactors.playlists.PlayListsInteractor +import com.github.anrimian.musicplayer.domain.models.folders.FileReference import com.github.anrimian.musicplayer.domain.models.playlist.PlayList import com.github.anrimian.musicplayer.domain.models.utils.ListPosition import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser import com.github.anrimian.musicplayer.ui.common.mvp.AppPresenter import io.reactivex.rxjava3.core.Scheduler -import java.util.* class PlayListsPresenter( - private val playListsInteractor: PlayListsInteractor, - uiScheduler: Scheduler, - errorParser: ErrorParser -) : AppPresenter(uiScheduler, errorParser) { - + private val playListsInteractor: PlayListsInteractor, + uiScheduler: Scheduler, + errorParser: ErrorParser +): AppPresenter(uiScheduler, errorParser) { + private var playLists: List = ArrayList() - + + private val playlistsForExport = ArrayList() + + private var lastPlaylistForImport: FileReference? = null + override fun onFirstViewAttach() { super.onFirstViewAttach() subscribeOnPlayLists() @@ -31,7 +37,7 @@ class PlayListsPresenter( fun onDeletePlayListDialogConfirmed(playList: PlayList) { playListsInteractor.deletePlayList(playList.id) - .subscribeOnUi({ onPlayListDeleted(playList) }, this::onPlayListDeletingError) + .subscribe({ onPlayListDeleted(playList) }, viewState::showDeletePlayListError) } fun onFragmentMovedToTop() { @@ -42,9 +48,51 @@ class PlayListsPresenter( viewState.showEditPlayListNameDialog(playList) } - private fun onPlayListDeletingError(throwable: Throwable) { - val errorCommand = errorParser.parseError(throwable) - viewState.showDeletePlayListError(errorCommand) + fun onExportPlaylistClicked(playList: PlayList) { + playlistsForExport.clear() + playlistsForExport.add(playList) + viewState.launchPickFolderScreen() + } + + fun onFolderForExportSelected(folder: FileReference) { + playListsInteractor.exportPlaylistsToFolder(playlistsForExport, folder) + .subscribe(this::onPlaylistsExported, viewState::showErrorMessage) + } + + fun onPlaylistFileReceived(file: FileReference) { + lastPlaylistForImport = file + importPlaylistFile(file, false) + } + + fun onOverwritePlaylistConfirmed() { + lastPlaylistForImport?.let { ref -> importPlaylistFile(ref, true) } + } + + private fun importPlaylistFile(file: FileReference, overwriteExisting: Boolean) { + playListsInteractor.importPlaylistFile(file, overwriteExisting) + .justSubscribeOnUi(this::onPlaylistImported, this::onPlaylistFileImportError) + } + + private fun onPlaylistImported(playlistId: Long) { + viewState.launchPlayListScreen(playlistId) + } + + private fun onPlaylistFileImportError(throwable: Throwable) { + when (throwable) { + is PlayListAlreadyExistsException -> viewState.showOverwritePlaylistDialog() + is PlaylistNotCompletelyImportedException -> { + viewState.showNotCompletelyImportedPlaylistDialog( + throwable.playlistId, + throwable.notFoundFilesCount + ) + } + else -> viewState.showErrorMessage(errorParser.parseError(throwable)) + } + } + + private fun onPlaylistsExported() { + viewState.showPlaylistExportSuccess(playlistsForExport) + playlistsForExport.clear() } private fun onPlayListDeleted(playList: PlayList) { @@ -53,7 +101,7 @@ class PlayListsPresenter( private fun subscribeOnPlayLists() { viewState.showLoading() - playListsInteractor.playListsObservable.unsafeSubscribeOnUi(this::onPlayListsReceived) + playListsInteractor.getPlayListsObservable().unsafeSubscribeOnUi(this::onPlayListsReceived) } private fun onPlayListsReceived(list: List) { @@ -65,7 +113,7 @@ class PlayListsPresenter( } else { viewState.showList() if (firstReceive) { - val listPosition = playListsInteractor.savedListPosition + val listPosition = playListsInteractor.getSavedListPosition() if (listPosition != null) { viewState.restoreListPosition(listPosition) } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlists/PlayListsView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlists/PlayListsView.kt index 8d6195763..b34a2eeab 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlists/PlayListsView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/playlist_screens/playlists/PlayListsView.kt @@ -40,4 +40,21 @@ interface PlayListsView : MvpView { @OneExecution fun restoreListPosition(listPosition: ListPosition) + @OneExecution + fun launchPickFolderScreen() + + @OneExecution + fun showErrorMessage(errorCommand: ErrorCommand) + + @OneExecution + fun showPlaylistExportSuccess(playlists: List) + + @OneExecution + fun launchPlayListScreen(playlistId: Long) + + @OneExecution + fun showOverwritePlaylistDialog() + + @OneExecution + fun showNotCompletelyImportedPlaylistDialog(playlistId: Long, notFoundFilesCount: Int) } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/SettingsFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/SettingsFragment.kt index 154f22b04..c5bb20bbd 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/SettingsFragment.kt @@ -15,15 +15,16 @@ import com.github.anrimian.musicplayer.ui.settings.headset.HeadsetSettingsFragme import com.github.anrimian.musicplayer.ui.settings.library.LibrarySettingsFragment import com.github.anrimian.musicplayer.ui.settings.player.PlayerSettingsFragment import com.github.anrimian.musicplayer.ui.settings.themes.ThemeSettingsFragment -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigation +import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigationListener import com.github.anrimian.musicplayer.ui.utils.slidr.SlidrPanel import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers /** * Created on 19.10.2017. */ -class SettingsFragment : Fragment(), FragmentLayerListener { +class SettingsFragment : Fragment(), + FragmentNavigationListener { private lateinit var viewBinding: FragmentSettingsBinding private lateinit var navigation: FragmentNavigation @@ -51,7 +52,7 @@ class SettingsFragment : Fragment(), FragmentLayerListener { SlidrPanel.simpleSwipeBack(viewBinding.flContainer, this, toolbar::onStackFragmentSlided) } - override fun onFragmentMovedOnTop() { + override fun onFragmentResumed() { val toolbar = requireActivity().findViewById(R.id.toolbar) toolbar.setTitle(R.string.settings) toolbar.subtitle = null diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/display/DisplaySettingsFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/display/DisplaySettingsFragment.kt index 822c3b9e8..c7d1057c5 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/display/DisplaySettingsFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/display/DisplaySettingsFragment.kt @@ -41,10 +41,17 @@ class DisplaySettingsFragment : MvpAppCompatFragment(), DisplaySettingsView { SlidrPanel.simpleSwipeBack(viewBinding.nsvContainer, this, toolbar::onStackFragmentSlided) - viewBinding.cbColoredNotification.visibility = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) VISIBLE else GONE + viewBinding.cbColoredNotification.visibility = if ( + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + && Build.VERSION.SDK_INT < Build.VERSION_CODES.S + ) VISIBLE else GONE + + viewBinding.cbShowCoverStubInNotification.visibility = if ( + Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU + ) VISIBLE else GONE onCheckChanged(viewBinding.cbUseFileName, presenter::onFileNameChecked) + onCheckChanged(viewBinding.cbPlayerScreensSwipe, presenter::onSwipePlayerScreensChecked) onCheckChanged(viewBinding.cbCovers, presenter::onCoversChecked) onCheckChanged(viewBinding.cbCoversInNotification, presenter::onCoversInNotificationChecked) onCheckChanged(viewBinding.cbColoredNotification, presenter::onColoredNotificationChecked) @@ -58,6 +65,10 @@ class DisplaySettingsFragment : MvpAppCompatFragment(), DisplaySettingsView { } } + override fun showPlayerScreensSwipeEnabled(enabled: Boolean) { + setChecked(viewBinding.cbPlayerScreensSwipe, enabled) + } + override fun showFileNameEnabled(enabled: Boolean) { setChecked(viewBinding.cbUseFileName, enabled) } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/display/DisplaySettingsPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/display/DisplaySettingsPresenter.kt index cc877f7e6..2af63c4e8 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/display/DisplaySettingsPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/display/DisplaySettingsPresenter.kt @@ -13,6 +13,7 @@ class DisplaySettingsPresenter(private val interactor: DisplaySettingsInteractor override fun onFirstViewAttach() { super.onFirstViewAttach() + viewState.showPlayerScreensSwipeEnabled(interactor.isPlayerScreensSwipeEnabled()) viewState.showFileNameEnabled(interactor.isDisplayFileNameEnabled()) viewState.showCoversChecked(interactor.isCoversEnabled()) viewState.showCoversInNotificationChecked(interactor.isCoversInNotificationEnabled()) @@ -28,6 +29,11 @@ class DisplaySettingsPresenter(private val interactor: DisplaySettingsInteractor interactor.setCoversEnabled(checked) } + fun onSwipePlayerScreensChecked(checked: Boolean) { + viewState.showPlayerScreensSwipeEnabled(checked) + interactor.setPlayerScreensSwipeEnabled(checked) + } + fun onFileNameChecked(checked: Boolean) { viewState.showFileNameEnabled(checked) interactor.setDisplayFileName(checked) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/display/DisplaySettingsView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/display/DisplaySettingsView.kt index 3500e86c9..0c9f5556d 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/display/DisplaySettingsView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/display/DisplaySettingsView.kt @@ -5,6 +5,9 @@ import moxy.viewstate.strategy.alias.AddToEndSingle interface DisplaySettingsView : MvpView { + @AddToEndSingle + fun showPlayerScreensSwipeEnabled(enabled: Boolean) + @AddToEndSingle fun showFileNameEnabled(enabled: Boolean) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/folders/ExcludedFoldersFragment.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/folders/ExcludedFoldersFragment.java deleted file mode 100644 index a456ae555..000000000 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/folders/ExcludedFoldersFragment.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.github.anrimian.musicplayer.ui.settings.folders; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.github.anrimian.musicplayer.R; -import com.github.anrimian.musicplayer.databinding.FragmentExcludedFoldersBinding; -import com.github.anrimian.musicplayer.di.Components; -import com.github.anrimian.musicplayer.domain.models.folders.IgnoredFolder; -import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand; -import com.github.anrimian.musicplayer.ui.common.format.MessagesUtils; -import com.github.anrimian.musicplayer.ui.common.toolbar.AdvancedToolbar; -import com.github.anrimian.musicplayer.ui.settings.folders.view.ExcludedFolderAdapter; -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener; -import com.github.anrimian.musicplayer.ui.utils.slidr.SlidrPanel; -import com.google.android.material.snackbar.Snackbar; - -import java.util.List; - -import moxy.MvpAppCompatFragment; -import moxy.presenter.InjectPresenter; -import moxy.presenter.ProvidePresenter; - -public class ExcludedFoldersFragment extends MvpAppCompatFragment implements ExcludedFoldersView, FragmentLayerListener { - - @InjectPresenter - ExcludedFoldersPresenter presenter; - - private FragmentExcludedFoldersBinding viewBinding; - private CoordinatorLayout clContainer; - private RecyclerView recyclerView; - - private ExcludedFolderAdapter adapter; - - @ProvidePresenter - ExcludedFoldersPresenter providePresenter() { - return Components.getLibraryComponent().excludedFoldersPresenter(); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - viewBinding = FragmentExcludedFoldersBinding.inflate(inflater, container, false); - clContainer = viewBinding.clContainer; - recyclerView = viewBinding.rvExcludedFolders; - return viewBinding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - AdvancedToolbar toolbar = requireActivity().findViewById(R.id.toolbar); - toolbar.setTitle(R.string.excluded_folders); - toolbar.setSubtitle(null); - toolbar.setTitleClickListener(null); - - adapter = new ExcludedFolderAdapter(recyclerView, presenter::onDeleteFolderClicked); - recyclerView.setAdapter(adapter); - recyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); - - SlidrPanel.simpleSwipeBack(clContainer, this, toolbar::onStackFragmentSlided); - } - - @Override - public void onFragmentMovedOnTop() { - AdvancedToolbar toolbar = requireActivity().findViewById(R.id.toolbar); - toolbar.clearOptionsMenu(); - } - - @Override - public void showListState() { - viewBinding.progressStateView.hideAll(); - } - - @Override - public void showEmptyListState() { - viewBinding.progressStateView.showMessage(R.string.no_excluded_folders); - } - - @Override - public void showErrorState(ErrorCommand errorCommand) { - viewBinding.progressStateView.showMessage(errorCommand.getMessage(), false); - } - - @Override - public void showExcludedFoldersList(List folders) { - adapter.submitList(folders); - } - - @Override - public void showRemovedFolderMessage(IgnoredFolder folder) { - MessagesUtils.makeSnackbar(clContainer, R.string.ignored_folder_removed, Snackbar.LENGTH_LONG) - .setAction(R.string.cancel, presenter::onRestoreRemovedFolderClicked) - .show(); - } - - @Override - public void showErrorMessage(ErrorCommand errorCommand) { - MessagesUtils.makeSnackbar(clContainer, errorCommand.getMessage(), Snackbar.LENGTH_SHORT).show(); - } -} diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/folders/ExcludedFoldersView.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/folders/ExcludedFoldersView.java deleted file mode 100644 index 3ec07cc79..000000000 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/folders/ExcludedFoldersView.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.github.anrimian.musicplayer.ui.settings.folders; - -import com.github.anrimian.musicplayer.domain.models.folders.IgnoredFolder; -import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand; - -import java.util.List; - -import moxy.MvpView; -import moxy.viewstate.strategy.AddToEndSingleTagStrategy; -import moxy.viewstate.strategy.StateStrategyType; -import moxy.viewstate.strategy.alias.AddToEndSingle; -import moxy.viewstate.strategy.alias.OneExecution; - -public interface ExcludedFoldersView extends MvpView { - - String LIST_STATE = "list_state"; - - @StateStrategyType(value = AddToEndSingleTagStrategy.class, tag = LIST_STATE) - void showListState(); - - @StateStrategyType(value = AddToEndSingleTagStrategy.class, tag = LIST_STATE) - void showEmptyListState(); - - @StateStrategyType(value = AddToEndSingleTagStrategy.class, tag = LIST_STATE) - void showErrorState(ErrorCommand errorCommand); - - @AddToEndSingle - void showExcludedFoldersList(List folders); - - @OneExecution - void showRemovedFolderMessage(IgnoredFolder folder); - - @OneExecution - void showErrorMessage(ErrorCommand errorCommand); -} diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/headset/HeadsetSettingsFragment.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/headset/HeadsetSettingsFragment.java deleted file mode 100644 index b68261291..000000000 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/headset/HeadsetSettingsFragment.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.github.anrimian.musicplayer.ui.settings.headset; - -import static com.github.anrimian.musicplayer.ui.utils.ViewUtils.onCheckChanged; -import static com.github.anrimian.musicplayer.ui.utils.ViewUtils.setChecked; - -import android.Manifest; -import android.annotation.TargetApi; -import android.os.Build; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.ActivityCompat; -import androidx.fragment.app.Fragment; - -import com.github.anrimian.musicplayer.R; -import com.github.anrimian.musicplayer.databinding.FragmentSettingsHeadsetBinding; -import com.github.anrimian.musicplayer.infrastructure.receivers.BluetoothConnectionReceiver; -import com.github.anrimian.musicplayer.ui.common.snackbars.AppSnackbar; -import com.github.anrimian.musicplayer.ui.common.toolbar.AdvancedToolbar; -import com.github.anrimian.musicplayer.ui.utils.AndroidUtilsKt; -import com.github.anrimian.musicplayer.ui.utils.PermissionRequester; -import com.github.anrimian.musicplayer.ui.utils.slidr.SlidrPanel; -import com.google.android.material.snackbar.Snackbar; - -public class HeadsetSettingsFragment extends Fragment { - - private FragmentSettingsHeadsetBinding viewBinding; - - private final PermissionRequester permissionRequester = new PermissionRequester(this, - this::onBluetoothConnectPermissionResult); - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - viewBinding = FragmentSettingsHeadsetBinding.inflate(inflater, container, false); - return viewBinding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - AdvancedToolbar toolbar = requireActivity().findViewById(R.id.toolbar); - toolbar.setTitle(R.string.settings); - toolbar.setSubtitle(R.string.headset); - toolbar.setTitleClickListener(null); - - SlidrPanel.simpleSwipeBack(viewBinding.clContainer, this, toolbar::onStackFragmentSlided); - - onCheckChanged(viewBinding.cbPlayOnConnect, this::onPlayOnConnectChecked); - - setChecked(viewBinding.cbPlayOnConnect, BluetoothConnectionReceiver.isEnabled(requireContext())); - } - - private void onPlayOnConnectChecked(boolean checked) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && checked) { - permissionRequester.request(Manifest.permission.BLUETOOTH_CONNECT); - return; - } - BluetoothConnectionReceiver.setEnabled(requireContext(), checked); - } - - @TargetApi(Build.VERSION_CODES.S) - private void onBluetoothConnectPermissionResult(boolean granted) { - BluetoothConnectionReceiver.setEnabled(requireContext(), granted); - setChecked(viewBinding.cbPlayOnConnect, granted); - if (!granted) { - if (!ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), Manifest.permission.BLUETOOTH_CONNECT)) { - AppSnackbar.make(viewBinding.clContainer, "Bluetooth connect permission required") - .setAction("Open app settings", () -> AndroidUtilsKt.startAppSettings(requireActivity())) - .duration(Snackbar.LENGTH_INDEFINITE) - .show(); - } - } - } -} diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/library/LibrarySettingsFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/library/LibrarySettingsFragment.kt index 2082c87dd..de901a76f 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/library/LibrarySettingsFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/library/LibrarySettingsFragment.kt @@ -8,13 +8,13 @@ import android.view.ViewGroup import com.github.anrimian.musicplayer.R import com.github.anrimian.musicplayer.databinding.FragmentLibrarySettingsBinding import com.github.anrimian.musicplayer.di.Components -import com.github.anrimian.musicplayer.ui.common.dialogs.DialogUtils +import com.github.anrimian.musicplayer.ui.common.dialogs.showNumberPickerDialog import com.github.anrimian.musicplayer.ui.common.toolbar.AdvancedToolbar import com.github.anrimian.musicplayer.ui.settings.folders.ExcludedFoldersFragment import com.github.anrimian.musicplayer.ui.utils.ViewUtils import com.github.anrimian.musicplayer.ui.utils.ViewUtils.onCheckChanged -import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentLayerListener import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigation +import com.github.anrimian.musicplayer.ui.utils.fragments.navigation.FragmentNavigationListener import com.github.anrimian.musicplayer.ui.utils.slidr.SlidrPanel import moxy.MvpAppCompatFragment import moxy.ktx.moxyPresenter @@ -22,11 +22,12 @@ import moxy.ktx.moxyPresenter /** * Created on 19.10.2017. */ -class LibrarySettingsFragment : MvpAppCompatFragment(), FragmentLayerListener, LibrarySettingsView { +class LibrarySettingsFragment : MvpAppCompatFragment(), + FragmentNavigationListener, LibrarySettingsView { private val presenter by moxyPresenter { Components.getSettingsComponent().librarySettingsPresenter() } - private lateinit var viewBinding: FragmentLibrarySettingsBinding + private lateinit var binding: FragmentLibrarySettingsBinding private lateinit var navigation: FragmentNavigation @@ -35,8 +36,8 @@ class LibrarySettingsFragment : MvpAppCompatFragment(), FragmentLayerListener, L container: ViewGroup?, savedInstanceState: Bundle? ): View { - viewBinding = FragmentLibrarySettingsBinding.inflate(inflater, container, false) - return viewBinding.root + binding = FragmentLibrarySettingsBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -45,25 +46,26 @@ class LibrarySettingsFragment : MvpAppCompatFragment(), FragmentLayerListener, L navigation = FragmentNavigation.from(parentFragmentManager) - viewBinding.tvExcludedFolders.setOnClickListener { + binding.tvExcludedFolders.setOnClickListener { navigation.addNewFragment(ExcludedFoldersFragment()) } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - viewBinding.cbDoNotShowDeleteDialog.visibility = View.GONE + binding.cbDoNotShowDeleteDialog.visibility = View.GONE } - onCheckChanged(viewBinding.cbDoNotShowDeleteDialog, presenter::doNotAppConfirmDialogChecked) - onCheckChanged(viewBinding.cbShowAllAudioFiles, presenter::onShowAllAudioFilesChecked) + onCheckChanged(binding.cbDoNotShowDeleteDialog, presenter::doNotAppConfirmDialogChecked) + onCheckChanged(binding.cbShowAllAudioFiles, presenter::onShowAllAudioFilesChecked) + onCheckChanged(binding.cbPlaylistInsertStart, presenter::onPlaylistInsertStartChecked) - viewBinding.flAudioMinDurationClickableArea.setOnClickListener { + binding.flAudioMinDurationClickableArea.setOnClickListener { presenter.onSelectMinDurationClicked() } - SlidrPanel.simpleSwipeBack(viewBinding.flContainer, this, toolbar::onStackFragmentSlided) + SlidrPanel.simpleSwipeBack(binding.nsvContainer, this, toolbar::onStackFragmentSlided) } - override fun onFragmentMovedOnTop() { + override fun onFragmentResumed() { val toolbar: AdvancedToolbar = requireActivity().findViewById(R.id.toolbar) toolbar.setTitle(R.string.settings) toolbar.setSubtitle(R.string.library) @@ -71,27 +73,31 @@ class LibrarySettingsFragment : MvpAppCompatFragment(), FragmentLayerListener, L } override fun showAppConfirmDeleteDialogEnabled(enabled: Boolean) { - ViewUtils.setChecked(viewBinding.cbDoNotShowDeleteDialog, !enabled) + ViewUtils.setChecked(binding.cbDoNotShowDeleteDialog, !enabled) } override fun showAllAudioFilesEnabled(enabled: Boolean) { - ViewUtils.setChecked(viewBinding.cbShowAllAudioFiles, enabled) + ViewUtils.setChecked(binding.cbShowAllAudioFiles, enabled) } override fun showAudioFileMinDurationMillis(millis: Long) { val seconds = (millis/1000L).toInt() - viewBinding.tvAudioMinDurationValue.text = getString( + binding.tvAudioMinDurationValue.text = getString( R.string.with_duration_less_than, resources.getQuantityString(R.plurals.seconds_template, seconds, seconds) ) } + override fun showPlaylistInsertStartEnabled(enabled: Boolean) { + ViewUtils.setChecked(binding.cbPlaylistInsertStart, enabled) + } + override fun showSelectMinAudioDurationDialog(currentValue: Long) { - DialogUtils.showNumberPickerDialog( + showNumberPickerDialog( requireContext(), 0, 60, - (currentValue / 1000L).toInt() + currentValue / 1000L ) { value -> presenter.onAudioFileMinDurationMillisPicked(value * 1000L) } } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/library/LibrarySettingsPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/library/LibrarySettingsPresenter.kt index fe0934edc..1e684dfa1 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/library/LibrarySettingsPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/library/LibrarySettingsPresenter.kt @@ -5,36 +5,43 @@ import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser import com.github.anrimian.musicplayer.ui.common.mvp.AppPresenter import io.reactivex.rxjava3.core.Scheduler -class LibrarySettingsPresenter(private val librarySettingsInteractor: LibrarySettingsInteractor, - uiScheduler: Scheduler, - errorParser: ErrorParser +class LibrarySettingsPresenter( + private val interactor: LibrarySettingsInteractor, + uiScheduler: Scheduler, + errorParser: ErrorParser ): AppPresenter(uiScheduler, errorParser) { override fun onFirstViewAttach() { super.onFirstViewAttach() - librarySettingsInteractor.getAppConfirmDeleteDialogEnabledObservable() + interactor.getAppConfirmDeleteDialogEnabledObservable() .unsafeSubscribeOnUi(viewState::showAppConfirmDeleteDialogEnabled) - librarySettingsInteractor.getShowAllAudioFilesEnabledObservable() + interactor.getShowAllAudioFilesEnabledObservable() .unsafeSubscribeOnUi(viewState::showAllAudioFilesEnabled) - librarySettingsInteractor.geAudioFileMinDurationMillisObservable() + interactor.geAudioFileMinDurationMillisObservable() .unsafeSubscribeOnUi(viewState::showAudioFileMinDurationMillis) + viewState.showPlaylistInsertStartEnabled(interactor.isPlaylistInsertStartEnabled()) } fun doNotAppConfirmDialogChecked(isChecked: Boolean) { - librarySettingsInteractor.setAppConfirmDeleteDialogEnabled(!isChecked) + interactor.setAppConfirmDeleteDialogEnabled(!isChecked) } fun onShowAllAudioFilesChecked(isChecked: Boolean) { - librarySettingsInteractor.setShowAllAudioFilesEnabled(isChecked) + interactor.setShowAllAudioFilesEnabled(isChecked) } fun onAudioFileMinDurationMillisPicked(millis: Long) { - librarySettingsInteractor.setAudioFileMinDurationMillis(millis) + interactor.setAudioFileMinDurationMillis(millis) + } + + fun onPlaylistInsertStartChecked(isChecked: Boolean) { + interactor.setPlaylistInsertStartEnabled(isChecked) + viewState.showPlaylistInsertStartEnabled(isChecked) } fun onSelectMinDurationClicked() { viewState.showSelectMinAudioDurationDialog( - librarySettingsInteractor.getAudioFileMinDurationMillis() + interactor.getAudioFileMinDurationMillis() ) } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/library/LibrarySettingsView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/library/LibrarySettingsView.kt index 5975bbb2c..50056cd1a 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/library/LibrarySettingsView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/library/LibrarySettingsView.kt @@ -15,6 +15,9 @@ interface LibrarySettingsView : MvpView { @AddToEndSingle fun showAudioFileMinDurationMillis(millis: Long) + @AddToEndSingle + fun showPlaylistInsertStartEnabled(enabled: Boolean) + @Skip fun showSelectMinAudioDurationDialog(currentValue: Long) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/player/PlayerSettingsFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/player/PlayerSettingsFragment.kt index 9f91335a6..cce7c4ab0 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/player/PlayerSettingsFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/player/PlayerSettingsFragment.kt @@ -11,6 +11,9 @@ import com.github.anrimian.musicplayer.data.controllers.music.equalizer.Equalize import com.github.anrimian.musicplayer.databinding.FragmentSettingsPlayerBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.models.player.SoundBalance +import com.github.anrimian.musicplayer.domain.utils.millisToMinutes +import com.github.anrimian.musicplayer.domain.utils.minutesToMillis +import com.github.anrimian.musicplayer.ui.common.dialogs.showNumberPickerDialog import com.github.anrimian.musicplayer.ui.common.dialogs.showSoundBalanceSelectorDialog import com.github.anrimian.musicplayer.ui.common.format.getMediaPlayerName import com.github.anrimian.musicplayer.ui.common.toolbar.AdvancedToolbar @@ -53,6 +56,7 @@ class PlayerSettingsFragment : MvpAppCompatFragment(), PlayerSettingsView { viewBinding.flEqualizerClickableArea.setOnClickListener { showEqualizerDialog() } viewBinding.flMediaPlayersClickableArea.setOnClickListener { showMediaPlayersSettingScreen() } + viewBinding.flKeepNotificationClickableArea.setOnClickListener { presenter.onSelectKeepNotificationTimeClicked() } viewBinding.flSoundBalanceClickableArea.setOnClickListener { presenter.onSoundBalanceClicked() } SlidrPanel.simpleSwipeBack(viewBinding.nsvContainer, this, toolbar::onStackFragmentSlided) @@ -85,6 +89,24 @@ class PlayerSettingsFragment : MvpAppCompatFragment(), PlayerSettingsView { viewBinding.tvEqualizerState.setText(getEqualizerTypeDescription(type)) } + override fun showKeepNotificationTime(millis: Long) { + val minutes = millis.millisToMinutes().toInt() + viewBinding.tvKeepNotificationTimeValue.text = resources.getQuantityString( + R.plurals.for_at_least_minutes, + minutes, + minutes + ) + } + + override fun showSelectKeepNotificationTimeDialog(currentValue: Long) { + showNumberPickerDialog( + requireContext(), + 0, + 15, + currentValue.millisToMinutes() + ) { value -> presenter.onKeepNotificationTimeSelected(value.minutesToMillis()) } + } + override fun showEnabledMediaPlayers(players: IntArray) { val sb = StringBuilder() players.forEachIndexed { index, id -> diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/player/PlayerSettingsPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/player/PlayerSettingsPresenter.kt index c47018938..96a52dea3 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/player/PlayerSettingsPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/player/PlayerSettingsPresenter.kt @@ -6,9 +6,10 @@ import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser import com.github.anrimian.musicplayer.ui.common.mvp.AppPresenter import io.reactivex.rxjava3.core.Scheduler -class PlayerSettingsPresenter(private val interactor: PlayerSettingsInteractor, - uiScheduler: Scheduler, - errorParser: ErrorParser +class PlayerSettingsPresenter( + private val interactor: PlayerSettingsInteractor, + uiScheduler: Scheduler, + errorParser: ErrorParser ) : AppPresenter(uiScheduler, errorParser) { override fun onFirstViewAttach() { @@ -19,6 +20,7 @@ class PlayerSettingsPresenter(private val interactor: PlayerSettingsInteractor, viewState.showPauseOnAudioFocusLossEnabled(interactor.isPauseOnAudioFocusLossEnabled()) viewState.showPauseOnZeroVolumeLevelEnabled(interactor.isPauseOnZeroVolumeLevelEnabled()) viewState.showSoundBalance(interactor.getSoundBalance()) + viewState.showKeepNotificationTime(interactor.getKeepNotificationTime()) viewState.showEnabledMediaPlayers(interactor.getEnabledMediaPlayers()) subscribeOnSelectedEqualizer() } @@ -38,6 +40,12 @@ class PlayerSettingsPresenter(private val interactor: PlayerSettingsInteractor, interactor.setPauseOnZeroVolumeLevelEnabled(checked) } + fun onSelectKeepNotificationTimeClicked() { + viewState.showSelectKeepNotificationTimeDialog( + interactor.getKeepNotificationTime() + ) + } + fun onSoundBalanceClicked() { viewState.showSoundBalanceDialog(interactor.getSoundBalance()) } @@ -57,6 +65,11 @@ class PlayerSettingsPresenter(private val interactor: PlayerSettingsInteractor, interactor.saveSoundBalance(soundBalance) } + fun onKeepNotificationTimeSelected(millis: Long) { + viewState.showKeepNotificationTime(millis) + interactor.setKeepNotificationTime(millis) + } + fun onEnabledMediaPlayersSelected(mediaPlayers: IntArray) { viewState.showEnabledMediaPlayers(mediaPlayers) interactor.setEnabledMediaPlayers(mediaPlayers) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/player/PlayerSettingsView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/player/PlayerSettingsView.kt index f35025d1d..a602570c8 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/player/PlayerSettingsView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/settings/player/PlayerSettingsView.kt @@ -22,6 +22,12 @@ interface PlayerSettingsView : MvpView { @AddToEndSingle fun showSelectedEqualizerType(type: Int) + @AddToEndSingle + fun showKeepNotificationTime(millis: Long) + + @Skip + fun showSelectKeepNotificationTimeDialog(currentValue: Long) + @AddToEndSingle fun showEnabledMediaPlayers(players: IntArray) diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/sleep_timer/SleepTimerDialogFragment.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/sleep_timer/SleepTimerDialogFragment.kt index 329272020..d25c2d12c 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/sleep_timer/SleepTimerDialogFragment.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/sleep_timer/SleepTimerDialogFragment.kt @@ -8,7 +8,9 @@ import com.github.anrimian.musicplayer.R import com.github.anrimian.musicplayer.databinding.DialogSleepTimerBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.interactors.sleep_timer.SleepTimerState -import com.github.anrimian.musicplayer.domain.interactors.sleep_timer.SleepTimerState.* +import com.github.anrimian.musicplayer.domain.interactors.sleep_timer.SleepTimerState.DISABLED +import com.github.anrimian.musicplayer.domain.interactors.sleep_timer.SleepTimerState.ENABLED +import com.github.anrimian.musicplayer.domain.interactors.sleep_timer.SleepTimerState.PAUSED import com.github.anrimian.musicplayer.ui.common.format.FormatUtils import com.github.anrimian.musicplayer.ui.sleep_timer.view.TimePickerWrapper import moxy.MvpAppCompatDialogFragment @@ -18,26 +20,26 @@ class SleepTimerDialogFragment : MvpAppCompatDialogFragment(), SleepTimerView { private val presenter by moxyPresenter { Components.getAppComponent().sleepTimerPresenter() } - private lateinit var viewBinding: DialogSleepTimerBinding + private lateinit var binding: DialogSleepTimerBinding private lateinit var timePickerWrapper: TimePickerWrapper override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - viewBinding = DialogSleepTimerBinding.inflate(LayoutInflater.from(context)) + binding = DialogSleepTimerBinding.inflate(LayoutInflater.from(context)) timePickerWrapper = TimePickerWrapper( - viewBinding.etSeconds, - viewBinding.etMinutes, - viewBinding.etHours, - presenter::onSleepTimerTimeChanged + binding.etSeconds, + binding.etMinutes, + binding.etHours, + presenter::onSleepTimerTimeChanged ) - viewBinding.btnClose.setOnClickListener { dismissAllowingStateLoss() } + binding.btnClose.setOnClickListener { dismissAllowingStateLoss() } val dialog = AlertDialog.Builder(activity) - .setTitle(R.string.sleep_timer) - .setView(viewBinding.root) - .create() + .setTitle(R.string.sleep_timer) + .setView(binding.root) + .create() dialog.show() return dialog @@ -53,46 +55,46 @@ class SleepTimerDialogFragment : MvpAppCompatDialogFragment(), SleepTimerView { setPickerEnabled(false) setTimerEnabled(true) - viewBinding.btnAction.setText(R.string.stop) - viewBinding.btnAction.setOnClickListener { presenter.onStopClicked() } + binding.btnAction.setText(R.string.stop) + binding.btnAction.setOnClickListener { presenter.onStopClicked() } } DISABLED -> { setPickerEnabled(true) setTimerEnabled(false) - viewBinding.btnAction.setText(R.string.start) - viewBinding.btnAction.setOnClickListener { presenter.onStartClicked() } + binding.btnAction.setText(R.string.start) + binding.btnAction.setOnClickListener { presenter.onStartClicked() } } PAUSED -> { setPickerEnabled(false) setTimerEnabled(true) - viewBinding.btnAction.setText(R.string.resume) - viewBinding.btnAction.setOnClickListener { presenter.onResumeClicked() } + binding.btnAction.setText(R.string.resume) + binding.btnAction.setOnClickListener { presenter.onResumeClicked() } } } } override fun showRemainingTimeMillis(millis: Long) { - viewBinding.tvRemainingTime.text = FormatUtils.formatMilliseconds(millis, false) + binding.tvRemainingTime.text = FormatUtils.formatMilliseconds(millis, false) } private fun setPickerVisibility(visibility: Int) { timePickerWrapper.setVisibility(visibility) - viewBinding.tvHoursDivider.visibility = visibility - viewBinding.tvMinutesDivider.visibility = visibility + binding.tvHoursDivider.visibility = visibility + binding.tvMinutesDivider.visibility = visibility } private fun setPickerEnabled(enabled: Boolean) { timePickerWrapper.setEnabled(enabled) - viewBinding.tvHoursDivider.isEnabled = enabled - viewBinding.tvMinutesDivider.isEnabled = enabled + binding.tvHoursDivider.isEnabled = enabled + binding.tvMinutesDivider.isEnabled = enabled } private fun setTimerEnabled(enabled: Boolean) { - viewBinding.tvRemainingTimeMessage.isEnabled = enabled - viewBinding.tvRemainingTime.isEnabled = enabled + binding.tvRemainingTimeMessage.isEnabled = enabled + binding.tvRemainingTime.isEnabled = enabled } } \ No newline at end of file diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/AndroidUtils.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/AndroidUtils.java index b42216c83..a4f80b786 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/AndroidUtils.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/AndroidUtils.java @@ -381,4 +381,10 @@ public static void setProgress(ProgressBar pb, int progress) { pb.setProgress(progress); } } + + public static boolean isLaunchedFromHistory(Activity activity) { + Intent intent = activity.getIntent(); + return intent != null && (intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0; + } + } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/ViewUtils.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/ViewUtils.kt index c7f007f5f..c423d1ec1 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/ViewUtils.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/ViewUtils.kt @@ -1,5 +1,7 @@ package com.github.anrimian.musicplayer.ui.utils +import android.animation.Animator +import android.animation.AnimatorSet import android.animation.ArgbEvaluator import android.animation.ValueAnimator import android.content.Context @@ -8,6 +10,7 @@ import android.view.ContextThemeWrapper import android.view.View import android.view.ViewGroup import android.view.animation.AccelerateInterpolator +import android.view.animation.DecelerateInterpolator import android.widget.Button import androidx.annotation.ColorInt import androidx.annotation.StringRes @@ -19,17 +22,33 @@ import androidx.viewpager2.widget.ViewPager2 fun View.runHighlightAnimation(@ColorInt highlightColor: Int) { - val colorAnimator = getHighlightAnimator(Color.TRANSPARENT, highlightColor) - colorAnimator.addUpdateListener { animator -> setBackgroundColor(animator.animatedValue as Int) } - colorAnimator.start() + getHighlightAnimator(Color.TRANSPARENT, highlightColor, ::setBackgroundColor).start() } -fun getHighlightAnimator(@ColorInt colorFrom: Int, @ColorInt highlightColor: Int): ValueAnimator { - val colorAnimation = ValueAnimator.ofObject(ArgbEvaluator(), colorFrom, highlightColor, colorFrom) - colorAnimation.interpolator = AccelerateInterpolator() - colorAnimation.duration = 600 - colorAnimation.repeatCount = 2 - return colorAnimation +fun getHighlightAnimator( + @ColorInt colorFrom: Int, + @ColorInt highlightColor: Int, + updateListener: (Int) -> Unit +): Animator { + val animatorUpdateListener = ValueAnimator.AnimatorUpdateListener { animator -> + updateListener(animator.animatedValue as Int) + } + return AnimatorSet().apply { + playSequentially( + ValueAnimator.ofObject(ArgbEvaluator(), colorFrom, highlightColor).apply { + startDelay = 50 + duration = 300 + interpolator = AccelerateInterpolator() + addUpdateListener(animatorUpdateListener) + }, + ValueAnimator.ofObject(ArgbEvaluator(), highlightColor, colorFrom).apply { + startDelay = 1300 + duration = 300 + interpolator = DecelerateInterpolator() + addUpdateListener(animatorUpdateListener) + } + ) + } } fun View.moveToParent(newParent: ViewGroup) { @@ -59,7 +78,7 @@ fun ViewPager2.reduceDragSensitivityBy(f: Int) { val touchSlop = touchSlopField.get(recyclerView) as Int touchSlopField.set(recyclerView, touchSlop * f) } catch (ignored: NoSuchFieldException) {} - catch (ignored: IllegalAccessException) {} + catch (ignored: IllegalAccessException) {} } fun Context.createStyledButton(@StyleRes styleRes: Int): Button { diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/fragments/navigation/FragmentLayerListener.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/fragments/navigation/FragmentLayerListener.java deleted file mode 100644 index 9786da78f..000000000 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/fragments/navigation/FragmentLayerListener.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.anrimian.musicplayer.ui.utils.fragments.navigation; - -public interface FragmentLayerListener { - - /** - * Be careful, it can call before onCreateView(). - * Can be useful for update come common ui, like title in toolbar - */ - void onFragmentMovedOnTop(); -} diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/fragments/navigation/FragmentNavigation.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/fragments/navigation/FragmentNavigation.java index 91080f1b6..39f61930b 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/fragments/navigation/FragmentNavigation.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/fragments/navigation/FragmentNavigation.java @@ -1,5 +1,7 @@ package com.github.anrimian.musicplayer.ui.utils.fragments.navigation; +import static com.github.anrimian.musicplayer.domain.utils.ListUtils.mapList; + import android.content.res.Resources; import android.os.Bundle; import android.os.Handler; @@ -21,8 +23,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import static com.github.anrimian.musicplayer.domain.utils.ListUtils.mapList; - @SuppressWarnings({"WeakerAccess", "unused"}) public class FragmentNavigation { @@ -50,6 +50,8 @@ public class FragmentNavigation { @Nullable private Runnable bottomFragmentRunnable; + private long bottomFragmentRunnableTime; + private long bottomFragmentRunnableDelay; public static FragmentNavigation from(FragmentManager fm) { NavigationFragment container = (NavigationFragment) fm.findFragmentByTag(NAVIGATION_FRAGMENT_TAG); @@ -387,8 +389,8 @@ public boolean isInitialized() { } private void notifyFragmentMovedToTop(Fragment fragment) { - if (isVisible && fragment instanceof FragmentLayerListener) { - ((FragmentLayerListener) fragment).onFragmentMovedOnTop(); + if (isVisible && fragment instanceof FragmentNavigationListener) { + ((FragmentNavigationListener) fragment).onFragmentResumed(); } } @@ -406,6 +408,21 @@ private void scheduleBottomFragmentReplacing(long delay) { actionHandler.postDelayed(bottomFragmentRunnable, delay); } + private void scheduleBottomFragmentClearing(long delay) { + if (bottomFragmentRunnable != null) { + actionHandler.removeCallbacks(bottomFragmentRunnable); + } + bottomFragmentRunnable = () -> { + silentlyClearBottomFragment(); + bottomFragmentRunnable = null; + bottomFragmentRunnableTime = 0; + bottomFragmentRunnableDelay = 0; + }; + bottomFragmentRunnableTime = System.currentTimeMillis(); + bottomFragmentRunnableDelay = delay; + actionHandler.postDelayed(bottomFragmentRunnable, delay); + } + private void silentlyReplaceBottomFragment() { if (screens.size() > 1) { FragmentMetaData bottomFragment = screens.get(screens.size() - 2); @@ -421,14 +438,6 @@ private void silentlyReplaceBottomFragment() { } } - private void scheduleBottomFragmentClearing(long delay) { - if (bottomFragmentRunnable != null) { - actionHandler.removeCallbacks(bottomFragmentRunnable); - } - bottomFragmentRunnable = this::silentlyClearBottomFragment; - actionHandler.postDelayed(bottomFragmentRunnable, delay); - } - private void silentlyClearBottomFragment() { Fragment fragment = getFragmentOnBottom(); if (fragment != null) { @@ -444,13 +453,20 @@ private void silentlyClearBottomFragment() { } private void runForwardAction(Callback runnable) { - actionExecutor.execute(() -> - actionHandler.post(() -> { - FragmentManager fm = fragmentManagerProvider.getFragmentManager(); - if (fm != null) { - runnable.call(fm); + actionExecutor.execute(() -> { + Runnable scheduledRunnable = () -> { + FragmentManager fm = fragmentManagerProvider.getFragmentManager(); + if (fm != null) { + runnable.call(fm); + } + }; + if (bottomFragmentRunnable != null) { + long delay = bottomFragmentRunnableDelay - (System.currentTimeMillis() - bottomFragmentRunnableTime) + 1; + actionHandler.postDelayed(scheduledRunnable, delay); + } else { + actionHandler.post(scheduledRunnable); } - }) + } ); } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/views/progress_bar/ProgressView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/views/progress_bar/ProgressView.kt index d65ca06af..d11575276 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/views/progress_bar/ProgressView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/views/progress_bar/ProgressView.kt @@ -16,10 +16,14 @@ import com.github.anrimian.musicplayer.R import com.github.anrimian.musicplayer.ui.utils.colorFromAttr import kotlin.math.min +//TODO (?) change design, draw circular progress bar in cover. +// but where to draw download/upload icon? + //TODO check redraw engine, consider copying circular progress indicator //TODO short blinking (cloud icon after start) //TODO close animation is too fast or just not works //TODO when state is translucent overlap is visible +// A: just set different color to corrupted composition //based on https://github.com/2hamed/ProgressCircula/blob/master/progresscircula/src/main/java/com/hmomeni/progresscircula/ProgressCircula.kt class ProgressView( context: Context, diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/views/recycler_view/RecyclerViewUtils.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/views/recycler_view/RecyclerViewUtils.java index dce2fa18b..fa5206ae6 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/views/recycler_view/RecyclerViewUtils.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/views/recycler_view/RecyclerViewUtils.java @@ -26,6 +26,7 @@ import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.touch_helper.swipe_to_delete.SwipeToDeleteItemDecorator; import com.github.anrimian.musicplayer.ui.utils.views.recycler_view.touch_helper.swipe_to_delete.SwipeToDeleteTouchHelperCallback; +import me.zhanghai.android.fastscroll.DefaultAnimationHelper; import me.zhanghai.android.fastscroll.FastScrollerBuilder; import me.zhanghai.android.fastscroll.PublicRecyclerViewHelper; @@ -142,11 +143,17 @@ public static void attachFastScroller(RecyclerView recyclerView, boolean useFabP 0, thumbVerticalPadding + listBottomPadding); - new FastScrollerBuilder(recyclerView) + FastScrollerBuilder builder = new FastScrollerBuilder(recyclerView) .setTrackDrawable(trackDrawable) .setThumbDrawable(thumbDrawable) .setPadding(padding) - .setViewHelper(new PublicRecyclerViewHelper(recyclerView)) - .build(); + .setViewHelper(new PublicRecyclerViewHelper(recyclerView)); + builder.setAnimationHelper(new DefaultAnimationHelper(recyclerView) { + @Override + public int getScrollbarAutoHideDelayMillis() { + return 250; + } + }); + builder.build(); } } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/views/recycler_view/diff_utils/adapter/CustomAsyncListDiffer.java b/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/views/recycler_view/diff_utils/adapter/CustomAsyncListDiffer.java index 47b80aacb..97deb66d8 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/views/recycler_view/diff_utils/adapter/CustomAsyncListDiffer.java +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/utils/views/recycler_view/diff_utils/adapter/CustomAsyncListDiffer.java @@ -307,6 +307,8 @@ public void submitList(@Nullable final List newList, return; } + //fixes freeze for large lists + boolean detectMovesGentle = mList.size() < 100 && newList.size() < 100; final List oldList = mList; mConfig.getBackgroundThreadExecutor().execute(new Runnable() { @Override @@ -364,7 +366,7 @@ public Object getChangePayload(int oldItemPosition, int newItemPosition) { // non-null which is the only case handled above. throw new AssertionError(); } - }, detectMoves); + }, detectMoves && detectMovesGentle); mMainThreadExecutor.execute(new Runnable() { @Override diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/widgets/menu/WidgetMenuActivity.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/widgets/menu/WidgetMenuActivity.kt index 2b9931e60..b37f80c4c 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/widgets/menu/WidgetMenuActivity.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/widgets/menu/WidgetMenuActivity.kt @@ -13,6 +13,7 @@ import com.github.anrimian.musicplayer.databinding.ActivityWidgetMenuBinding import com.github.anrimian.musicplayer.databinding.PartialWidgetMenuHeaderBinding import com.github.anrimian.musicplayer.di.Components import com.github.anrimian.musicplayer.domain.models.composition.Composition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.ui.common.dialogs.shareComposition import com.github.anrimian.musicplayer.ui.common.dialogs.showConfirmDeleteDialog import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand @@ -109,7 +110,7 @@ class WidgetMenuActivity: MvpAppCompatActivity(), WidgetMenuView { finish() } - override fun showDeleteCompositionMessage(composition: Composition) { + override fun showDeleteCompositionMessage(composition: DeletedComposition) { val text = MessagesUtils.getDeleteCompleteMessage(this, listOf(composition)) Toast.makeText(this, text, Toast.LENGTH_LONG).show() } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/widgets/menu/WidgetMenuPresenter.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/widgets/menu/WidgetMenuPresenter.kt index 339105068..a26690c1f 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/widgets/menu/WidgetMenuPresenter.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/widgets/menu/WidgetMenuPresenter.kt @@ -2,6 +2,7 @@ package com.github.anrimian.musicplayer.ui.widgets.menu import com.github.anrimian.musicplayer.domain.interactors.player.LibraryPlayerInteractor import com.github.anrimian.musicplayer.domain.models.composition.Composition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.ui.common.error.parser.ErrorParser import com.github.anrimian.musicplayer.ui.common.mvp.AppPresenter import io.reactivex.rxjava3.core.Completable @@ -63,7 +64,7 @@ class WidgetMenuPresenter( } } - private fun onDeleteCompositionsSuccess(compositionsToDelete: Composition) { + private fun onDeleteCompositionsSuccess(compositionsToDelete: DeletedComposition) { viewState.showDeleteCompositionMessage(compositionsToDelete) viewState.closeScreen() } diff --git a/app/src/main/java/com/github/anrimian/musicplayer/ui/widgets/menu/WidgetMenuView.kt b/app/src/main/java/com/github/anrimian/musicplayer/ui/widgets/menu/WidgetMenuView.kt index ba56e27dc..52c3f64e6 100644 --- a/app/src/main/java/com/github/anrimian/musicplayer/ui/widgets/menu/WidgetMenuView.kt +++ b/app/src/main/java/com/github/anrimian/musicplayer/ui/widgets/menu/WidgetMenuView.kt @@ -1,6 +1,7 @@ package com.github.anrimian.musicplayer.ui.widgets.menu import com.github.anrimian.musicplayer.domain.models.composition.Composition +import com.github.anrimian.musicplayer.domain.models.composition.DeletedComposition import com.github.anrimian.musicplayer.ui.common.error.ErrorCommand import moxy.MvpView import moxy.viewstate.strategy.AddToEndSingleTagStrategy @@ -27,7 +28,7 @@ interface WidgetMenuView: MvpView { fun closeScreen() @OneExecution - fun showDeleteCompositionMessage(composition: Composition) + fun showDeleteCompositionMessage(composition: DeletedComposition) @OneExecution fun showDeleteCompositionError(errorCommand: ErrorCommand) diff --git a/app/src/main/res/layout/activity_composition_edit.xml b/app/src/main/res/layout/activity_composition_edit.xml index caa2b3b91..aad2097fe 100644 --- a/app/src/main/res/layout/activity_composition_edit.xml +++ b/app/src/main/res/layout/activity_composition_edit.xml @@ -356,6 +356,153 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/tv_album_artist"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -394,7 +541,7 @@ android:layout_height="0dp" android:foreground="?selectableItemBackground" android:contentDescription="@string/change_album_artist" - app:layout_constraintTop_toBottomOf="@id/divider_album_artist" + app:layout_constraintTop_toBottomOf="@id/divider_comment" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@id/divider_lyrics"/> diff --git a/app/src/main/res/layout/dialog_common_input_simple.xml b/app/src/main/res/layout/dialog_common_input_simple.xml index dfc7c4382..304303bea 100644 --- a/app/src/main/res/layout/dialog_common_input_simple.xml +++ b/app/src/main/res/layout/dialog_common_input_simple.xml @@ -5,7 +5,7 @@ xmlns:tools="http://schemas.android.com/tools"> - - + android:layout_height="wrap_content"> - + - + - + - + + + + + - + - + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings_display.xml b/app/src/main/res/layout/fragment_settings_display.xml index 7461944f3..346a3b89e 100644 --- a/app/src/main/res/layout/fragment_settings_display.xml +++ b/app/src/main/res/layout/fragment_settings_display.xml @@ -56,10 +56,10 @@ + app:layout_constraintTop_toBottomOf="@id/tvInterfaceTitle"/> + + + app:layout_constraintTop_toBottomOf="@id/tvCompositionsTitle"/> + + + app:layout_constraintTop_toBottomOf="@id/tvCoversTitle"/> + app:layout_constraintTop_toBottomOf="@id/cbCovers" /> + app:layout_constraintTop_toBottomOf="@id/cbCoversInNotification" /> - + app:layout_constraintTop_toBottomOf="@id/cbColoredNotification" /> + app:layout_constraintTop_toBottomOf="@id/cbShowCoverStubInNotification" /> diff --git a/app/src/main/res/layout/fragment_settings_headset.xml b/app/src/main/res/layout/fragment_settings_headset.xml index 8475a6929..c9e6f9183 100644 --- a/app/src/main/res/layout/fragment_settings_headset.xml +++ b/app/src/main/res/layout/fragment_settings_headset.xml @@ -1,6 +1,7 @@ @@ -23,14 +24,53 @@ style="@style/CheckboxStyle" android:id="@+id/cb_play_on_connect" android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginStart="@dimen/content_horizontal_margin" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/iv_play_on_connect_delay" + app:layout_constraintTop_toTopOf="@id/tv_play_on_connect" + app:layout_constraintBottom_toBottomOf="@id/tv_play_on_connect_delay"/> + + + + + + + + app:layout_constraintTop_toTopOf="@id/cb_play_on_connect" + app:layout_constraintBottom_toBottomOf="@id/cb_play_on_connect"/> + diff --git a/app/src/main/res/layout/fragment_settings_player.xml b/app/src/main/res/layout/fragment_settings_player.xml index 62f5f645f..b4f42668f 100644 --- a/app/src/main/res/layout/fragment_settings_player.xml +++ b/app/src/main/res/layout/fragment_settings_player.xml @@ -92,7 +92,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:foreground="?selectableItemBackground" - android:contentDescription="@string/equalizer" + android:contentDescription="@string/sound_balance" app:layout_constraintTop_toTopOf="@id/tvSoundBalance" app:layout_constraintBottom_toBottomOf="@id/tvSoundBalanceState" app:layout_constraintStart_toStartOf="parent" @@ -157,6 +157,44 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/flEqualizerClickableArea"/> + + + + + + diff --git a/app/src/main/res/layout/partial_drawer_header.xml b/app/src/main/res/layout/partial_drawer_header.xml index ee6a2f8cc..594effed1 100644 --- a/app/src/main/res/layout/partial_drawer_header.xml +++ b/app/src/main/res/layout/partial_drawer_header.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="180dp" - tools:background="@color/colorPrimary"> + tools:background="@color/color_purple_primary"> + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/artist_menu.xml b/app/src/main/res/menu/artist_menu.xml index fd5dfd3b6..7a8005005 100644 --- a/app/src/main/res/menu/artist_menu.xml +++ b/app/src/main/res/menu/artist_menu.xml @@ -1,8 +1,23 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/play_list_menu.xml b/app/src/main/res/menu/play_list_menu.xml index 626e07f9d..1d944be2e 100644 --- a/app/src/main/res/menu/play_list_menu.xml +++ b/app/src/main/res/menu/play_list_menu.xml @@ -8,6 +8,12 @@ app:iconTint="?attr/toolbarTextColorPrimary" app:showAsAction="never"/> + + + + Левы: %1$d%%, Правы: %2$d%% Мова Выкарыстоўваць мову сістэмы - Перамясціць Кампазіцыі ў тэчцы не знойдзены Ужываць сістэмную тэму @@ -251,6 +250,32 @@ Перамесціце аўдыё файлы ў гэтыя тэчкі і дзеянне павінна будзе працаваць карэктна. Гэтая тэчка ўжо выключана з сканавання + Адсутнічае дазвол для мадыфікацыі файла + Інтэрфейс + Уключыць пераключэнне жэстамі паміж экранамі чаргі прайгравання і тэксту кампазіцыі + Недастаткова дадзеных для прайгравання кампазіцыі + Патрабуецца дазвол + Адкрыць налады прыкладання + Файл пашкоджаны + Прадухіляць аўтаматычнае закрыццё апавяшчэння аб прайграванні пасля паўзы + Невядомая памылка прайгравання файла + Перавышаны час чытання файла + Памылка чытання тэгаў: %s + Нумар трэка + Змяніць нумар трэка + Нумар дыска + Змяніць нумар дыска + Каментар + Змяніць каментар + Дыск %d + Трэк %d + Праз %.1fс + Устаўляць новыя кампазіцыі ў пачатак плэйліста + Захаваць як .m3u файл + Імпартаваць плэйліст + Плэйліст з такім імем ужо існуе. \nПеразапісаць яго? + Плейліст не мае кампазіцый + Файл \"%1$s.m3u\" быў захаваны ва ўнутранае сховішча %1$s кампазіцыя @@ -322,4 +347,25 @@ Выдаліць %d кампазіцый + + На працягу не менш %d хвіліны + На працягу не менш %d хвілін + На працягу не менш %d хвілін + На працягу не менш %d хвілін + + + + %d плэйліст быў захаваны ва ўнутранае сховішча + %d плэйліста былі захаваныя ва ўнутранае сховішча + %d плэйлістоў былі захаваныя ва ўнутранае сховішча + %d плэйлістоў былі захаваныя ва ўнутранае сховішча + + + + Плэйліст быў імпартаваны паспяхова, але %d файл быў не знойдзены ў бібліятэцы + Плэйліст быў імпартаваны паспяхова, але %d файла былі не знойдзены ў бібліятэцы + Плэйліст быў імпартаваны паспяхова, але %d файлаў былі не знойдзены ў бібліятэцы + Плэйліст быў імпартаваны паспяхова, але %d файлаў былі не знойдзены ў бібліятэцы + + \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 943099f75..51e0d3f6c 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -257,6 +257,32 @@ Vložte audio soubory do těchto složek a operace by měly proběhnout. Tato složka je už vyloučena z prohledávání + Bez oprávnění upravit soubotr + Rozhraní + Povolit gesto swipnutí pro přepunutí mezi frontou přehrávání a texty + Málo dat pro přehrání skladby + Vyžadováno oprávnění + Otevřít nastavení aplikace + Soubor je poškozen + Zabránit automatickému zavření upozornění přehrávače po pauze + Chyba přehrávání neznámého souboru + Překročen časový limit čtení souboru + Chyba čtení značky: %s + Číslo skladby + Změnit číslo skladby + Číslo disku + Změnit číslo disku + Komentář + Změnit omentář + Disk %d + Skladba %d + S %.1fs prodlevou + Vložit novou skladbu na začátek seznamu skladeb + Uložit jako .m3u soubor + Importovat seznam sladeb + Seznam skladeb s tímto názvem již existuje. \nPřepsat? + Seznam skladeb neobsahuje žádné skladby + Soubor \"%1$s.m3u\" byl uložen do vnitřního úložiště %1$s píseň @@ -328,4 +354,25 @@ Smazat %d písní + + Pro alespoň %d minutu + Pro alespoň %d minuty + Pro alespoň %d minut + Pro alespoň %d minut + + + + %d seznam skladeb byl uložen do vnitřního úložiště + %d seznamy skladeb byly uložen do vnitřního úložiště + %d seznamů skladeb bylo uložen do vnitřního úložiště + %d seznamů skladeb bylo uložen do vnitřního úložiště + + + + Seznam skladeb byl úspěšně importován, ale %d soubor nebyl nalezen v knihovně + Seznam skladeb byly úspěšně importován, ale %d soubory nebyl nalezen v knihovně + Seznam skladeb byly úspěšně importován, ale %d souborů nebyl nalezen v knihovně + Seznam skladeb byly úspěšně importován, ale %d souborů nebyl nalezen v knihovně + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7796b36f7..d66b65fd5 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -257,7 +257,32 @@ Bitte Audiodateien dort speichern, dann sollte die Aktion klappen. Dieser Ordner wird bei der Musiksuche bereits ignoriert. - + Keine Berechtigung, die Datei zu verändern + Benutzeroberfläche + Wischgesten aktivieren, um zwischen Wiedergabeliste und Lyrics zu wechseln + Nicht genug Daten, um Titel abzuspielen + Berechtigung nötig + App-Einstellungen öffnen + Datei ist beschädigt + Player-Benachrichtigung nach einer Pause nicht automatisch schließen + Unbekannter Fehler beim Abspielen + Lesen der Datei dauert zu lang + Tag-Lesefehler: %s + Titelnummer + Titelnummer ändern + CD-Nummer + CD-Nummer ändern + Kommentar + Kommentar ändern + CD %d + Titelnummer %d + Mit %.1fs Verzögerung + Neue Titel am Anfang der Wiedergabeliste einfügen + Als .m3u-Datei speichern + Playlist importieren + Es gibt bereits eine Playlist mit diesem Namen. \nDiese überschreiben? + Playlist ist leer + Datei „%1$s.m3u“ erfolgreich auf internen Speicher gesichert %1$s Titel @@ -309,4 +334,19 @@ %d Titel löschen + + Für mindestens %d Minute + Für mindestens %d Minuten + + + + %d Playlist auf internen Speicher gesichert + %d Playlists auf internen Speicher gesichert + + + + Playlist erfolgreich importiert, aber %d Datei wurde nicht in der Sammlung gefunden + Playlist erfolgreich importiert, aber %d Dateien wurden nicht in der Sammlung gefunden + + diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 9632aa978..8cec84280 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -257,6 +257,32 @@ Τοποθετήστε τα αρχεία ήχου σε αυτούς τους φακέλους και η εργασία αυτή θα λειτουργήσει σωστά. Αυτός ο φάκελος εξαιρείται ήδη από την αναζήτηση + Χωρίς άδεια επεξεργασίας αρχείου + Διεπαφή χρήστη + Ενεργοποίηση χειρονομιών για εναλλαγή μεταξύ της ουράς αναπαραγωγής και της οθόνης στίχων + Ανεπαρκή δεδομένα για αναπαραγωγή τραγουδιού + Απαιτείται παραχώρηση δικαιώματος + Άνοιγμα ρυθμήσεων εφαρμογής + Το αρχείο είναι κατεστραμμένο + Αποτροπή αυτόματου κλεισίματος ειδοποίησης εφαρμογής κατά την παύση + Άγνωστο σφάλμα αναπαραγωγής αρχείου + Λήξη χρονικού ορίου ανάγνωσης αρχείου + Σφάλμα ανάγνωσης ετικέτας: %s + Αριθμός κομματιού + Αλλαγή αριθμού κομματιού + Αριθμός δίσκου + Αλλαγή αριθμού δίσκου + Σχόλιο + Αλλαγή σχολίου + Δίσκος %d + Κομμάτι %d + Με %.1fs καθυστέρηση + Εισαγωγή νέων τραγουδιών στην αρχή της λίστας αναπαραγωγής + Αποθήκευση ως αρχείο .m3u + Εισαγωγή λίστας αναπαραγωγής + Υπάρχει ήδη μια λίστα αναπαραγωγής με το ίδιο όνομα. \nΝα αντικατασταθεί; + Η λίστα αναπαραγωγής δεν έχει καμία καταχώρηση + Το αρχείο \"%1$s.m3u\" αποθηκεύτηκε στον εσωτερικό χώρο αποθήκευσης %1$s τραγούδι @@ -308,4 +334,19 @@ Διαγραφή %d τραγιυδιών + + Για τουλάχιστον %d λεπτό + Για τουλάχιστον %d λεπτά + + + + %d λίστα αναπαραγωγής αποθηκεύτηκε στον εσωτερικό χώρο αποθήκευσης + %d λίστες αναπαραγωγής αποθηκεύτηκαν στον εσωτερικό χώρο αποθήκευσης + + + + Έγινε εισαγωγή της λίστας αναπαραγωγής επιτυχώς, αλλά %d αρχείο δεν βρέθηκε στη συλλογή + Έγινε εισαγωγή της λίστας αναπαραγωγής επιτυχώς, αλλά %d αρχεία δεν βρέθηκαν στη συλλογή + + \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 0f19b5607..ff42756d9 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -212,10 +212,12 @@ Excluir composiciones del escaneo Con duración inferior a %s - Deje de jugar en llamadas, reproducción de música / video, etc. + + Dejar de reproducir en llamadas, reproducción de música / video, etc. + Mostrar talón de portada en la notificación cuando no se encuentra la portada Sin permiso para modificar este archivo. Pruebe a través del administrador de archivos. - Mezclar todo y jugar + Mezclar todo y reproducir Rebobinar Avance rápido Mostrar portadas en la pantalla de bloqueo y en Android Auto @@ -264,6 +266,34 @@ Esta carpeta ya está excluida del escaneo + + Sin permiso para modificar archivo + Interfaz + Habilite el gesto de deslizar para cambiar entre la cola de reproducción y la pantalla de letras + No hay suficientes datos para reproducir la composición + Se requiere permiso + Configuración de la aplicación abierta + El archivo está dañado + Evitar que la notificación del reproductor se cierre automáticamente después de la pausa + Error de reproducción de archivo desconocido + Tiempo de espera de lectura de archivo + Error de lectura de etiqueta: %s + Número de pista + Cambiar número de pista + Número de disco + Cambiar número de disco + Comentario + Cambiar comentario + Disco %d + Pista %d + Con %.1fs de retraso + Insertar nuevas composiciones al principio de la lista de reproducción + Guardar como archivo .m3u + Importar lista de reproducción + Ya existe una lista de reproducción con este nombre. \n¿Sobrescribirlo? + La lista de reproducción no tiene registros + El archivo \"%1$s.m3u\" se guardó en el almacenamiento interno + %1$s canción @@ -319,4 +349,21 @@ + + + Durante al menos %d minuto + Durante al menos %d minutos + + + + %d lista de reproducción se guardó en el almacenamiento interno + %d listas de reproducción se guardaron en el almacenamiento interno + + + + La lista de reproducción se importó correctamente, pero el archivo %d no se encontró en la biblioteca + La lista de reproducción se importó con éxito, pero %d archivos no se encontraron en la biblioteca + + + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9da7f596b..1323179b5 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -258,6 +258,34 @@ Placer les fichiers audio dans ces dossiers et l\'operation fonctionnera correctement The dossier est déjà exclus de la liste des dossiers à analyser + + Aucune autorisation de modifier le fichier + Interface + Activer le geste de balayage pour basculer entre la file d\'attente de lecture et l\'écran des paroles + Pas assez de données pour lire la composition + L\'autorisation est requise + Ouvrir les paramètres de l\'application + Le fichier est corrompu + Empêcher la notification du joueur de se fermer automatiquement après une pause + Erreur de lecture de fichier inconnue + Délai de lecture du fichier + Erreur de lecture de balise: %s + Numéro de piste + Changer le numéro de piste + Numéro de disque + Modifier le numéro de disque + Commentaire + Modifier le commentaire + Disque %d + Piste %d + Avec %.1fs de retard + Insérer de nouvelles compositions au début de la playlist + Enregistrer en tant que fichier .m3u + Importer une liste de lecture + La liste de lecture portant ce nom existe déjà. \nEcraser? + La liste de lecture n\'a aucun enregistrement + Le fichier \"%1$s.m3u\" a été enregistré dans le stockage interne + %1$s morceau @@ -319,4 +347,24 @@ Supprimer %d titres + + + Pendant au moins % dminute + Pendant au moins %d minutes + Pendant au moins %d minutes + + + + %d liste de lecture a été enregistrée dans la mémoire de stockage interne + %d listes de lecture ont été enregistrées dans la mémoire de stockage interne + %d listes de lecture ont été enregistrées dans la mémoire de stockage interne + + + + La liste de lecture a été importée avec succès, mais %d fichier n\'a pas été trouvé dans la bibliothèque + La liste de lecture a été importée avec succès, mais %d fichiers n\'ont pas été trouvés dans la bibliothèque + La liste de lecture a été importée avec succès, mais %d fichiers n\'ont pas été trouvés dans la bibliothèque + + + \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 70b001789..50b755c9a 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -257,6 +257,34 @@ Tempatkan file audio ke dalam pelipat ini, dan tindakan harusnya dapat berjalan dengan benar. Pelipat ini sudah dikecualikan dari pemindaian + + Tidak ada izin untuk memodifikasi file + Antarmuka + Aktifkan gerakan gesek untuk beralih antara antrean putar dan layar lirik + Data tidak cukup untuk memainkan komposisi + Diperlukan izin + Buka pengaturan aplikasi + File rusak + Mencegah notifikasi pemain menutup otomatis setelah jeda + Kesalahan pemutaran file tidak dikenal + Waktu baca file habis + Kesalahan membaca tag: %s + Nomor lintasan + Ubah nomor trek + Nomor cakram + Ubah nomor disk + Komentar + Ubah komentar + Disk %d + Lacak %d + Dengan penundaan %.1fs + Sisipkan komposisi baru di awal daftar putar + Simpan sebagai file .m3u + Impor daftar putar + Daftar Putar dengan nama ini sudah ada. \nTimpa itu? + Daftar putar tidak memiliki rekaman + File \"%1$s.m3u\" disimpan ke penyimpanan internal + %1$s Lagu @@ -298,4 +326,18 @@ Hapus %d lagu + + + Setidaknya selama %d menit + + + + %d playlist disimpan ke penyimpanan internal + + + + Daftar putar berhasil diimpor, tetapi %d file tidak ditemukan di pustaka + + + diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index ff9e2c8cc..b2a106e5e 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -264,6 +264,34 @@ Esta pasta já foi excluída da verificação + + Sem permissão para modificar o arquivo + Interface + Ative o gesto de deslizar para alternar entre a fila de reprodução e a tela de letras + Não há dados suficientes para reproduzir a composição + A permissão é necessária + Abrir configurações do aplicativo + O arquivo está corrompido + Evitar que a notificação do jogador feche automaticamente após a pausa + Erro de reprodução de arquivo desconhecido + Tempo limite de leitura do arquivo + Erro de leitura da tag: %s + Número da faixa + Alterar número da faixa + Número do disco + Alterar número do disco + Comentário + Alterar comentário + Disco %d + Faixa %d + Com atraso de %.1fs + Inserir novas composições no início da playlist + Salvar como arquivo .m3u + Importar playlist + Já existe uma playlist com este nome. \nSubstituir? + A playlist não tem registros + O arquivo \"%1$s.m3u\" foi salvo no armazenamento interno + %1$s música @@ -319,4 +347,21 @@ + + + Por pelo menos %d minuto + Por pelo menos %d minutos + + + + %d lista de reprodução foi salva no armazenamento interno + %d listas de reprodução foram salvas no armazenamento interno + + + + A playlist foi importada com sucesso, mas o arquivo %d não foi encontrado na biblioteca + A playlist foi importada com sucesso, mas %d arquivos não foram encontrados na biblioteca + + + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 0f5cd971b..e1b1d3579 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -254,6 +254,32 @@ Переместите аудио файлы в эти папки и действие должно будет работать корректно. Эта папка уже исключена из сканирования + Отсутствует разрешение для модификации файла + Интерфейс + Включить переключение жестами между экранами очереди воспроизведения и текста композиции + Недостаточно данных для проигрывания композиции + Требуется разрешение + Открыть настройки приложения + Файл повреждён + Предотвращать автоматическое закрытие уведомления о проигрывании после паузы + Неизвестная ошибка проигрывания файла + Превышено время чтения файла + Ошибка чтения тегов: %s + Номер трека + Изменить номер трека + Номер диска + Изменить номер диска + Комментарий + Изменить комментарий + Диск %d + Трек %d + Через %.1fс + Вставлять новые композиции в начало плейлиста + Сохранить как .m3u файл + Импортировать плейлист + Плейлист с таким именем уже существует. \nПерезаписать его? + Плейлист не имеет композиций + Файл \"%1$s.m3u\" был сохранён во внутреннее хранилище %1$s композиция @@ -325,4 +351,25 @@ Удалить %d композиций + + В течении не менее %d минуты + В течении не менее %d минут + В течении не менее %d минут + В течении не менее %d минут + + + + %d плейлист был сохранён во внутреннее хранилище + %d плейлиста были сохранены во внутреннее хранилище + %d плейлистов были сохранены во внутреннее хранилище + %d плейлистов были сохранены во внутреннее хранилище + + + + Плейлист был импортирован успешно, но %d файл был не найден в библиотеке + Плейлист был импортирован успешно, но %d файла были не найдены в библиотеке + Плейлист был импортирован успешно, но %d файлов были не найдены в библиотеке + Плейлист был импортирован успешно, но %d файлов были не найдены в библиотеке + + \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 96a10c9ae..7450f9135 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -257,7 +257,32 @@ Ses dosyalarını bu klasörlere yerleştirin ve işlemin doğru şekilde gerçekleştirilmesi gerekir. Bu klasör zaten tarama dışında bırakıldı - + Dosyayı değiştirme izni yok + Arayüz + Çalma sırası ile şarkı sözü ekranı arasında geçiş yapmak için kaydırma hareketini etkinleştir + Kompozisyon çalmak için yeterli veri yok + İzin gerekli + Uygulama ayarlarını aç + Dosya bozuk + Oynatıcı bildiriminin duraklatıldıktan sonra otomatik olarak kapanmasını engelle + Bilinmeyen dosya yürütme hatası + Dosya okuma zaman aşımı + Etiket okuma hatası: %s + Parça numarası + Parça numarasını değiştir + Disk numarası + Disk numarasını değiştir + Yorum + Yorumu değiştir + Disk %d + Parça %d + %.1fs gecikmeli + Çalma listesinin başına yeni besteler ekleyin + .m3u dosyası olarak kaydet + Çalma listesini içe aktar + Bu ada sahip oynatma listesi zaten var. \nÜzerine yazılsın mı? + Oynatma listesinde kayıt yok + \"%1$s.m3u\" dosyası dahili depolamaya kaydedildi %1$s şarkı @@ -309,4 +334,19 @@ Sil %d + + En az %d dakika için + En az %d dakika boyunca + + + + %d oynatma listesi dahili depolamaya kaydedildi + %d oynatma listeleri dahili depolamaya kaydedildi + + + + Oynatma listesi başarıyla içe aktarıldı, ancak %d dosya kitaplıkta bulunamadı + Oynatma listesi başarıyla içe aktarıldı, ancak kitaplıkta %d dosya bulunamadı + + \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index c2daa274a..10ad41873 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -250,6 +250,32 @@ Перемістіть аудіо файли в ці папки і дія повинна працювати коректно. Цю папку вже виключено зі сканування + Відсутній дозвіл для модифікації файлу + Інтерфейс + Увімкнути переключення жестами між екранами черги відтворювання і тексту композиції + Недостатньо даних для відтворення композиції + Потрібен дозвіл + Відкрити налаштування додатку + Файл пошкоджено + Запобігати автоматичному закриттю сповіщення про відтворення після паузи + Невідома помилка відтворення файлу + Перевищено час читання файлу + Помилка читання тегів: %s + Номер треку + Змінити номер треку + Номер диску + Змінити номер диску + Комментар + Змінити комментар + Диск %d + Трек %d + Через %.1fс + Вставляти нові композиції на початок плейлиста + Зберегти як .m3u файл + Імпортувати плейлист + Плейлист з таки іменем вже існує. \nПерезаписати його? + Плейлист не має композицій + Файл \"%1$s.m3u\" було збережено у внутрішнє сховище %1$s композиція @@ -321,4 +347,25 @@ Видалити %d композицій + + Протягом не менше %d хвилини + Протягом не менше %d хвилин + Протягом не менше %d хвилин + Протягом не менше %d хвилин + + + + %d плейлист було збережено у внутрішнє сховище + %d плейлиста були збережені у внутрішнє сховище + %d плейлистів були збережені у внутрішнє сховище + %d плейлистів були збережені у внутрішнє сховище + + + + Плейлист було імпортовано успішно, але %d файл був не знайдений в бібліотеці + Плейлист було імпортовано успішно, але %d файли були не знайдені в бібліотеці + Плейлист було імпортовано успішно, але %d файлів були не знайдені в бібліотеці + Плейлист було імпортовано успішно, але %d файлів були не знайдені в бібліотеці + + \ No newline at end of file diff --git a/app/src/main/res/values-v31/themes.xml b/app/src/main/res/values-v31/themes.xml index 13d6c347a..bd9426411 100644 --- a/app/src/main/res/values-v31/themes.xml +++ b/app/src/main/res/values-v31/themes.xml @@ -27,7 +27,8 @@ @android:color/system_accent1_500 @android:color/system_neutral1_800 @android:color/system_neutral1_500 - @android:color/system_neutral1_100 + @android:color/system_neutral1_10 + @android:color/system_neutral1_0 @android:color/system_neutral1_800 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1710a037f..113ab7f50 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,8 +1,8 @@ - #673AB7 - #512DA8 + #673AB7 + #512DA8 #009688 #4DB6AC @@ -88,8 +88,8 @@ #33000000 - #3F51B5 - #303F9F + #3F51B5 + #303F9F #4CAF50 #81C784 @@ -104,13 +104,24 @@ #9b0000 #FF5252 + + #FB8C00 + #EF6C00 + #FFA726 + + + #039BE5 + #0288D1 + #FF9800 + #FFA726 + #848282 #2196F3 #64B5F6 - #e65100 + #e65100 #00c853 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index bb1f2db98..dd7178d76 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -57,6 +57,7 @@ 12dp + 56dp 16dp @@ -118,6 +119,7 @@ 130 + 500 1000 @@ -155,4 +157,7 @@ 0 + + 48dp + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cfe30b891..9a5ca2b28 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,7 +47,6 @@ Save as playlist Cancel Deleting - Song \"%1$s\" deleted Search Play queue @@ -174,7 +173,7 @@ Log file deleted Pick a email app to send Clear play queue - Folders excluded from scanning will be appear here + Folders excluded from scanning will appear here Keep playing after window closing Equalizer System equalizer @@ -257,6 +256,34 @@ Place audio files into these folders and operation should perform correctly. This folder is already excluded from scanning + No permission to modify file + Interface + Enable swipe gesture for switching between play queue and lyrics screen + Not enough data to play composition + Permission is required + Open app settings + File is corrupted + Prevent player notification from auto-closing after pause + Unknown file play error + File read timeout + Tag reading error: %s + Track number + Change track number + Disc number + Change disc number + Comment + Change comment + Disc %d + Track %d + With %.1fs delay + Insert new compositions at the beginning of the playlist + Save as .m3u file + Import playlist + Playlist with this name already exists. \nOverwrite it? + Playlist has no records + File \"%1$s.m3u\" was saved to internal storage + + name is too long %1$s song @@ -308,4 +335,19 @@ Delete %d songs + + For at least %d minute + For at least %d minutes + + + + %d playlist was saved to internal storage + %d playlists were saved to internal storage + + + + Playlist was imported successfully, but %d file was not found in library + Playlist was imported successfully, but %d files were not found in library + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 0e58aed4c..373e23017 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -9,11 +9,11 @@ + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 68803b924..8ec97620a 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -60,9 +60,9 @@ @color/colorAccentContrast - + + + + + +