├── .github ├── ISSUE_TEMPLATE │ ├── Bug_report.md │ └── Feature_request.md └── pull_request_template.md ├── .gitignore ├── .travis.yml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── naman14 │ │ │ └── timberx │ │ │ ├── MainModule.kt │ │ │ ├── PrefsModule.kt │ │ │ ├── TimberXApp.kt │ │ │ ├── cast │ │ │ ├── CastHelper.kt │ │ │ ├── CastOptionsProvider.kt │ │ │ └── CastServer.kt │ │ │ ├── constants │ │ │ ├── AlbumSortOrder.kt │ │ │ ├── AppThemes.kt │ │ │ ├── Constants.kt │ │ │ ├── SongSortOrder.kt │ │ │ └── StartPage.kt │ │ │ ├── db │ │ │ ├── QueueDao.kt │ │ │ ├── QueueEntity.kt │ │ │ ├── QueueHelper.kt │ │ │ ├── RoomModule.kt │ │ │ ├── SongEntity.kt │ │ │ └── TimberDatabase.kt │ │ │ ├── extensions │ │ │ ├── ActivityExtensions.kt │ │ │ ├── CollectionsExtensions.kt │ │ │ ├── ContextExtensions.kt │ │ │ ├── CursorExtensions.kt │ │ │ ├── FloatExt.kt │ │ │ ├── FragmentExtensions.kt │ │ │ ├── IOExtensions.kt │ │ │ ├── LayoutInflaterExtensions.kt │ │ │ ├── LiveDataExtensions.kt │ │ │ ├── MediaExtensions.kt │ │ │ ├── MediaMetadataExt.kt │ │ │ ├── PlaybackStateExt.kt │ │ │ ├── RxDisposableExtensions.kt │ │ │ ├── RxExtensions.kt │ │ │ ├── RxRetrofitExtensions.kt │ │ │ ├── SongExtensions.kt │ │ │ └── ViewExtensions.kt │ │ │ ├── logging │ │ │ └── FabricTree.kt │ │ │ ├── models │ │ │ ├── Album.kt │ │ │ ├── Artist.kt │ │ │ ├── CastStatus.kt │ │ │ ├── CategorySongData.kt │ │ │ ├── Genre.kt │ │ │ ├── MediaData.kt │ │ │ ├── MediaID.kt │ │ │ ├── Playlist.kt │ │ │ ├── QueueData.kt │ │ │ └── Song.kt │ │ │ ├── network │ │ │ ├── LastFmModule.kt │ │ │ ├── LyricsModule.kt │ │ │ ├── NetworkModule.kt │ │ │ ├── Outcome.kt │ │ │ ├── api │ │ │ │ ├── LastFmRestService.kt │ │ │ │ └── LyricsRestService.kt │ │ │ ├── conversion │ │ │ │ └── LyricsConverterFactory.kt │ │ │ └── models │ │ │ │ ├── AlbumInfo.kt │ │ │ │ ├── ArtistBio.kt │ │ │ │ ├── ArtistInfo.kt │ │ │ │ ├── Artwork.kt │ │ │ │ ├── LastfmAlbum.kt │ │ │ │ └── LastfmArtist.kt │ │ │ ├── notifications │ │ │ ├── Notifications.kt │ │ │ └── NotificationsModule.kt │ │ │ ├── permissions │ │ │ ├── PermissionsManager.kt │ │ │ └── PermissionsModule.kt │ │ │ ├── playback │ │ │ ├── BecomingNoisyReceiver.kt │ │ │ ├── MediaModule.kt │ │ │ ├── MediaSessionConnection.kt │ │ │ ├── TimberMusicService.kt │ │ │ └── players │ │ │ │ ├── MediaSessionCallback.kt │ │ │ │ ├── MusicPlayer.kt │ │ │ │ ├── Queue.kt │ │ │ │ └── SongPlayer.kt │ │ │ ├── repository │ │ │ ├── AlbumRepository.kt │ │ │ ├── ArtistRepository.kt │ │ │ ├── FoldersRepository.kt │ │ │ ├── GenreRepository.kt │ │ │ ├── PlaylistRepository.kt │ │ │ ├── RepositoriesModule.kt │ │ │ └── SongsRepository.kt │ │ │ ├── ui │ │ │ ├── activities │ │ │ │ ├── ExpandedControlsActivity.java │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── SettingsActivity.kt │ │ │ │ └── base │ │ │ │ │ └── PermissionsActivity.kt │ │ │ ├── adapters │ │ │ │ ├── AlbumAdapter.kt │ │ │ │ ├── ArtistAdapter.kt │ │ │ │ ├── FolderAdapter.kt │ │ │ │ ├── GenreAdapter.kt │ │ │ │ ├── PlaylistAdapter.kt │ │ │ │ └── SongsAdapter.kt │ │ │ ├── bindings │ │ │ │ ├── Bindings.kt │ │ │ │ └── LastFmImageBindings.kt │ │ │ ├── dialogs │ │ │ │ ├── AboutDialog.kt │ │ │ │ ├── AddToPlaylistDialog.kt │ │ │ │ ├── CreatePlaylistDialog.kt │ │ │ │ └── DeleteSongDialog.kt │ │ │ ├── fragments │ │ │ │ ├── BottomControlsFragment.kt │ │ │ │ ├── FolderFragment.kt │ │ │ │ ├── GenreFragment.kt │ │ │ │ ├── LyricsFragment.kt │ │ │ │ ├── MainFragment.kt │ │ │ │ ├── NowPlayingFragment.kt │ │ │ │ ├── PlaylistFragment.kt │ │ │ │ ├── QueueFragment.kt │ │ │ │ ├── SearchFragment.kt │ │ │ │ ├── SettingsFragment.kt │ │ │ │ ├── album │ │ │ │ │ ├── AlbumDetailFragment.kt │ │ │ │ │ └── AlbumsFragment.kt │ │ │ │ ├── artist │ │ │ │ │ ├── ArtistDetailFragment.kt │ │ │ │ │ └── ArtistFragment.kt │ │ │ │ ├── base │ │ │ │ │ ├── BaseNowPlayingFragment.kt │ │ │ │ │ ├── CoroutineFragment.kt │ │ │ │ │ └── MediaItemFragment.kt │ │ │ │ └── songs │ │ │ │ │ ├── CategorySongsFragment.kt │ │ │ │ │ └── SongsFragment.kt │ │ │ ├── listeners │ │ │ │ ├── PopupMenuListener.kt │ │ │ │ └── SortMenuListener.kt │ │ │ ├── viewmodels │ │ │ │ ├── MainViewModel.kt │ │ │ │ ├── MediaItemFragmentViewModel.kt │ │ │ │ ├── NowPlayingViewModel.kt │ │ │ │ ├── SearchViewModel.kt │ │ │ │ ├── ViewModelsModule.kt │ │ │ │ └── base │ │ │ │ │ └── CoroutineViewModel.kt │ │ │ └── widgets │ │ │ │ ├── AlbumSortMenu.kt │ │ │ │ ├── BottomSheetListener.kt │ │ │ │ ├── DragSortRecycler.java │ │ │ │ ├── MediaProgressBar.kt │ │ │ │ ├── MediaProgressTextView.kt │ │ │ │ ├── MediaSeekBar.kt │ │ │ │ ├── MusicVisualizer.kt │ │ │ │ ├── RecyclerItemClickListener.kt │ │ │ │ ├── SongPopupMenu.kt │ │ │ │ ├── SongSortMenu.kt │ │ │ │ ├── SquareImageView.kt │ │ │ │ └── TextDrawable.java │ │ │ └── util │ │ │ ├── AutoClearedValue.kt │ │ │ ├── Event.kt │ │ │ ├── MusicUtils.kt │ │ │ ├── SpacesItemDecoration.java │ │ │ └── Utils.kt │ └── res │ │ ├── animator │ │ ├── appbar_elevation_disable.xml │ │ └── appbar_elevation_enable.xml │ │ ├── drawable │ │ ├── bg_playlist_fab.xml │ │ ├── bg_search_box.xml │ │ ├── bg_up_next.xml │ │ ├── default_album_art_large.xml │ │ ├── default_album_art_small.xml │ │ ├── divider.xml │ │ ├── ic_add.xml │ │ ├── ic_arrow_back.xml │ │ ├── ic_down_outline.xml │ │ ├── ic_file_music_dark.xml │ │ ├── ic_folder_open_black_24dp.xml │ │ ├── ic_folder_parent_dark.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_more_vert.xml │ │ ├── ic_more_vert_black_24dp.xml │ │ ├── ic_music_note.xml │ │ ├── ic_next.xml │ │ ├── ic_notification.xml │ │ ├── ic_pause.xml │ │ ├── ic_pause_outline.xml │ │ ├── ic_play.xml │ │ ├── ic_play_outline.xml │ │ ├── ic_playlist_play.xml │ │ ├── ic_previous.xml │ │ ├── ic_previous_outline.xml │ │ ├── ic_queue_music.xml │ │ ├── ic_reorder.xml │ │ ├── ic_repeat_all.xml │ │ ├── ic_repeat_none.xml │ │ ├── ic_repeat_one.xml │ │ ├── ic_search.xml │ │ ├── ic_shuffle_all.xml │ │ ├── ic_shuffle_none.xml │ │ ├── ic_skip_outline.xml │ │ ├── ic_sort_black.xml │ │ ├── ic_timer_wait.xml │ │ ├── icon.png │ │ ├── progress_drawable.xml │ │ ├── rounded_corner.xml │ │ ├── rounded_corners_background.xml │ │ ├── seek_drawable.xml │ │ └── splashscreen.xml │ │ ├── font │ │ ├── rubik_medium.ttf │ │ └── rubik_regular.ttf │ │ ├── layout │ │ ├── activity_settings.xml │ │ ├── fragment_album_detail.xml │ │ ├── fragment_artist_detail.xml │ │ ├── fragment_category_songs.xml │ │ ├── fragment_lyrics.xml │ │ ├── fragment_now_playing.xml │ │ ├── fragment_playlists.xml │ │ ├── fragment_queue.xml │ │ ├── fragment_search.xml │ │ ├── item_album.xml │ │ ├── item_albums_header.xml │ │ ├── item_artist.xml │ │ ├── item_artist_album.xml │ │ ├── item_folder_list.xml │ │ ├── item_genre.xml │ │ ├── item_playlist.xml │ │ ├── item_songs.xml │ │ ├── item_songs_header.xml │ │ ├── layout_bottomsheet_controls.xml │ │ ├── layout_recyclerview.xml │ │ ├── layout_recyclerview_padding.xml │ │ ├── main_activity.xml │ │ ├── main_fragment.xml │ │ └── toolbar.xml │ │ ├── menu │ │ ├── album_sort_by.xml │ │ ├── menu_expanded_controller.xml │ │ ├── menu_main.xml │ │ ├── menu_popup_song.xml │ │ └── song_sort_by.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.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 │ │ ├── values-es │ │ ├── strings-about.xml │ │ └── strings.xml │ │ ├── values-it │ │ ├── strings.xml │ │ └── strins-about.xml │ │ ├── values-land │ │ └── integers.xml │ │ ├── values-nl │ │ └── strings.xml │ │ ├── values-pt-rBR │ │ ├── strings-about.xml │ │ └── strings.xml │ │ ├── values-v23 │ │ ├── plurals.xml │ │ └── styles.xml │ │ ├── values-v27 │ │ └── styles.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── ids.xml │ │ ├── integers.xml │ │ ├── plurals.xml │ │ ├── strings-about.xml │ │ ├── strings-notranslate.xml │ │ ├── strings.xml │ │ ├── styles-text.xml │ │ ├── styles-views.xml │ │ └── styles.xml │ │ └── xml │ │ ├── automotive_app_desc.xml │ │ └── preferences.xml │ └── test │ └── java │ └── com │ └── naman14 │ └── timberx │ └── permissions │ └── PermissionManagerTest.kt ├── art ├── auto1.png ├── auto2.png ├── auto3.jpg ├── auto4.jpg ├── cast1.jpg ├── cast2.jpg ├── icon.png ├── icon2.png ├── phone1.png ├── phone2.png ├── phone3.png ├── phone4.png ├── wear1.png └── wear2.png ├── build.gradle ├── dependencies.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── mock.gradle ├── mock ├── mock-google-services.json └── mock-secret.xml ├── settings.gradle ├── spotless.gradle └── spotless.license.kt /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something is crashing or not working as intended 4 | 5 | --- 6 | 7 | *Please consider making a Pull Request if you are capable of doing so.* 8 | 9 | **App Version:** 10 | 11 | x.x.x 12 | 13 | **Affected Device(s):** 14 | 15 | Google Pixel 3 XL with Android 9.0 16 | 17 | **Describe the Bug:** 18 | 19 | A clear description of what is the bug is. 20 | 21 | **To Reproduce:** 22 | 1. 23 | 2. 24 | 3. 25 | 26 | **Expected Behavior:** 27 | 28 | A clear description of what you expected to happen. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | *Please consider making a Pull Request if you are capable of doing so.* 8 | 9 | **Description what you'd like to happen:** 10 | 11 | A clear description if the feature or behavior you'd like implemented. 12 | 13 | **Describe alternatives you've considered:** 14 | 15 | A clear description of any alternative solutions you've considered. 16 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Guidelines 2 | 3 | 1. You must run the `spotlessApply` task before committing, either through Android Studio or with `./gradlew spotlessApply`. 4 | 2. A PR should be focused and contained. If you are changing multiple unrelated things, they should be in separate PRs. 5 | 3. A PR should fix a bug or solve a problem - something that only you would use is not necessarily something that should be published. 6 | 4. Give your PR a detailed title and description - look over your code one last time before actually creating the PR. Give it a self-review. 7 | 8 | **If you do not follow the guidelines, your PR will be rejected.** 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | /.idea/* 8 | .idea 9 | .DS_Store 10 | /build 11 | /captures 12 | .externalNativeBuild 13 | app/google-services.json 14 | app/src/main/res/values/secret.xml 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | android: 4 | components: 5 | - platform-tools 6 | - tools 7 | 8 | - build-tools-28.0.3 9 | - android-28 10 | 11 | - extra-google-m2repository 12 | - extra-android-m2repository 13 | licenses: 14 | - 'android-sdk-preview-license-52d11cd2' 15 | - 'android-sdk-license-.+' 16 | - 'google-gdk-license-.+' 17 | 18 | jdk: 19 | - oraclejdk8 20 | 21 | script: 22 | - chmod +x ./gradlew 23 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /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 | # 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/src/main/java/com/naman14/timberx/MainModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx 16 | 17 | import android.app.Application 18 | import android.content.ComponentName 19 | import android.content.ContentResolver 20 | import com.naman14.timberx.playback.MediaSessionConnection 21 | import com.naman14.timberx.playback.RealMediaSessionConnection 22 | import com.naman14.timberx.playback.TimberMusicService 23 | import io.reactivex.android.schedulers.AndroidSchedulers 24 | import org.koin.dsl.module.module 25 | 26 | const val MAIN = "main" 27 | 28 | val mainModule = module { 29 | 30 | factory { 31 | get().contentResolver 32 | } 33 | 34 | single { 35 | val component = ComponentName(get(), TimberMusicService::class.java) 36 | RealMediaSessionConnection(get(), component) 37 | } bind MediaSessionConnection::class 38 | 39 | factory(name = MAIN) { 40 | AndroidSchedulers.mainThread() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/TimberXApp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | @file:Suppress("unused") 16 | 17 | package com.naman14.timberx 18 | 19 | import android.app.Application 20 | import com.naman14.timberx.BuildConfig.DEBUG 21 | import com.naman14.timberx.db.roomModule 22 | import com.naman14.timberx.logging.FabricTree 23 | import com.naman14.timberx.network.lastFmModule 24 | import com.naman14.timberx.network.lyricsModule 25 | import com.naman14.timberx.network.networkModule 26 | import com.naman14.timberx.notifications.notificationModule 27 | import com.naman14.timberx.permissions.permissionsModule 28 | import com.naman14.timberx.playback.mediaModule 29 | import com.naman14.timberx.repository.repositoriesModule 30 | import com.naman14.timberx.ui.viewmodels.viewModelsModule 31 | import org.koin.android.ext.android.startKoin 32 | import timber.log.Timber 33 | 34 | class TimberXApp : Application() { 35 | 36 | override fun onCreate() { 37 | super.onCreate() 38 | 39 | if (DEBUG) { 40 | Timber.plant(Timber.DebugTree()) 41 | } else { 42 | Timber.plant(FabricTree()) 43 | } 44 | 45 | val modules = listOf( 46 | mainModule, 47 | permissionsModule, 48 | mediaModule, 49 | prefsModule, 50 | networkModule, 51 | roomModule, 52 | notificationModule, 53 | repositoriesModule, 54 | viewModelsModule, 55 | lyricsModule, 56 | lastFmModule 57 | ) 58 | startKoin( 59 | androidContext = this, 60 | modules = modules 61 | ) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/cast/CastOptionsProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | @file:Suppress("unused") 16 | 17 | package com.naman14.timberx.cast 18 | 19 | import android.content.Context 20 | import com.google.android.gms.cast.framework.CastOptions 21 | import com.google.android.gms.cast.framework.OptionsProvider 22 | import com.google.android.gms.cast.framework.SessionProvider 23 | import com.google.android.gms.cast.framework.media.CastMediaOptions 24 | import com.google.android.gms.cast.framework.media.MediaIntentReceiver.ACTION_STOP_CASTING 25 | import com.google.android.gms.cast.framework.media.MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK 26 | import com.google.android.gms.cast.framework.media.NotificationOptions 27 | import com.naman14.timberx.R 28 | import com.naman14.timberx.ui.activities.ExpandedControlsActivity 29 | 30 | class CastOptionsProvider : OptionsProvider { 31 | 32 | override fun getCastOptions(context: Context): CastOptions { 33 | val buttonActions = arrayListOf(ACTION_TOGGLE_PLAYBACK, ACTION_STOP_CASTING) 34 | val compatButtonActionsIndicies = intArrayOf(0, 1) 35 | 36 | val notificationOptions = NotificationOptions.Builder().apply { 37 | setActions(buttonActions, compatButtonActionsIndicies) 38 | setTargetActivityClassName(ExpandedControlsActivity::class.java.name) 39 | }.build() 40 | 41 | val mediaOptions = CastMediaOptions.Builder().apply { 42 | setNotificationOptions(notificationOptions) 43 | setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name) 44 | }.build() 45 | 46 | return CastOptions.Builder().apply { 47 | setReceiverApplicationId(context.getString(R.string.cast_app_id)) 48 | setCastMediaOptions(mediaOptions) 49 | }.build() 50 | } 51 | 52 | override fun getAdditionalSessionProviders(context: Context): List? = null 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/constants/AlbumSortOrder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.constants 16 | 17 | import android.provider.MediaStore 18 | 19 | enum class AlbumSortOrder(val rawValue: String) { 20 | /* Album sort order A-Z */ 21 | ALBUM_A_Z(MediaStore.Audio.Albums.DEFAULT_SORT_ORDER), 22 | /* Album sort order Z-A */ 23 | ALBUM_Z_A(MediaStore.Audio.Albums.DEFAULT_SORT_ORDER + " DESC"), 24 | /* Album sort order songs */ 25 | ALBUM_NUMBER_OF_SONGS(MediaStore.Audio.Albums.NUMBER_OF_SONGS + " DESC"), 26 | /* Album sort order year */ 27 | ALBUM_YEAR(MediaStore.Audio.Albums.FIRST_YEAR + " DESC"); 28 | 29 | companion object { 30 | fun fromString(raw: String): AlbumSortOrder { 31 | return AlbumSortOrder.values().single { it.rawValue == raw } 32 | } 33 | 34 | fun toString(value: AlbumSortOrder): String = value.rawValue 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/constants/AppThemes.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.constants 16 | 17 | import com.naman14.timberx.R 18 | 19 | enum class AppThemes(val rawValue: String, val themeRes: Int) { 20 | LIGHT("light", R.style.AppTheme_Light), 21 | DARK("dark", R.style.AppTheme_Dark), 22 | BLACK("black", R.style.AppTheme_Black); 23 | 24 | companion object { 25 | fun fromString(raw: String): AppThemes { 26 | return AppThemes.values().single { it.rawValue == raw } 27 | } 28 | 29 | fun toString(value: AppThemes): String = value.rawValue 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/constants/Constants.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.constants 16 | 17 | object Constants { 18 | const val SONGS_LIST = "songs_list" 19 | const val QUEUE_TITLE = "queue_title" 20 | const val SEEK_TO_POS = "seek_to_pos" 21 | const val ACTION_SET_MEDIA_STATE = "action_set_media_state" 22 | const val ACTION_REPEAT_SONG = "action_repeat_song" 23 | const val ACTION_REPEAT_QUEUE = "action_repeat_queue" 24 | const val ACTION_PLAY_NEXT = "action_play_next" 25 | const val ACTION_QUEUE_REORDER = "action_queue_reorder" 26 | const val ACTION_SONG_DELETED = "action_song_deleted" 27 | const val ACTION_REMOVED_FROM_PLAYLIST = "action_removed_from_playlist" 28 | const val ACTION_RESTORE_MEDIA_SESSION = "action_restore_media_session" 29 | const val ACTION_CAST_CONNECTED = "action_cast_connected" 30 | const val ACTION_CAST_DISCONNECTED = "action_cast_disconnected" 31 | const val ALBUM = "album" 32 | const val ARTIST = "artist" 33 | const val SONG = "song" 34 | const val SONGS = "songs" 35 | const val QUEUE_FROM = "queue_from" 36 | const val QUEUE_TO = "queue_to" 37 | const val REPEAT_MODE = "repeat_mode" 38 | const val SHUFFLE_MODE = "shuffle_mode" 39 | const val CATEGORY_SONG_DATA = "category_song_data" 40 | const val NOW_PLAYING = "now_playing" 41 | const val ACTION_PLAY_PAUSE = "action_play_pause" 42 | const val ACTION_NEXT = "action_next" 43 | const val ACTION_PREVIOUS = "action_previous" 44 | const val APP_PACKAGE_NAME = "com.naman14.timberx" 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/constants/SongSortOrder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.constants 16 | 17 | import android.provider.MediaStore 18 | 19 | enum class SongSortOrder(val rawValue: String) { 20 | /* Song sort order A-Z */ 21 | SONG_A_Z(MediaStore.Audio.Media.DEFAULT_SORT_ORDER), 22 | /* Song sort order Z-A */ 23 | SONG_Z_A(MediaStore.Audio.Media.DEFAULT_SORT_ORDER + " DESC"), 24 | /* Song sort order year */ 25 | SONG_YEAR(MediaStore.Audio.Media.YEAR + " DESC"), 26 | /* Song sort order duration */ 27 | SONG_DURATION(MediaStore.Audio.Media.DURATION + " DESC"); 28 | 29 | companion object { 30 | fun fromString(raw: String): SongSortOrder { 31 | return SongSortOrder.values().single { it.rawValue == raw } 32 | } 33 | 34 | fun toString(value: SongSortOrder): String = value.rawValue 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/constants/StartPage.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.constants 16 | 17 | enum class StartPage(val index: Int) { 18 | SONGS(0), 19 | ALBUMS(1), 20 | PLAYLISTS(2), 21 | ARTISTS(3), 22 | FOLDERS(4), 23 | GENRES(5); 24 | 25 | companion object { 26 | fun fromString(raw: String): StartPage { 27 | return StartPage.values().single { it.name.toLowerCase() == raw } 28 | } 29 | 30 | fun fromIndex(index: Int): StartPage { 31 | return StartPage.values().single { it.index == index } 32 | } 33 | 34 | fun toString(value: StartPage): String = value.name.toLowerCase() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/db/QueueEntity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.db 16 | 17 | import androidx.room.ColumnInfo 18 | import androidx.room.Entity 19 | import androidx.room.Ignore 20 | import androidx.room.PrimaryKey 21 | 22 | @Entity(tableName = "queue_meta_data") 23 | data class QueueEntity @Ignore constructor( 24 | @PrimaryKey(autoGenerate = false) var id: Long = 0, 25 | @ColumnInfo(name = "current_id") var currentId: Long? = 0, 26 | @ColumnInfo(name = "current_seek_pos") var currentSeekPos: Long? = 0, 27 | @ColumnInfo(name = "repeat_mode") var repeatMode: Int? = 0, 28 | @ColumnInfo(name = "shuffle_mode") var shuffleMode: Int? = 0, 29 | @ColumnInfo(name = "play_state") var playState: Int? = 0, 30 | @ColumnInfo(name = "queue_title") var queueTitle: String = "All songs" 31 | ) { 32 | constructor() : this(0, 0, 0, 0, 0, 0, "") 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/db/QueueHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.db 16 | 17 | import com.naman14.timberx.extensions.equalsBy 18 | import com.naman14.timberx.extensions.toSongEntityList 19 | import com.naman14.timberx.repository.SongsRepository 20 | 21 | interface QueueHelper { 22 | 23 | fun updateQueueSongs( 24 | queueSongs: LongArray?, 25 | currentSongId: Long? 26 | ) 27 | 28 | fun updateQueueData(queueData: QueueEntity) 29 | } 30 | 31 | class RealQueueHelper( 32 | private val queueDao: QueueDao, 33 | private val songsRepository: SongsRepository 34 | ) : QueueHelper { 35 | 36 | override fun updateQueueSongs(queueSongs: LongArray?, currentSongId: Long?) { 37 | if (queueSongs == null || currentSongId == null) { 38 | return 39 | } 40 | val currentList = queueDao.getQueueSongsSync() 41 | val songListToSave = queueSongs.toSongEntityList(songsRepository) 42 | 43 | val listsEqual = currentList.equalsBy(songListToSave) { left, right -> 44 | left.id == right.id 45 | } 46 | if (queueSongs.isNotEmpty() && !listsEqual) { 47 | queueDao.clearQueueSongs() 48 | queueDao.insertAllSongs(songListToSave) 49 | setCurrentSongId(currentSongId) 50 | } else { 51 | setCurrentSongId(currentSongId) 52 | } 53 | } 54 | 55 | override fun updateQueueData(queueData: QueueEntity) = queueDao.insert(queueData) 56 | 57 | private fun setCurrentSongId(id: Long) = queueDao.setCurrentId(id) 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/db/RoomModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.db 16 | 17 | import android.app.Application 18 | import androidx.room.Room 19 | import org.koin.dsl.module.module 20 | 21 | val roomModule = module { 22 | 23 | single { 24 | Room.databaseBuilder(get(), TimberDatabase::class.java, "queue.db") 25 | .allowMainThreadQueries() 26 | .fallbackToDestructiveMigration() 27 | .build() 28 | } 29 | 30 | factory { get().queueDao() } 31 | 32 | factory { 33 | RealQueueHelper(get(), get()) 34 | } bind QueueHelper::class 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/db/SongEntity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.db 16 | 17 | import androidx.room.ColumnInfo 18 | import androidx.room.Entity 19 | import androidx.room.PrimaryKey 20 | 21 | @Entity(tableName = "queue_songs") 22 | data class SongEntity( 23 | @PrimaryKey(autoGenerate = true) var uid: Int? = null, 24 | @ColumnInfo(name = "id") var id: Long 25 | ) 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/db/TimberDatabase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.db 16 | 17 | import androidx.room.Database 18 | import androidx.room.RoomDatabase 19 | 20 | @Database(entities = [QueueEntity::class, SongEntity::class], version = 2, exportSchema = false) 21 | abstract class TimberDatabase : RoomDatabase() { 22 | 23 | abstract fun queueDao(): QueueDao 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/extensions/ActivityExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.extensions 16 | 17 | import android.app.Activity 18 | import androidx.annotation.IdRes 19 | import androidx.annotation.LayoutRes 20 | import androidx.appcompat.app.AppCompatActivity 21 | import androidx.databinding.DataBindingUtil 22 | import androidx.databinding.ViewDataBinding 23 | import androidx.fragment.app.Fragment 24 | import androidx.fragment.app.FragmentTransaction 25 | import com.naman14.timberx.R 26 | 27 | fun Activity.setDataBindingContentView(@LayoutRes res: Int): T { 28 | return DataBindingUtil.setContentView(this, res) 29 | } 30 | 31 | fun Activity?.addFragment( 32 | @IdRes id: Int = R.id.container, 33 | fragment: Fragment, 34 | tag: String? = null, 35 | addToBackStack: Boolean = true 36 | ) { 37 | val compatActivity = this as? AppCompatActivity ?: return 38 | compatActivity.supportFragmentManager.beginTransaction() 39 | .apply { 40 | add(id, fragment, tag) 41 | setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) 42 | if (addToBackStack) { 43 | addToBackStack(null) 44 | } 45 | commit() 46 | } 47 | } 48 | 49 | fun Activity?.replaceFragment( 50 | @IdRes id: Int = R.id.container, 51 | fragment: Fragment, 52 | tag: String? = null, 53 | addToBackStack: Boolean = false 54 | ) { 55 | val compatActivity = this as? AppCompatActivity ?: return 56 | compatActivity.supportFragmentManager.beginTransaction() 57 | .apply { 58 | replace(id, fragment, tag) 59 | if (addToBackStack) { 60 | addToBackStack(null) 61 | } 62 | commit() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/extensions/CollectionsExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.extensions 16 | 17 | fun List?.moveElement(fromIndex: Int, toIndex: Int): List { 18 | if (this == null) { 19 | return emptyList() 20 | } 21 | return toMutableList().apply { add(toIndex, removeAt(fromIndex)) } 22 | } 23 | 24 | fun List.equalsBy(other: List, by: (left: T, right: T) -> Boolean): Boolean { 25 | if (this.size != other.size) { 26 | return false 27 | } 28 | for ((index, item) in withIndex()) { 29 | val otherItem = other[index] 30 | val itemsEqual = by(item, otherItem) 31 | if (!itemsEqual) { 32 | return false 33 | } 34 | } 35 | return true 36 | } 37 | 38 | fun IntArray.asString(): String { 39 | return joinToString(separator = ", ", prefix = "[", postfix = "]") 40 | } 41 | 42 | fun LongArray.asString(): String { 43 | return joinToString(separator = ", ", prefix = "[", postfix = "]") 44 | } 45 | 46 | fun Array.asString(): String { 47 | return joinToString(separator = ", ", prefix = "[", postfix = "]") 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/extensions/ContextExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.extensions 16 | 17 | import android.app.Activity 18 | import android.content.Context 19 | import android.graphics.drawable.Drawable 20 | import android.widget.Toast 21 | import android.widget.Toast.LENGTH_SHORT 22 | import androidx.annotation.DrawableRes 23 | import androidx.annotation.StringRes 24 | import androidx.core.content.ContextCompat 25 | import androidx.fragment.app.Fragment 26 | 27 | private var toast: Toast? = null 28 | 29 | fun Context?.toast(message: String) { 30 | if (this == null) { 31 | return 32 | } 33 | toast?.cancel() 34 | toast = Toast.makeText(this, message, LENGTH_SHORT) 35 | .apply { 36 | show() 37 | } 38 | } 39 | 40 | fun Context?.toast(@StringRes message: Int) { 41 | if (this == null) { 42 | return 43 | } 44 | toast?.cancel() 45 | toast = Toast.makeText(this, message, LENGTH_SHORT) 46 | .apply { 47 | show() 48 | } 49 | } 50 | 51 | fun Fragment.drawable(@DrawableRes res: Int): Drawable? { 52 | val context = activity ?: return null 53 | return context.drawable(res) 54 | } 55 | 56 | fun Activity?.drawable(@DrawableRes res: Int): Drawable? { 57 | if (this == null) { 58 | return null 59 | } 60 | return ContextCompat.getDrawable(this, res) 61 | } 62 | 63 | @Suppress("UNCHECKED_CAST") 64 | fun Context.systemService(name: String): T { 65 | return getSystemService(name) as T 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/extensions/FloatExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.extensions 16 | 17 | import android.content.Context 18 | import android.util.DisplayMetrics.DENSITY_DEFAULT 19 | 20 | fun Float.dpToPixels(context: Context): Int { 21 | val resources = context.resources 22 | val metrics = resources.displayMetrics 23 | return (this * (metrics.densityDpi.toFloat() / DENSITY_DEFAULT)).toInt() 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/extensions/FragmentExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | @file:Suppress("UNCHECKED_CAST") 16 | 17 | package com.naman14.timberx.extensions 18 | 19 | import androidx.fragment.app.Fragment 20 | import androidx.fragment.app.FragmentActivity 21 | 22 | inline val Fragment.safeActivity: FragmentActivity 23 | get() = activity ?: throw IllegalStateException("Fragment not attached") 24 | 25 | fun Fragment.argument(name: String): T { 26 | return arguments?.get(name) as? T ?: throw IllegalStateException("Argument $name not found.") 27 | } 28 | 29 | fun Fragment.argumentOr(name: String, default: T): T { 30 | return arguments?.get(name) as? T ?: default 31 | } 32 | 33 | fun Fragment.argumentOrEmpty(name: String): String { 34 | return arguments?.get(name) as? String ?: "" 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/extensions/IOExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.extensions 16 | 17 | import java.io.Closeable 18 | 19 | fun Closeable?.closeQuietly() { 20 | try { 21 | this?.close() 22 | } catch (_: Throwable) { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/extensions/LayoutInflaterExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | @file:Suppress("UNCHECKED_CAST") 16 | 17 | package com.naman14.timberx.extensions 18 | 19 | import android.view.LayoutInflater 20 | import android.view.View 21 | import android.view.ViewGroup 22 | import androidx.annotation.LayoutRes 23 | import androidx.databinding.DataBindingUtil 24 | import androidx.databinding.ViewDataBinding 25 | 26 | fun LayoutInflater.inflateTo( 27 | @LayoutRes layoutRes: Int, 28 | parent: ViewGroup? = null, 29 | attachToRoot: Boolean = false 30 | ): T = inflate(layoutRes, parent, attachToRoot) as T 31 | 32 | fun LayoutInflater.inflateWithBinding( 33 | @LayoutRes layoutRes: Int, 34 | parent: ViewGroup?, 35 | attachToRoot: Boolean = false 36 | ): T { 37 | return DataBindingUtil.inflate(this, layoutRes, parent, attachToRoot) as T 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/extensions/LiveDataExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.extensions 16 | 17 | import androidx.lifecycle.LifecycleOwner 18 | import androidx.lifecycle.LiveData 19 | import androidx.lifecycle.MediatorLiveData 20 | import androidx.lifecycle.Observer 21 | import androidx.lifecycle.Transformations 22 | 23 | fun LiveData.observe(owner: LifecycleOwner, onEmission: (T) -> Unit) { 24 | return observe(owner, Observer { 25 | if (it != null) { 26 | onEmission(it) 27 | } 28 | }) 29 | } 30 | 31 | fun LiveData.map(mapper: (X) -> Y) = 32 | Transformations.map(this, mapper) 33 | 34 | typealias LiveDataFilter = (T) -> Boolean 35 | 36 | /** @author Aidan Follestad (@afollestad) */ 37 | class FilterLiveData( 38 | source1: LiveData, 39 | private val filter: LiveDataFilter 40 | ) : MediatorLiveData() { 41 | 42 | init { 43 | super.addSource(source1) { 44 | if (filter(it)) { 45 | value = it 46 | } 47 | } 48 | } 49 | 50 | override fun addSource( 51 | source: LiveData, 52 | onChanged: Observer 53 | ) { 54 | throw UnsupportedOperationException() 55 | } 56 | 57 | override fun removeSource(toRemote: LiveData) { 58 | throw UnsupportedOperationException() 59 | } 60 | } 61 | 62 | fun LiveData.filter(filter: LiveDataFilter): MediatorLiveData = FilterLiveData(this, filter) 63 | 64 | fun LiveData.observeOnce(onEmission: (T) -> Unit) { 65 | val observer = object : Observer { 66 | override fun onChanged(value: T) { 67 | onEmission(value) 68 | removeObserver(this) 69 | } 70 | } 71 | observeForever(observer) 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/extensions/PlaybackStateExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.extensions 16 | 17 | import android.support.v4.media.session.PlaybackStateCompat 18 | 19 | /** 20 | * Useful extension methods for [PlaybackStateCompat]. 21 | */ 22 | inline val PlaybackStateCompat.isPrepared 23 | get() = (state == PlaybackStateCompat.STATE_BUFFERING) || 24 | (state == PlaybackStateCompat.STATE_PLAYING) || 25 | (state == PlaybackStateCompat.STATE_PAUSED) 26 | 27 | inline val PlaybackStateCompat.isPlaying 28 | get() = (state == PlaybackStateCompat.STATE_BUFFERING) || 29 | (state == PlaybackStateCompat.STATE_PLAYING) 30 | 31 | inline val PlaybackStateCompat.isPlayEnabled 32 | get() = (actions and PlaybackStateCompat.ACTION_PLAY != 0L) || 33 | ((actions and PlaybackStateCompat.ACTION_PLAY_PAUSE != 0L) && 34 | (state == PlaybackStateCompat.STATE_PAUSED)) 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/extensions/RxExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | @file:Suppress("unused") 16 | 17 | package com.naman14.timberx.extensions 18 | 19 | import androidx.lifecycle.Lifecycle 20 | import androidx.lifecycle.LifecycleObserver 21 | import androidx.lifecycle.LifecycleOwner 22 | import androidx.lifecycle.OnLifecycleEvent 23 | import io.reactivex.Observable 24 | import io.reactivex.android.schedulers.AndroidSchedulers.mainThread 25 | import io.reactivex.disposables.Disposable 26 | import io.reactivex.schedulers.Schedulers.io 27 | 28 | fun Observable.ioToMain(): Observable { 29 | return observeOn(mainThread()) 30 | .subscribeOn(io()) 31 | } 32 | 33 | class LifecycleAwareDisposable( 34 | private val disposable: Disposable 35 | ) : LifecycleObserver { 36 | 37 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) 38 | fun dispose() = disposable.dispose() 39 | } 40 | 41 | fun LifecycleOwner.ownRx(disposable: Disposable) { 42 | if (this.lifecycle.currentState == Lifecycle.State.DESTROYED) { 43 | disposable.dispose() 44 | return 45 | } 46 | this.lifecycle.addObserver(LifecycleAwareDisposable(disposable)) 47 | } 48 | 49 | fun Disposable.attachLifecycle(lifecycleOwner: LifecycleOwner) { 50 | lifecycleOwner.ownRx(this) 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/extensions/RxRetrofitExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.extensions 16 | 17 | import com.naman14.timberx.network.Outcome 18 | import io.reactivex.Observable 19 | import io.reactivex.disposables.Disposable 20 | import okhttp3.ResponseBody 21 | import org.json.JSONObject 22 | import retrofit2.HttpException 23 | import java.io.IOException 24 | import java.net.SocketTimeoutException 25 | 26 | fun Observable.subscribeForOutcome(onOutcome: (Outcome) -> Unit): Disposable { 27 | return subscribe({ onOutcome(Outcome.success(it)) }, { onOutcome(processError(it)) }) 28 | } 29 | 30 | private fun processError(error: Throwable): Outcome { 31 | return when (error) { 32 | is HttpException -> { 33 | val response = error.response() 34 | val body = response.errorBody()!! 35 | Outcome.apiError(getError(body, error)) 36 | } 37 | is SocketTimeoutException, is IOException -> Outcome.failure(error) 38 | else -> Outcome.failure(error) 39 | } 40 | } 41 | 42 | private fun getError( 43 | responseBody: ResponseBody, 44 | throwable: Throwable 45 | ): Throwable { 46 | return try { 47 | val jsonObject = JSONObject(responseBody.string()) 48 | Exception(jsonObject.getString("message"), throwable) 49 | } catch (e: Exception) { 50 | Exception(e.message ?: "$e") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/extensions/ViewExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.extensions 16 | 17 | import android.view.LayoutInflater 18 | import android.view.View 19 | import android.view.ViewGroup 20 | import androidx.annotation.LayoutRes 21 | import androidx.databinding.DataBindingUtil 22 | import androidx.databinding.ViewDataBinding 23 | import androidx.recyclerview.widget.RecyclerView 24 | import com.naman14.timberx.ui.widgets.RecyclerItemClickListener 25 | import com.naman14.timberx.ui.widgets.RecyclerViewItemClickListener 26 | 27 | fun RecyclerView.addOnItemClick(listener: RecyclerViewItemClickListener) { 28 | this.addOnChildAttachStateChangeListener(RecyclerItemClickListener(this, listener, null)) 29 | } 30 | 31 | @Suppress("UNCHECKED_CAST") 32 | fun ViewGroup.inflate(@LayoutRes layout: Int): T { 33 | return LayoutInflater.from(context).inflate(layout, this, false) as T 34 | } 35 | 36 | fun ViewGroup.inflateWithBinding(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): T { 37 | val layoutInflater = LayoutInflater.from(context) 38 | return DataBindingUtil.inflate(layoutInflater, layoutRes, this, attachToRoot) as T 39 | } 40 | 41 | fun View.show() { 42 | visibility = View.VISIBLE 43 | } 44 | 45 | fun View.hide() { 46 | visibility = View.GONE 47 | } 48 | 49 | fun View.showOrHide(show: Boolean) = if (show) show() else hide() 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/logging/FabricTree.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.logging 16 | 17 | import com.google.firebase.crashlytics.FirebaseCrashlytics 18 | import timber.log.Timber 19 | 20 | /** @author Aidan Follestad (@afollestad) */ 21 | class FabricTree : Timber.Tree() { 22 | 23 | override fun log( 24 | priority: Int, 25 | tag: String?, 26 | message: String, 27 | t: Throwable? 28 | ) { 29 | try { 30 | if (t != null) { 31 | if (tag != null) 32 | FirebaseCrashlytics.getInstance().setCustomKey("crash_tag", tag) 33 | FirebaseCrashlytics.getInstance().recordException(t) 34 | } else { 35 | FirebaseCrashlytics.getInstance().log(message) 36 | } 37 | } catch (e: IllegalStateException) { 38 | // TODO this is caught so that Robolelectric tests which test classes that make use of Timber don't crash. 39 | // TODO they crash because Robolectric initializes the app and this tree in release configurations, 40 | // TODO and calls to Timber logging ends up here. 41 | // TODO we should maybe somehow avoid adding this tree to Timber in the context of Robolectric tests, 42 | // TODO somehow? 43 | e.printStackTrace() 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/models/Album.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.models 16 | 17 | import android.database.Cursor 18 | import android.provider.MediaStore.Audio.Albums.* 19 | import android.support.v4.media.MediaBrowserCompat 20 | import android.support.v4.media.MediaDescriptionCompat 21 | import com.naman14.timberx.playback.TimberMusicService.Companion.TYPE_ALBUM 22 | import com.naman14.timberx.extensions.value 23 | import com.naman14.timberx.extensions.valueOrEmpty 24 | import com.naman14.timberx.util.Utils 25 | import kotlinx.android.parcel.Parcelize 26 | 27 | @Parcelize 28 | data class Album( 29 | var id: Long = 0, 30 | var title: String = "", 31 | var artist: String = "", 32 | var artistId: Long = 0, 33 | var songCount: Int = 0, 34 | var year: Int = 0 35 | ) : MediaBrowserCompat.MediaItem( 36 | MediaDescriptionCompat.Builder() 37 | .setMediaId(MediaID(TYPE_ALBUM.toString(), id.toString()).asString()) 38 | .setTitle(title) 39 | .setIconUri(Utils.getAlbumArtUri(id)) 40 | .setSubtitle(artist) 41 | .build(), FLAG_BROWSABLE) { 42 | 43 | companion object { 44 | fun fromCursor(cursor: Cursor): Album { 45 | return Album( 46 | id = cursor.value(_ID), 47 | title = cursor.valueOrEmpty(ALBUM), 48 | artist = cursor.valueOrEmpty(ARTIST), 49 | artistId = cursor.value("artist_id"), 50 | songCount = cursor.value(NUMBER_OF_SONGS), 51 | year = cursor.value(FIRST_YEAR) 52 | ) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/models/Artist.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.models 16 | 17 | import android.database.Cursor 18 | import android.provider.MediaStore.Audio.Artists.ARTIST 19 | import android.provider.MediaStore.Audio.Artists.NUMBER_OF_ALBUMS 20 | import android.provider.MediaStore.Audio.Artists.NUMBER_OF_TRACKS 21 | import android.provider.MediaStore.Audio.Artists._ID 22 | import android.support.v4.media.MediaBrowserCompat 23 | import android.support.v4.media.MediaDescriptionCompat 24 | import com.naman14.timberx.playback.TimberMusicService.Companion.TYPE_ARTIST 25 | import com.naman14.timberx.extensions.value 26 | import kotlinx.android.parcel.Parcelize 27 | 28 | @Parcelize 29 | data class Artist( 30 | var id: Long = 0, 31 | var name: String = "", 32 | var songCount: Int = 0, 33 | var albumCount: Int = 0 34 | ) : MediaBrowserCompat.MediaItem( 35 | MediaDescriptionCompat.Builder() 36 | .setMediaId(MediaID(TYPE_ARTIST.toString(), id.toString()).asString()) 37 | .setTitle(name) 38 | .setSubtitle("$albumCount albums") 39 | .build(), FLAG_BROWSABLE) { 40 | companion object { 41 | fun fromCursor(cursor: Cursor): Artist { 42 | return Artist( 43 | id = cursor.value(_ID), 44 | name = cursor.value(ARTIST), 45 | songCount = cursor.value(NUMBER_OF_TRACKS), 46 | albumCount = cursor.value(NUMBER_OF_ALBUMS) 47 | ) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/models/CategorySongData.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.models 16 | 17 | import android.os.Parcelable 18 | import kotlinx.android.parcel.Parcelize 19 | 20 | @Parcelize 21 | data class CategorySongData( 22 | val title: String, 23 | val songCount: Int, 24 | val type: Int, 25 | val id: Long 26 | ) : Parcelable 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/models/Genre.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.models 16 | 17 | import android.database.Cursor 18 | import android.provider.MediaStore.Audio.Genres.NAME 19 | import android.provider.MediaStore.Audio.Genres._ID 20 | import android.support.v4.media.MediaBrowserCompat 21 | import android.support.v4.media.MediaDescriptionCompat 22 | import com.naman14.timberx.playback.TimberMusicService.Companion.TYPE_GENRE 23 | import com.naman14.timberx.extensions.value 24 | import com.naman14.timberx.extensions.valueOrEmpty 25 | import kotlinx.android.parcel.Parcelize 26 | 27 | @Parcelize 28 | data class Genre( 29 | val id: Long, 30 | val name: String, 31 | val songCount: Int 32 | ) : MediaBrowserCompat.MediaItem( 33 | MediaDescriptionCompat.Builder() 34 | .setMediaId(MediaID("$TYPE_GENRE", "$id").asString()) 35 | .setTitle(name) 36 | .setSubtitle("$songCount songs") 37 | .build(), FLAG_BROWSABLE) { 38 | companion object { 39 | fun fromCursor(cursor: Cursor, songCount: Int): Genre { 40 | return Genre( 41 | id = cursor.value(_ID), 42 | name = cursor.valueOrEmpty(NAME), 43 | songCount = songCount 44 | ) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/models/MediaID.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.models 16 | 17 | import android.support.v4.media.MediaBrowserCompat 18 | 19 | class MediaID(var type: String? = null, var mediaId: String? = "NA", var caller: String? = currentCaller) { 20 | companion object { 21 | const val CALLER_SELF = "self" 22 | const val CALLER_OTHER = "other" 23 | 24 | private const val TYPE = "type: " 25 | private const val MEDIA_ID = "media_id: " 26 | private const val CALLER = "caller: " 27 | private const val SEPARATOR = " | " 28 | 29 | var currentCaller: String? = MediaID.CALLER_SELF 30 | } 31 | 32 | var mediaItem: MediaBrowserCompat.MediaItem? = null 33 | 34 | fun asString(): String { 35 | return TYPE + type + SEPARATOR + MEDIA_ID + mediaId + SEPARATOR + CALLER + caller 36 | } 37 | 38 | fun fromString(s: String): MediaID { 39 | this.type = s.substring(6, s.indexOf(SEPARATOR)) 40 | this.mediaId = s.substring(s.indexOf(SEPARATOR) + 3 + 10, s.lastIndexOf(SEPARATOR)) 41 | this.caller = s.substring(s.lastIndexOf(SEPARATOR) + 3 + 8, s.length) 42 | return this 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/models/Playlist.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.models 16 | 17 | import android.database.Cursor 18 | import android.support.v4.media.MediaBrowserCompat 19 | import android.support.v4.media.MediaDescriptionCompat 20 | import com.naman14.timberx.playback.TimberMusicService.Companion.TYPE_PLAYLIST 21 | import com.naman14.timberx.extensions.value 22 | import com.naman14.timberx.extensions.valueOrEmpty 23 | import kotlinx.android.parcel.Parcelize 24 | import android.provider.MediaStore.Audio.Playlists._ID 25 | import android.provider.MediaStore.Audio.Playlists.NAME 26 | 27 | @Parcelize 28 | data class Playlist( 29 | val id: Long, 30 | val name: String, 31 | val songCount: Int 32 | ) : MediaBrowserCompat.MediaItem( 33 | MediaDescriptionCompat.Builder() 34 | .setMediaId(MediaID(TYPE_PLAYLIST.toString(), id.toString()).asString()) 35 | .setTitle(name) 36 | .setSubtitle("$songCount songs") 37 | .build(), FLAG_BROWSABLE) { 38 | companion object { 39 | fun fromCursor(cursor: Cursor, songCount: Int): Playlist { 40 | return Playlist( 41 | id = cursor.value(_ID), 42 | name = cursor.valueOrEmpty(NAME), 43 | songCount = songCount 44 | ) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/models/QueueData.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.models 16 | 17 | import android.support.v4.media.MediaMetadataCompat.METADATA_KEY_MEDIA_ID 18 | import android.support.v4.media.session.MediaControllerCompat 19 | import com.naman14.timberx.extensions.toIDList 20 | 21 | data class QueueData( 22 | var queueTitle: String = "All songs", 23 | var queue: LongArray = LongArray(0), 24 | var currentId: Long = 0 25 | ) { 26 | fun fromMediaController(mediaControllerCompat: MediaControllerCompat?): QueueData { 27 | mediaControllerCompat?.let { 28 | return QueueData( 29 | queueTitle = mediaControllerCompat.queueTitle?.toString().orEmpty().let { 30 | if (it.isEmpty()) "All songs" else it 31 | }, 32 | queue = mediaControllerCompat.queue?.toIDList() ?: LongArray(0), 33 | currentId = mediaControllerCompat.metadata?.getString(METADATA_KEY_MEDIA_ID)?.toLong() ?: 0 34 | ) 35 | } 36 | return QueueData() 37 | } 38 | 39 | override fun equals(other: Any?): Boolean { 40 | if (this === other) return true 41 | if (javaClass != other?.javaClass) return false 42 | 43 | other as QueueData 44 | 45 | if (queueTitle != other.queueTitle) return false 46 | if (!queue.contentEquals(other.queue)) return false 47 | if (currentId != other.currentId) return false 48 | 49 | return true 50 | } 51 | 52 | override fun hashCode(): Int { 53 | var result = queueTitle.hashCode() 54 | result = 31 * result + queue.contentHashCode() 55 | result = 31 * result + currentId.hashCode() 56 | return result 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/network/LastFmModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.network 16 | 17 | import com.google.gson.Gson 18 | import com.naman14.timberx.network.api.LastFmRestService 19 | import okhttp3.OkHttpClient 20 | import org.koin.dsl.module.module 21 | import retrofit2.Retrofit 22 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 23 | import retrofit2.converter.gson.GsonConverterFactory 24 | 25 | private const val LAST_FM_API_HOST = "http://ws.audioscrobbler.com/2.0/" 26 | 27 | val lastFmModule = module { 28 | 29 | single { 30 | val client = get() 31 | val gson = get() 32 | val retrofit = Retrofit.Builder() 33 | .baseUrl(LAST_FM_API_HOST) 34 | .client(client) 35 | .addConverterFactory(GsonConverterFactory.create(gson)) 36 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 37 | .build() 38 | retrofit.create(LastFmRestService::class.java) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/network/LyricsModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.network 16 | 17 | import com.naman14.timberx.network.api.LyricsRestService 18 | import com.naman14.timberx.network.conversion.LyricsConverterFactory 19 | import okhttp3.OkHttpClient 20 | import org.koin.dsl.module.module 21 | import retrofit2.Retrofit 22 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 23 | 24 | private const val LYRICS_API_HOST = "https://makeitpersonal.co" 25 | 26 | val lyricsModule = module { 27 | 28 | single { 29 | val client = get() 30 | val retrofit = Retrofit.Builder() 31 | .baseUrl(LYRICS_API_HOST) 32 | .client(client) 33 | .addConverterFactory(LyricsConverterFactory()) 34 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 35 | .build() 36 | retrofit.create(LyricsRestService::class.java) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/network/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.network 16 | 17 | import android.app.Application 18 | import com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES 19 | import com.google.gson.GsonBuilder 20 | import okhttp3.Cache 21 | import okhttp3.OkHttpClient 22 | import org.koin.dsl.module.module 23 | 24 | private const val CACHE_MAX_AGE = 60 * 60 * 24 * 7 25 | private const val CACHE_MAX_STALE = 31536000 26 | private const val CACHE_SIZE = 1024L * 1024 27 | private const val CACHE_CONTROL = "Cache-Control" 28 | 29 | val networkModule = module { 30 | 31 | // OkHttp 32 | single { 33 | val cacheHeader = "max-age=$CACHE_MAX_AGE,max-stale=$CACHE_MAX_STALE" 34 | val cache = Cache(get().cacheDir, CACHE_SIZE) 35 | OkHttpClient.Builder() 36 | .cache(cache) 37 | .addInterceptor { 38 | val newRequest = it.request() 39 | .newBuilder() 40 | .addHeader(CACHE_CONTROL, cacheHeader) 41 | .build() 42 | it.proceed(newRequest) 43 | } 44 | .build() 45 | } 46 | 47 | // Gson 48 | single { 49 | GsonBuilder() 50 | .setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES) 51 | .create() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/network/Outcome.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.network 16 | 17 | sealed class Outcome { 18 | data class Success(var data: T) : Outcome() 19 | data class Failure(val e: Throwable) : Outcome() 20 | data class ApiError(val e: Throwable) : Outcome() 21 | 22 | companion object { 23 | fun success(data: T): Outcome = Success(data) 24 | 25 | fun failure(e: Throwable): Outcome = Failure(e) 26 | 27 | fun apiError(e: Throwable): Outcome = ApiError(e) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/network/api/LastFmRestService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.network.api 16 | 17 | import com.naman14.timberx.network.models.AlbumInfo 18 | import com.naman14.timberx.network.models.ArtistInfo 19 | import io.reactivex.Observable 20 | import retrofit2.http.GET 21 | import retrofit2.http.Headers 22 | import retrofit2.http.Query 23 | 24 | private const val API_KEY = "fdb3a51437d4281d4d64964d333531d4" 25 | private const val FORMAT = "json" 26 | 27 | private const val BASE_PARAMETERS_ALBUM = "?method=album.getinfo&api_key=$API_KEY&format=$FORMAT" 28 | private const val BASE_PARAMETERS_ARTIST = "?method=artist.getinfo&api_key=$API_KEY&format=$FORMAT" 29 | 30 | interface LastFmRestService { 31 | 32 | @Headers("Cache-Control: public") 33 | @GET(BASE_PARAMETERS_ALBUM) 34 | fun getAlbumInfo(@Query("artist") artist: String, @Query("album") album: String): Observable 35 | 36 | @Headers("Cache-Control: public") 37 | @GET(BASE_PARAMETERS_ARTIST) 38 | fun getArtistInfo(@Query("artist") artist: String): Observable 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/network/api/LyricsRestService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.network.api 16 | 17 | import io.reactivex.Observable 18 | import retrofit2.http.GET 19 | import retrofit2.http.Headers 20 | import retrofit2.http.Query 21 | 22 | interface LyricsRestService { 23 | 24 | @Headers("Cache-Control: public") 25 | @GET("/lyrics") 26 | fun getLyrics(@Query("artist") artist: String, @Query("title") title: String): Observable 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/network/conversion/LyricsConverterFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.network.conversion 16 | 17 | import java.lang.reflect.Type 18 | 19 | import okhttp3.MediaType 20 | import okhttp3.RequestBody 21 | import okhttp3.ResponseBody 22 | import retrofit2.Converter 23 | import retrofit2.Retrofit 24 | 25 | class LyricsConverterFactory : Converter.Factory() { 26 | 27 | override fun responseBodyConverter( 28 | type: Type?, 29 | annotations: Array?, 30 | retrofit: Retrofit? 31 | ): Converter? { 32 | return if (String::class.java == type) { 33 | Converter { value -> value.string() } 34 | } else null 35 | } 36 | 37 | override fun requestBodyConverter( 38 | type: Type?, 39 | parameterAnnotations: Array?, 40 | methodAnnotations: Array?, 41 | retrofit: Retrofit? 42 | ): Converter<*, RequestBody>? { 43 | 44 | return if (String::class.java == type) { 45 | Converter { value -> RequestBody.create(MEDIA_TYPE, value) } 46 | } else null 47 | } 48 | 49 | companion object { 50 | private val MEDIA_TYPE = MediaType.parse("text/plain") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/network/models/AlbumInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.network.models 16 | 17 | import com.google.gson.annotations.SerializedName 18 | 19 | data class AlbumInfo(@SerializedName("album") val album: LastfmAlbum?) 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/network/models/ArtistBio.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.network.models 16 | 17 | import com.google.gson.annotations.SerializedName 18 | 19 | data class ArtistBio( 20 | @SerializedName("summary") val summary: String, 21 | @SerializedName("content") val content: String 22 | ) 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/network/models/ArtistInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.network.models 16 | 17 | import com.google.gson.annotations.SerializedName 18 | 19 | data class ArtistInfo(@SerializedName("artist") val artist: LastfmArtist?) 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/network/models/Artwork.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.network.models 16 | 17 | import com.google.gson.annotations.SerializedName 18 | 19 | data class Artwork( 20 | @SerializedName("#text") val url: String, 21 | @SerializedName("size") val size: String 22 | ) 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/network/models/LastfmAlbum.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.network.models 16 | 17 | import com.google.gson.annotations.SerializedName 18 | 19 | enum class ArtworkSize(val apiValue: String) { 20 | SMALL("small"), 21 | MEDIUM("medium"), 22 | LARGE("large"), 23 | EXTRA_LARGE("extralarge"), 24 | MEGA("mega") 25 | } 26 | 27 | data class LastfmAlbum(@SerializedName("image") val artwork: List) 28 | 29 | fun List.ofSize(size: ArtworkSize): Artwork { 30 | val result = firstOrNull { it.size == size.apiValue } ?: last() 31 | return if (size == ArtworkSize.MEGA) { 32 | result.copy(url = result.url.replace("300x300", "700x700")) 33 | } else { 34 | result 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/network/models/LastfmArtist.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.network.models 16 | 17 | import com.google.gson.annotations.SerializedName 18 | 19 | data class LastfmArtist(@SerializedName("image") val artwork: List, @SerializedName("bio") val bio: ArtistBio) 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/notifications/NotificationsModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.notifications 16 | 17 | import android.app.Application 18 | import android.app.NotificationManager 19 | import android.content.Context 20 | import com.naman14.timberx.extensions.systemService 21 | import org.koin.dsl.module.module 22 | 23 | val notificationModule = module { 24 | 25 | factory { 26 | get().systemService(Context.NOTIFICATION_SERVICE) 27 | } 28 | 29 | single { 30 | RealNotifications(get(), get()) 31 | } bind Notifications::class 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/permissions/PermissionsModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.permissions 16 | 17 | import com.naman14.timberx.MAIN 18 | import org.koin.dsl.module.module 19 | 20 | val permissionsModule = module { 21 | 22 | single { 23 | RealPermissionsManager(get(), get(name = MAIN)) 24 | } bind PermissionsManager::class 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/playback/BecomingNoisyReceiver.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.playback 16 | 17 | import android.content.BroadcastReceiver 18 | import android.content.Context 19 | import android.content.Intent 20 | import android.content.IntentFilter 21 | import android.media.AudioManager 22 | import android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY 23 | import android.support.v4.media.session.MediaControllerCompat 24 | import android.support.v4.media.session.MediaSessionCompat 25 | 26 | /** 27 | * Helper class for listening for when headphones are unplugged (or the audio 28 | * will otherwise cause playback to become "noisy"). 29 | */ 30 | class BecomingNoisyReceiver( 31 | private val context: Context, 32 | sessionToken: MediaSessionCompat.Token 33 | ) : BroadcastReceiver() { 34 | 35 | private val noisyIntentFilter = IntentFilter(ACTION_AUDIO_BECOMING_NOISY) 36 | private val controller = MediaControllerCompat(context, sessionToken) 37 | 38 | private var registered = false 39 | 40 | fun register() { 41 | if (!registered) { 42 | context.registerReceiver(this, noisyIntentFilter) 43 | registered = true 44 | } 45 | } 46 | 47 | fun unregister() { 48 | if (registered) { 49 | context.unregisterReceiver(this) 50 | registered = false 51 | } 52 | } 53 | 54 | override fun onReceive(context: Context, intent: Intent) { 55 | if (intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY) { 56 | controller.transportControls.pause() 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/playback/MediaModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.playback 16 | 17 | import com.naman14.timberx.playback.players.MusicPlayer 18 | import com.naman14.timberx.playback.players.Queue 19 | import com.naman14.timberx.playback.players.RealMusicPlayer 20 | import com.naman14.timberx.playback.players.RealQueue 21 | import com.naman14.timberx.playback.players.RealSongPlayer 22 | import com.naman14.timberx.playback.players.SongPlayer 23 | import org.koin.dsl.module.module 24 | 25 | val mediaModule = module { 26 | 27 | factory { 28 | RealMusicPlayer(get()) 29 | } bind MusicPlayer::class 30 | 31 | factory { 32 | RealQueue(get(), get(), get()) 33 | } bind Queue::class 34 | 35 | factory { 36 | RealSongPlayer(get(), get(), get(), get(), get()) 37 | } bind SongPlayer::class 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/repository/RepositoriesModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.repository 16 | 17 | import com.naman14.timberx.PREF_ALBUM_SORT_ORDER 18 | import com.naman14.timberx.PREF_SONG_SORT_ORDER 19 | import org.koin.dsl.module.module 20 | 21 | val repositoriesModule = module { 22 | 23 | factory { 24 | RealSongsRepository(get(), get(name = PREF_SONG_SORT_ORDER)) 25 | } bind SongsRepository::class 26 | 27 | factory { 28 | RealAlbumRepository(get(), get(name = PREF_ALBUM_SORT_ORDER)) 29 | } bind AlbumRepository::class 30 | 31 | factory { 32 | RealArtistRepository(get()) 33 | } bind ArtistRepository::class 34 | 35 | factory { 36 | RealGenreRepository(get()) 37 | } bind GenreRepository::class 38 | 39 | factory { 40 | RealPlaylistRepository(get()) 41 | } bind PlaylistRepository::class 42 | 43 | factory { 44 | RealFoldersRepository() 45 | } bind FoldersRepository::class 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/activities/ExpandedControlsActivity.java: -------------------------------------------------------------------------------- 1 | package com.naman14.timberx.ui.activities; 2 | 3 | import android.os.Bundle; 4 | import android.view.Menu; 5 | import android.view.View; 6 | import com.google.android.gms.cast.framework.CastButtonFactory; 7 | import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity; 8 | import com.naman14.timberx.R; 9 | 10 | public class ExpandedControlsActivity extends ExpandedControllerActivity { 11 | 12 | @Override 13 | public boolean onCreateOptionsMenu(Menu menu) { 14 | super.onCreateOptionsMenu(menu); 15 | getMenuInflater().inflate(R.menu.menu_expanded_controller, menu); 16 | CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item); 17 | return true; 18 | } 19 | 20 | @Override 21 | protected void onCreate(Bundle bundle) { 22 | super.onCreate(bundle); 23 | getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/activities/base/PermissionsActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | @file:Suppress("MemberVisibilityCanBePrivate") 16 | 17 | package com.naman14.timberx.ui.activities.base 18 | 19 | import android.os.Bundle 20 | import androidx.appcompat.app.AppCompatActivity 21 | import com.naman14.timberx.permissions.PermissionsManager 22 | import org.koin.android.ext.android.inject 23 | 24 | /** 25 | * Automatically attaches and detaches the activity from [PermissionsManager]. Also automatically 26 | * handles permission results by pushing them back into the manager. 27 | */ 28 | abstract class PermissionsActivity : AppCompatActivity() { 29 | protected val permissionsManager by inject() 30 | 31 | override fun onCreate(savedInstanceState: Bundle?) { 32 | super.onCreate(savedInstanceState) 33 | permissionsManager.attach(this) 34 | } 35 | 36 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { 37 | permissionsManager.processResult(requestCode, permissions, grantResults) 38 | } 39 | 40 | override fun onDestroy() { 41 | permissionsManager.detach(this) 42 | super.onDestroy() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/adapters/ArtistAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.adapters 16 | 17 | import android.view.ViewGroup 18 | import androidx.recyclerview.widget.RecyclerView 19 | import com.naman14.timberx.R 20 | import com.naman14.timberx.databinding.ItemArtistBinding 21 | import com.naman14.timberx.models.Artist 22 | import com.naman14.timberx.extensions.inflateWithBinding 23 | 24 | class ArtistAdapter : RecyclerView.Adapter() { 25 | var artists: List = emptyList() 26 | private set 27 | 28 | init { 29 | setHasStableIds(true) 30 | } 31 | 32 | fun updateData(artists: List) { 33 | this.artists = artists 34 | notifyDataSetChanged() 35 | } 36 | 37 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 38 | return ViewHolder(parent.inflateWithBinding(R.layout.item_artist)) 39 | } 40 | 41 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 42 | holder.binding.albumArt.setImageDrawable(null) 43 | holder.bind(artists[position]) 44 | } 45 | 46 | override fun getItemCount() = artists.size 47 | 48 | override fun getItemId(position: Int) = artists[position].id 49 | 50 | class ViewHolder constructor(var binding: ItemArtistBinding) : RecyclerView.ViewHolder(binding.root) { 51 | 52 | fun bind(artist: Artist) { 53 | binding.artist = artist 54 | binding.executePendingBindings() 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/adapters/GenreAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.adapters 16 | 17 | import android.view.ViewGroup 18 | import androidx.recyclerview.widget.RecyclerView 19 | import com.naman14.timberx.R 20 | import com.naman14.timberx.databinding.ItemGenreBinding 21 | import com.naman14.timberx.models.Genre 22 | import com.naman14.timberx.extensions.inflateWithBinding 23 | 24 | class GenreAdapter : RecyclerView.Adapter() { 25 | var genres: List = emptyList() 26 | private set 27 | 28 | fun updateData(genres: List) { 29 | this.genres = genres 30 | notifyDataSetChanged() 31 | } 32 | 33 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 34 | return ViewHolder(parent.inflateWithBinding(R.layout.item_genre)) 35 | } 36 | 37 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 38 | holder.bind(genres[position]) 39 | } 40 | 41 | override fun getItemCount() = genres.size 42 | 43 | class ViewHolder constructor(var binding: ItemGenreBinding) : RecyclerView.ViewHolder(binding.root) { 44 | 45 | fun bind(genre: Genre) { 46 | binding.genre = genre 47 | binding.executePendingBindings() 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/adapters/PlaylistAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.adapters 16 | 17 | import android.view.ViewGroup 18 | import androidx.recyclerview.widget.RecyclerView 19 | import com.naman14.timberx.R 20 | import com.naman14.timberx.databinding.ItemPlaylistBinding 21 | import com.naman14.timberx.models.Playlist 22 | import com.naman14.timberx.extensions.inflateWithBinding 23 | 24 | class PlaylistAdapter : RecyclerView.Adapter() { 25 | var playlists: List = emptyList() 26 | private set 27 | 28 | fun updateData(playlists: List) { 29 | this.playlists = playlists 30 | notifyDataSetChanged() 31 | } 32 | 33 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 34 | return ViewHolder(parent.inflateWithBinding(R.layout.item_playlist)) 35 | } 36 | 37 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 38 | holder.bind(playlists[position]) 39 | } 40 | 41 | override fun getItemCount() = playlists.size 42 | 43 | class ViewHolder constructor(var binding: ItemPlaylistBinding) : RecyclerView.ViewHolder(binding.root) { 44 | 45 | fun bind(playlist: Playlist) { 46 | binding.playlist = playlist 47 | binding.executePendingBindings() 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/dialogs/AboutDialog.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.dialogs 16 | 17 | import android.app.Dialog 18 | import android.os.Bundle 19 | import androidx.annotation.NonNull 20 | import androidx.fragment.app.DialogFragment 21 | import androidx.fragment.app.FragmentActivity 22 | import com.afollestad.materialdialogs.MaterialDialog 23 | import com.afollestad.materialdialogs.callbacks.onDismiss 24 | import com.naman14.timberx.R 25 | 26 | class AboutDialog : DialogFragment() { 27 | 28 | companion object { 29 | private const val TAG = "AboutDialog" 30 | 31 | fun show(activity: FragmentActivity) = AboutDialog().show(activity.supportFragmentManager, TAG) 32 | } 33 | 34 | @NonNull 35 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 36 | return MaterialDialog(activity!!).show { 37 | title(R.string.about_dialog_title) 38 | message(R.string.about_dialog_body, lineHeightMultiplier = 1.4f, html = true) 39 | positiveButton(R.string.about_dialog_dismiss) 40 | onDismiss { 41 | // Make sure the DialogFragment dismisses as well 42 | this@AboutDialog.dismiss() 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/fragments/SettingsFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.fragments 16 | 17 | import android.os.Bundle 18 | import androidx.preference.PreferenceFragmentCompat 19 | import com.naman14.timberx.R 20 | 21 | class SettingsFragment : PreferenceFragmentCompat() { 22 | 23 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 24 | addPreferencesFromResource(R.xml.preferences) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/fragments/base/BaseNowPlayingFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.fragments.base 16 | 17 | import android.os.Bundle 18 | import com.naman14.timberx.R 19 | import com.naman14.timberx.extensions.observe 20 | import com.naman14.timberx.extensions.safeActivity 21 | import com.naman14.timberx.ui.activities.MainActivity 22 | import com.naman14.timberx.ui.fragments.NowPlayingFragment 23 | import com.naman14.timberx.ui.viewmodels.MainViewModel 24 | import com.naman14.timberx.ui.viewmodels.NowPlayingViewModel 25 | import org.koin.androidx.viewmodel.ext.android.sharedViewModel 26 | 27 | open class BaseNowPlayingFragment : CoroutineFragment() { 28 | 29 | protected val mainViewModel by sharedViewModel() 30 | protected val nowPlayingViewModel by sharedViewModel() 31 | 32 | override fun onActivityCreated(savedInstanceState: Bundle?) { 33 | super.onActivityCreated(savedInstanceState) 34 | nowPlayingViewModel.currentData.observe(this) { showHideBottomSheet() } 35 | } 36 | 37 | override fun onPause() { 38 | showHideBottomSheet() 39 | super.onPause() 40 | } 41 | 42 | private fun showHideBottomSheet() { 43 | val activity = safeActivity as MainActivity 44 | nowPlayingViewModel.currentData.value?.let { 45 | if (!it.title.isNullOrEmpty()) { 46 | if (activity.supportFragmentManager.findFragmentById(R.id.container) is NowPlayingFragment) { 47 | activity.hideBottomSheet() 48 | } else { 49 | activity.showBottomSheet() 50 | } 51 | } else { 52 | activity.hideBottomSheet() 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/fragments/base/CoroutineFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | @file:Suppress("MemberVisibilityCanBePrivate") 16 | 17 | package com.naman14.timberx.ui.fragments.base 18 | 19 | import androidx.fragment.app.Fragment 20 | import kotlinx.coroutines.CoroutineDispatcher 21 | import kotlinx.coroutines.CoroutineScope 22 | import kotlinx.coroutines.CoroutineStart 23 | import kotlinx.coroutines.Dispatchers.Main 24 | import kotlinx.coroutines.Job 25 | import kotlinx.coroutines.launch 26 | import kotlin.coroutines.CoroutineContext 27 | 28 | /** 29 | * A base Fragment that allows you to launch coroutines that cancel if they are active when the 30 | * Fragment is destroyed. 31 | * 32 | * @author Aidan Follestad (@afollestad) 33 | */ 34 | abstract class CoroutineFragment : Fragment() { 35 | 36 | protected val mainDispatcher: CoroutineDispatcher get() = Main 37 | 38 | private val job = Job() 39 | protected val scope = CoroutineScope(job + mainDispatcher) 40 | 41 | protected fun launch( 42 | context: CoroutineContext = mainDispatcher, 43 | start: CoroutineStart = CoroutineStart.DEFAULT, 44 | block: suspend CoroutineScope.() -> Unit 45 | ) = scope.launch(context, start, block) 46 | 47 | override fun onDestroyView() { 48 | job.cancel() 49 | super.onDestroyView() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/listeners/PopupMenuListener.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.listeners 16 | 17 | import android.content.Context 18 | import com.naman14.timberx.models.Song 19 | 20 | interface PopupMenuListener { 21 | 22 | fun play(song: Song) 23 | 24 | fun goToAlbum(song: Song) 25 | 26 | fun goToArtist(song: Song) 27 | 28 | fun addToPlaylist(context: Context, song: Song) 29 | 30 | fun deleteSong(context: Context, song: Song) 31 | 32 | fun removeFromPlaylist(song: Song, playlistId: Long) 33 | 34 | fun playNext(song: Song) 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/listeners/SortMenuListener.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.listeners 16 | 17 | interface SortMenuListener { 18 | 19 | fun shuffleAll() 20 | 21 | fun sortAZ() 22 | 23 | fun sortZA() 24 | 25 | fun sortYear() 26 | 27 | fun sortDuration() 28 | 29 | fun numOfSongs() 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/viewmodels/MediaItemFragmentViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.viewmodels 16 | 17 | import android.support.v4.media.MediaBrowserCompat 18 | import android.support.v4.media.MediaBrowserCompat.MediaItem 19 | import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback 20 | import androidx.lifecycle.LiveData 21 | import androidx.lifecycle.MutableLiveData 22 | import androidx.lifecycle.ViewModel 23 | import com.naman14.timberx.playback.MediaSessionConnection 24 | import com.naman14.timberx.models.MediaID 25 | 26 | class MediaItemFragmentViewModel( 27 | private val mediaId: MediaID, 28 | mediaSessionConnection: MediaSessionConnection 29 | ) : ViewModel() { 30 | 31 | private val _mediaItems = MutableLiveData>() 32 | .apply { postValue(emptyList()) } 33 | 34 | val mediaItems: LiveData> = _mediaItems 35 | 36 | private val subscriptionCallback = object : SubscriptionCallback() { 37 | override fun onChildrenLoaded(parentId: String, children: List) { 38 | _mediaItems.postValue(children) 39 | } 40 | } 41 | 42 | private val mediaSessionConnection = mediaSessionConnection.also { 43 | it.subscribe(mediaId.asString(), subscriptionCallback) 44 | } 45 | 46 | //hacky way to force reload items (e.g. song sort order changed) 47 | fun reloadMediaItems() { 48 | mediaSessionConnection.unsubscribe(mediaId.asString(), subscriptionCallback) 49 | mediaSessionConnection.subscribe(mediaId.asString(), subscriptionCallback) 50 | } 51 | 52 | override fun onCleared() { 53 | super.onCleared() 54 | // And then, finally, unsubscribe the media ID that was being watched. 55 | mediaSessionConnection.unsubscribe(mediaId.asString(), subscriptionCallback) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/viewmodels/ViewModelsModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.viewmodels 16 | 17 | import com.naman14.timberx.models.MediaID 18 | import org.koin.androidx.viewmodel.ext.koin.viewModel 19 | import org.koin.dsl.module.module 20 | 21 | val viewModelsModule = module { 22 | viewModel { 23 | MainViewModel(get(), get(), get(), get(), get(), get()) 24 | } 25 | 26 | viewModel { 27 | SearchViewModel(get(), get(), get()) 28 | } 29 | 30 | viewModel { (mediaId: MediaID) -> 31 | MediaItemFragmentViewModel(mediaId, get()) 32 | } 33 | 34 | viewModel { 35 | NowPlayingViewModel(get()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/viewmodels/base/CoroutineViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.viewmodels.base 16 | 17 | import androidx.lifecycle.ViewModel 18 | import kotlinx.coroutines.CoroutineDispatcher 19 | import kotlinx.coroutines.CoroutineScope 20 | import kotlinx.coroutines.CoroutineStart 21 | import kotlinx.coroutines.Job 22 | import kotlinx.coroutines.launch 23 | import kotlin.coroutines.CoroutineContext 24 | 25 | /** 26 | * A base view model that allows you to launch coroutines that cancel if they are active when the 27 | * view model is destroyed. 28 | * 29 | * @author Aidan Follestad (@afollestad) 30 | */ 31 | abstract class CoroutineViewModel( 32 | private val mainDispatcher: CoroutineDispatcher 33 | ) : ViewModel() { 34 | 35 | private val job = Job() 36 | protected val scope = CoroutineScope(job + mainDispatcher) 37 | 38 | protected fun launch( 39 | context: CoroutineContext = mainDispatcher, 40 | start: CoroutineStart = CoroutineStart.DEFAULT, 41 | block: suspend CoroutineScope.() -> Unit 42 | ) = scope.launch(context, start, block) 43 | 44 | override fun onCleared() { 45 | super.onCleared() 46 | job.cancel() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/widgets/AlbumSortMenu.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.widgets 16 | 17 | import android.content.Context 18 | import android.util.AttributeSet 19 | import androidx.appcompat.widget.AppCompatImageView 20 | import androidx.appcompat.widget.PopupMenu 21 | import com.naman14.timberx.R 22 | import com.naman14.timberx.ui.listeners.SortMenuListener 23 | 24 | class AlbumSortMenu constructor(context: Context, attrs: AttributeSet) : AppCompatImageView(context, attrs) { 25 | 26 | private var sortMenuListener: SortMenuListener? = null 27 | 28 | init { 29 | setImageResource(R.drawable.ic_sort_black) 30 | setOnClickListener { 31 | val popupMenu = PopupMenu(context, this) 32 | popupMenu.setOnMenuItemClickListener { 33 | when (it.itemId) { 34 | R.id.menu_sort_by_az -> sortMenuListener?.sortAZ() 35 | R.id.menu_sort_by_za -> sortMenuListener?.sortZA() 36 | R.id.menu_sort_by_year -> sortMenuListener?.sortYear() 37 | R.id.menu_sort_by_number_of_songs -> sortMenuListener?.numOfSongs() 38 | } 39 | true 40 | } 41 | popupMenu.inflate(R.menu.album_sort_by) 42 | popupMenu.show() 43 | } 44 | } 45 | 46 | fun setupMenu(listener: SortMenuListener?) { 47 | this.sortMenuListener = listener 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/widgets/BottomSheetListener.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.widgets 16 | 17 | import android.view.View 18 | import androidx.annotation.NonNull 19 | 20 | interface BottomSheetListener { 21 | fun onStateChanged(@NonNull bottomSheet: View, newState: Int) 22 | fun onSlide(@NonNull bottomSheet: View, slideOffset: Float) 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/widgets/RecyclerItemClickListener.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.widgets 16 | 17 | import android.view.View 18 | import androidx.recyclerview.widget.RecyclerView 19 | 20 | typealias RecyclerViewItemClickListener = (position: Int, view: View) -> Unit 21 | 22 | class RecyclerItemClickListener( 23 | private val mRecycler: RecyclerView, 24 | private val clickListener: RecyclerViewItemClickListener? = null, 25 | private val longClickListener: RecyclerViewItemClickListener? = null 26 | ) : RecyclerView.OnChildAttachStateChangeListener { 27 | 28 | override fun onChildViewDetachedFromWindow(view: View) { 29 | view.setOnClickListener(null) 30 | view.setOnLongClickListener(null) 31 | } 32 | 33 | override fun onChildViewAttachedToWindow(view: View) { 34 | view.setOnClickListener { v -> setOnChildAttachedToWindow(v) } 35 | } 36 | 37 | private fun setOnChildAttachedToWindow(v: View?) { 38 | if (v != null) { 39 | val position = mRecycler.getChildLayoutPosition(v) 40 | if (position >= 0) { 41 | clickListener?.invoke(position, v) 42 | longClickListener?.invoke(position, v) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/widgets/SongSortMenu.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.widgets 16 | 17 | import android.content.Context 18 | import android.util.AttributeSet 19 | import androidx.appcompat.widget.AppCompatImageView 20 | import androidx.appcompat.widget.PopupMenu 21 | import com.naman14.timberx.R 22 | import com.naman14.timberx.ui.listeners.SortMenuListener 23 | 24 | class SongSortMenu constructor(context: Context, attrs: AttributeSet) : AppCompatImageView(context, attrs) { 25 | 26 | private var sortMenuListener: SortMenuListener? = null 27 | 28 | init { 29 | setImageResource(R.drawable.ic_sort_black) 30 | setOnClickListener { 31 | val popupMenu = PopupMenu(context, this) 32 | popupMenu.setOnMenuItemClickListener { 33 | when (it.itemId) { 34 | R.id.menu_sort_by_az -> sortMenuListener?.sortAZ() 35 | R.id.menu_sort_by_za -> sortMenuListener?.sortZA() 36 | R.id.menu_sort_by_year -> sortMenuListener?.sortYear() 37 | R.id.menu_sort_by_duration -> sortMenuListener?.sortDuration() 38 | } 39 | true 40 | } 41 | popupMenu.inflate(R.menu.song_sort_by) 42 | popupMenu.show() 43 | } 44 | } 45 | 46 | fun setupMenu(listener: SortMenuListener?) { 47 | this.sortMenuListener = listener 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/ui/widgets/SquareImageView.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.ui.widgets 16 | 17 | import android.content.Context 18 | import android.util.AttributeSet 19 | import androidx.appcompat.widget.AppCompatImageView 20 | 21 | class SquareImageView : AppCompatImageView { 22 | 23 | constructor(context: Context) : super(context) 24 | 25 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) 26 | 27 | constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) 28 | 29 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 30 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 31 | setMeasuredDimension(measuredWidth, measuredWidth) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/util/AutoClearedValue.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.util 16 | 17 | import androidx.fragment.app.Fragment 18 | import androidx.lifecycle.Lifecycle 19 | import androidx.lifecycle.LifecycleObserver 20 | import androidx.lifecycle.OnLifecycleEvent 21 | import kotlin.properties.ReadWriteProperty 22 | import kotlin.reflect.KProperty 23 | 24 | /** 25 | * A lazy property that gets cleaned up when the fragment is destroyed. 26 | * 27 | * Accessing this variable in a destroyed fragment will throw NPE. 28 | */ 29 | class AutoClearedValue(val fragment: Fragment) : ReadWriteProperty { 30 | private var _value: T? = null 31 | 32 | init { 33 | fragment.lifecycle.addObserver(object : LifecycleObserver { 34 | @Suppress("unused") 35 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) 36 | fun onDestroy() { 37 | _value = null 38 | } 39 | }) 40 | } 41 | 42 | override fun getValue(thisRef: Fragment, property: KProperty<*>): T { 43 | return _value ?: throw IllegalStateException( 44 | "should never call auto-cleared-value get when it might not be available" 45 | ) 46 | } 47 | 48 | override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) { 49 | _value = value 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/util/Event.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.util 16 | 17 | /** 18 | * Used as a wrapper for data that is exposed via a LiveData that represents an event. 19 | * 20 | * For more information, see: 21 | * https://medium.com/google-developers/livedata-with-events-ac2622673150 22 | */ 23 | class Event(private val content: T) { 24 | 25 | private var hasBeenHandled = false 26 | 27 | /** 28 | * Returns the content and prevents its use again. 29 | */ 30 | fun getContentIfNotHandled(): T? { 31 | return if (hasBeenHandled) { 32 | null 33 | } else { 34 | hasBeenHandled = true 35 | content 36 | } 37 | } 38 | 39 | /** 40 | * Returns the content, even if it's already been handled. 41 | */ 42 | fun peekContent(): T = content 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/util/MusicUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ 15 | package com.naman14.timberx.util 16 | 17 | import android.content.ContentUris 18 | import android.content.Context 19 | import android.graphics.Bitmap 20 | import android.graphics.BitmapFactory 21 | import android.net.Uri 22 | import android.provider.MediaStore 23 | import com.naman14.timberx.R 24 | import java.io.FileNotFoundException 25 | import android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI as AUDIO_URI 26 | import timber.log.Timber.d as log 27 | 28 | // TODO get rid of this and move things to respective repositories 29 | object MusicUtils { 30 | 31 | fun getSongUri(id: Long): Uri { 32 | return ContentUris.withAppendedId(AUDIO_URI, id) 33 | } 34 | 35 | fun getRealPathFromURI(context: Context, contentUri: Uri): String { 36 | val projection = arrayOf(MediaStore.Audio.Media.DATA) 37 | log("Querying $contentUri") 38 | return context.contentResolver.query(contentUri, projection, null, null, null)?.use { 39 | val dataIndex = it.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA) 40 | if (it.moveToFirst()) { 41 | it.getString(dataIndex) 42 | } else { 43 | "" 44 | } 45 | } ?: throw IllegalStateException("Unable to query $contentUri, system returned null.") 46 | } 47 | 48 | fun getAlbumArtBitmap(context: Context, albumId: Long?): Bitmap? { 49 | if (albumId == null) return null 50 | return try { 51 | MediaStore.Images.Media.getBitmap(context.contentResolver, Utils.getAlbumArtUri(albumId)) 52 | } catch (e: FileNotFoundException) { 53 | BitmapFactory.decodeResource(context.resources, R.drawable.icon) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/naman14/timberx/util/SpacesItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.naman14.timberx.util; 2 | 3 | import android.graphics.Rect; 4 | import android.view.View; 5 | import androidx.recyclerview.widget.RecyclerView; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public class SpacesItemDecoration extends RecyclerView.ItemDecoration { 9 | private final int space; 10 | 11 | public SpacesItemDecoration(int space) { 12 | this.space = space; 13 | } 14 | 15 | @Override 16 | public void getItemOffsets( 17 | @NotNull Rect outRect, 18 | @NotNull View view, 19 | @NotNull RecyclerView parent, 20 | @NotNull RecyclerView.State state) { 21 | outRect.left = space; 22 | outRect.right = space; 23 | outRect.bottom = space; 24 | 25 | // Add top margin only for the first item to avoid double space between items 26 | // if (parent.getChildLayoutPosition(view) == 0) { 27 | // outRect.top = space; 28 | // } else { 29 | // outRect.top = 0; 30 | // } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/res/animator/appbar_elevation_disable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/animator/appbar_elevation_enable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_playlist_fab.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_search_box.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_up_next.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/default_album_art_large.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/default_album_art_small.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_down_outline.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_file_music_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_folder_open_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_folder_parent_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_more_vert.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_more_vert_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_music_note.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_next.xml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notification.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pause.xml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pause_outline.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play.xml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play_outline.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_playlist_play.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_previous.xml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_previous_outline.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_queue_music.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_reorder.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_repeat_all.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_repeat_none.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_repeat_one.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_shuffle_all.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_shuffle_none.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_skip_outline.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sort_black.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_timer_wait.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/app/src/main/res/drawable/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/progress_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_corner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_corners_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/seek_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/splashscreen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/font/rubik_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/app/src/main/res/font/rubik_medium.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/rubik_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/app/src/main/res/font/rubik_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 20 | 21 | 27 | 28 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_category_songs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 18 | 19 | 26 | 27 | 36 | 37 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_playlists.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 17 | 18 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_queue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 18 | 19 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_album.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 13 | 14 | 15 | 25 | 26 | 38 | 39 | 49 | 50 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_artist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 21 | 22 | 32 | 33 | 41 | 42 | 49 | 50 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_artist_album.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | 24 | 36 | 37 | 47 | 48 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_folder_list.xml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | 19 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_genre.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 20 | 21 | 28 | 29 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_playlist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 19 | 20 | 27 | 28 | 34 | 35 | 42 | 43 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_recyclerview.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_recyclerview_padding.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 16 | 17 | 21 | 22 | 26 | 27 | 34 | 35 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 19 | 20 | 31 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 19 | 20 | 29 | 30 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/menu/album_sort_by.xml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 20 | 23 | 26 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_expanded_controller.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_popup_song.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 12 | 13 | 16 | 17 | 20 | 21 | 24 | 25 | 28 | 29 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/menu/song_sort_by.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-es/strings-about.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @string/app_name 4 | TimberX es un reproductor de música con diseño material, con todas las funcionalidades y 6 | open source, que funciona en todos los dispositivos incluyendo teléfonos, wear, Auto y dispositivos de streaming.

7 | Diseñado y desarrollado por Naman Dwivedi. Visita mi 8 | página web y GitHub! 9 | ]]>
10 | Descartar 11 |
-------------------------------------------------------------------------------- /app/src/main/res/values-it/strins-about.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @string/app_name 5 | TimberX è un player musicale completo, open source e dal design Material che funziona 7 | su tutti i generi di dispositivi, inclusi telefoni, indossabili, automoboli e dispositivi con Assistant e Cast.

8 | Disegnato e sviluppato da Naman Dwivedi. Dai un\'occhiata al mio 9 | Website e al mio GitHub! 10 | ]]>
11 | Ignora 12 | 13 |
14 | -------------------------------------------------------------------------------- /app/src/main/res/values-land/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 3 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-nl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Nummers 4 | Albums 5 | Artiesten 6 | Genres 7 | Mappen 8 | Afspeellijsten 9 | 10 | Afspelen 11 | Toevoegen aan afspeellijst 12 | Ga naar Album 13 | Ga naar artiest 14 | Volgende afspelen 15 | Verwijderen van apparaat 16 | 17 | A-Z 18 | Z-A 19 | Jaar 20 | Duur 21 | 22 | %d nummer toegevoegd aan afspeellijst. 23 | %d nummers toegevoegd aan afspeellijst. 24 | 25 | Creëren 26 | Verwijderen 27 | 28 | Nieuwe afspeellijst creëren 29 | Nummer verwijderen? 30 | Voer een naam in voor de afspeellijst… 31 | Afspeellijst gecreëerd! 32 | Kan afspeellijst niet creëren! 33 | 34 | Media afspelen 35 | Afspeelbediening 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/values-pt-rBR/strings-about.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TimberX é um reprodutor de música temático de material livre com todos os recursos, que funciona em todos os 6 | fatores, incluindo telefones, wear, auto, assistente e dispositivos de cast.

7 | Projetado e desenvolvido por Naman Dwivedi. Confira meu 8 | Website e GitHub! 9 | ]]>
10 | Dispensar 11 | 12 |
13 | -------------------------------------------------------------------------------- /app/src/main/res/values-v23/plurals.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @string/number_song_add_playlist 6 | @string/number_songs_add_playlist 7 | 8 | 9 | 10 | %d song was deleted. 11 | %d songs were deleted. 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values-v23/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values-v27/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 14 | 15 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | @string/light 23 | @string/dark 24 | @string/black 25 | 26 | 27 | 28 | light 29 | dark 30 | black 31 | 32 | 33 | 34 | @string/songs 35 | @string/albums 36 | @string/artists 37 | @string/folders 38 | @string/genres 39 | @string/playlists 40 | 41 | 42 | 43 | songs 44 | albums 45 | artists 46 | folders 47 | genres 48 | playlists 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ffffff 4 | #ffffff 5 | #427add 6 | #000000 7 | #22000000 8 | #66000000 9 | #f5f5f5 10 | #eeeeee 11 | 12 | #303030 13 | #303030 14 | #427add 15 | #ffffff 16 | #22ffffff 17 | #66ffffff 18 | #404040 19 | #36454f 20 | 21 | #000000 22 | #000000 23 | #427add 24 | #ffffff 25 | #22ffffff 26 | #66ffffff 27 | #202020 28 | #36454f 29 | 30 | #ffffff 31 | #000000 32 | 33 | #185abb 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 55dp 5 | 100dp 6 | 250dp 7 | 8 | 2dp 9 | 42dp 10 | 11 | 4dp 12 | 8dp 13 | 10dp 14 | 15 | 65dp 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #2979FF 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/plurals.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d album 6 | %d albums 7 | 8 | 9 | 10 | %d song 11 | %d songs 12 | 13 | 14 | 15 | %d song was deleted. 16 | %d songs were deleted. 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings-about.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @string/app_name 5 | TimberX is a fully featured, open source, Material themed music player that works across all 7 | form factors including phones, Wear, Auto, Assistant and Cast devices.

8 | Designed and developed by Naman Dwivedi. Check out my 9 | Website and GitHub! 10 | ]]>
11 | Dismiss 12 | 13 |
14 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings-notranslate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TimberX 5 | Timber 6 | 7 | \u2022 8 | 9 | %2$d:%3$02d 10 | %1$d:%2$02d:%3$02d 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles-views.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/xml/automotive_app_desc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 19 | 20 | 28 | 29 | 37 | 38 | 44 | 45 | 51 | 52 | -------------------------------------------------------------------------------- /art/auto1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/art/auto1.png -------------------------------------------------------------------------------- /art/auto2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/art/auto2.png -------------------------------------------------------------------------------- /art/auto3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/art/auto3.jpg -------------------------------------------------------------------------------- /art/auto4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/art/auto4.jpg -------------------------------------------------------------------------------- /art/cast1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/art/cast1.jpg -------------------------------------------------------------------------------- /art/cast2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/art/cast2.jpg -------------------------------------------------------------------------------- /art/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/art/icon.png -------------------------------------------------------------------------------- /art/icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/art/icon2.png -------------------------------------------------------------------------------- /art/phone1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/art/phone1.png -------------------------------------------------------------------------------- /art/phone2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/art/phone2.png -------------------------------------------------------------------------------- /art/phone3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/art/phone3.png -------------------------------------------------------------------------------- /art/phone4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/art/phone4.png -------------------------------------------------------------------------------- /art/wear1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/art/wear1.png -------------------------------------------------------------------------------- /art/wear2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/art/wear2.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | apply from: './dependencies.gradle' 3 | 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | 9 | dependencies { 10 | classpath "com.android.tools.build:gradle:${versions.gradlePlugin}" 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" 12 | classpath "com.google.gms:google-services:${versions.googleServices}" 13 | classpath "com.google.firebase:firebase-crashlytics-gradle:${versions.crashlyticsPlugin}" 14 | classpath "com.diffplug.spotless:spotless-plugin-gradle:${versions.spotlessPlugin}" 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | mavenCentral() 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /dependencies.gradle: -------------------------------------------------------------------------------- 1 | ext.versions = [ 2 | // App 3 | minSdk : 21, 4 | targetSdk : 28, 5 | versionCode : 10, 6 | versionName : "1.9", 7 | 8 | // Plugins 9 | gradlePlugin : "4.2.0-alpha13", 10 | googleServices : "4.2.0", 11 | crashlyticsPlugin : "2.3.0", 12 | spotlessPlugin : "3.17.0", 13 | 14 | // Kotlin 15 | kotlin : "1.4.10", 16 | coroutines : "1.1.1", 17 | 18 | // AndroidX 19 | constraintLayout : "1.1.3", 20 | appCompat : "1.0.2", 21 | coordinatorlayout : "1.0.0", 22 | recyclerView : "1.0.0", 23 | cardView : "1.0.0", 24 | materialComponents: "1.0.0", 25 | ktx : "1.0.1", 26 | legacySupport : "1.0.0", 27 | preference : "1.1.0-alpha01", 28 | 29 | // Compose 30 | compose : "1.0.0-alpha04", 31 | 32 | // Arch Components 33 | lifecycle : "2.1.0-alpha02", 34 | room : "2.2.5", 35 | databinding : "3.2.0-alpha10", 36 | 37 | // RxJava 38 | rxJava : "2.2.6", 39 | rxAndroid : "2.1.0", 40 | rxRetrofit : "2.5.0", 41 | 42 | // Other Google 43 | mediaCompat : "28.0.0", 44 | firebase : "16.0.6", 45 | cast : "16.1.2", 46 | 47 | // Third Party 48 | rxkPrefs : "1.2.5", 49 | materialDialogs : "2.1.0", 50 | crashlytics : "17.2.2", 51 | glide : "4.8.0", 52 | gson : "2.8.4", 53 | retrofit : "2.4.0", 54 | retrofitGson : "2.3.0", 55 | nanoHttpd : "2.3.1", 56 | koin : "1.0.2", 57 | 58 | // Debug 59 | timber : "4.7.1", 60 | debugDb : "1.0.3", 61 | 62 | // Unit Testing 63 | junit : "4.12", 64 | mockito : "2.23.4", 65 | mockitoKotlin : "2.1.0", 66 | truth : "0.42", 67 | robolectric : "4.1", 68 | 69 | // Instrumentation Testing 70 | androidxTestRunner: "1.1.1", 71 | androidxTest : "1.1.0", 72 | archTesting : "2.0.0", 73 | espresso : "3.0.2" 74 | ] 75 | -------------------------------------------------------------------------------- /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 | android.useAndroidX=true 15 | android.enableJetifier=True 16 | android.databinding.enableV2=true 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naman14/TimberX/6a258d2c3dc4211ba0968458a3e913d27a07dff9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Sep 18 21:54:57 IST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip 7 | -------------------------------------------------------------------------------- /mock.gradle: -------------------------------------------------------------------------------- 1 | // This script must be applied in app/build.gradle for the paths here to work correctly 2 | 3 | def copyMockFilesNeeded() { 4 | def srcGoogleServicesFile = file("../mock/mock-google-services.json") 5 | def srcCastSecretFile = file("../mock/mock-secret.xml") 6 | 7 | def destGoogleServicesFile = file("google-services.json") 8 | def destCastSecretFile = file("src/main/res/values/secret.xml") 9 | 10 | if (!destGoogleServicesFile.exists()) { 11 | destGoogleServicesFile.write(srcGoogleServicesFile.text) 12 | } 13 | if (!destCastSecretFile.exists()) { 14 | destCastSecretFile.write(srcCastSecretFile.text) 15 | } 16 | } 17 | 18 | afterEvaluate { 19 | copyMockFilesNeeded() 20 | } -------------------------------------------------------------------------------- /mock/mock-google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "123456789000", 4 | "firebase_url": "https://mockproject-1234.firebaseio.com", 5 | "project_id": "mockproject-1234", 6 | "storage_bucket": "mockproject-1234.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", 12 | "android_client_info": { 13 | "package_name": "com.naman14.timberx" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo" 25 | } 26 | ], 27 | "services": { 28 | "analytics_service": { 29 | "status": 1 30 | }, 31 | "appinvite_service": { 32 | "status": 1, 33 | "other_platform_oauth_client": [] 34 | }, 35 | "ads_service": { 36 | "status": 2 37 | } 38 | } 39 | } 40 | ], 41 | "configuration_version": "1" 42 | } 43 | -------------------------------------------------------------------------------- /mock/mock-secret.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 123456 11 | 12 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /spotless.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.diffplug.gradle.spotless" 2 | spotless { 3 | java { 4 | target "**/*.java" 5 | trimTrailingWhitespace() 6 | removeUnusedImports() 7 | googleJavaFormat() 8 | endWithNewline() 9 | } 10 | kotlin { 11 | target "**/*.kt" 12 | ktlint().userData(['indent_size': '4', 'continuation_indent_size': '2']) 13 | licenseHeaderFile '../spotless.license.kt' 14 | trimTrailingWhitespace() 15 | endWithNewline() 16 | } 17 | } -------------------------------------------------------------------------------- /spotless.license.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Naman Dwivedi. 3 | * 4 | * Licensed under the GNU General Public License v3 5 | * 6 | * This is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. 13 | * 14 | */ --------------------------------------------------------------------------------