├── data ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ └── java │ │ └── com │ │ └── parabola │ │ └── data │ │ ├── utils │ │ └── FileUtil.java │ │ ├── model │ │ ├── FolderData.java │ │ ├── ArtistData.java │ │ ├── PlaylistData.java │ │ └── AlbumData.java │ │ ├── executor │ │ └── SchedulerProviderImpl.java │ │ └── repository │ │ ├── ResourceRepositoryImpl.java │ │ ├── FolderRepositoryImpl.java │ │ ├── ArtistRepositoryImpl.java │ │ └── AlbumRepositoryImpl.java ├── proguard-rules.pro └── build.gradle ├── domain ├── .gitignore ├── src │ └── main │ │ └── java │ │ └── com │ │ └── parabola │ │ └── domain │ │ ├── interactor │ │ ├── type │ │ │ └── Irrelevant.java │ │ ├── RepositoryInteractor.java │ │ ├── player │ │ │ └── PlayerSetting.java │ │ └── FolderInteractor.java │ │ ├── exception │ │ ├── ItemNotFoundException.java │ │ └── AlreadyExistsException.java │ │ ├── repository │ │ ├── ResourceRepository.java │ │ ├── FolderRepository.java │ │ ├── ExcludedFolderRepository.java │ │ ├── PermissionHandler.java │ │ ├── ArtistRepository.java │ │ ├── AlbumRepository.java │ │ ├── PlaylistRepository.java │ │ └── SortingRepository.java │ │ ├── executor │ │ └── SchedulerProvider.java │ │ ├── model │ │ ├── Artist.java │ │ ├── Album.java │ │ ├── Playlist.java │ │ ├── Folder.java │ │ └── Track.java │ │ └── utils │ │ ├── TracklistTool.java │ │ └── EmptyItems.java └── build.gradle ├── search_feature ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── parabola │ └── search_feature │ ├── SearchResult.kt │ └── SearchInteractor.kt ├── Slidr ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── r0adkll │ │ │ └── slidr │ │ │ ├── model │ │ │ ├── SlidrInterface.java │ │ │ ├── SlidrPosition.java │ │ │ ├── SlidrListenerAdapter.java │ │ │ └── SlidrListener.java │ │ │ ├── ConfigPanelSlideListener.java │ │ │ ├── ColorPanelSlideListener.java │ │ │ └── FragmentPanelSlideListener.java │ │ └── res │ │ └── values │ │ └── ids.xml └── build.gradle ├── player_feature ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── values-ru │ │ │ └── strings.xml │ │ ├── values │ │ │ └── strings.xml │ │ └── drawable │ │ │ ├── ic_notification_favourite.xml │ │ │ └── ic_notification_not_favourite.xml │ │ └── java │ │ └── com │ │ └── parabola │ │ └── player_feature │ │ ├── AudioRenderersFactory.kt │ │ └── PlayerSettingImpl.kt ├── proguard-rules.pro └── build.gradle ├── sleep_timer_feature ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── parabola │ └── sleep_timer_feature │ └── SleepTimerInteractor.kt ├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── drawable │ │ │ ├── isaac_newtone.jpg │ │ │ ├── text_view_circle_corners_bg.xml │ │ │ ├── color_picker_selector.xml │ │ │ ├── song_progress_thumb.xml │ │ │ ├── setting_item_container_background.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── ic_circle.xml │ │ │ ├── ic_recent_add.xml │ │ │ ├── widget_background.xml │ │ │ ├── ic_folder.xml │ │ │ ├── ic_play.xml │ │ │ ├── ic_arrow_drop.xml │ │ │ ├── ic_kebab.xml │ │ │ ├── ic_add.xml │ │ │ ├── ic_pause.xml │ │ │ ├── ic_info.xml │ │ │ ├── ic_edit.xml │ │ │ ├── ic_favourite_select.xml │ │ │ ├── ic_favourite_white.xml │ │ │ ├── ic_next.xml │ │ │ ├── ic_delete.xml │ │ │ ├── ic_prev.xml │ │ │ ├── ic_radio_button_checked.xml │ │ │ ├── ic_remove_circle.xml │ │ │ ├── ic_sorting.xml │ │ │ ├── ic_arrow_left.xml │ │ │ ├── ic_hamburger.xml │ │ │ ├── ic_eq.xml │ │ │ ├── check_circle.xml │ │ │ ├── ic_album.xml │ │ │ ├── ic_play_accent.xml │ │ │ ├── ic_close.xml │ │ │ ├── ic_pause_accent.xml │ │ │ ├── ic_search.xml │ │ │ ├── eq_thumb_selector.xml │ │ │ ├── ic_playlist.xml │ │ │ ├── ic_share.xml │ │ │ ├── ic_favourite.xml │ │ │ ├── album_default.xml │ │ │ ├── ic_add_playlist.xml │ │ │ ├── ic_album_colored.xml │ │ │ ├── ic_lyrics.xml │ │ │ ├── fx_eq_icon.xml │ │ │ ├── ic_queue.xml │ │ │ ├── ic_launcher_foreground.xml │ │ │ ├── ic_timer.xml │ │ │ ├── ic_drag.xml │ │ │ ├── ic_shuffled.xml │ │ │ ├── fx_ic_tune.xml │ │ │ ├── ic_loop.xml │ │ │ ├── ic_artist.xml │ │ │ ├── ic_loop_one.xml │ │ │ ├── ic_artist_colored.xml │ │ │ ├── ic_clef.xml │ │ │ ├── ic_clef_colored.xml │ │ │ └── ic_setting.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── anim │ │ │ ├── anim_in.xml │ │ │ └── anim_out.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── xml │ │ │ ├── app_widget_info.xml │ │ │ ├── file_provider_paths.xml │ │ │ └── data_extraction_rules.xml │ │ ├── color │ │ │ ├── mtrl_toggle_btn_bg_color_selector.xml │ │ │ ├── drag_button_background_color_selector.xml │ │ │ └── mtrl_toggle_btn_text_color_selector.xml │ │ ├── values-v21 │ │ │ └── styles.xml │ │ ├── menu │ │ │ ├── album_menu.xml │ │ │ ├── artist_menu.xml │ │ │ ├── tab_track_menu.xml │ │ │ ├── playlist_menu.xml │ │ │ ├── folder_menu.xml │ │ │ ├── main_menu.xml │ │ │ ├── player_menu.xml │ │ │ └── track_menu.xml │ │ ├── layout │ │ │ ├── fragment_excluded_folders.xml │ │ │ ├── edit_text_container.xml │ │ │ ├── tab_item_view.xml │ │ │ ├── list_track.xml │ │ │ ├── dialog_sorting.xml │ │ │ ├── fragment_tab_artist.xml │ │ │ ├── fragment_folders_list.xml │ │ │ ├── fragment_tab_album.xml │ │ │ ├── dialog_folder_picker.xml │ │ │ ├── item_menu.xml │ │ │ ├── item_artist.xml │ │ │ ├── request_permission_panel.xml │ │ │ ├── item_system_playlist.xml │ │ │ ├── dialog_playlist_choose.xml │ │ │ ├── item_eq_band.xml │ │ │ ├── activity_main.xml │ │ │ ├── item_folder.xml │ │ │ ├── dialog_audio_effects.xml │ │ │ ├── item_playlist.xml │ │ │ ├── item_playlist_lv.xml │ │ │ └── tab_fx_eq.xml │ │ ├── values │ │ │ ├── sorting_id.xml │ │ │ ├── dimens.xml │ │ │ └── plural_strings.xml │ │ └── values-ru │ │ │ └── plural_strings.xml │ │ └── java │ │ └── com │ │ └── parabola │ │ └── newtone │ │ ├── presentation │ │ ├── base │ │ │ ├── Scrollable.kt │ │ │ ├── Sortable.kt │ │ │ ├── DialogDismissLifecycleObserver.kt │ │ │ └── BaseDialogFragment.java │ │ ├── player │ │ │ ├── sleeptimer │ │ │ │ └── SleepTimerView.kt │ │ │ ├── timetosleepinfo │ │ │ │ └── TimeToSleepInfoView.kt │ │ │ └── PlayerView.kt │ │ ├── main │ │ │ ├── start │ │ │ │ ├── StartView.kt │ │ │ │ └── StartPresenter.kt │ │ │ ├── playlists │ │ │ │ └── TabPlaylistView.kt │ │ │ ├── albums │ │ │ │ └── TabAlbumView.kt │ │ │ ├── artists │ │ │ │ └── TabArtistView.kt │ │ │ └── tracks │ │ │ │ └── TabTrackView.kt │ │ ├── playlist │ │ │ ├── chooseplaylist │ │ │ │ ├── ChoosePlaylistView.kt │ │ │ │ └── ChoosePlaylistPresenter.kt │ │ │ ├── folderslist │ │ │ │ └── FoldersListView.kt │ │ │ ├── createplaylist │ │ │ │ └── CreatePlaylistView.kt │ │ │ ├── renameplaylist │ │ │ │ └── RenamePlaylistView.kt │ │ │ ├── recentlyadded │ │ │ │ └── RecentlyAddedPlaylistView.kt │ │ │ ├── favourites │ │ │ │ └── FavouritesPlaylistView.kt │ │ │ ├── playlist │ │ │ │ └── PlaylistView.kt │ │ │ └── queue │ │ │ │ └── QueueView.kt │ │ ├── settings │ │ │ ├── SettingView.kt │ │ │ ├── colorthemeselector │ │ │ │ ├── ColorThemeSelectorView.kt │ │ │ │ └── ColorThemeSelectorPresenter.kt │ │ │ └── dialog │ │ │ │ └── IsaacNewtoneDialog.kt │ │ ├── trackadditionalinfo │ │ │ ├── TrackAdditionalInfoView.kt │ │ │ └── TrackAdditionalInfoPresenter.kt │ │ ├── audioeffects │ │ │ ├── equalizer │ │ │ │ ├── FxEqualizerView.kt │ │ │ │ └── FxEqualizerPresenter.kt │ │ │ ├── settings │ │ │ │ └── FxAudioSettingsView.kt │ │ │ └── EqPresetsSelectorDialog.kt │ │ ├── artist │ │ │ └── ArtistView.kt │ │ ├── folder │ │ │ └── FolderView.kt │ │ ├── album │ │ │ └── AlbumView.kt │ │ ├── artisttracks │ │ │ └── ArtistTracksView.kt │ │ ├── search │ │ │ └── SearchFragmentView.kt │ │ ├── mainactivity │ │ │ └── MainView.java │ │ └── view │ │ │ ├── LockableViewPager.java │ │ │ └── WrapContentHeightViewPager.java │ │ ├── di │ │ ├── ComponentFactory.kt │ │ └── app │ │ │ ├── modules │ │ │ ├── NavigationModule.kt │ │ │ ├── ConfigModule.kt │ │ │ └── AndroidAppModule.kt │ │ │ └── AppComponent.kt │ │ ├── util │ │ ├── OnTabSelectedAdapter.java │ │ ├── SeekBarChangeAdapter.java │ │ ├── TimeFormatterTool.java │ │ └── RecyclerViewExt.kt │ │ └── adapter │ │ └── ExcludedFolderAdapter.java ├── lint.xml ├── proguard-rules.pro ├── proguard-rules-debug.pro └── google-services.json ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── .idea ├── encodings.xml ├── vcs.xml ├── codeStyles │ └── codeStyleConfig.xml ├── compiler.xml ├── checkstyleidea-libs │ └── readme.txt ├── dictionaries │ └── Arslan.xml ├── statistic.xml └── checkstyle-idea.xml ├── .gitignore ├── gradle.properties └── README.md /data/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /search_feature/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /Slidr/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml 3 | -------------------------------------------------------------------------------- /player_feature/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sleep_timer_feature/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release 3 | -------------------------------------------------------------------------------- /data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /player_feature/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Slidr/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /data/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | data 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parabola47/Newtone/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/isaac_newtone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parabola47/Newtone/HEAD/app/src/main/res/drawable/isaac_newtone.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parabola47/Newtone/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parabola47/Newtone/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parabola47/Newtone/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parabola47/Newtone/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parabola47/Newtone/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parabola47/Newtone/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parabola47/Newtone/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parabola47/Newtone/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parabola47/Newtone/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parabola47/Newtone/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':Slidr' 2 | include ':app', ':domain', ':data', ':player_feature' 3 | include ':search_feature' 4 | include ':sleep_timer_feature' 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/interactor/type/Irrelevant.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.interactor.type; 2 | 3 | public enum Irrelevant { 4 | INSTANCE 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/base/Scrollable.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.base 2 | 3 | interface Scrollable { 4 | fun smoothScrollToTop() 5 | } 6 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/exception/ItemNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.exception; 2 | 3 | public class ItemNotFoundException extends Exception { 4 | } 5 | -------------------------------------------------------------------------------- /Slidr/src/main/java/com/r0adkll/slidr/model/SlidrInterface.java: -------------------------------------------------------------------------------- 1 | package com.r0adkll.slidr.model; 2 | 3 | 4 | public interface SlidrInterface { 5 | 6 | void lock(); 7 | void unlock(); 8 | } 9 | -------------------------------------------------------------------------------- /Slidr/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Slidr/src/main/java/com/r0adkll/slidr/model/SlidrPosition.java: -------------------------------------------------------------------------------- 1 | package com.r0adkll.slidr.model; 2 | 3 | 4 | public enum SlidrPosition { 5 | 6 | LEFT, 7 | RIGHT, 8 | TOP, 9 | BOTTOM, 10 | VERTICAL, 11 | HORIZONTAL 12 | } 13 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/player/sleeptimer/SleepTimerView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.player.sleeptimer 2 | 3 | import moxy.MvpView 4 | import moxy.viewstate.strategy.alias.OneExecution 5 | 6 | @OneExecution 7 | interface SleepTimerView : MvpView 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/anim_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/anim/anim_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/text_view_circle_corners_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /domain/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | implementation fileTree(dir: 'libs', include: ['*.jar']) 5 | 6 | api(libs.rxJava) 7 | } 8 | 9 | var jv = JavaVersion.toVersion(libs.versions.java.get()) 10 | sourceCompatibility = jv 11 | targetCompatibility = jv 12 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/main/start/StartView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.main.start 2 | 3 | import moxy.MvpView 4 | import moxy.viewstate.strategy.alias.AddToEndSingle 5 | 6 | @AddToEndSingle 7 | interface StartView : MvpView { 8 | fun setPermissionPanelVisibility(visible: Boolean) 9 | } 10 | -------------------------------------------------------------------------------- /sleep_timer_feature/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'org.jetbrains.kotlin.jvm' 4 | } 5 | 6 | dependencies { 7 | // RxJava 8 | api(libs.rxJava) 9 | } 10 | 11 | 12 | java { 13 | var jv = JavaVersion.toVersion(libs.versions.java.get()) 14 | sourceCompatibility = jv 15 | targetCompatibility = jv 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/xml/app_widget_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/interactor/RepositoryInteractor.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.interactor; 2 | 3 | import io.reactivex.Observable; 4 | 5 | public interface RepositoryInteractor { 6 | 7 | 8 | Observable observeLoadingState(); 9 | 10 | 11 | enum LoadingState { 12 | IN_LOADING, LOADED 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /player_feature/src/main/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Player Feature 4 | 5 | Добавить в избранное 6 | Удалить из избранного 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/color/mtrl_toggle_btn_bg_color_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/color_picker_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/repository/ResourceRepository.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.repository; 2 | 3 | public interface ResourceRepository { 4 | 5 | String getString(int strResId); 6 | 7 | String getString(int strResId, Object... formatArgs); 8 | 9 | 10 | String getQuantityString(int pluralStringId, int quantity); 11 | } 12 | -------------------------------------------------------------------------------- /search_feature/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'kotlin' 4 | } 5 | 6 | java { 7 | var jv = JavaVersion.toVersion(libs.versions.java.get()) 8 | sourceCompatibility = jv 9 | targetCompatibility = jv 10 | } 11 | 12 | dependencies { 13 | implementation project(path: ':domain') 14 | 15 | implementation(libs.simmetrics) 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/song_progress_thumb.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/setting_item_container_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /player_feature/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Player Feature 4 | Newtone 5 | 6 | Add to favorites 7 | Remove from favorites 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/main/playlists/TabPlaylistView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.main.playlists 2 | 3 | import com.parabola.domain.model.Playlist 4 | import moxy.MvpView 5 | import moxy.viewstate.strategy.alias.AddToEndSingle 6 | 7 | @AddToEndSingle 8 | interface TabPlaylistView : MvpView { 9 | fun refreshPlaylists(playlists: List) 10 | 11 | fun setItemDividerShowing(showed: Boolean) 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/playlist/chooseplaylist/ChoosePlaylistView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.playlist.chooseplaylist 2 | 3 | import com.parabola.domain.model.Playlist 4 | import moxy.MvpView 5 | import moxy.viewstate.strategy.alias.AddToEndSingle 6 | 7 | @AddToEndSingle 8 | interface ChoosePlaylistView : MvpView { 9 | fun refreshPlaylists(playlists: List) 10 | 11 | fun closeScreen() 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_recent_add.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/playlist/folderslist/FoldersListView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.playlist.folderslist 2 | 3 | import com.parabola.domain.model.Folder 4 | import moxy.MvpView 5 | import moxy.viewstate.strategy.alias.AddToEndSingle 6 | 7 | @AddToEndSingle 8 | interface FoldersListView : MvpView { 9 | fun refreshFolders(folders: List) 10 | 11 | fun setItemDividerShowing(showed: Boolean) 12 | } 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/executor/SchedulerProvider.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.executor; 2 | 3 | import io.reactivex.Scheduler; 4 | import io.reactivex.annotations.NonNull; 5 | 6 | public interface SchedulerProvider { 7 | @NonNull 8 | Scheduler computation(); 9 | 10 | @NonNull 11 | Scheduler newThread(); 12 | 13 | @NonNull 14 | Scheduler io(); 15 | 16 | @NonNull 17 | Scheduler ui(); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/widget_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/exception/AlreadyExistsException.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.exception; 2 | 3 | public class AlreadyExistsException extends IllegalArgumentException { 4 | 5 | public AlreadyExistsException() { 6 | } 7 | 8 | public AlreadyExistsException(String s) { 9 | super(s); 10 | } 11 | 12 | public AlreadyExistsException(String s, Throwable throwable) { 13 | super(s, throwable); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/model/Artist.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.model; 2 | 3 | public interface Artist { 4 | int getId(); 5 | String getName(); 6 | 7 | int getAlbumsCount(); 8 | int getTracksCount(); 9 | 10 | default String getSearchView() { 11 | return getName().toLowerCase(); 12 | } 13 | 14 | default boolean equals(Artist o) { 15 | return getId() == o.getId(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/settings/SettingView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.settings 2 | 3 | import moxy.MvpView 4 | import moxy.viewstate.strategy.alias.AddToEndSingle 5 | 6 | @AddToEndSingle 7 | interface SettingView : MvpView { 8 | fun setNotificationColorSwitchChecked(isChecked: Boolean) 9 | fun setNotificationArtworkSwitchChecked(isChecked: Boolean) 10 | fun setShowListItemDividerSwitchChecked(isChecked: Boolean) 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/playlist/createplaylist/CreatePlaylistView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.playlist.createplaylist 2 | 3 | import moxy.MvpView 4 | import moxy.viewstate.strategy.alias.OneExecution 5 | 6 | @OneExecution 7 | interface CreatePlaylistView : MvpView { 8 | fun focusOnInputField() 9 | 10 | fun showPlaylistTitleIsEmptyError() 11 | fun showPlaylistTitleAlreadyExistsError() 12 | 13 | fun closeScreen() 14 | } 15 | -------------------------------------------------------------------------------- /.idea/checkstyleidea-libs/readme.txt: -------------------------------------------------------------------------------- 1 | This folder contains libraries copied from the "Newtone" project. 2 | It is managed by the CheckStyle-IDEA IDE plugin. 3 | Do not modify this folder while the IDE is running. 4 | When the IDE is stopped, you may delete this folder at any time. It will be recreated as needed. 5 | In order to prevent the CheckStyle-IDEA IDE plugin from creating this folder, 6 | uncheck the "Copy libraries from project directory" option in the CheckStyle-IDEA settings dialog. 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/player/timetosleepinfo/TimeToSleepInfoView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.player.timetosleepinfo 2 | 3 | import moxy.MvpView 4 | import moxy.viewstate.strategy.alias.AddToEndSingle 5 | import moxy.viewstate.strategy.alias.OneExecution 6 | 7 | @AddToEndSingle 8 | interface TimeToSleepInfoView : MvpView { 9 | fun updateTimeToEndText(timeToEndText: String) 10 | 11 | @OneExecution 12 | fun closeScreen() 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/di/ComponentFactory.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.di 2 | 3 | import com.parabola.newtone.MainApplication 4 | import com.parabola.newtone.di.app.AppComponent 5 | 6 | object ComponentFactory { 7 | // TODO убрать аннотацию после того как MainApplication будет переведён на котлин 8 | @JvmStatic 9 | fun createApplicationComponent(app: MainApplication): AppComponent { 10 | return AppComponent.Initializer.init(app) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/base/Sortable.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.base 2 | 3 | /** 4 | * Реализуйте этот интерфейс в фрагементах, для которых может настраиваться сортировка пользователем. 5 | * При реализации интерфейса в меню нижней панели будет отображаться пункт меню сортировки, после чего 6 | * будет открываться окно предлагающее выбор типа сортировки текущего видимого списка 7 | */ 8 | interface Sortable { 9 | val listType: String 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/di/app/modules/NavigationModule.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.di.app.modules 2 | 3 | import com.parabola.newtone.presentation.router.MainRouter 4 | import com.parabola.newtone.presentation.router.MainRouterImpl 5 | import dagger.Module 6 | import dagger.Provides 7 | import javax.inject.Singleton 8 | 9 | @Module 10 | class NavigationModule { 11 | @Singleton 12 | @Provides 13 | fun provideMainRouter(): MainRouter = MainRouterImpl() 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_folder.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/repository/FolderRepository.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.repository; 2 | 3 | import com.parabola.domain.model.Folder; 4 | 5 | import java.util.List; 6 | 7 | import io.reactivex.Single; 8 | 9 | public interface FolderRepository { 10 | 11 | //Проверяет количество треков в указанной папке, а также во всех подпапках 12 | long tracksCountInFolderRecursively(String folderPath); 13 | 14 | Single> getAll(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/trackadditionalinfo/TrackAdditionalInfoView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.trackadditionalinfo 2 | 3 | import com.parabola.domain.model.Track 4 | import moxy.MvpView 5 | import moxy.viewstate.strategy.alias.AddToEndSingle 6 | import moxy.viewstate.strategy.alias.OneExecution 7 | 8 | @AddToEndSingle 9 | interface TrackAdditionalInfoView : MvpView { 10 | fun setTrack(track: Track) 11 | 12 | @OneExecution 13 | fun closeScreen() 14 | } -------------------------------------------------------------------------------- /app/src/main/res/menu/album_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/menu/artist_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/menu/tab_track_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/dictionaries/Arslan.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | acra 5 | arium 6 | butterknife 7 | croller 8 | interactor 9 | moxy 10 | newtone 11 | nocase 12 | recyclerview 13 | tracklist 14 | tracklists 15 | unfavourite 16 | virtualizer 17 | 18 | 19 | -------------------------------------------------------------------------------- /Slidr/src/main/java/com/r0adkll/slidr/model/SlidrListenerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.r0adkll.slidr.model; 2 | 3 | 4 | public class SlidrListenerAdapter implements SlidrListener { 5 | 6 | @Override 7 | public void onSlideStateChanged(int state) { 8 | } 9 | 10 | @Override 11 | public void onSlideChange(float percent) { 12 | } 13 | 14 | @Override 15 | public void onSlideOpened() { 16 | } 17 | 18 | @Override 19 | public boolean onSlideClosed() { 20 | return false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/util/OnTabSelectedAdapter.java: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.util; 2 | 3 | import com.google.android.material.tabs.TabLayout; 4 | 5 | public abstract class OnTabSelectedAdapter implements TabLayout.OnTabSelectedListener { 6 | @Override 7 | public void onTabSelected(TabLayout.Tab tab) { 8 | } 9 | 10 | @Override 11 | public void onTabUnselected(TabLayout.Tab tab) { 12 | } 13 | 14 | @Override 15 | public void onTabReselected(TabLayout.Tab tab) { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/res/color/drag_button_background_color_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_drop.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_kebab.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pause.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/file_provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/util/SeekBarChangeAdapter.java: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.util; 2 | 3 | import android.widget.SeekBar; 4 | 5 | public abstract class SeekBarChangeAdapter implements SeekBar.OnSeekBarChangeListener { 6 | @Override 7 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 8 | } 9 | 10 | @Override 11 | public void onStartTrackingTouch(SeekBar seekBar) { 12 | } 13 | 14 | @Override 15 | public void onStopTrackingTouch(SeekBar seekBar) { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/audioeffects/equalizer/FxEqualizerView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.audioeffects.equalizer 2 | 3 | import com.parabola.domain.interactor.player.AudioEffectsInteractor.EqBand 4 | import moxy.MvpView 5 | import moxy.viewstate.strategy.alias.AddToEndSingle 6 | 7 | @AddToEndSingle 8 | interface FxEqualizerView : MvpView { 9 | 10 | fun setEqChecked(checked: Boolean) 11 | fun setMaxEqLevel(level: Int) 12 | fun setMinEqLevel(level: Int) 13 | fun refreshBands(bands: List) 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/playlist/renameplaylist/RenamePlaylistView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.playlist.renameplaylist 2 | 3 | import moxy.MvpView 4 | import moxy.viewstate.strategy.alias.OneExecution 5 | 6 | @OneExecution 7 | interface RenamePlaylistView : MvpView { 8 | fun focusOnInputField() 9 | 10 | fun setPlaylistTitle(playlistTitle: String) 11 | fun setTitleSelected() 12 | 13 | fun closeScreen() 14 | 15 | fun showPlaylistTitleIsEmptyError() 16 | fun showPlaylistTitleAlreadyExistsError() 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_excluded_folders.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/interactor/player/PlayerSetting.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.interactor.player; 2 | 3 | import io.reactivex.Observable; 4 | 5 | public interface PlayerSetting { 6 | void setNotificationBackgroundColorized(boolean colorized); 7 | boolean isNotificationBackgroundColorized(); 8 | Observable observeIsNotificationBackgroundColorized(); 9 | 10 | 11 | void setNotificationArtworkShow(boolean show); 12 | boolean isNotificationArtworkShow(); 13 | Observable observeNotificationArtworkShow(); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/edit_text_container.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /player_feature/src/main/res/drawable/ic_notification_favourite.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favourite_select.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favourite_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/settings/colorthemeselector/ColorThemeSelectorView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.settings.colorthemeselector 2 | 3 | import com.parabola.domain.settings.ViewSettingsInteractor.ColorTheme 4 | import com.parabola.domain.settings.ViewSettingsInteractor.PrimaryColor 5 | import moxy.MvpView 6 | import moxy.viewstate.strategy.alias.AddToEndSingle 7 | 8 | @AddToEndSingle 9 | interface ColorThemeSelectorView : MvpView { 10 | fun setDarkLightTheme(colorTheme: ColorTheme) 11 | fun setPrimaryColor(primaryColor: PrimaryColor) 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_next.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/model/Album.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.model; 2 | 3 | public interface Album { 4 | int getId(); 5 | String getTitle(); 6 | ArtImage getArtImage(); 7 | 8 | int getYear(); 9 | int getArtistId(); 10 | String getArtistName(); 11 | 12 | int getTracksCount(); 13 | 14 | default String getSearchView() { 15 | return (getArtistName() + " " + getTitle()).toLowerCase(); 16 | } 17 | 18 | default boolean equals(Album o) { 19 | return getId() == o.getId(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/model/Playlist.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.model; 2 | 3 | import java.util.List; 4 | 5 | public interface Playlist { 6 | int getId(); 7 | String getTitle(); 8 | List getPlaylistTracks(); 9 | int size(); 10 | 11 | default String getSearchView() { 12 | return getTitle().toLowerCase(); 13 | } 14 | 15 | default boolean equals(Playlist o) { 16 | return getId() == o.getId(); 17 | } 18 | 19 | 20 | interface TrackItem { 21 | int getTrackId(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/repository/ExcludedFolderRepository.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.repository; 2 | 3 | import com.parabola.domain.interactor.type.Irrelevant; 4 | 5 | import java.util.List; 6 | 7 | import io.reactivex.Completable; 8 | import io.reactivex.Observable; 9 | 10 | public interface ExcludedFolderRepository { 11 | 12 | 13 | Observable onExcludeFoldersUpdatesObserver(); 14 | Completable addExcludedFolder(String folder); 15 | Completable refreshExcludedFolders(List folders); 16 | List getExcludedFolders(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_prev.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_radio_button_checked.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/sorting_id.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 5 | 20 6 | 30 7 | 40 8 | 50 9 | 10 | 11 | 10 12 | 20 13 | 30 14 | 15 | 16 | 10 17 | 20 18 | 19 | -------------------------------------------------------------------------------- /.idea/statistic.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/main/albums/TabAlbumView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.main.albums 2 | 3 | import com.parabola.domain.model.Album 4 | import com.parabola.domain.settings.ViewSettingsInteractor.AlbumItemView 5 | import moxy.MvpView 6 | import moxy.viewstate.strategy.alias.AddToEndSingle 7 | 8 | @AddToEndSingle 9 | interface TabAlbumView : MvpView { 10 | fun refreshAlbums(albums: List) 11 | 12 | fun setAlbumViewSettings(viewSettings: AlbumItemView) 13 | fun setItemDividerShowing(showed: Boolean) 14 | 15 | fun setSectionShowing(enable: Boolean) 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/main/artists/TabArtistView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.main.artists 2 | 3 | import com.parabola.domain.model.Artist 4 | import com.parabola.domain.settings.ViewSettingsInteractor.ArtistItemView 5 | import moxy.MvpView 6 | import moxy.viewstate.strategy.alias.AddToEndSingle 7 | 8 | @AddToEndSingle 9 | interface TabArtistView : MvpView { 10 | fun refreshArtists(artists: List) 11 | 12 | fun setItemViewSettings(viewSettings: ArtistItemView) 13 | fun setItemDividerShowing(showed: Boolean) 14 | 15 | fun setSectionShowing(enable: Boolean) 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_remove_circle.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sorting.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/repository/PermissionHandler.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.repository; 2 | 3 | import io.reactivex.Observable; 4 | 5 | public interface PermissionHandler { 6 | 7 | // оповещение об обновлении в указанном разрешении. 8 | // Разрешение может быть не выдано, данный метод призывается лишь для перепроверки после 9 | // возможной выдачи разрешения 10 | void invalidatePermission(Type type); 11 | 12 | 13 | boolean hasPermission(Type type); 14 | 15 | Observable observePermissionUpdates(Type type); 16 | 17 | enum Type { 18 | FILE_STORAGE 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_hamburger.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /data/src/main/java/com/parabola/data/utils/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.parabola.data.utils; 2 | 3 | import java.io.File; 4 | 5 | public final class FileUtil { 6 | 7 | private FileUtil() { 8 | throw new IllegalAccessError(); 9 | } 10 | 11 | 12 | //true если файл по данному пути поличилось удалить или если файл отсутсвовал изначально, 13 | // false, если не удалось удалить файл 14 | public static boolean deleteFile(String path) { 15 | File deleteFile = new File(path); 16 | if (deleteFile.exists()) { 17 | return deleteFile.delete(); 18 | } 19 | return true; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle files 2 | .gradle/ 3 | build/ 4 | 5 | # Local configuration file (sdk path, etc) 6 | local.properties 7 | 8 | # Log/OS Files 9 | *.log 10 | 11 | # Android Studio generated files and folders 12 | captures/ 13 | .externalNativeBuild/ 14 | .cxx/ 15 | *.aab 16 | *.apk 17 | output-metadata.json 18 | 19 | # IntelliJ 20 | *.iml 21 | .idea/ 22 | misc.xml 23 | deploymentTargetDropDown.xml 24 | render.experimental.xml 25 | 26 | 27 | 28 | # Keystore files 29 | *.jks 30 | *.keystore 31 | 32 | # Google Services (e.g. APIs or Firebase) 33 | google-services.json 34 | 35 | # Android Profiling 36 | *.hprof 37 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_eq.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/check_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_album.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64dp 4 | 4dp 5 | 4dp 6 | 4dp 7 | 48dp 8 | 48dp 9 | 10 | 24dp 11 | 8dp 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/menu/playlist_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Slidr/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | namespace 'com.r0adkll.slidr' 5 | 6 | compileSdkVersion 33 7 | 8 | defaultConfig { 9 | minSdkVersion 21 10 | targetSdkVersion 33 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | 14 | testOptions { 15 | unitTests.returnDefaultValues = true 16 | } 17 | 18 | compileOptions { 19 | var jv = JavaVersion.toVersion(libs.versions.java.get()) 20 | sourceCompatibility = jv 21 | targetCompatibility = jv 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation(libs.androidx.appcompat) 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play_accent.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/color/mtrl_toggle_btn_text_color_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/checkstyle-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/artist/ArtistView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.artist 2 | 3 | import com.parabola.domain.model.Album 4 | import com.parabola.domain.settings.ViewSettingsInteractor.AlbumItemView 5 | import moxy.MvpView 6 | import moxy.viewstate.strategy.alias.AddToEndSingle 7 | 8 | @AddToEndSingle 9 | interface ArtistView : MvpView { 10 | fun setArtistName(artistName: String) 11 | fun setTracksCount(tracksCount: Int) 12 | fun setAlbumsCount(albumsCount: Int) 13 | 14 | fun refreshAlbums(albums: List) 15 | 16 | fun setAlbumViewSettings(albumViewSettings: AlbumItemView) 17 | fun setItemDividerShowing(showed: Boolean) 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/folder/FolderView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.folder 2 | 3 | import com.parabola.domain.model.Track 4 | import com.parabola.domain.settings.ViewSettingsInteractor.TrackItemView 5 | import moxy.MvpView 6 | import moxy.viewstate.strategy.alias.AddToEndSingle 7 | 8 | @AddToEndSingle 9 | interface FolderView : MvpView { 10 | fun setFolderPath(folderPath: String) 11 | 12 | fun refreshTracks(tracks: List) 13 | 14 | fun setItemViewSettings(viewSettings: TrackItemView) 15 | fun setItemDividerShowing(showed: Boolean) 16 | fun setSectionShowing(enable: Boolean) 17 | 18 | fun setCurrentTrack(trackId: Int) 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/playlist/recentlyadded/RecentlyAddedPlaylistView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.playlist.recentlyadded 2 | 3 | import com.parabola.domain.model.Track 4 | import com.parabola.domain.settings.ViewSettingsInteractor.TrackItemView 5 | import moxy.MvpView 6 | import moxy.viewstate.strategy.alias.AddToEndSingle 7 | 8 | @AddToEndSingle 9 | interface RecentlyAddedPlaylistView : MvpView { 10 | fun setCurrentTrack(trackId: Int) 11 | fun refreshTracks(tracks: List) 12 | 13 | fun setItemViewSettings(viewSettings: TrackItemView) 14 | fun setItemDividerShowing(showed: Boolean) 15 | 16 | fun removeTrack(trackId: Int) 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/res/menu/folder_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/main/tracks/TabTrackView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.main.tracks 2 | 3 | import com.parabola.domain.model.Track 4 | import com.parabola.domain.settings.ViewSettingsInteractor.TrackItemView 5 | import moxy.MvpView 6 | import moxy.viewstate.strategy.alias.AddToEndSingle 7 | 8 | @AddToEndSingle 9 | interface TabTrackView : MvpView { 10 | fun setCurrentTrack(trackId: Int) 11 | fun refreshTracks(tracks: List) 12 | 13 | fun setItemViewSettings(viewSettings: TrackItemView) 14 | fun setItemDividerShowing(showed: Boolean) 15 | 16 | fun setSectionShowing(enable: Boolean) 17 | 18 | fun removeTrack(trackId: Int) 19 | } 20 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Для сохранения строки вылета в исходном коде 16 | -keepattributes SourceFile,LineNumberTable 17 | -renamesourcefileattribute SourceFile 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/playlist/favourites/FavouritesPlaylistView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.playlist.favourites 2 | 3 | import com.parabola.domain.model.Track 4 | import com.parabola.domain.settings.ViewSettingsInteractor.TrackItemView 5 | import moxy.MvpView 6 | import moxy.viewstate.strategy.alias.AddToEndSingle 7 | 8 | @AddToEndSingle 9 | interface FavouritesPlaylistView : MvpView { 10 | fun setPlaylistChangerActivation(activate: Boolean) 11 | 12 | fun setCurrentTrack(trackId: Int) 13 | fun refreshTracks(tracks: List) 14 | 15 | fun setItemViewSettings(viewSettings: TrackItemView) 16 | fun setItemDividerShowing(showed: Boolean) 17 | } 18 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/repository/ArtistRepository.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.repository; 2 | 3 | import com.parabola.domain.model.Artist; 4 | 5 | import java.util.List; 6 | 7 | import io.reactivex.Observable; 8 | import io.reactivex.Single; 9 | 10 | public interface ArtistRepository { 11 | 12 | Single getById(int artistId); 13 | Single> getAll(Sorting sorting); 14 | default Single> getAll() { 15 | return getAll(null); 16 | } 17 | Observable getAllAsObservable(); 18 | 19 | 20 | enum Sorting { 21 | BY_NAME, BY_NAME_DESC, 22 | BY_TRACKS_COUNT, BY_TRACKS_COUNT_DESC, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pause_accent.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/album/AlbumView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.album 2 | 3 | import com.parabola.domain.model.Track 4 | import com.parabola.domain.settings.ViewSettingsInteractor.TrackItemView 5 | import moxy.MvpView 6 | import moxy.viewstate.strategy.alias.AddToEndSingle 7 | 8 | @AddToEndSingle 9 | interface AlbumView : MvpView { 10 | fun setAlbumTitle(albumTitle: String) 11 | fun setAlbumArtist(artistName: String) 12 | fun setAlbumArt(artCover: Any?) 13 | 14 | fun refreshTracks(tracks: List) 15 | fun setItemViewSettings(itemViewSettings: TrackItemView) 16 | fun setItemDividerShowing(showed: Boolean) 17 | 18 | fun setCurrentTrack(trackId: Int) 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/eq_thumb_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_playlist.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /player_feature/src/main/res/drawable/ic_notification_not_favourite.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/artisttracks/ArtistTracksView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.artisttracks 2 | 3 | import com.parabola.domain.model.Track 4 | import com.parabola.domain.settings.ViewSettingsInteractor.TrackItemView 5 | import moxy.MvpView 6 | import moxy.viewstate.strategy.alias.AddToEndSingle 7 | 8 | @AddToEndSingle 9 | interface ArtistTracksView : MvpView { 10 | fun refreshTracks(tracks: List) 11 | fun setItemViewSettings(viewSettings: TrackItemView) 12 | fun setItemDividerShowing(showed: Boolean) 13 | fun setSectionShowing(enable: Boolean) 14 | 15 | fun setArtistName(artistName: String) 16 | fun setTracksCountTxt(tracksCountStr: String) 17 | 18 | fun setCurrentTrack(trackId: Int) 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favourite.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/album_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /data/src/main/java/com/parabola/data/model/FolderData.java: -------------------------------------------------------------------------------- 1 | package com.parabola.data.model; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import com.parabola.domain.model.Folder; 6 | 7 | public final class FolderData implements Folder { 8 | public String folderPath; 9 | public int tracksCount; 10 | 11 | @Override 12 | public String getAbsolutePath() { 13 | return folderPath; 14 | } 15 | 16 | @Override 17 | public int getTracksCount() { 18 | return tracksCount; 19 | } 20 | 21 | @Override 22 | public boolean equals(@Nullable Object obj) { 23 | if (!(obj instanceof Folder)) { 24 | return false; 25 | } 26 | return equals((Folder) obj); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sleep_timer_feature/src/main/java/com/parabola/sleep_timer_feature/SleepTimerInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.sleep_timer_feature 2 | 3 | import io.reactivex.Completable 4 | import io.reactivex.Observable 5 | import io.reactivex.Single 6 | 7 | interface SleepTimerInteractor { 8 | fun start(timeToSleepMs: Long): Completable 9 | 10 | fun reset(): Completable 11 | 12 | fun launched(): Boolean 13 | 14 | fun remainingTimeToEnd(): Single 15 | 16 | fun observeRemainingTimeToEnd(): Observable 17 | fun observeIsTimerRunning(): Observable 18 | 19 | fun onTimerFinished(): Observable 20 | 21 | class TimerNotLaunchedException : IllegalStateException() 22 | class TimerAlreadyLaunchedException : IllegalStateException() 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/playlist/playlist/PlaylistView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.playlist.playlist 2 | 3 | import com.parabola.domain.model.Track 4 | import com.parabola.domain.settings.ViewSettingsInteractor.TrackItemView 5 | import moxy.MvpView 6 | import moxy.viewstate.strategy.alias.AddToEndSingle 7 | 8 | @AddToEndSingle 9 | interface PlaylistView : MvpView { 10 | fun setPlaylistTitle(playlistTitle: String) 11 | 12 | fun setPlaylistChangerActivation(activate: Boolean) 13 | 14 | fun refreshTracks(tracks: List) 15 | fun setTracksCount(playlistSize: Int) 16 | fun setCurrentTrack(trackId: Int) 17 | 18 | fun setItemViewSettings(viewSettings: TrackItemView) 19 | fun setItemDividerShowing(showed: Boolean) 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_playlist.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/playlist/queue/QueueView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.playlist.queue 2 | 3 | import com.parabola.domain.model.Track 4 | import com.parabola.domain.settings.ViewSettingsInteractor.TrackItemView 5 | import moxy.MvpView 6 | import moxy.viewstate.strategy.alias.AddToEndSingle 7 | import moxy.viewstate.strategy.alias.OneExecution 8 | 9 | @AddToEndSingle 10 | interface QueueView : MvpView { 11 | fun setCurrentTrackPosition(currentTrackPosition: Int) 12 | fun refreshTracks(tracks: List) 13 | fun setTrackCount(tracksCount: Int) 14 | 15 | fun setItemViewSettings(viewSettings: TrackItemView) 16 | fun setItemDividerShowing(showed: Boolean) 17 | 18 | @OneExecution 19 | fun goToItem(itemPosition: Int) 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_album_colored.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /data/src/main/java/com/parabola/data/executor/SchedulerProviderImpl.java: -------------------------------------------------------------------------------- 1 | package com.parabola.data.executor; 2 | 3 | import com.parabola.domain.executor.SchedulerProvider; 4 | 5 | import io.reactivex.Scheduler; 6 | import io.reactivex.android.schedulers.AndroidSchedulers; 7 | import io.reactivex.schedulers.Schedulers; 8 | 9 | public class SchedulerProviderImpl implements SchedulerProvider { 10 | 11 | public Scheduler computation() { 12 | return Schedulers.computation(); 13 | } 14 | 15 | 16 | public Scheduler io() { 17 | return Schedulers.io(); 18 | } 19 | 20 | 21 | public Scheduler newThread() { 22 | return Schedulers.newThread(); 23 | } 24 | 25 | 26 | public Scheduler ui() { 27 | return AndroidSchedulers.mainThread(); 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_lyrics.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/fx_eq_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_queue.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_timer.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/base/DialogDismissLifecycleObserver.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.base 2 | 3 | import androidx.appcompat.app.AlertDialog 4 | import androidx.lifecycle.Lifecycle 5 | import androidx.lifecycle.LifecycleObserver 6 | import androidx.lifecycle.LifecycleOwner 7 | import androidx.lifecycle.OnLifecycleEvent 8 | 9 | class DialogDismissLifecycleObserver(dialog: AlertDialog) : LifecycleObserver { 10 | 11 | private var dialog: AlertDialog? 12 | 13 | 14 | init { 15 | this.dialog = dialog 16 | } 17 | 18 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) 19 | fun onDestroy(owner: LifecycleOwner) { 20 | owner.lifecycle.removeObserver(this) 21 | if (dialog != null) { 22 | dialog!!.dismiss() 23 | dialog = null 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /search_feature/src/main/java/com/parabola/search_feature/SearchResult.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.search_feature 2 | 3 | import com.parabola.domain.model.Album 4 | import com.parabola.domain.model.Artist 5 | import com.parabola.domain.model.Playlist 6 | import com.parabola.domain.model.Track 7 | import java.util.* 8 | 9 | class SearchResult( 10 | artists: List, 11 | albums: List, 12 | tracks: List, 13 | playlists: List 14 | ) { 15 | @JvmField 16 | val artists: List = Collections.unmodifiableList(artists) 17 | 18 | @JvmField 19 | val albums: List = Collections.unmodifiableList(albums) 20 | 21 | @JvmField 22 | val tracks: List = Collections.unmodifiableList(tracks) 23 | 24 | @JvmField 25 | val playlists: List = Collections.unmodifiableList(playlists) 26 | } 27 | -------------------------------------------------------------------------------- /data/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /player_feature/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/proguard-rules-debug.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -dontobfuscate 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_drag.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_shuffled.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/fx_ic_tune.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/audioeffects/settings/FxAudioSettingsView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.audioeffects.settings 2 | 3 | import moxy.MvpView 4 | import moxy.viewstate.strategy.alias.AddToEndSingle 5 | 6 | @AddToEndSingle 7 | interface FxAudioSettingsView : MvpView { 8 | fun setPlaybackSpeedSwitch(enabled: Boolean) 9 | fun setPlaybackSpeedSeekbar(progress: Int) 10 | fun setPlaybackSpeedText(speed: Float) 11 | 12 | fun setPlaybackPitchSwitch(enabled: Boolean) 13 | fun setPlaybackPitchSeekbar(progress: Int) 14 | fun setPlaybackPitchText(pitch: Float) 15 | 16 | fun hideBassBoostPanel() 17 | fun setBassBoostSeekbar(currentLevel: Int) 18 | fun setBassBoostSwitch(bassBoostEnabled: Boolean) 19 | 20 | fun hideVirtualizerPanel() 21 | fun setVirtualizerSeekbar(currentLevel: Int) 22 | fun setVirtualizerSwitch(virtualizerEnabled: Boolean) 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/res/values-ru/plural_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %1$d трек 6 | %1$d трека 7 | %1$d треков 8 | %1$d треков 9 | 10 | 11 | 12 | %1$d альбом 13 | %1$d альбома 14 | %1$d альбомов 15 | %1$d альбомов 16 | 17 | 18 | 19 | %1$d папка 20 | %1$d папки 21 | %1$d папок 22 | %1$d папок 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/repository/AlbumRepository.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.repository; 2 | 3 | import com.parabola.domain.model.Album; 4 | 5 | import java.util.List; 6 | 7 | import io.reactivex.Observable; 8 | import io.reactivex.Single; 9 | 10 | public interface AlbumRepository { 11 | 12 | Single getById(int albumId); 13 | 14 | Single> getAll(Sorting sorting); 15 | default Single> getAll() { 16 | return getAll(null); 17 | } 18 | Observable getAllAsObservable(); 19 | 20 | Single> getByArtist(int artistId, Sorting sorting); 21 | default Single> getByArtist(int artistId) { 22 | return getByArtist(artistId, null); 23 | } 24 | 25 | enum Sorting { 26 | BY_TITLE, BY_TITLE_DESC, 27 | BY_ARTIST, BY_ARTIST_DESC, 28 | BY_YEAR, BY_YEAR_DESC, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Slidr/src/main/java/com/r0adkll/slidr/model/SlidrListener.java: -------------------------------------------------------------------------------- 1 | package com.r0adkll.slidr.model; 2 | 3 | import androidx.customview.widget.ViewDragHelper; 4 | 5 | /** 6 | * This listener interface is for receiving events from the sliding panel such as state changes 7 | * and slide progress 8 | */ 9 | public interface SlidrListener { 10 | 11 | /** 12 | * This is called when the {@link ViewDragHelper} calls it's 13 | * state change callback. 14 | * 15 | * @see ViewDragHelper#STATE_IDLE 16 | * @see ViewDragHelper#STATE_DRAGGING 17 | * @see ViewDragHelper#STATE_SETTLING 18 | * 19 | * @param state the {@link ViewDragHelper} state 20 | */ 21 | void onSlideStateChanged(int state); 22 | 23 | void onSlideChange(float percent); 24 | 25 | void onSlideOpened(); 26 | 27 | /** 28 | * @return true than event was processed in the callback. 29 | */ 30 | boolean onSlideClosed(); 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/base/BaseDialogFragment.java: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.base; 2 | 3 | import android.app.Dialog; 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | import androidx.appcompat.app.AlertDialog; 9 | import androidx.fragment.app.DialogFragment; 10 | 11 | public class BaseDialogFragment extends DialogFragment { 12 | 13 | private AlertDialog dialog; 14 | 15 | public static BaseDialogFragment build(@NonNull AlertDialog dialog) { 16 | BaseDialogFragment dialogFragment = new BaseDialogFragment(); 17 | dialogFragment.dialog = dialog; 18 | 19 | return dialogFragment; 20 | } 21 | 22 | public BaseDialogFragment() { 23 | setRetainInstance(true); 24 | } 25 | 26 | @NonNull 27 | @Override 28 | public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 29 | return dialog; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /data/src/main/java/com/parabola/data/repository/ResourceRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.parabola.data.repository; 2 | 3 | import android.content.Context; 4 | 5 | import com.parabola.domain.repository.ResourceRepository; 6 | 7 | public final class ResourceRepositoryImpl implements ResourceRepository { 8 | 9 | private final Context context; 10 | 11 | public ResourceRepositoryImpl(Context context) { 12 | this.context = context; 13 | } 14 | 15 | @Override 16 | public String getString(int strResId) { 17 | return context.getString(strResId); 18 | } 19 | 20 | @Override 21 | public String getString(int strResId, Object... formatArgs) { 22 | return context.getString(strResId, formatArgs); 23 | } 24 | 25 | 26 | @Override 27 | public String getQuantityString(int pluralStringId, int quantity) { 28 | return context.getResources().getQuantityString(pluralStringId, quantity, quantity); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_loop.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /data/src/main/java/com/parabola/data/model/ArtistData.java: -------------------------------------------------------------------------------- 1 | package com.parabola.data.model; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import com.parabola.domain.model.Artist; 6 | 7 | public final class ArtistData implements Artist { 8 | public int id; 9 | public String name; 10 | 11 | public int albumsCount; 12 | public int tracksCount; 13 | 14 | @Override 15 | public int getId() { 16 | return id; 17 | } 18 | 19 | @Override 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | @Override 25 | public int getAlbumsCount() { 26 | return albumsCount; 27 | } 28 | 29 | @Override 30 | public int getTracksCount() { 31 | return tracksCount; 32 | } 33 | 34 | @Override 35 | public boolean equals(@Nullable Object obj) { 36 | if (!(obj instanceof Artist)) { 37 | return false; 38 | } 39 | return equals((Artist) obj); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /data/src/main/java/com/parabola/data/repository/FolderRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.parabola.data.repository; 2 | 3 | import com.parabola.domain.model.Folder; 4 | import com.parabola.domain.repository.FolderRepository; 5 | 6 | import java.util.List; 7 | 8 | import io.reactivex.Single; 9 | 10 | public final class FolderRepositoryImpl implements FolderRepository { 11 | 12 | private final DataExtractor dataExtractor; 13 | 14 | 15 | public FolderRepositoryImpl(DataExtractor dataExtractor) { 16 | this.dataExtractor = dataExtractor; 17 | } 18 | 19 | 20 | @Override 21 | public Single> getAll() { 22 | return Single.fromCallable(() -> dataExtractor.folders); 23 | } 24 | 25 | @Override 26 | public long tracksCountInFolderRecursively(String folderPath) { 27 | return dataExtractor.tracks.stream() 28 | .filter(track -> track.getFilePath().startsWith(folderPath)) 29 | .count(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/interactor/FolderInteractor.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.interactor; 2 | 3 | import com.parabola.domain.interactor.player.PlayerInteractor; 4 | import com.parabola.domain.repository.TrackRepository; 5 | 6 | import io.reactivex.internal.functions.Functions; 7 | import io.reactivex.internal.observers.ConsumerSingleObserver; 8 | 9 | public final class FolderInteractor { 10 | 11 | private final TrackRepository trackRepo; 12 | private final PlayerInteractor playerInteractor; 13 | 14 | public FolderInteractor(TrackRepository trackRepo, PlayerInteractor playerInteractor) { 15 | this.trackRepo = trackRepo; 16 | this.playerInteractor = playerInteractor; 17 | } 18 | 19 | 20 | public void shuffleFolder(String folderPath) { 21 | trackRepo.getByFolder(folderPath) 22 | .subscribe(new ConsumerSingleObserver<>( 23 | playerInteractor::startInShuffleMode, 24 | Functions.ERROR_CONSUMER)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/di/app/modules/ConfigModule.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.di.app.modules 2 | 3 | import android.content.SharedPreferences 4 | import com.parabola.data.repository.SortingRepositoryImpl 5 | import com.parabola.data.settings.ViewSettingsInteractorImpl 6 | import com.parabola.domain.repository.PermissionHandler 7 | import com.parabola.domain.repository.SortingRepository 8 | import com.parabola.domain.settings.ViewSettingsInteractor 9 | import dagger.Module 10 | import dagger.Provides 11 | import javax.inject.Singleton 12 | 13 | @Module 14 | class ConfigModule { 15 | 16 | @Singleton 17 | @Provides 18 | fun provideTrackSortingRepository( 19 | preferences: SharedPreferences, 20 | accessRepo: PermissionHandler, 21 | ): SortingRepository = SortingRepositoryImpl(preferences, accessRepo) 22 | 23 | @Singleton 24 | @Provides 25 | fun provideViewSettingsInteractor(preferences: SharedPreferences): ViewSettingsInteractor = 26 | ViewSettingsInteractorImpl(preferences) 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/search/SearchFragmentView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.search 2 | 3 | import com.parabola.domain.model.Album 4 | import com.parabola.domain.model.Artist 5 | import com.parabola.domain.model.Playlist 6 | import com.parabola.domain.model.Track 7 | import com.parabola.domain.settings.ViewSettingsInteractor.TrackItemView 8 | import moxy.MvpView 9 | import moxy.viewstate.strategy.alias.AddToEndSingle 10 | import moxy.viewstate.strategy.alias.OneExecution 11 | 12 | @AddToEndSingle 13 | interface SearchFragmentView : MvpView { 14 | @OneExecution 15 | fun focusOnSearchView() 16 | 17 | fun refreshArtists(artists: List) 18 | fun refreshAlbums(albums: List) 19 | fun refreshTracks(tracks: List) 20 | fun refreshPlaylists(playlists: List) 21 | 22 | fun setTrackItemViewSettings(trackItemView: TrackItemView) 23 | fun setItemDividerShowing(showed: Boolean) 24 | 25 | fun clearAllLists() 26 | 27 | fun setLoadDataProgressBarVisibility(visible: Boolean) 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_artist.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/tab_item_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/trackadditionalinfo/TrackAdditionalInfoPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.trackadditionalinfo 2 | 3 | import com.parabola.domain.repository.TrackRepository 4 | import com.parabola.newtone.di.app.AppComponent 5 | import io.reactivex.internal.functions.Functions 6 | import io.reactivex.internal.observers.ConsumerSingleObserver 7 | import moxy.InjectViewState 8 | import moxy.MvpPresenter 9 | import javax.inject.Inject 10 | 11 | @InjectViewState 12 | class TrackAdditionalInfoPresenter( 13 | appComponent: AppComponent, 14 | private val trackId: Int, 15 | ) : MvpPresenter() { 16 | 17 | @Inject 18 | lateinit var trackRepo: TrackRepository 19 | 20 | init { 21 | appComponent.inject(this) 22 | } 23 | 24 | 25 | override fun onFirstViewAttach() { 26 | trackRepo.getById(trackId).subscribe( 27 | ConsumerSingleObserver( 28 | viewState::setTrack, 29 | Functions.ERROR_CONSUMER 30 | ) 31 | ) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_sorting.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/settings/dialog/IsaacNewtoneDialog.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.settings.dialog 2 | 3 | import android.app.Dialog 4 | import android.os.Bundle 5 | import android.widget.ImageView 6 | import androidx.appcompat.widget.AppCompatImageView 7 | import androidx.core.content.ContextCompat.getDrawable 8 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 9 | import com.parabola.newtone.R 10 | import moxy.MvpAppCompatDialogFragment 11 | 12 | class IsaacNewtoneDialog : MvpAppCompatDialogFragment() { 13 | 14 | private lateinit var newtoneImage: ImageView 15 | 16 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 17 | newtoneImage = AppCompatImageView(requireContext()) 18 | newtoneImage.setImageDrawable( 19 | getDrawable(requireContext(), R.drawable.isaac_newtone) 20 | ) 21 | 22 | return MaterialAlertDialogBuilder(requireContext()) 23 | .setTitle(R.string.app_name) 24 | .setNegativeButton(R.string.dialog_cancel, null) 25 | .setView(newtoneImage) 26 | .create() 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/model/Folder.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.model; 2 | 3 | import java.io.File; 4 | 5 | public interface Folder { 6 | default String getFolderName() { 7 | String absolutePath = getAbsolutePath(); 8 | 9 | if (absolutePath.endsWith(File.separator)) { 10 | absolutePath = absolutePath.substring(0, absolutePath.length() - 1); 11 | } 12 | 13 | int index = absolutePath.lastIndexOf(File.separator); 14 | 15 | return absolutePath.substring(index + 1); 16 | } 17 | default String getPathToParent() { 18 | String absolutePath = getAbsolutePath(); 19 | 20 | if (absolutePath.endsWith(File.separator)) { 21 | absolutePath = absolutePath.substring(0, absolutePath.length() - 1); 22 | } 23 | 24 | int index = absolutePath.lastIndexOf(File.separator); 25 | 26 | return absolutePath.substring(0, index); 27 | } 28 | 29 | String getAbsolutePath(); 30 | 31 | int getTracksCount(); 32 | 33 | default boolean equals(Folder o) { 34 | return getAbsolutePath().equals(o.getAbsolutePath()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_loop_one.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_tab_artist.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_folders_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_tab_album.xml: -------------------------------------------------------------------------------- 1 | 2 | 22 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/utils/TracklistTool.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.utils; 2 | 3 | import com.parabola.domain.model.Folder; 4 | import com.parabola.domain.model.Track; 5 | 6 | import java.util.List; 7 | 8 | public final class TracklistTool { 9 | 10 | private TracklistTool() { 11 | throw new IllegalAccessError(); 12 | } 13 | 14 | public static boolean isFolderListsIdentical(List firstList, List secondList) { 15 | if (firstList.size() != secondList.size()) { 16 | return false; 17 | } 18 | 19 | for (int i = 0; i < firstList.size(); i++) { 20 | if (!firstList.get(i).equals(secondList.get(i))) 21 | return false; 22 | } 23 | 24 | return true; 25 | } 26 | 27 | 28 | public static boolean isTracklistsIdentical(List firstList, List secondList) { 29 | if (firstList.size() != secondList.size()) { 30 | return false; 31 | } 32 | 33 | for (int i = 0; i < firstList.size(); i++) { 34 | if (!firstList.get(i).equals(secondList.get(i))) 35 | return false; 36 | } 37 | 38 | return true; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "853841908272", 4 | "firebase_url": "https://newtone-9e95e.firebaseio.com", 5 | "project_id": "newtone-9e95e", 6 | "storage_bucket": "newtone-9e95e.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:853841908272:android:627f2c9771de5ba83b13f5", 12 | "android_client_info": { 13 | "package_name": "com.parabola.newtone" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "853841908272-rit03s9g8gu6sg0g48v32c980epo8mcr.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyCiU4g0kizsk354JdcPXQtU58W9udCW9Qo" 25 | } 26 | ], 27 | "services": { 28 | "appinvite_service": { 29 | "other_platform_oauth_client": [ 30 | { 31 | "client_id": "853841908272-rit03s9g8gu6sg0g48v32c980epo8mcr.apps.googleusercontent.com", 32 | "client_type": 3 33 | } 34 | ] 35 | } 36 | } 37 | } 38 | ], 39 | "configuration_version": "1" 40 | } -------------------------------------------------------------------------------- /app/src/main/res/values/plural_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | no tracks 6 | %1$d track 7 | %1$d tracks 8 | %1$d tracks 9 | %1$d tracks 10 | %1$d tracks 11 | 12 | 13 | 14 | no albums 15 | %1$d album 16 | %1$d albums 17 | %1$d albums 18 | %1$d albums 19 | %1$d albums 20 | 21 | 22 | 23 | no folders 24 | %1$d folder 25 | %1$d folders 26 | %1$d folders 27 | %1$d folders 28 | %1$d folders 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/mainactivity/MainView.java: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.mainactivity; 2 | 3 | import com.parabola.domain.settings.ViewSettingsInteractor.PrimaryColor; 4 | 5 | import moxy.MvpView; 6 | import moxy.viewstate.strategy.OneExecutionStateStrategy; 7 | import moxy.viewstate.strategy.StateStrategyType; 8 | import moxy.viewstate.strategy.alias.AddToEndSingle; 9 | 10 | @AddToEndSingle 11 | public interface MainView extends MvpView { 12 | 13 | void refreshPrimaryColor(PrimaryColor primaryColor); 14 | 15 | void setTrackTitle(String trackTitle); 16 | void setArtistName(String artist); 17 | 18 | void setPlaybackButtonAsPause(); 19 | void setPlaybackButtonAsPlay(); 20 | 21 | @StateStrategyType(OneExecutionStateStrategy.class) 22 | void showBottomSlider(); 23 | @StateStrategyType(OneExecutionStateStrategy.class) 24 | void hideBottomSlider(); 25 | 26 | @StateStrategyType(OneExecutionStateStrategy.class) 27 | void requestStoragePermissionDialog(); 28 | 29 | void setDurationMax(int max); 30 | void setDurationProgress(int progress); 31 | 32 | void setPlayerBarOpacity(float alpha); 33 | void setPlayerBarVisibility(boolean visible); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_artist_colored.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /data/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | namespace 'com.parabola.data' 5 | 6 | compileSdkVersion 33 7 | 8 | 9 | defaultConfig { 10 | minSdkVersion 21 11 | targetSdkVersion 33 12 | multiDexEnabled true 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | compileOptions { 24 | // Flag to enable support for the new language APIs 25 | coreLibraryDesugaringEnabled true 26 | 27 | var jv = JavaVersion.toVersion(libs.versions.java.get()) 28 | sourceCompatibility = jv 29 | targetCompatibility = jv 30 | } 31 | 32 | } 33 | 34 | dependencies { 35 | implementation fileTree(dir: 'libs', include: ['*.jar']) 36 | implementation project(path: ':domain') 37 | implementation project(path: ':search_feature') 38 | 39 | implementation(libs.androidx.appcompat) 40 | coreLibraryDesugaring(libs.android.desugar.jdk) 41 | implementation(libs.rxAndroid) 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clef.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 13 | 16 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | android.defaults.buildfeatures.buildconfig=true 21 | android.nonTransitiveRClass=false 22 | android.nonFinalResIds=false 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/util/TimeFormatterTool.java: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.util; 2 | 3 | import java.util.Locale; 4 | 5 | public final class TimeFormatterTool { 6 | 7 | private TimeFormatterTool() { 8 | } 9 | 10 | public static String formatMillisecondsToMinutes(long ms) { 11 | return formatSecondsToMinutes(ms / 1000); 12 | } 13 | 14 | /** 15 | * @param s количество секунд, которое необходимо преобразовать 16 | * @return строка в формате MM:SS, если количество минут меньше 60. 17 | * Если количество минут больше 60, то в формате H:MM:SS 18 | */ 19 | public static String formatSecondsToMinutes(long s) { 20 | if (s < 0) { 21 | throw new IllegalArgumentException("Parameter s must be larger than or equal to 0. Value: " + s); 22 | } 23 | long hours = s / (3600); 24 | long minutes = (s - (hours * 3600)) / 60; 25 | long seconds = s - ((hours * 3600) + (minutes * 60)); 26 | 27 | if (hours < 1) { 28 | return String.format(Locale.getDefault(), "%1d:%02d", minutes, seconds); 29 | } else { 30 | return String.format(Locale.getDefault(), "%1d:%02d:%02d", hours, minutes, seconds); 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /data/src/main/java/com/parabola/data/model/PlaylistData.java: -------------------------------------------------------------------------------- 1 | package com.parabola.data.model; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import com.parabola.domain.model.Playlist; 6 | 7 | import java.util.List; 8 | 9 | public final class PlaylistData implements Playlist { 10 | public int id; 11 | public String title; 12 | public List playlistTracks; 13 | 14 | @Override 15 | public int getId() { 16 | return id; 17 | } 18 | 19 | @Override 20 | public String getTitle() { 21 | return title; 22 | } 23 | 24 | @Override 25 | public List getPlaylistTracks() { 26 | return playlistTracks; 27 | } 28 | 29 | @Override 30 | public int size() { 31 | return playlistTracks.size(); 32 | } 33 | 34 | @Override 35 | public boolean equals(@Nullable Object obj) { 36 | if (!(obj instanceof Playlist)) { 37 | return false; 38 | } 39 | return equals((Playlist) obj); 40 | } 41 | 42 | public static class TrackItemData implements TrackItem { 43 | public int trackId; 44 | 45 | @Override 46 | public int getTrackId() { 47 | return trackId; 48 | } 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/audioeffects/EqPresetsSelectorDialog.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.audioeffects 2 | 3 | import android.app.Dialog 4 | import android.os.Bundle 5 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 6 | import com.parabola.domain.interactor.player.AudioEffectsInteractor 7 | import com.parabola.newtone.MainApplication 8 | import com.parabola.newtone.R 9 | import moxy.MvpAppCompatDialogFragment 10 | import javax.inject.Inject 11 | 12 | class EqPresetsSelectorDialog : MvpAppCompatDialogFragment() { 13 | 14 | @Inject 15 | lateinit var fxInteractor: AudioEffectsInteractor 16 | 17 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 18 | (requireActivity().application as MainApplication).appComponent.inject(this) 19 | val presets = fxInteractor.presets 20 | 21 | return MaterialAlertDialogBuilder(requireContext()) 22 | .setTitle(R.string.preset_selector_dialog_title) 23 | .setItems(presets.toTypedArray()) { _, presetIndex -> 24 | fxInteractor.usePreset(presetIndex) 25 | fxInteractor.setEqEnable(true) 26 | } 27 | .setNegativeButton(R.string.dialog_cancel, null) 28 | .create() 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/repository/PlaylistRepository.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.repository; 2 | 3 | import com.parabola.domain.interactor.type.Irrelevant; 4 | import com.parabola.domain.model.Playlist; 5 | 6 | import java.util.List; 7 | 8 | import io.reactivex.Completable; 9 | import io.reactivex.Observable; 10 | import io.reactivex.Single; 11 | 12 | public interface PlaylistRepository { 13 | 14 | Single getById(int playlistId); 15 | Single> getAll(); 16 | Observable getAllAsObservable(); 17 | 18 | //выбрасывает ошибку в Rx AlreadyExistsException, 19 | //если плейлист с таким именем уже существует 20 | Single addNew(String newPlaylistTitle); 21 | //выбрасывает ошибку в Rx AlreadyExistsException, 22 | //если плейлист с таким именем, за исключением переданного в playlistId уже существует 23 | Completable rename(int playlistId, String newTitle); 24 | Completable remove(int playlistId); 25 | 26 | Observable observePlaylistsUpdates(); 27 | 28 | Completable addTracksToPlaylist(int playlistId, int... tracksIds); 29 | Completable removeTrack(int playlistId, int trackId); 30 | Completable moveTrack(int playlistId, int oldPosition, int newPosition); 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/view/LockableViewPager.java: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.view; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | 7 | import androidx.viewpager.widget.ViewPager; 8 | 9 | public class LockableViewPager extends ViewPager { 10 | 11 | private boolean swipeLocked; 12 | 13 | public LockableViewPager(Context context) { 14 | super(context); 15 | } 16 | 17 | public LockableViewPager(Context context, AttributeSet attrs) { 18 | super(context, attrs); 19 | } 20 | 21 | public boolean getSwipeLocked() { 22 | return swipeLocked; 23 | } 24 | 25 | public void setSwipeLocked(boolean swipeLocked) { 26 | this.swipeLocked = swipeLocked; 27 | } 28 | 29 | @Override 30 | public boolean onTouchEvent(MotionEvent event) { 31 | return !swipeLocked && super.onTouchEvent(event); 32 | } 33 | 34 | @Override 35 | public boolean onInterceptTouchEvent(MotionEvent event) { 36 | return !swipeLocked && super.onInterceptTouchEvent(event); 37 | } 38 | 39 | @Override 40 | public boolean canScrollHorizontally(int direction) { 41 | return !swipeLocked && super.canScrollHorizontally(direction); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/player/PlayerView.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.player 2 | 3 | import com.parabola.domain.interactor.player.PlayerInteractor 4 | import com.parabola.domain.model.Track 5 | import moxy.MvpView 6 | import moxy.viewstate.strategy.alias.AddToEndSingle 7 | 8 | @AddToEndSingle 9 | interface PlayerView : MvpView { 10 | fun setArtist(artistName: String) 11 | fun setAlbum(albumTitle: String) 12 | fun setTitle(trackTitle: String) 13 | fun setDurationText(durationFormatted: String) 14 | fun setDurationMs(durationMs: Int) 15 | fun setIsFavourite(isFavourite: Boolean) 16 | 17 | fun setPlaybackButtonAsPause() 18 | fun setPlaybackButtonAsPlay() 19 | fun setRepeatMode(repeatMode: PlayerInteractor.RepeatMode) 20 | fun setShuffleEnabling(enable: Boolean) 21 | 22 | fun setCurrentTimeMs(currentTimeMs: Int) 23 | 24 | fun setTimerButtonVisibility(visible: Boolean) 25 | 26 | fun setViewPagerSlide(lock: Boolean) 27 | 28 | fun refreshTracks(tracks: List) 29 | fun setAlbumImagePosition(currentTrackPosition: Int, smoothScroll: Boolean) 30 | 31 | //в градусах 32 | fun setTrackSettingsRotation(rotation: Float) 33 | 34 | fun setRootViewOpacity(alpha: Float) 35 | fun setRootViewVisibility(visible: Boolean) 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_folder_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 22 | 23 | 24 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/menu/player_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | 23 | 24 | 28 | 29 | 33 | 34 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 21 | 22 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_artist.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 22 | 23 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/request_permission_panel.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 21 | 22 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /player_feature/src/main/java/com/parabola/player_feature/AudioRenderersFactory.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.player_feature 2 | 3 | import android.content.Context 4 | import android.os.Handler 5 | import com.google.android.exoplayer2.Renderer 6 | import com.google.android.exoplayer2.RenderersFactory 7 | import com.google.android.exoplayer2.audio.AudioRendererEventListener 8 | import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer 9 | import com.google.android.exoplayer2.mediacodec.MediaCodecSelector 10 | import com.google.android.exoplayer2.metadata.MetadataOutput 11 | import com.google.android.exoplayer2.text.TextOutput 12 | import com.google.android.exoplayer2.video.VideoRendererEventListener 13 | 14 | internal class AudioRenderersFactory(private val context: Context) : RenderersFactory { 15 | 16 | override fun createRenderers( 17 | eventHandler: Handler, 18 | videoRendererEventListener: VideoRendererEventListener, 19 | audioRendererEventListener: AudioRendererEventListener, 20 | textRendererOutput: TextOutput, 21 | metadataRendererOutput: MetadataOutput 22 | ): Array { 23 | val audioRenderer = MediaCodecAudioRenderer( 24 | context, 25 | MediaCodecSelector.DEFAULT, 26 | eventHandler, 27 | audioRendererEventListener 28 | ) 29 | 30 | return arrayOf(audioRenderer) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /player_feature/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | 5 | android { 6 | namespace 'com.parabola.player_feature' 7 | 8 | compileSdkVersion 33 9 | 10 | defaultConfig { 11 | minSdkVersion 21 12 | targetSdkVersion 33 13 | multiDexEnabled true 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | // Flag to enable support for the new language APIs 27 | coreLibraryDesugaringEnabled true 28 | 29 | var jv = JavaVersion.toVersion(libs.versions.java.get()) 30 | sourceCompatibility = jv 31 | targetCompatibility = jv 32 | } 33 | 34 | } 35 | 36 | dependencies { 37 | implementation project(path: ':domain') 38 | 39 | implementation fileTree(dir: 'libs', include: ['*.jar']) 40 | 41 | //kotlin 42 | implementation(libs.androidx.core.ktx) 43 | implementation(libs.kotlin.stdlib) 44 | 45 | coreLibraryDesugaring(libs.android.desugar.jdk) 46 | 47 | implementation(libs.bundles.exoplayer) 48 | implementation(libs.rxAndroid) 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clef_colored.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 16 | 19 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /data/src/main/java/com/parabola/data/repository/ArtistRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.parabola.data.repository; 2 | 3 | import static com.parabola.data.utils.SortingUtil.getArtistComparatorBySorting; 4 | 5 | import com.parabola.domain.model.Artist; 6 | import com.parabola.domain.repository.ArtistRepository; 7 | 8 | import java.util.List; 9 | 10 | import io.reactivex.Observable; 11 | import io.reactivex.Single; 12 | 13 | 14 | public final class ArtistRepositoryImpl implements ArtistRepository { 15 | private static final String LOG_TAG = ArtistRepositoryImpl.class.getSimpleName(); 16 | 17 | private final DataExtractor dataExtractor; 18 | 19 | 20 | public ArtistRepositoryImpl(DataExtractor dataExtractor) { 21 | this.dataExtractor = dataExtractor; 22 | } 23 | 24 | 25 | @Override 26 | public Single getById(int artistId) { 27 | return Observable.fromIterable(dataExtractor.artists) 28 | .filter(artist -> artist.getId() == artistId) 29 | .firstOrError(); 30 | } 31 | 32 | 33 | @Override 34 | public Single> getAll(Sorting sorting) { 35 | return Observable.fromIterable(dataExtractor.artists) 36 | .toSortedList(getArtistComparatorBySorting(sorting)); 37 | } 38 | 39 | 40 | @Override 41 | public Observable getAllAsObservable() { 42 | return Observable.fromIterable(dataExtractor.artists); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/util/RecyclerViewExt.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.util 2 | 3 | import androidx.recyclerview.widget.LinearLayoutManager 4 | import androidx.recyclerview.widget.RecyclerView 5 | 6 | 7 | /** 8 | * Производит скроллинг к [position], только если первый видимый элемент больше [position]. 9 | * То есть если текущая позиция верхнего видимого элемента ниже [position]. 10 | * Использовать только в случае, если [RecyclerView.getLayoutManager] имеет тип [LinearLayoutManager] 11 | * 12 | * @param position позиция, к которой будет производиться скроллинг 13 | * @return true, если в результате был выполнен скроллинг; false, если нет 14 | */ 15 | fun RecyclerView.scrollUp(position: Int): Boolean { 16 | if (layoutManager !is LinearLayoutManager) { 17 | return false 18 | } 19 | 20 | val linearLayoutManager = layoutManager as LinearLayoutManager 21 | val firstVisibleItemPosition = linearLayoutManager.findFirstCompletelyVisibleItemPosition() 22 | 23 | if (firstVisibleItemPosition > position) { 24 | scrollToPosition(position) 25 | return true 26 | } 27 | 28 | return false 29 | } 30 | 31 | 32 | fun RecyclerView.smoothScrollToTop() { 33 | smoothScrollToPosition(0) 34 | } 35 | 36 | 37 | // Отображаемое на экране количество элементов. 38 | // Если элемент виден только частично, то он тоже считается видимым 39 | fun LinearLayoutManager.visibleItemsCount(): Int = 40 | findLastVisibleItemPosition() - findFirstVisibleItemPosition() 41 | -------------------------------------------------------------------------------- /search_feature/src/main/java/com/parabola/search_feature/SearchInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.search_feature 2 | 3 | import com.parabola.domain.model.Album 4 | import com.parabola.domain.model.Artist 5 | import com.parabola.domain.model.Playlist 6 | import com.parabola.domain.model.Track 7 | import io.reactivex.Single 8 | 9 | val EMPTY_SEARCH_RESULT = SearchResult(emptyList(), emptyList(), emptyList(), emptyList()) 10 | 11 | const val ARTISTS_SEARCH_MAX_LIMIT = 3L 12 | const val ALBUMS_SEARCH_MAX_LIMIT = 5L 13 | const val TRACKS_SEARCH_MAX_LIMIT = 20L 14 | const val PLAYLIST_SEARCH_MAX_LIMIT = 5L 15 | 16 | abstract class SearchInteractor { 17 | fun search(query: String): Single { 18 | return Single.zip( 19 | searchArtists(query), 20 | searchAlbums(query), 21 | searchTracks(query), 22 | searchPlaylists(query) 23 | ) { artists: List, albums: List, tracks: List, playlists: List -> 24 | SearchResult( 25 | artists, 26 | albums, 27 | tracks, 28 | playlists 29 | ) 30 | } 31 | .onErrorReturnItem(EMPTY_SEARCH_RESULT) 32 | } 33 | 34 | abstract fun searchArtists(query: String): Single> 35 | abstract fun searchAlbums(query: String): Single> 36 | abstract fun searchTracks(query: String): Single> 37 | abstract fun searchPlaylists(query: String): Single> 38 | 39 | } 40 | -------------------------------------------------------------------------------- /data/src/main/java/com/parabola/data/model/AlbumData.java: -------------------------------------------------------------------------------- 1 | package com.parabola.data.model; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import com.parabola.domain.model.Album; 8 | 9 | import java.util.function.Function; 10 | 11 | public final class AlbumData implements Album { 12 | public int id; 13 | public String title; 14 | public Function getArtFunction; 15 | 16 | public int year; 17 | 18 | public int artistId; 19 | public String artistName; 20 | 21 | public int tracksCount; 22 | 23 | 24 | @Override 25 | public int getId() { 26 | return id; 27 | } 28 | 29 | @Override 30 | public String getTitle() { 31 | return title; 32 | } 33 | 34 | @Override 35 | public Bitmap getArtImage() { 36 | return getArtFunction.apply(this); 37 | } 38 | 39 | @Override 40 | public int getYear() { 41 | return year; 42 | } 43 | 44 | @Override 45 | public int getArtistId() { 46 | return artistId; 47 | } 48 | 49 | @Override 50 | public String getArtistName() { 51 | return artistName; 52 | } 53 | 54 | @Override 55 | public int getTracksCount() { 56 | return tracksCount; 57 | } 58 | 59 | 60 | @Override 61 | public boolean equals(@Nullable Object obj) { 62 | if (!(obj instanceof Album)) { 63 | return false; 64 | } 65 | return equals((Album) obj); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/view/WrapContentHeightViewPager.java: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.view; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | 7 | import androidx.viewpager.widget.ViewPager; 8 | 9 | /** 10 | * ViewPager для отображения в {@link androidx.fragment.app.DialogFragment}.

11 | * Высчитывает свою высоту на основе внутреннего {@link View} с максимальной высотой 12 | */ 13 | public class WrapContentHeightViewPager extends ViewPager { 14 | 15 | public WrapContentHeightViewPager(Context context) { 16 | super(context); 17 | } 18 | 19 | public WrapContentHeightViewPager(Context context, AttributeSet attrs) { 20 | super(context, attrs); 21 | } 22 | 23 | @Override 24 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 25 | boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST; 26 | if (wrapHeight) { 27 | for (int i = 0; i < getChildCount(); i++) { 28 | View child = getChildAt(i); 29 | if (child != null) { 30 | child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 31 | int h = child.getMeasuredHeight(); 32 | 33 | heightMeasureSpec = Math.max(heightMeasureSpec, MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY)); 34 | } 35 | } 36 | } 37 | 38 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/res/menu/track_menu.xml: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | 23 | 24 | 28 | 29 | 33 | 34 | 38 | 39 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/repository/SortingRepository.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.repository; 2 | 3 | import io.reactivex.Observable; 4 | 5 | public interface SortingRepository { 6 | 7 | TrackRepository.Sorting allTracksSorting(); 8 | void setAllTracksSorting(TrackRepository.Sorting sorting); 9 | Observable observeAllTracksSorting(); 10 | 11 | TrackRepository.Sorting albumTracksSorting(); 12 | void setAlbumTracksSorting(TrackRepository.Sorting sorting); 13 | Observable observeAlbumTracksSorting(); 14 | 15 | TrackRepository.Sorting artistTracksSorting(); 16 | void setArtistTracksSorting(TrackRepository.Sorting sorting); 17 | Observable observeArtistTracksSorting(); 18 | 19 | TrackRepository.Sorting folderTracksSorting(); 20 | void setFolderTracksSorting(TrackRepository.Sorting sorting); 21 | Observable observeFolderTracksSorting(); 22 | 23 | AlbumRepository.Sorting allAlbumsSorting(); 24 | void setAllAlbumsSorting(AlbumRepository.Sorting sorting); 25 | Observable observeAllAlbumsSorting(); 26 | 27 | AlbumRepository.Sorting artistAlbumsSorting(); 28 | void setArtistAlbumsSorting(AlbumRepository.Sorting sorting); 29 | Observable observeArtistAlbumsSorting(); 30 | 31 | ArtistRepository.Sorting allArtistsSorting(); 32 | void setAllArtistsSorting(ArtistRepository.Sorting sorting); 33 | Observable observeAllArtistsSorting(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_system_playlist.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 24 | 25 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_playlist_choose.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 25 | 26 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/main/start/StartPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.main.start 2 | 3 | import com.parabola.domain.interactor.TrackInteractor 4 | import com.parabola.domain.repository.PermissionHandler 5 | import com.parabola.newtone.di.app.AppComponent 6 | import com.parabola.newtone.presentation.router.MainRouter 7 | import io.reactivex.disposables.Disposable 8 | import moxy.InjectViewState 9 | import moxy.MvpPresenter 10 | import javax.inject.Inject 11 | 12 | @InjectViewState 13 | class StartPresenter(appComponent: AppComponent) : MvpPresenter() { 14 | 15 | @Inject 16 | lateinit var router: MainRouter 17 | 18 | @Inject 19 | lateinit var accessRepo: PermissionHandler 20 | 21 | @Inject 22 | lateinit var trackInteractor: TrackInteractor 23 | 24 | private lateinit var storagePermissionObserver: Disposable 25 | 26 | 27 | init { 28 | appComponent.inject(this) 29 | } 30 | 31 | 32 | override fun onFirstViewAttach() { 33 | storagePermissionObserver = 34 | accessRepo.observePermissionUpdates(PermissionHandler.Type.FILE_STORAGE) 35 | .subscribe { viewState.setPermissionPanelVisibility(!it) } 36 | } 37 | 38 | override fun onDestroy() { 39 | storagePermissionObserver.dispose() 40 | } 41 | 42 | 43 | fun onClickRequestPermission() { 44 | router.openRequestStoragePermissionScreen() 45 | } 46 | 47 | fun onClickMenuShuffleAll() { 48 | trackInteractor.shuffleAll() 49 | } 50 | 51 | fun onClickMenuExcludedFolders() { 52 | router.openExcludedFolders() 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_eq_band.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 18 | 19 | 24 | 25 | 28 | 29 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Slidr/src/main/java/com/r0adkll/slidr/ConfigPanelSlideListener.java: -------------------------------------------------------------------------------- 1 | package com.r0adkll.slidr; 2 | 3 | 4 | import android.app.Activity; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.r0adkll.slidr.model.SlidrConfig; 9 | 10 | 11 | class ConfigPanelSlideListener extends ColorPanelSlideListener { 12 | 13 | private final SlidrConfig config; 14 | 15 | 16 | ConfigPanelSlideListener(@NonNull Activity activity, @NonNull SlidrConfig config) { 17 | super(activity, -1, -1); 18 | this.config = config; 19 | } 20 | 21 | 22 | @Override 23 | public void onStateChanged(int state) { 24 | if(config.getListener() != null){ 25 | config.getListener().onSlideStateChanged(state); 26 | } 27 | } 28 | 29 | 30 | @Override 31 | public void onClosed() { 32 | if(config.getListener() != null){ 33 | if(config.getListener().onSlideClosed()) { 34 | return; 35 | } 36 | } 37 | super.onClosed(); 38 | } 39 | 40 | 41 | @Override 42 | public void onOpened() { 43 | if(config.getListener() != null){ 44 | config.getListener().onSlideOpened(); 45 | } 46 | } 47 | 48 | 49 | @Override 50 | public void onSlideChange(float percent) { 51 | super.onSlideChange(percent); 52 | if(config.getListener() != null){ 53 | config.getListener().onSlideChange(percent); 54 | } 55 | } 56 | 57 | 58 | @Override 59 | protected int getPrimaryColor() { 60 | return config.getPrimaryColor(); 61 | } 62 | 63 | 64 | @Override 65 | protected int getSecondaryColor() { 66 | return config.getSecondaryColor(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 20 | 21 | 25 | 26 | 31 | 32 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /data/src/main/java/com/parabola/data/repository/AlbumRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.parabola.data.repository; 2 | 3 | import static com.parabola.data.utils.SortingUtil.getAlbumComparatorBySorting; 4 | 5 | import com.parabola.domain.model.Album; 6 | import com.parabola.domain.repository.AlbumRepository; 7 | 8 | import java.util.List; 9 | 10 | import io.reactivex.Observable; 11 | import io.reactivex.Single; 12 | 13 | public final class AlbumRepositoryImpl implements AlbumRepository { 14 | private static final String LOG_TAG = AlbumRepositoryImpl.class.getSimpleName(); 15 | 16 | 17 | private final DataExtractor dataExtractor; 18 | 19 | 20 | public AlbumRepositoryImpl(DataExtractor dataExtractor) { 21 | this.dataExtractor = dataExtractor; 22 | } 23 | 24 | 25 | @Override 26 | public Single getById(int albumId) { 27 | return Observable.fromIterable(dataExtractor.albums) 28 | .filter(album -> album.getId() == albumId) 29 | .firstOrError(); 30 | } 31 | 32 | 33 | @Override 34 | public Single> getAll(Sorting sorting) { 35 | return Observable.fromIterable(dataExtractor.albums) 36 | .toSortedList(getAlbumComparatorBySorting(sorting)); 37 | } 38 | 39 | 40 | @Override 41 | public Observable getAllAsObservable() { 42 | return Observable.fromIterable(dataExtractor.albums); 43 | } 44 | 45 | 46 | @Override 47 | public Single> getByArtist(int artistId, Sorting sorting) { 48 | return Observable.fromIterable(dataExtractor.albums) 49 | .filter(album -> album.getArtistId() == artistId) 50 | .toSortedList(getAlbumComparatorBySorting(sorting)); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Newtone 2 | 3 | [![API](https://img.shields.io/badge/API-16%2B-green.svg?style=flat)](https://android-arsenal.com/api?level=16) 4 | 5 | Newtone is a free open source android music player. 6 | 7 | You can download Newtone app by following [this](https://play.google.com/store/apps/details?id=com.parabola.newtone) link. 8 | 9 | ### Screenshots 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
TracksPlaylistsAlbumsPlayer
25 | 26 | ### Technology stack 27 | 28 | • Java + Kotlin\ 29 | • [RxJava](https://github.com/ReactiveX/RxJava)\ 30 | • Clean Architecture\ 31 | • MVP with [Moxy](https://github.com/moxy-community/Moxy)\ 32 | • [Dagger](https://github.com/google/dagger)\ 33 | • [ExoPlayer 2](https://github.com/google/ExoPlayer) 34 | 35 | ### Features 36 | 37 | • Beautiful design\ 38 | • Convenient navigation\ 39 | • Responsive interface\ 40 | • Fine control of display\ 41 | • Control of various audio effects\ 42 | • Powerful equalizer\ 43 | • Quick system scan\ 44 | • Continuous playback\ 45 | • Favourite tracks\ 46 | • Own playlists\ 47 | • Sleep timer 48 | -------------------------------------------------------------------------------- /Slidr/src/main/java/com/r0adkll/slidr/ColorPanelSlideListener.java: -------------------------------------------------------------------------------- 1 | package com.r0adkll.slidr; 2 | 3 | import android.animation.ArgbEvaluator; 4 | import android.app.Activity; 5 | 6 | import androidx.annotation.ColorInt; 7 | 8 | import com.r0adkll.slidr.widget.SliderPanel; 9 | 10 | 11 | class ColorPanelSlideListener implements SliderPanel.OnPanelSlideListener { 12 | 13 | private final Activity activity; 14 | private final int primaryColor; 15 | private final int secondaryColor; 16 | private final ArgbEvaluator evaluator = new ArgbEvaluator(); 17 | 18 | 19 | ColorPanelSlideListener(Activity activity, @ColorInt int primaryColor, @ColorInt int secondaryColor) { 20 | this.activity = activity; 21 | this.primaryColor = primaryColor; 22 | this.secondaryColor = secondaryColor; 23 | } 24 | 25 | 26 | @Override 27 | public void onStateChanged(int state) { 28 | // Unused. 29 | } 30 | 31 | 32 | @Override 33 | public void onClosed() { 34 | activity.finish(); 35 | activity.overridePendingTransition(0, 0); 36 | } 37 | 38 | 39 | @Override 40 | public void onOpened() { 41 | // Unused. 42 | } 43 | 44 | 45 | @Override 46 | public void onSlideChange(float percent) { 47 | if (areColorsValid()) { 48 | int newColor = (int) evaluator.evaluate(percent, getPrimaryColor(), getSecondaryColor()); 49 | activity.getWindow().setStatusBarColor(newColor); 50 | } 51 | } 52 | 53 | 54 | protected int getPrimaryColor() { 55 | return primaryColor; 56 | } 57 | 58 | 59 | protected int getSecondaryColor() { 60 | return secondaryColor; 61 | } 62 | 63 | 64 | private boolean areColorsValid() { 65 | return getPrimaryColor() != -1 && getSecondaryColor() != -1; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/di/app/modules/AndroidAppModule.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.di.app.modules 2 | 3 | import android.content.ContentResolver 4 | import android.content.Context 5 | import android.content.SharedPreferences 6 | import com.parabola.data.executor.SchedulerProviderImpl 7 | import com.parabola.data.repository.PermissionHandlerImpl 8 | import com.parabola.data.repository.ResourceRepositoryImpl 9 | import com.parabola.domain.executor.SchedulerProvider 10 | import com.parabola.domain.repository.PermissionHandler 11 | import com.parabola.domain.repository.ResourceRepository 12 | import com.parabola.newtone.MainApplication 13 | import dagger.Module 14 | import dagger.Provides 15 | import javax.inject.Singleton 16 | 17 | @Module 18 | class AndroidAppModule(private val newtoneApp: MainApplication) { 19 | 20 | @Singleton 21 | @Provides 22 | fun provideContext(): Context = newtoneApp 23 | 24 | @Singleton 25 | @Provides 26 | fun schedulerProvider(): SchedulerProvider = SchedulerProviderImpl() 27 | 28 | @Singleton 29 | @Provides 30 | fun provideSharedPreferences(context: Context): SharedPreferences = 31 | context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE) 32 | 33 | @Singleton 34 | @Provides 35 | fun provideContentResolver(): ContentResolver = newtoneApp.contentResolver 36 | 37 | @Singleton 38 | @Provides 39 | fun permissionService(context: Context): PermissionHandler = PermissionHandlerImpl(context) 40 | 41 | @Singleton 42 | @Provides 43 | fun provideResourcesProvider(context: Context): ResourceRepository = 44 | ResourceRepositoryImpl(context) 45 | 46 | companion object { 47 | private const val SHARED_PREFERENCES_NAME = "com.parabola.newtone.SHARED_PREFERENCES" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_folder.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 24 | 25 | 32 | 33 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_audio_effects.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 21 | 22 | 26 | 27 | 31 | 32 | 33 | 34 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_playlist.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 26 | 27 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/audioeffects/equalizer/FxEqualizerPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.audioeffects.equalizer 2 | 3 | import com.parabola.domain.interactor.player.AudioEffectsInteractor 4 | import com.parabola.newtone.di.app.AppComponent 5 | import com.parabola.newtone.presentation.router.MainRouter 6 | import io.reactivex.disposables.CompositeDisposable 7 | import io.reactivex.disposables.Disposable 8 | import moxy.InjectViewState 9 | import moxy.MvpPresenter 10 | import javax.inject.Inject 11 | 12 | @InjectViewState 13 | class FxEqualizerPresenter(appComponent: AppComponent) : MvpPresenter() { 14 | 15 | @Inject 16 | lateinit var fxInteractor: AudioEffectsInteractor 17 | 18 | @Inject 19 | lateinit var router: MainRouter 20 | 21 | private val disposables = CompositeDisposable() 22 | 23 | 24 | init { 25 | appComponent.inject(this) 26 | } 27 | 28 | 29 | public override fun onFirstViewAttach() { 30 | viewState.setMaxEqLevel(fxInteractor.maxEqBandLevel) 31 | viewState.setMinEqLevel(fxInteractor.minEqBandLevel) 32 | viewState.refreshBands(fxInteractor.bands) 33 | disposables.addAll(observeEqEnabling()) 34 | } 35 | 36 | override fun onDestroy() { 37 | disposables.dispose() 38 | } 39 | 40 | 41 | private fun observeEqEnabling(): Disposable { 42 | return fxInteractor.observeEqEnabling() 43 | .subscribe { enabled -> 44 | viewState.setEqChecked(enabled) 45 | viewState.refreshBands(fxInteractor.bands) 46 | } 47 | } 48 | 49 | 50 | fun onClickEqSwitcher(enabled: Boolean) { 51 | fxInteractor.setEqEnable(enabled) 52 | } 53 | 54 | fun onChangeBandLevel(bandId: Int, newLevel: Int) { 55 | fxInteractor.setBandLevel(bandId, newLevel) 56 | } 57 | 58 | fun onClickShowPresets() { 59 | router.openEqPresetsSelectorDialog() 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_playlist_lv.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 23 | 24 | 35 | 36 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/tab_fx_eq.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 26 | 27 | 37 | 38 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/utils/EmptyItems.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.utils; 2 | 3 | import com.parabola.domain.model.Track; 4 | 5 | public final class EmptyItems { 6 | 7 | private EmptyItems() { 8 | throw new IllegalAccessError(); 9 | } 10 | 11 | 12 | public static final Track NO_TRACK = new Track() { 13 | public int getId() { 14 | return -1; 15 | } 16 | 17 | public String getTitle() { 18 | return ""; 19 | } 20 | 21 | public int getAlbumId() { 22 | return -1; 23 | } 24 | 25 | public String getAlbumTitle() { 26 | return ""; 27 | } 28 | 29 | public Object getArtImage() { 30 | return null; 31 | } 32 | 33 | public int getArtistId() { 34 | return -1; 35 | } 36 | 37 | public String getArtistName() { 38 | return ""; 39 | } 40 | 41 | public long getDurationMs() { 42 | return 0; 43 | } 44 | 45 | @Override 46 | public long getDateAdded() { 47 | return 0; 48 | } 49 | 50 | public int getPositionInCd() { 51 | return 0; 52 | } 53 | 54 | public int getGenreId() { 55 | return -1; 56 | } 57 | 58 | public String getGenreName() { 59 | return ""; 60 | } 61 | 62 | public int getYear() { 63 | return 1970; 64 | } 65 | 66 | public int getFileSize() { 67 | return 0; 68 | } 69 | 70 | public int getBitrate() { 71 | return 0; 72 | } 73 | 74 | public int getSampleRate() { 75 | return 0; 76 | } 77 | 78 | public String getFilePath() { 79 | return ""; 80 | } 81 | 82 | public boolean isFavourite() { 83 | return false; 84 | } 85 | }; 86 | 87 | } 88 | -------------------------------------------------------------------------------- /Slidr/src/main/java/com/r0adkll/slidr/FragmentPanelSlideListener.java: -------------------------------------------------------------------------------- 1 | package com.r0adkll.slidr; 2 | 3 | 4 | import android.view.View; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.fragment.app.FragmentActivity; 8 | 9 | import com.r0adkll.slidr.model.SlidrConfig; 10 | import com.r0adkll.slidr.widget.SliderPanel; 11 | 12 | 13 | class FragmentPanelSlideListener implements SliderPanel.OnPanelSlideListener { 14 | 15 | private final View view; 16 | private final SlidrConfig config; 17 | 18 | 19 | FragmentPanelSlideListener(@NonNull View view, @NonNull SlidrConfig config) { 20 | this.view = view; 21 | this.config = config; 22 | } 23 | 24 | 25 | @Override 26 | public void onStateChanged(int state) { 27 | if (config.getListener() != null) { 28 | config.getListener().onSlideStateChanged(state); 29 | } 30 | } 31 | 32 | 33 | @Override 34 | public void onClosed() { 35 | if (config.getListener() != null) { 36 | if(config.getListener().onSlideClosed()) { 37 | return; 38 | } 39 | } 40 | 41 | // Ensure that we are attached to a FragmentActivity 42 | if (view.getContext() instanceof FragmentActivity) { 43 | final FragmentActivity activity = (FragmentActivity) view.getContext(); 44 | if (activity.getSupportFragmentManager().getBackStackEntryCount() == 0) { 45 | activity.finish(); 46 | activity.overridePendingTransition(0, 0); 47 | } else { 48 | activity.getSupportFragmentManager().popBackStack(); 49 | } 50 | } 51 | } 52 | 53 | @Override 54 | public void onOpened() { 55 | if (config.getListener() != null) { 56 | config.getListener().onSlideOpened(); 57 | } 58 | } 59 | 60 | 61 | @Override 62 | public void onSlideChange(float percent) { 63 | if (config.getListener() != null) { 64 | config.getListener().onSlideChange(percent); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /domain/src/main/java/com/parabola/domain/model/Track.java: -------------------------------------------------------------------------------- 1 | package com.parabola.domain.model; 2 | 3 | import java.io.File; 4 | 5 | public interface Track { 6 | int getId(); 7 | String getTitle(); 8 | 9 | int getAlbumId(); 10 | String getAlbumTitle(); 11 | ArtImage getArtImage(); 12 | 13 | int getArtistId(); 14 | String getArtistName(); 15 | 16 | long getDurationMs(); 17 | 18 | long getDateAdded(); 19 | int getPositionInCd(); 20 | 21 | int getGenreId(); 22 | String getGenreName(); 23 | 24 | int getYear(); 25 | 26 | String getFilePath(); 27 | int getFileSize(); //в байтах 28 | default int getFileSizeKilobytes() { 29 | return getFileSize() / 1024; 30 | } 31 | 32 | int getBitrate(); 33 | int getSampleRate(); 34 | 35 | boolean isFavourite(); 36 | 37 | default String getSearchView() { 38 | return (getArtistName() + " " + getAlbumTitle() + " " + getTitle()).toLowerCase(); 39 | } 40 | 41 | 42 | default boolean equals(Track o) { 43 | return getId() == o.getId(); 44 | } 45 | 46 | default String getFolderPath() { 47 | return getFolderPath(getFilePath()); 48 | } 49 | 50 | static String getFolderPath(String trackPath) { 51 | int lastSlashIndex = trackPath.lastIndexOf(File.separator); 52 | 53 | return trackPath.substring(0, lastSlashIndex); 54 | } 55 | 56 | default String getFileName() { 57 | int lastSlashIndex = getFilePath().lastIndexOf(File.separator); 58 | 59 | return getFilePath().substring(lastSlashIndex + 1); 60 | } 61 | 62 | String FILE_TYPE_DIVIDER = "."; 63 | 64 | default String getFileNameWithoutExtension() { 65 | int lastDotIndex = getFileName().lastIndexOf(FILE_TYPE_DIVIDER); 66 | 67 | //если в расширении нет точки, то возвращаем полное имя файла 68 | if (lastDotIndex == -1) 69 | return getFileName(); 70 | 71 | return getFileName().substring(0, lastDotIndex); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/di/app/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.di.app 2 | 3 | import android.content.Intent 4 | import com.parabola.domain.interactor.player.PlayerInteractor 5 | import com.parabola.domain.repository.TrackRepository 6 | import com.parabola.domain.settings.ViewSettingsInteractor 7 | import com.parabola.newtone.MainApplication 8 | import com.parabola.newtone.di.app.modules.* 9 | import com.parabola.sleep_timer_feature.SleepTimerInteractor 10 | import dagger.Component 11 | import javax.inject.Named 12 | import javax.inject.Singleton 13 | 14 | @Singleton 15 | @Component( 16 | modules = [ 17 | AndroidAppModule::class, 18 | IntentModule::class, 19 | InteractorModule::class, 20 | DataModule::class, 21 | NavigationModule::class, 22 | ConfigModule::class, 23 | ] 24 | ) 25 | interface AppComponent : AppComponentInjects { 26 | 27 | object Initializer { 28 | fun init(app: MainApplication): AppComponent { 29 | return DaggerAppComponent.builder() 30 | .androidAppModule(AndroidAppModule(app)) 31 | .build() 32 | } 33 | } 34 | 35 | fun providePlayerInteractor(): PlayerInteractor 36 | fun provideSleepTimerInteractor(): SleepTimerInteractor 37 | fun provideTrackRepo(): TrackRepository 38 | fun provideViewSettingsInteractor(): ViewSettingsInteractor 39 | 40 | @Named(IntentModule.TOGGLE_PLAYER_STATE_INTENT) 41 | fun provideTogglePlayerIntent(): Intent 42 | 43 | @Named(IntentModule.GO_NEXT_TRACK_INTENT) 44 | fun provideGoNextIntent(): Intent 45 | 46 | @Named(IntentModule.GO_PREVIOUS_TRACK_INTENT) 47 | fun provideGoPreviousIntent(): Intent 48 | 49 | @Named(IntentModule.TOGGLE_REPEAT_MODE_INTENT) 50 | fun provideToggleRepeatModeIntent(): Intent 51 | 52 | @Named(IntentModule.TOGGLE_SHUFFLE_MODE_INTENT) 53 | fun provideToggleShuffleModeIntent(): Intent 54 | 55 | @Named(IntentModule.OPEN_ACTIVITY_INTENT) 56 | fun provideOpenActivityIntent(): Intent 57 | 58 | } 59 | -------------------------------------------------------------------------------- /player_feature/src/main/java/com/parabola/player_feature/PlayerSettingImpl.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.player_feature 2 | 3 | import com.google.android.exoplayer2.ui.PlayerNotificationManager 4 | import com.parabola.domain.interactor.player.PlayerSetting 5 | import io.reactivex.Observable 6 | import io.reactivex.subjects.BehaviorSubject 7 | 8 | class PlayerSettingImpl( 9 | private val settingSaver: PlayerSettingSaver, 10 | private val notificationManager: PlayerNotificationManager, 11 | ) : PlayerSetting { 12 | 13 | private val observeIsNotificationColorized: BehaviorSubject = 14 | BehaviorSubject.createDefault(settingSaver.isNotificationBackgroundColorized) 15 | private val observeIsNotificationArtworkShow: BehaviorSubject = 16 | BehaviorSubject.createDefault(settingSaver.isNotificationArtworkShow) 17 | 18 | 19 | init { 20 | // Восстанавливаем режим отображения уведомлений 21 | notificationManager.setColorized(observeIsNotificationColorized.value!!) 22 | } 23 | 24 | override fun setNotificationBackgroundColorized(colorized: Boolean) { 25 | settingSaver.isNotificationBackgroundColorized = colorized 26 | observeIsNotificationColorized.onNext(colorized) 27 | notificationManager.setColorized(colorized) 28 | } 29 | 30 | override fun isNotificationBackgroundColorized(): Boolean = 31 | observeIsNotificationColorized.value!! 32 | 33 | override fun observeIsNotificationBackgroundColorized(): Observable = 34 | observeIsNotificationColorized 35 | 36 | 37 | override fun setNotificationArtworkShow(show: Boolean) { 38 | settingSaver.isNotificationArtworkShow = show 39 | observeIsNotificationArtworkShow.onNext(show) 40 | notificationManager.invalidate() 41 | } 42 | 43 | override fun isNotificationArtworkShow(): Boolean = 44 | observeIsNotificationArtworkShow.value!! 45 | 46 | override fun observeNotificationArtworkShow(): Observable = 47 | observeIsNotificationArtworkShow 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/settings/colorthemeselector/ColorThemeSelectorPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.settings.colorthemeselector 2 | 3 | import com.parabola.domain.settings.ViewSettingsInteractor 4 | import com.parabola.domain.settings.ViewSettingsInteractor.ColorTheme 5 | import com.parabola.domain.settings.ViewSettingsInteractor.PrimaryColor 6 | import com.parabola.newtone.di.app.AppComponent 7 | import com.parabola.newtone.presentation.router.MainRouter 8 | import io.reactivex.disposables.CompositeDisposable 9 | import io.reactivex.disposables.Disposable 10 | import moxy.InjectViewState 11 | import moxy.MvpPresenter 12 | import javax.inject.Inject 13 | 14 | @InjectViewState 15 | class ColorThemeSelectorPresenter(appComponent: AppComponent) : 16 | MvpPresenter() { 17 | 18 | @Inject 19 | lateinit var router: MainRouter 20 | 21 | @Inject 22 | lateinit var viewSettingsInteractor: ViewSettingsInteractor 23 | 24 | private val disposables = CompositeDisposable() 25 | 26 | 27 | init { 28 | appComponent.inject(this) 29 | } 30 | 31 | 32 | override fun onFirstViewAttach() { 33 | disposables.addAll( 34 | observeColorTheme(), 35 | observePrimaryColor() 36 | ) 37 | } 38 | 39 | override fun onDestroy() { 40 | disposables.dispose() 41 | } 42 | 43 | private fun observeColorTheme(): Disposable { 44 | return viewSettingsInteractor.observeColorTheme() 45 | .subscribe(viewState::setDarkLightTheme) 46 | } 47 | 48 | private fun observePrimaryColor(): Disposable { 49 | return viewSettingsInteractor.observePrimaryColor() 50 | .subscribe(viewState::setPrimaryColor) 51 | } 52 | 53 | fun onDarkLightSelection(colorTheme: ColorTheme) { 54 | viewSettingsInteractor.colorTheme = colorTheme 55 | } 56 | 57 | fun onPrimaryColorSelection(primaryColor: PrimaryColor) { 58 | viewSettingsInteractor.primaryColor = primaryColor 59 | } 60 | 61 | fun onClickBackButton() { 62 | router.goBack() 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/presentation/playlist/chooseplaylist/ChoosePlaylistPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.presentation.playlist.chooseplaylist 2 | 3 | import com.parabola.domain.executor.SchedulerProvider 4 | import com.parabola.domain.repository.PlaylistRepository 5 | import com.parabola.newtone.di.app.AppComponent 6 | import com.parabola.newtone.presentation.router.MainRouter 7 | import io.reactivex.disposables.CompositeDisposable 8 | import io.reactivex.disposables.Disposable 9 | import io.reactivex.internal.observers.CallbackCompletableObserver 10 | import moxy.InjectViewState 11 | import moxy.MvpPresenter 12 | import javax.inject.Inject 13 | 14 | @InjectViewState 15 | class ChoosePlaylistPresenter( 16 | appComponent: AppComponent, 17 | private val trackIds: IntArray, 18 | ) : MvpPresenter() { 19 | 20 | @Inject 21 | lateinit var playlistRepo: PlaylistRepository 22 | 23 | @Inject 24 | lateinit var schedulers: SchedulerProvider 25 | 26 | @Inject 27 | lateinit var router: MainRouter 28 | 29 | private val disposables = CompositeDisposable() 30 | 31 | 32 | init { 33 | appComponent.inject(this) 34 | } 35 | 36 | 37 | override fun onFirstViewAttach() { 38 | disposables.addAll(observePlaylistsUpdates()) 39 | } 40 | 41 | override fun onDestroy() { 42 | disposables.dispose() 43 | } 44 | 45 | 46 | private fun observePlaylistsUpdates(): Disposable { 47 | return playlistRepo.observePlaylistsUpdates() 48 | .flatMapSingle { playlistRepo.all } 49 | .subscribeOn(schedulers.io()) 50 | .observeOn(schedulers.ui()) 51 | .subscribe(viewState::refreshPlaylists) 52 | } 53 | 54 | 55 | fun onClickCreateNewPlaylist() { 56 | router.openCreatePlaylistDialog() 57 | } 58 | 59 | fun onClickPlaylistItem(playlistId: Int) { 60 | playlistRepo.addTracksToPlaylist(playlistId, *trackIds) 61 | .subscribeOn(schedulers.io()) 62 | .observeOn(schedulers.ui()) 63 | .subscribe(CallbackCompletableObserver { viewState.closeScreen() }) 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/parabola/newtone/adapter/ExcludedFolderAdapter.java: -------------------------------------------------------------------------------- 1 | package com.parabola.newtone.adapter; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.recyclerview.widget.RecyclerView; 8 | 9 | import com.parabola.newtone.R; 10 | import com.parabola.newtone.databinding.ItemExcludedFolderBinding; 11 | 12 | import java.io.File; 13 | 14 | public final class ExcludedFolderAdapter extends SimpleListAdapter { 15 | 16 | 17 | @NonNull 18 | @Override 19 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 20 | View view = inflateByViewType(parent.getContext(), R.layout.item_excluded_folder, parent); 21 | return new ViewHolder(ItemExcludedFolderBinding.bind(view)); 22 | } 23 | 24 | 25 | @Override 26 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 27 | super.onBindViewHolder(holder, position); 28 | 29 | String absolutePath = get(holder.getAdapterPosition()); 30 | holder.binding.folderPath.setText(absolutePath); 31 | if (absolutePath.endsWith(File.separator)) { 32 | absolutePath = absolutePath.substring(0, absolutePath.length() - 1); 33 | } 34 | int index = absolutePath.lastIndexOf(File.separator); 35 | 36 | holder.binding.folderName.setText(absolutePath.substring(index + 1)); 37 | holder.binding.removeButton.setOnClickListener(view -> { 38 | if (onRemoveClickListener != null) { 39 | onRemoveClickListener.onClickRemoveItem(holder.getAdapterPosition()); 40 | } 41 | }); 42 | } 43 | 44 | @Override 45 | public char getSection(int position) { 46 | return get(position).charAt(0); 47 | } 48 | 49 | 50 | static class ViewHolder extends RecyclerView.ViewHolder { 51 | private final ItemExcludedFolderBinding binding; 52 | 53 | 54 | private ViewHolder(ItemExcludedFolderBinding binding) { 55 | super(binding.getRoot()); 56 | this.binding = binding; 57 | } 58 | } 59 | 60 | } 61 | --------------------------------------------------------------------------------