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 |
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 | [](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 | | Tracks |
14 | Playlists |
15 | Albums |
16 | Player |
17 |
18 |
19 |  |
20 |  |
21 |  |
22 |  |
23 |
24 |
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 |
--------------------------------------------------------------------------------