├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── 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-night
│ │ │ │ └── colors.xml
│ │ │ ├── drawable
│ │ │ │ ├── ic_play_black_48dp.xml
│ │ │ │ ├── ic_pause_black_48dp.xml
│ │ │ │ ├── ic_skip_next_black_48dp.xml
│ │ │ │ ├── ic_stop_black_24dp.xml
│ │ │ │ ├── ic_home_black_24dp.xml
│ │ │ │ ├── ic_skip_next_black_24dp.xml
│ │ │ │ ├── ic_skip_previous_black_24dp.xml
│ │ │ │ ├── ic_playlist_black_24dp.xml
│ │ │ │ ├── ic_music_note_black_12dp.xml
│ │ │ │ ├── ic_now_playing_black_24dp.xml
│ │ │ │ ├── ic_music_note_black_24dp.xml
│ │ │ │ ├── ic_genres_black_24dp.xml
│ │ │ │ ├── ic_album_black_24dp.xml
│ │ │ │ ├── ic_tracks_black_24dp.xml
│ │ │ │ ├── ic_artist_black_24dp.xml
│ │ │ │ ├── bg_no_art.xml
│ │ │ │ ├── now_playing_anim.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── values
│ │ │ │ ├── themes.xml
│ │ │ │ ├── styles.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── strings.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── layout
│ │ │ │ ├── item_track.xml
│ │ │ │ └── activity_dashboard.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── github
│ │ │ │ └── odaridavid
│ │ │ │ └── zikk
│ │ │ │ ├── models
│ │ │ │ ├── Genre.kt
│ │ │ │ ├── PlaybackStatus.kt
│ │ │ │ ├── MediaId.kt
│ │ │ │ ├── Playlist.kt
│ │ │ │ ├── Artist.kt
│ │ │ │ ├── Album.kt
│ │ │ │ ├── PlayableTrack.kt
│ │ │ │ ├── Track.kt
│ │ │ │ └── MediaCategoryInfo.kt
│ │ │ │ ├── utils
│ │ │ │ ├── Constants.kt
│ │ │ │ ├── SdkUtils.kt
│ │ │ │ ├── InjectorUtils.kt
│ │ │ │ ├── MediaControllerUtils.kt
│ │ │ │ ├── PermissionUtils.kt
│ │ │ │ ├── TimeUtils.kt
│ │ │ │ ├── DebugUtils.kt
│ │ │ │ ├── MediaItemUtils.kt
│ │ │ │ ├── ViewUtils.kt
│ │ │ │ └── MediaMetadataUtils.kt
│ │ │ │ ├── di
│ │ │ │ ├── GenreModule.kt
│ │ │ │ ├── ArtistModule.kt
│ │ │ │ ├── DataModule.kt
│ │ │ │ ├── PlaylistModule.kt
│ │ │ │ ├── AlbumModule.kt
│ │ │ │ ├── TrackModule.kt
│ │ │ │ ├── AppComponent.kt
│ │ │ │ ├── PlaybackModule.kt
│ │ │ │ └── AppModule.kt
│ │ │ │ ├── mappers
│ │ │ │ ├── MediaItemToPlayableTrack.kt
│ │ │ │ └── TrackToPlayableTrack.kt
│ │ │ │ ├── ZikkApp.kt
│ │ │ │ ├── data
│ │ │ │ └── LastPlayedTrackPreference.kt
│ │ │ │ ├── playback
│ │ │ │ ├── BecomingNoisyReceiver.kt
│ │ │ │ ├── player
│ │ │ │ │ └── TrackPlayer.kt
│ │ │ │ └── session
│ │ │ │ │ ├── ZikkMediaService.kt
│ │ │ │ │ ├── MediaLoader.kt
│ │ │ │ │ └── MediaSessionCallback.kt
│ │ │ │ ├── base
│ │ │ │ └── BaseActivity.kt
│ │ │ │ ├── ui
│ │ │ │ ├── DashboardViewModel.kt
│ │ │ │ ├── TracksAdapter.kt
│ │ │ │ └── DashboardActivity.kt
│ │ │ │ ├── notification
│ │ │ │ ├── NotificationsChannelManager.kt
│ │ │ │ └── PlaybackNotificationBuilder.kt
│ │ │ │ └── repositories
│ │ │ │ ├── GenreRepository.kt
│ │ │ │ ├── PlaylistRepository.kt
│ │ │ │ ├── ArtistRepository.kt
│ │ │ │ ├── AlbumRepository.kt
│ │ │ │ └── TrackRepository.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── github
│ │ │ └── odaridavid
│ │ │ └── zikk
│ │ │ ├── SongRepositoryTest.kt
│ │ │ ├── DashboardViewModelTest.kt
│ │ │ └── RecentsRepositoryTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── github
│ │ └── odaridavid
│ │ └── zikk
│ │ └── DashboardActivityTest.kt
├── proguard-rules.pro
├── google-services.json
└── build.gradle
├── settings.gradle
├── art
└── app.gif
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── README.md
├── gradle.properties
├── gradlew.bat
├── gradlew
└── LICENSE
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name='Zikk'
2 | include ':app'
3 |
--------------------------------------------------------------------------------
/art/app.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odaridavid/Zikk-Music-App/HEAD/art/app.gif
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odaridavid/Zikk-Music-App/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odaridavid/Zikk-Music-App/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odaridavid/Zikk-Music-App/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odaridavid/Zikk-Music-App/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odaridavid/Zikk-Music-App/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odaridavid/Zikk-Music-App/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odaridavid/Zikk-Music-App/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odaridavid/Zikk-Music-App/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odaridavid/Zikk-Music-App/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odaridavid/Zikk-Music-App/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odaridavid/Zikk-Music-App/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @color/grey800
4 | @color/grey800Dark
5 | @color/yellowA700
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Apr 09 01:31:24 EAT 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-5.6.4-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play_black_48dp.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_pause_black_48dp.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_skip_next_black_48dp.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_stop_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_home_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_skip_next_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_skip_previous_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_playlist_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_music_note_black_12dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_now_playing_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_music_note_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_genres_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_album_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_tracks_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 4dp
5 | 4dp
6 | 120dp
7 | 80dp
8 | 72dp
9 | 72dp
10 | 8dp
11 | 48dp
12 | 48dp
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @color/grey100
4 | @color/grey100Dark
5 | @color/yellowA700
6 |
7 |
8 | #ffff52
9 | #ffd600
10 | #f5f5f5
11 | #c2c2c2
12 | #424242
13 | #1b1b1b
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_artist_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/test/java/com/github/odaridavid/zikk/SongRepositoryTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
--------------------------------------------------------------------------------
/app/src/test/java/com/github/odaridavid/zikk/DashboardViewModelTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | class DashboardViewModelTest
--------------------------------------------------------------------------------
/app/src/test/java/com/github/odaridavid/zikk/RecentsRepositoryTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | class RecentsRepositoryTest
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/github/odaridavid/zikk/DashboardActivityTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | class DashboardActivityTest
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/models/Genre.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.models
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | internal data class Genre(val id: Long, val name: String)
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/models/PlaybackStatus.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.models
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | class PlaybackStatus(val prevTrackIndex: Int, val currentTrackIndex: Int)
--------------------------------------------------------------------------------
/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/github/odaridavid/zikk/models/MediaId.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.models
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | enum class MediaId {
17 | ARTIST,
18 | GENRE,
19 | ALBUM,
20 | TRACK,
21 | PLAYLIST
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/models/Playlist.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.models
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | internal data class Playlist(
17 | val id: Long,
18 | val name: String,
19 | val modified: String
20 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/utils/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.utils
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | object Constants {
17 | const val PLAYBACK_NOTIFICATION_ID = 1000
18 | const val PREFERENCE_NAME = "zikk_preferences"
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/models/Artist.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.models
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | internal data class Artist(
17 | val id: Long,
18 | val name: String,
19 | val noOfAlbums: Int,
20 | val noOfTracks: Int
21 | )
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_no_art.xml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/models/Album.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.models
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | internal data class Album(
17 | val id: Long,
18 | val title: String,
19 | val artist: String,
20 | val noOfSongs: Int,
21 | val albumArt: String
22 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/models/PlayableTrack.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.models
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.net.Uri
17 |
18 | /**
19 | * Track displayed on the UI
20 | */
21 | data class PlayableTrack(
22 | val mediaId: String?,
23 | val title: String?,
24 | val artist: String,
25 | val icon: Uri?,
26 | val isPlaying: Boolean = false
27 | )
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Zikk
2 |
3 | A sample media player application making use of android MediaPlayer Apis
4 | The sample is still under development and may contain a number of bugs.Feel
5 | free to file an issue if you spot one or send a PR😎
6 |
7 | ### Demo/Screenshots
8 |
9 |
10 |
11 | ### Prerequisites
12 |
13 | Setup a firebase project and hook it up.
14 |
15 | ### License
16 | ```
17 | Copyright 2020 David Odari
18 |
19 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
20 | in compliance with the License. You may obtain a copy of the License at
21 | http://www.apache.org/licenses/LICENSE-2.0
22 | Unless required by applicable law or agreed to in writing, software distributed under the License
23 | is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
24 | or implied. See the License for the specific language governing permissions and limitations under
25 | the License.
26 | ```
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/models/Track.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.models
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | internal data class Track(
17 | val id: Long,
18 | val artistId: Long,
19 | val title: String,
20 | val album: String,
21 | val artist: String,
22 | val displayName: String,
23 | val track: String,
24 | val duration: String,
25 | val filePath: String,
26 | val albumArt: String
27 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/models/MediaCategoryInfo.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.models
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.net.Uri
17 | import android.support.v4.media.MediaBrowserCompat
18 |
19 |
20 | data class MediaCategoryInfo(
21 | val id: MediaId,
22 | val title: String,
23 | val subtitle: String,
24 | val iconUri: Uri,
25 | val description: String,
26 | @MediaBrowserCompat.MediaItem.Flags val mediaFlags: Int
27 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/utils/SdkUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.utils
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.os.Build
17 | import androidx.annotation.IntRange
18 |
19 | fun versionFrom(@IntRange(from = 1, to = 29) versionCode: Int): Boolean {
20 | return Build.VERSION.SDK_INT >= versionCode
21 | }
22 |
23 | fun versionUntil(@IntRange(from = 1, to = 29) versionCode: Int): Boolean {
24 | return Build.VERSION.SDK_INT <= versionCode
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/utils/InjectorUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.utils
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.app.Activity
17 | import android.app.Service
18 | import com.github.odaridavid.zikk.ZikkApp
19 | import com.github.odaridavid.zikk.di.AppComponent
20 |
21 | internal val Activity.injector: AppComponent
22 | get() = (applicationContext as ZikkApp).appComponent
23 |
24 | internal val Service.injector: AppComponent
25 | get() = (applicationContext as ZikkApp).appComponent
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/di/GenreModule.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.di
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.content.Context
17 | import com.github.odaridavid.zikk.repositories.GenreRepository
18 | import dagger.Module
19 | import dagger.Provides
20 |
21 | @Module
22 | internal class GenreModule {
23 |
24 | @Provides
25 | fun providesGenreProvider(applicationContext: Context): GenreRepository {
26 | return GenreRepository(
27 | applicationContext
28 | )
29 | }
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/di/ArtistModule.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.di
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.content.Context
17 | import com.github.odaridavid.zikk.repositories.ArtistRepository
18 | import dagger.Module
19 | import dagger.Provides
20 |
21 |
22 | @Module
23 | internal class ArtistModule {
24 |
25 | @Provides
26 | fun providesArtistsProvider(applicationContext: Context): ArtistRepository {
27 | return ArtistRepository(
28 | applicationContext
29 | )
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/mappers/MediaItemToPlayableTrack.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.mappers
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.support.v4.media.MediaBrowserCompat
17 | import com.github.odaridavid.zikk.models.PlayableTrack
18 |
19 |
20 | fun MediaBrowserCompat.MediaItem.toTrack(): PlayableTrack {
21 | return PlayableTrack(
22 | this.mediaId,
23 | this.description.title.toString(),
24 | this.description.subtitle.toString(),
25 | this.description.iconUri
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/di/DataModule.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.di
2 | /**
3 | *
4 | * Copyright 2020 David Odari
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7 | * in compliance with the License. You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | *
14 | **/
15 | import android.content.SharedPreferences
16 | import com.github.odaridavid.zikk.data.LastPlayedTrackPreference
17 | import dagger.Module
18 | import dagger.Provides
19 |
20 |
21 | @Module
22 | internal class DataModule {
23 |
24 | @Provides
25 | fun providesLastPlayedTrackPreference(sharedPreferences: SharedPreferences):LastPlayedTrackPreference{
26 | return LastPlayedTrackPreference(sharedPreferences)
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/di/PlaylistModule.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.di
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.content.Context
17 | import com.github.odaridavid.zikk.repositories.PlaylistRepository
18 | import dagger.Module
19 | import dagger.Provides
20 |
21 |
22 | @Module
23 | internal class PlaylistModule {
24 |
25 | @Provides
26 | fun providesPlaylistProvider(applicationContext: Context): PlaylistRepository {
27 | return PlaylistRepository(
28 | applicationContext
29 | )
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/di/AlbumModule.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.di
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.content.Context
17 | import com.github.odaridavid.zikk.repositories.AlbumRepository
18 | import dagger.Module
19 | import dagger.Provides
20 |
21 | /**
22 | * Creats Album Module Dependencies
23 | */
24 | @Module
25 | internal class AlbumModule {
26 |
27 | @Provides
28 | fun providesAlbumProvider(applicationContext: Context): AlbumRepository {
29 | return AlbumRepository(
30 | applicationContext
31 | )
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/mappers/TrackToPlayableTrack.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.mappers
2 |
3 | import androidx.core.net.toUri
4 | import com.github.odaridavid.zikk.models.MediaId
5 | import com.github.odaridavid.zikk.models.PlayableTrack
6 | import com.github.odaridavid.zikk.models.Track
7 |
8 | /**
9 | *
10 | * Copyright 2020 David Odari
11 | *
12 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
13 | * in compliance with the License. You may obtain a copy of the License at
14 | * http://www.apache.org/licenses/LICENSE-2.0
15 | * Unless required by applicable law or agreed to in writing, software distributed under the License
16 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
17 | * or implied. See the License for the specific language governing permissions and limitations under
18 | * the License.
19 | *
20 | **/
21 |
22 | internal fun Track.toPlayableTrack(): PlayableTrack {
23 | val mediaId = "${MediaId.TRACK}-${id}"
24 |
25 | return PlayableTrack(
26 | mediaId,
27 | title,
28 | artist,
29 | albumArt.toUri()
30 | )
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/utils/MediaControllerUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.utils
2 |
3 | import android.support.v4.media.session.MediaControllerCompat
4 | import com.github.odaridavid.zikk.ui.DashboardActivity
5 |
6 | /**
7 | *
8 | * Copyright 2020 David Odari
9 | *
10 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
11 | * in compliance with the License. You may obtain a copy of the License at
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | * Unless required by applicable law or agreed to in writing, software distributed under the License
14 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
15 | * or implied. See the License for the specific language governing permissions and limitations under
16 | * the License.
17 | *
18 | **/
19 | internal val DashboardActivity.mediaTranspotControls: MediaControllerCompat.TransportControls?
20 | get() = MediaControllerCompat.getMediaController(this).transportControls
21 |
22 | internal val DashboardActivity.mediaControllerCompat: MediaControllerCompat?
23 | get() = MediaControllerCompat.getMediaController(this)
24 |
25 |
--------------------------------------------------------------------------------
/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "636424474576",
4 | "firebase_url": "https://zikki-cad03.firebaseio.com",
5 | "project_id": "zikki-cad03",
6 | "storage_bucket": "zikki-cad03.appspot.com"
7 | },
8 | "client": [
9 | {
10 | "client_info": {
11 | "mobilesdk_app_id": "1:636424474576:android:8283c4217c46117b87f3d5",
12 | "android_client_info": {
13 | "package_name": "com.github.odaridavid.zikk"
14 | }
15 | },
16 | "oauth_client": [
17 | {
18 | "client_id": "636424474576-73p72pplpnp3njov7tufafhb2h0lvge1.apps.googleusercontent.com",
19 | "client_type": 3
20 | }
21 | ],
22 | "api_key": [
23 | {
24 | "current_key": "AIzaSyBXnRIrGnP22nZ-gvbTVm_LFdvgp_hfkEk"
25 | }
26 | ],
27 | "services": {
28 | "appinvite_service": {
29 | "other_platform_oauth_client": [
30 | {
31 | "client_id": "636424474576-73p72pplpnp3njov7tufafhb2h0lvge1.apps.googleusercontent.com",
32 | "client_type": 3
33 | }
34 | ]
35 | }
36 | }
37 | }
38 | ],
39 | "configuration_version": "1"
40 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/utils/PermissionUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.utils
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.Manifest
17 | import android.content.Context
18 | import android.content.pm.PackageManager
19 | import androidx.core.content.ContextCompat
20 |
21 | object PermissionUtils {
22 |
23 | val STORAGE_PERMISSIONS = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
24 |
25 | fun checkAllPermissionsGranted(context: Context, permissions: Array) = permissions.all {
26 | ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/di/TrackModule.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.di
2 |
3 | import android.content.Context
4 | import com.github.odaridavid.zikk.repositories.AlbumRepository
5 | import com.github.odaridavid.zikk.repositories.TrackRepository
6 | import dagger.Module
7 | import dagger.Provides
8 |
9 | /**
10 | *
11 | * Copyright 2020 David Odari
12 | *
13 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
14 | * in compliance with the License. You may obtain a copy of the License at
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | * Unless required by applicable law or agreed to in writing, software distributed under the License
17 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
18 | * or implied. See the License for the specific language governing permissions and limitations under
19 | * the License.
20 | *
21 | **/
22 | @Module
23 | internal class TrackModule {
24 |
25 | @Provides
26 | fun providesTrackProvider(context: Context, albumRepository: AlbumRepository): TrackRepository {
27 | return TrackRepository(
28 | context,
29 | albumRepository
30 | )
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/ZikkApp.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.app.Application
17 | import com.github.odaridavid.zikk.di.AppComponent
18 | import com.github.odaridavid.zikk.di.DaggerAppComponent
19 | import timber.log.Timber
20 |
21 | internal class ZikkApp : Application() {
22 |
23 | lateinit var appComponent: AppComponent
24 |
25 | override fun onCreate() {
26 | super.onCreate()
27 |
28 | if (BuildConfig.DEBUG)
29 | Timber.plant(Timber.DebugTree())
30 |
31 | appComponent = DaggerAppComponent.factory().create(applicationContext)
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/utils/TimeUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.utils
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import java.util.concurrent.TimeUnit
17 |
18 | /**
19 | * Converts a long to a duration representatioin e.g 3:19,01:00:00
20 | */
21 | fun convertMillisecondsToDuration(millis: Long): String {
22 | val hrs = TimeUnit.MILLISECONDS.toHours(millis)
23 | val minutes = TimeUnit.MILLISECONDS.toMinutes(millis) % TimeUnit.HOURS.toMinutes(1)
24 | val seconds = TimeUnit.MILLISECONDS.toSeconds(millis) % TimeUnit.MINUTES.toSeconds(1)
25 | return if (hrs > 0L)
26 | String.format("%02d:%02d:%02d", hrs, minutes, seconds)
27 | else
28 | String.format("%02d:%02d", minutes, seconds)
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/data/LastPlayedTrackPreference.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.data
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.content.SharedPreferences
17 | import javax.inject.Inject
18 |
19 | /**
20 | * Used to show or hide the player
21 | */
22 | internal class LastPlayedTrackPreference @Inject constructor(private val sharedPreferences: SharedPreferences) {
23 |
24 | var lastPlayedTrackId: Long
25 | set(value) {
26 | val editor = sharedPreferences.edit()
27 | editor.putLong(KEY_LAST_PLAYED_TRACK_ID, value)
28 | editor.apply()
29 | }
30 | get() {
31 | return sharedPreferences.getLong(KEY_LAST_PLAYED_TRACK_ID, DEFAULT_VALUE)
32 | }
33 |
34 | companion object {
35 | private const val KEY_LAST_PLAYED_TRACK_ID = "last_played_id"
36 | private const val DEFAULT_VALUE = -1L
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/playback/BecomingNoisyReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.playback
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | *
16 | **/
17 | import android.content.BroadcastReceiver
18 | import android.content.Context
19 | import android.content.Intent
20 | import android.media.AudioManager
21 | import android.support.v4.media.session.MediaControllerCompat
22 | import javax.inject.Inject
23 |
24 | /**
25 | * @see Don't be noisy
26 | */
27 | class BecomingNoisyReceiver @Inject constructor(private val mediaControllerCompat: MediaControllerCompat) :
28 | BroadcastReceiver() {
29 | override fun onReceive(context: Context, intent: Intent) {
30 | if (intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY) {
31 | mediaControllerCompat.transportControls.pause()
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/utils/DebugUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.utils
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.support.v4.media.session.PlaybackStateCompat
17 |
18 | //TODO Move to a debug source set
19 | internal object DebugUtils {
20 | fun getPlaybackState(state: Int): String {
21 | return when (state) {
22 | PlaybackStateCompat.STATE_NONE -> "Playback None"
23 | PlaybackStateCompat.STATE_STOPPED -> "Playback Stopped"
24 | PlaybackStateCompat.STATE_ERROR -> "Playback Error"
25 | PlaybackStateCompat.STATE_PLAYING -> "Playback Playing"
26 | PlaybackStateCompat.STATE_PAUSED -> "Playback Paused"
27 | PlaybackStateCompat.STATE_SKIPPING_TO_NEXT -> "Playback Skipping To Next"
28 | PlaybackStateCompat.STATE_BUFFERING -> "Playback Buffering"
29 | else -> "Unknown Playback State"
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/base/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.base
2 |
3 | import android.os.Build
4 | import android.os.Bundle
5 | import android.view.View
6 | import androidx.appcompat.app.AppCompatActivity
7 | import com.github.odaridavid.zikk.notification.NotificationsChannelManager
8 | import com.github.odaridavid.zikk.notification.NotificationsChannelManager.Companion.PLAYBACK_CHANNEL_ID
9 | import com.github.odaridavid.zikk.utils.injector
10 | import com.github.odaridavid.zikk.utils.versionFrom
11 | import javax.inject.Inject
12 |
13 | internal abstract class BaseActivity : AppCompatActivity() {
14 | @Inject
15 | lateinit var notificationsChannelManager: NotificationsChannelManager
16 |
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | matchStatusBarWithBackground()
20 | injector.inject(this)
21 | super.onCreate(savedInstanceState)
22 | initNotificationChannel()
23 | }
24 |
25 | private fun matchStatusBarWithBackground() {
26 | if (versionFrom(Build.VERSION_CODES.M)) {
27 | window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
28 | window.statusBarColor = getColor(android.R.color.background_light)
29 | }
30 | }
31 |
32 | private fun initNotificationChannel() {
33 | with(notificationsChannelManager) {
34 | if (versionFrom(Build.VERSION_CODES.O) && !hasChannel(PLAYBACK_CHANNEL_ID))
35 | createNotificationChannel(PLAYBACK_CHANNEL_ID)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/di/AppComponent.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.di
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.content.Context
17 | import com.github.odaridavid.zikk.base.BaseActivity
18 | import com.github.odaridavid.zikk.playback.session.ZikkMediaService
19 | import com.github.odaridavid.zikk.ui.DashboardActivity
20 | import dagger.BindsInstance
21 | import dagger.Component
22 | import javax.inject.Singleton
23 |
24 | /**
25 | * Application Dependency Graph
26 | */
27 | @Singleton
28 | @Component(
29 | modules = [
30 | AppModule::class,
31 |
32 | PlaybackModule::class,
33 | DataModule::class,
34 |
35 | //Media Categories
36 | AlbumModule::class,
37 | ArtistModule::class,
38 | PlaylistModule::class,
39 | TrackModule::class,
40 | GenreModule::class
41 | ]
42 | )
43 | internal interface AppComponent {
44 |
45 | fun inject(baseActivity: BaseActivity)
46 |
47 | fun inject(zikkMediaService: ZikkMediaService)
48 |
49 | fun inject(dashboardActivity: DashboardActivity)
50 |
51 | @Component.Factory
52 | interface Factory {
53 |
54 | fun create(@BindsInstance context: Context): AppComponent
55 |
56 | }
57 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/di/PlaybackModule.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.di
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.content.Context
17 | import com.github.odaridavid.zikk.playback.session.MediaLoader
18 | import com.github.odaridavid.zikk.playback.player.TrackPlayer
19 | import com.github.odaridavid.zikk.repositories.*
20 | import dagger.Module
21 | import dagger.Provides
22 |
23 | @Module
24 | internal class PlaybackModule {
25 |
26 | @Provides
27 | fun providesMediaLoader(
28 | context: Context,
29 | albumRepository: AlbumRepository,
30 | artistRepository: ArtistRepository,
31 | genreRepository: GenreRepository,
32 | trackRepository: TrackRepository,
33 | playlistRepository: PlaylistRepository
34 | ): MediaLoader {
35 | return MediaLoader(
36 | context,
37 | albumRepository,
38 | artistRepository,
39 | genreRepository,
40 | trackRepository,
41 | playlistRepository
42 | )
43 | }
44 |
45 | @Provides
46 | fun providesTrackPlayer(context: Context, trackRepository: TrackRepository): TrackPlayer {
47 | return TrackPlayer(
48 | context,
49 | trackRepository
50 | )
51 | }
52 |
53 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/utils/MediaItemUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.utils
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.content.Context
17 | import android.net.Uri
18 | import android.support.v4.media.MediaBrowserCompat
19 | import android.support.v4.media.MediaDescriptionCompat
20 |
21 | fun createMediaItem(
22 | title: String,
23 | subtitle: String,
24 | mediaItemId: String,
25 | iconUri: Uri?,
26 | description: String,
27 | @MediaBrowserCompat.MediaItem.Flags mediaItemFlags: Int
28 | ): MediaBrowserCompat.MediaItem {
29 | return MediaBrowserCompat.MediaItem(
30 | MediaDescriptionCompat.Builder()
31 | .setMediaId(mediaItemId)
32 | .setTitle(title)
33 | .setSubtitle(subtitle)
34 | .setIconUri(iconUri)
35 | .setMediaUri(null)
36 | .setDescription(description)
37 | .build(),
38 | mediaItemFlags
39 | )
40 | }
41 |
42 | fun getDrawableUri(context: Context, drawableName: String): Uri {
43 | val appPackageName = context.packageName
44 | return Uri.parse("android.resource://$appPackageName/drawable/$drawableName")
45 | }
46 |
47 | fun convertMediaIdToTrackId(mediaId: String): Long {
48 | val spIndex = mediaId.indexOf('-')
49 | return mediaId.substring(spIndex + 1).toLong()
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/di/AppModule.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.di
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.app.NotificationManager
17 | import android.content.Context
18 | import android.content.SharedPreferences
19 | import android.media.AudioManager
20 | import com.github.odaridavid.zikk.notification.NotificationsChannelManager
21 | import com.github.odaridavid.zikk.utils.Constants
22 | import dagger.Module
23 | import dagger.Provides
24 |
25 | /**
26 | * Application wide dependencies
27 | */
28 | @Module
29 | class AppModule {
30 |
31 | @Provides
32 | fun providesNotificationManager(context: Context): NotificationManager {
33 | return context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
34 | }
35 |
36 | @Provides
37 | fun providesAudioManager(context: Context): AudioManager {
38 | return context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
39 | }
40 |
41 | @Provides
42 | fun providesSharedPreference(context: Context): SharedPreferences {
43 | return context.getSharedPreferences(Constants.PREFERENCE_NAME, Context.MODE_PRIVATE)
44 | }
45 |
46 | @Provides
47 | fun providesNotificationChannelsManager(
48 | context: Context,
49 | notificationManager: NotificationManager
50 | ): NotificationsChannelManager {
51 | return NotificationsChannelManager(context, notificationManager)
52 | }
53 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Zikk
3 |
4 |
5 | Songs
6 | Recent
7 | Tracks
8 | Playlists
9 | Albums
10 | Artists
11 | Genres
12 |
13 |
14 | Storage Permissions Not Granted
15 |
16 |
17 |
18 | - %d song
19 | - %d songs
20 |
21 |
22 | - %d album
23 | - %d albums
24 |
25 |
26 |
27 | Playback
28 | Controls Media Playback
29 |
30 |
31 | Pause
32 | Play
33 | Stop
34 | Skip
35 |
36 |
37 | All Tracks
38 | All Albums
39 | All Artists
40 | All Playlists
41 | All Genres
42 |
43 |
44 | Needs Storage Permission
45 | This permission is required to access music stored in the phones storage.
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/utils/ViewUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.utils
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.content.Context
17 | import android.graphics.Color
18 | import android.view.View
19 | import android.widget.Toast
20 | import androidx.annotation.StringRes
21 | import androidx.appcompat.app.AlertDialog
22 |
23 | fun View.show() {
24 | visibility = View.VISIBLE
25 | }
26 |
27 | fun View.invisible() {
28 | visibility = View.INVISIBLE
29 | }
30 |
31 | fun View.hide() {
32 | visibility = View.GONE
33 | }
34 |
35 | fun Context.showToast(msg: String) {
36 | Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
37 | }
38 |
39 | fun Context.showDialog(
40 | @StringRes title: Int,
41 | @StringRes message: Int,
42 | positiveBlock: () -> (Unit),
43 | negativeBlock: () -> (Unit)
44 | ) {
45 | AlertDialog.Builder(this)
46 | .setTitle(getString(title))
47 | .setMessage(getString(message))
48 | .setPositiveButton(android.R.string.yes) { dialog, _ ->
49 | positiveBlock()
50 | dialog.dismiss()
51 | }
52 | .setNegativeButton(android.R.string.no) { dialog, _ ->
53 | negativeBlock()
54 | dialog.dismiss()
55 | }
56 | .setIcon(android.R.drawable.ic_dialog_alert)
57 | .show().apply {
58 | getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(Color.BLACK)
59 | getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(Color.BLACK)
60 | }
61 |
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/ui/DashboardViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.ui
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import androidx.lifecycle.LiveData
17 | import androidx.lifecycle.MutableLiveData
18 | import androidx.lifecycle.ViewModel
19 | import androidx.lifecycle.ViewModelProvider
20 | import com.github.odaridavid.zikk.data.LastPlayedTrackPreference
21 | import com.github.odaridavid.zikk.models.PlaybackStatus
22 |
23 | internal class DashboardViewModel(private val lastPlayedTrackPreference: LastPlayedTrackPreference) :
24 | ViewModel() {
25 |
26 | private val _isMediaBrowserConnected = MutableLiveData()
27 | val isMediaBrowserConnected: LiveData
28 | get() = _isMediaBrowserConnected
29 |
30 | private val _playbackStatus = MutableLiveData()
31 | val playbackStatus: LiveData
32 | get() = _playbackStatus
33 |
34 | fun setIsConnected(value: Boolean) {
35 | _isMediaBrowserConnected.value = value
36 | }
37 |
38 | fun setNowPlayingStatus(playbackStatus: PlaybackStatus) {
39 | _playbackStatus.value = playbackStatus
40 | }
41 |
42 | fun setCurrentlyPlayingTrackId(trackId: Long) {
43 | lastPlayedTrackPreference.lastPlayedTrackId = trackId
44 | }
45 |
46 | class Factory(
47 | private val lastPlayedTrackPreference: LastPlayedTrackPreference
48 | ) : ViewModelProvider.NewInstanceFactory() {
49 |
50 | @Suppress("unchecked_cast")
51 | override fun create(modelClass: Class): T {
52 | return DashboardViewModel(lastPlayedTrackPreference) as T
53 | }
54 | }
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/notification/NotificationsChannelManager.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.notification
2 |
3 | import android.app.NotificationChannel
4 | import android.app.NotificationManager
5 | import android.content.Context
6 | import android.os.Build
7 | import androidx.annotation.RequiresApi
8 | import com.github.odaridavid.zikk.R
9 | import javax.inject.Inject
10 |
11 | /**
12 | *
13 | * Copyright 2020 David Odari
14 | *
15 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
16 | * in compliance with the License. You may obtain a copy of the License at
17 | * http://www.apache.org/licenses/LICENSE-2.0
18 | * Unless required by applicable law or agreed to in writing, software distributed under the License
19 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
20 | * or implied. See the License for the specific language governing permissions and limitations under
21 | * the License.
22 | *
23 | **/
24 | class NotificationsChannelManager @Inject constructor(
25 | val context: Context,
26 | private val notificationManager: NotificationManager
27 | ) {
28 |
29 | @RequiresApi(Build.VERSION_CODES.O)
30 | fun createNotificationChannel(channelId: String) {
31 | val channel = when (channelId) {
32 | PLAYBACK_CHANNEL_ID -> {
33 | val name = context.getString(R.string.notification_playback_channel_name)
34 | val descriptionText =
35 | context.getString(R.string.notification_playback_channel_description)
36 | val importance = NotificationManager.IMPORTANCE_DEFAULT
37 | NotificationChannel(channelId, name, importance).apply {
38 | description = descriptionText
39 | }
40 | }
41 | else -> throw IllegalArgumentException("Unknown Channel Id Received")
42 | }
43 | notificationManager.createNotificationChannel(channel)
44 | }
45 |
46 | @RequiresApi(Build.VERSION_CODES.O)
47 | fun hasChannel(channelId: String): Boolean {
48 | return notificationManager.getNotificationChannel(channelId) != null
49 | }
50 |
51 | companion object {
52 | const val PLAYBACK_CHANNEL_ID = "playback"
53 | }
54 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/repositories/GenreRepository.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.repositories
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.content.Context
17 | import android.database.Cursor
18 | import android.provider.MediaStore
19 | import com.github.odaridavid.zikk.models.Genre
20 |
21 |
22 | internal class GenreRepository(val applicationContext: Context) {
23 |
24 | /**
25 | * Returns a list of genres after extracting from a cursor
26 | */
27 | fun loadAllGenres(): List {
28 | val genres = mutableListOf()
29 | val cursor = getGenreCursor() ?: throw IllegalStateException("Genres cursor is null")
30 | while (cursor.moveToNext()) {
31 | genres.add(cursor.mapToGenreEntity())
32 | }
33 | cursor.close()
34 | return genres
35 | }
36 |
37 | private fun Cursor.mapToGenreEntity(): Genre {
38 | val genreName = getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME)
39 | val genreId = getColumnIndexOrThrow(MediaStore.Audio.Genres._ID)
40 | return Genre(
41 | id = getLong(genreId),
42 | name = getString(genreName)
43 | )
44 | }
45 |
46 | private fun getGenreCursor(): Cursor? {
47 | val cr = applicationContext.contentResolver
48 | val uri = MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI
49 | val projection: Array = getGenreColumns()
50 | val sortOrder = "${MediaStore.Audio.Genres.NAME} DESC"
51 | return cr.query(uri, projection, null, null, sortOrder)
52 | }
53 |
54 | private fun getGenreColumns(): Array {
55 | return arrayOf(
56 | MediaStore.Audio.Genres._ID,
57 | MediaStore.Audio.Genres.NAME
58 | )
59 | }
60 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/utils/MediaMetadataUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.utils
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.graphics.BitmapFactory
6 | import android.graphics.ImageDecoder
7 | import android.net.Uri
8 | import android.os.Build
9 | import android.provider.MediaStore
10 | import android.support.v4.media.MediaMetadataCompat
11 | import androidx.core.net.toUri
12 | import com.github.odaridavid.zikk.R
13 | import java.io.FileNotFoundException
14 |
15 | /**
16 | *
17 | * Copyright 2020 David Odari
18 | *
19 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
20 | * in compliance with the License. You may obtain a copy of the License at
21 | * http://www.apache.org/licenses/LICENSE-2.0
22 | * Unless required by applicable law or agreed to in writing, software distributed under the License
23 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
24 | * or implied. See the License for the specific language governing permissions and limitations under
25 | * the License.
26 | *
27 | **/
28 | inline val MediaMetadataCompat.id: String
29 | get() = getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID)
30 |
31 | inline val MediaMetadataCompat.title: String?
32 | get() = getString(MediaMetadataCompat.METADATA_KEY_TITLE)
33 |
34 | inline val MediaMetadataCompat.artist: String?
35 | get() = getString(MediaMetadataCompat.METADATA_KEY_ARTIST)
36 |
37 | inline val MediaMetadataCompat.duration
38 | get() = getLong(MediaMetadataCompat.METADATA_KEY_DURATION)
39 |
40 | inline val MediaMetadataCompat.album: String?
41 | get() = getString(MediaMetadataCompat.METADATA_KEY_ALBUM)
42 |
43 | inline val MediaMetadataCompat.albumArtUri: Uri
44 | get() = this.getString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI).toUri()
45 |
46 | inline val MediaMetadataCompat.albumArt: Bitmap?
47 | get() = getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART)
48 |
49 |
50 | fun getAlbumArtBitmap(context: Context, imageUri: Uri): Bitmap? {
51 | val cr = context.contentResolver
52 | return try {
53 | if (versionFrom(Build.VERSION_CODES.P)) {
54 | val src = ImageDecoder.createSource(cr, imageUri)
55 | ImageDecoder.decodeBitmap(src)
56 | } else MediaStore.Images.Media.getBitmap(cr, imageUri)
57 | } catch (e: FileNotFoundException) {
58 | BitmapFactory.decodeResource(context.resources, R.drawable.bg_no_art)
59 | }
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/repositories/PlaylistRepository.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.repositories
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.content.Context
17 | import android.database.Cursor
18 | import android.provider.MediaStore
19 | import com.github.odaridavid.zikk.models.Playlist
20 |
21 | internal class PlaylistRepository(val applicationContext: Context) {
22 |
23 | /**
24 | * Returns a list of playlists after extracting from a cursor
25 | */
26 | fun loadAllPlaylists(): List {
27 | val playlists = mutableListOf()
28 | val cursor = getPlaylistCursor() ?: throw IllegalStateException("Playlists cursor is null")
29 | while (cursor.moveToNext()) {
30 | playlists.add(cursor.mapToPlaylistEntity())
31 | }
32 | cursor.close()
33 | return playlists
34 | }
35 |
36 | private fun Cursor.mapToPlaylistEntity(): Playlist {
37 | val playlistId = getColumnIndexOrThrow(MediaStore.Audio.Playlists._ID)
38 | val playlistName = getColumnIndexOrThrow(MediaStore.Audio.Playlists.NAME)
39 | val playlistLastModifiedDate =
40 | getColumnIndexOrThrow(MediaStore.Audio.Playlists.DATE_MODIFIED)
41 | return Playlist(
42 | id = getLong(playlistId),
43 | name = getString(playlistName),
44 | modified = getString(playlistLastModifiedDate)
45 | )
46 | }
47 |
48 | private fun getPlaylistCursor(): Cursor? {
49 | val cr = applicationContext.contentResolver
50 | val uri = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI
51 | val projection: Array = getPlaylistColumns()
52 | val sortOrder = "${MediaStore.Audio.Playlists.NAME} DESC"
53 | return cr.query(uri, projection, null, null, sortOrder)
54 | }
55 |
56 | private fun getPlaylistColumns(): Array {
57 | return arrayOf(
58 | MediaStore.Audio.Playlists._ID,
59 | MediaStore.Audio.Playlists.NAME,
60 | MediaStore.Audio.Playlists.DATE_MODIFIED
61 | )
62 | }
63 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/repositories/ArtistRepository.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.repositories
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.content.Context
17 | import android.database.Cursor
18 | import android.provider.MediaStore
19 | import com.github.odaridavid.zikk.models.Artist
20 |
21 |
22 | internal class ArtistRepository(val applicationContext: Context) {
23 |
24 | /**
25 | * Returns a list of artists after extracting from a cursor
26 | */
27 | fun loadAllArtists(): List {
28 | val artists = mutableListOf()
29 | val cursor = getArtistCursor() ?: throw IllegalStateException("Artists cursor is null")
30 | while (cursor.moveToNext()) {
31 | artists.add(cursor.mapToArtistEntity())
32 | }
33 | cursor.close()
34 | return artists
35 | }
36 |
37 | private fun Cursor.mapToArtistEntity(): Artist {
38 | val artistId = getColumnIndexOrThrow(MediaStore.Audio.Artists._ID)
39 | val artistName = getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST)
40 | val noOfAlbums = getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_ALBUMS)
41 | val noOfTracks = getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_TRACKS)
42 | return Artist(
43 | id = getLong(artistId),
44 | name = getString(artistName),
45 | noOfAlbums = getInt(noOfAlbums),
46 | noOfTracks = getInt(noOfTracks)
47 | )
48 | }
49 |
50 | private fun getArtistCursor(): Cursor? {
51 | val cr = applicationContext.contentResolver
52 | val uri = MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI
53 | val projection: Array = getArtistColumns()
54 | val sortOrder = "${MediaStore.Audio.Artists.ARTIST} DESC"
55 | return cr.query(uri, projection, null, null, sortOrder)
56 | }
57 |
58 | private fun getArtistColumns(): Array {
59 | return arrayOf(
60 | MediaStore.Audio.Artists._ID,
61 | MediaStore.Audio.Artists.ARTIST,
62 | MediaStore.Audio.Artists.NUMBER_OF_ALBUMS,
63 | MediaStore.Audio.Artists.NUMBER_OF_TRACKS
64 | )
65 | }
66 |
67 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/now_playing_anim.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
26 |
33 |
41 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_track.xml:
--------------------------------------------------------------------------------
1 |
14 |
25 |
26 |
29 |
30 |
39 |
40 |
54 |
55 |
67 |
68 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2020 David Odari
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License. You may obtain a copy of the License at
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | * Unless required by applicable law or agreed to in writing, software distributed under the License
9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10 | * or implied. See the License for the specific language governing permissions and limitations under
11 | * the License.
12 | *
13 | **/
14 | apply plugin: 'com.android.application'
15 | apply plugin: 'kotlin-android'
16 | apply plugin: 'kotlin-android-extensions'
17 | apply plugin: 'kotlin-kapt'
18 |
19 | android {
20 | compileSdkVersion 29
21 | buildToolsVersion "29.0.3"
22 |
23 | defaultConfig {
24 | applicationId "com.github.odaridavid.zikk"
25 | minSdkVersion 21
26 | targetSdkVersion 29
27 | versionCode 1
28 | versionName "1.0"
29 |
30 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
31 | }
32 |
33 | buildTypes {
34 | release {
35 | minifyEnabled false
36 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
37 | }
38 | }
39 | kotlinOptions {
40 | jvmTarget = JavaVersion.VERSION_1_8
41 | }
42 | viewBinding {
43 | enabled = true
44 | }
45 |
46 | }
47 |
48 | dependencies {
49 |
50 | def lifecycle_version = "2.2.0"
51 | def coil_version = "0.9.5"
52 | def room_version = "2.2.5"
53 | def dagger2_version = "2.27"
54 | def leak_canary_version = "2.2"
55 | def material_design_version = "1.2.0-alpha05"
56 | def timber_version = "4.7.1"
57 |
58 | implementation fileTree(dir: 'libs', include: ['*.jar'])
59 |
60 | //Kotlin
61 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
62 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5'
63 |
64 | //Jetpack
65 | implementation 'androidx.appcompat:appcompat:1.1.0'
66 | implementation 'androidx.core:core-ktx:1.2.0'
67 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
68 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
69 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
70 | implementation "androidx.legacy:legacy-support-v4:1.0.0"
71 | implementation "androidx.activity:activity-ktx:1.1.0"
72 |
73 | //Material Design
74 | implementation "com.google.android.material:material:$material_design_version"
75 |
76 | //DI
77 | implementation "com.google.dagger:dagger:$dagger2_version"
78 | kapt "com.google.dagger:dagger-compiler:$dagger2_version"
79 |
80 | //Persistence
81 | implementation "androidx.room:room-runtime:$room_version"
82 | kapt "androidx.room:room-compiler:$room_version"
83 | implementation "androidx.room:room-ktx:$room_version"
84 |
85 | //Logging
86 | implementation "com.jakewharton.timber:timber:$timber_version"
87 |
88 | //Animations
89 | implementation 'jp.wasabeef:recyclerview-animators:3.0.0'
90 |
91 | //Image Loading
92 | implementation "io.coil-kt:coil:$coil_version"
93 |
94 | //Analytics
95 | implementation 'com.google.firebase:firebase-analytics:17.3.0'
96 |
97 | //Mem leaks
98 | debugImplementation "com.squareup.leakcanary:leakcanary-android:$leak_canary_version"
99 |
100 | //Unit Testing
101 | testImplementation 'junit:junit:4.13'
102 | testImplementation 'org.amshove.kluent:kluent-android:1.60'
103 | testImplementation "io.mockk:mockk:1.9.3"
104 |
105 | //Instrumented Testing
106 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
107 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
108 | }
109 | apply plugin: 'com.google.gms.google-services'
110 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/repositories/AlbumRepository.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.repositories
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.content.Context
17 | import android.database.Cursor
18 | import android.net.Uri
19 | import android.provider.MediaStore
20 | import com.github.odaridavid.zikk.models.Album
21 |
22 |
23 | internal class AlbumRepository(private val applicationContext: Context) {
24 |
25 | /**
26 | * Returns a list of albums after extracting from a cursor
27 | */
28 | fun loadAllAlbums(): List {
29 | val albums = mutableListOf()
30 | val cursor = getAlbumsCursor() ?: throw IllegalStateException("Albums cursor is null")
31 | while (cursor.moveToNext()) {
32 | albums.add(cursor.mapToAlbumEntity())
33 | }
34 | cursor.close()
35 | return albums
36 | }
37 |
38 | /**
39 | * Returns a filtered list of albums based on the query after extracting from a cursor
40 | */
41 | fun loadAlbumsByQuery(selection: String, selectionArgs: Array): List {
42 | val albums = mutableListOf()
43 | val cursor = getAlbumsCursor(selection, selectionArgs)
44 | ?: throw IllegalStateException("Albums cursor is null")
45 | if (cursor.count != 0) {
46 | cursor.moveToFirst()
47 | do {
48 | albums.add(cursor.mapToAlbumEntity())
49 | } while (cursor.moveToNext())
50 | }
51 | cursor.close()
52 | return albums
53 | }
54 |
55 | /**
56 | * Convert cursor rows to an album entity
57 | */
58 | private fun Cursor.mapToAlbumEntity(): Album {
59 | val id = getColumnIndexOrThrow(MediaStore.Audio.Albums._ID)
60 | val albumTitle = getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM)
61 | val albumArtist = getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST)
62 | val noOfSongs = getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS)
63 | val albumArt = getAlbumArtPath(getLong(id))
64 | return Album(
65 | id = getLong(id),
66 | title = getString(albumTitle),
67 | artist = getString(albumArtist),
68 | noOfSongs = getInt(noOfSongs),
69 | albumArt = albumArt
70 | )
71 | }
72 |
73 | private fun getAlbumArtPath(id: Long): String {
74 | val artworkUri = Uri.parse("content://media/external/audio/albumart")
75 | return Uri.withAppendedPath(artworkUri, "$id").toString()
76 | }
77 |
78 | /**
79 | * Loads information on albums using a content resolver and returns a cursor object
80 | */
81 | private fun getAlbumsCursor(
82 | selection: String? = null,
83 | selectionArgs: Array? = null
84 | ): Cursor? {
85 | val cr = applicationContext.contentResolver
86 | val uri = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI
87 | val projection: Array = getAlbumColumns()
88 | val sortOrder = "${MediaStore.Audio.Albums.LAST_YEAR} DESC"
89 | return cr.query(uri, projection, selection, selectionArgs, sortOrder)
90 | }
91 |
92 | private fun getAlbumColumns(): Array {
93 | return arrayOf(
94 | MediaStore.Audio.Albums._ID,
95 | MediaStore.Audio.Albums.ARTIST,
96 | MediaStore.Audio.Albums.ALBUM,
97 | MediaStore.Audio.Albums.NUMBER_OF_SONGS
98 | )
99 | }
100 |
101 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/notification/PlaybackNotificationBuilder.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.notification
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.app.Notification
17 | import android.app.NotificationManager
18 | import android.content.Context
19 | import android.support.v4.media.session.MediaSessionCompat
20 | import android.support.v4.media.session.PlaybackStateCompat.*
21 | import androidx.core.app.NotificationCompat
22 | import androidx.core.content.ContextCompat
23 | import androidx.media.session.MediaButtonReceiver
24 | import com.github.odaridavid.zikk.R
25 | import com.github.odaridavid.zikk.notification.NotificationsChannelManager.Companion.PLAYBACK_CHANNEL_ID
26 | import com.github.odaridavid.zikk.utils.Constants.PLAYBACK_NOTIFICATION_ID
27 | import com.github.odaridavid.zikk.utils.album
28 | import com.github.odaridavid.zikk.utils.albumArt
29 | import com.github.odaridavid.zikk.utils.artist
30 | import com.github.odaridavid.zikk.utils.title
31 | import javax.inject.Inject
32 |
33 | /**
34 | * Responsible for playback notification.
35 | */
36 | internal class PlaybackNotificationBuilder @Inject constructor(
37 | private val context: Context,
38 | private val notificationManager: NotificationManager,
39 | private val mediaSessionCompat: MediaSessionCompat
40 | ) {
41 |
42 | fun build(): Notification {
43 |
44 | val mediaMetadata = mediaSessionCompat.controller.metadata
45 |
46 | val notificationBuilder = NotificationCompat.Builder(context, PLAYBACK_CHANNEL_ID)
47 | .apply {
48 | // Add the metadata for the currently playing track
49 | setContentTitle(mediaMetadata.title)
50 | setContentText(mediaMetadata.artist)
51 | setSubText(mediaMetadata.album)
52 | setLargeIcon(mediaMetadata.albumArt)
53 |
54 | // Enable launching the player by clicking the notification
55 | setContentIntent(mediaSessionCompat.controller.sessionActivity)
56 |
57 | setDeleteIntent(
58 | MediaButtonReceiver.buildMediaButtonPendingIntent(
59 | context,
60 | ACTION_STOP
61 | )
62 | )
63 |
64 | setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
65 |
66 | setSmallIcon(R.drawable.ic_music_note_black_24dp)
67 | color = ContextCompat.getColor(context, R.color.colorAccent)
68 |
69 | val playPauseRes =
70 | if (mediaSessionCompat.controller.playbackState.state == STATE_PLAYING)
71 | R.drawable.ic_pause_black_48dp
72 | else R.drawable.ic_play_black_48dp
73 |
74 | addAction(
75 | NotificationCompat.Action(
76 | playPauseRes,
77 | context.getString(R.string.playback_action_pause),
78 | MediaButtonReceiver.buildMediaButtonPendingIntent(
79 | context,
80 | ACTION_PLAY_PAUSE
81 | )
82 | )
83 | )
84 |
85 | setStyle(
86 | androidx.media.app.NotificationCompat.MediaStyle()
87 | .setMediaSession(mediaSessionCompat.sessionToken)
88 | .setShowActionsInCompactView(0)
89 | )
90 | }
91 | val notification = notificationBuilder.build()
92 | notificationManager.notify(PLAYBACK_NOTIFICATION_ID, notification)
93 | return notification
94 | }
95 |
96 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/playback/player/TrackPlayer.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.playback.player
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.content.ContentUris
17 | import android.content.Context
18 | import android.media.AudioAttributes
19 | import android.media.MediaPlayer
20 | import android.os.PowerManager
21 | import android.provider.MediaStore
22 | import com.github.odaridavid.zikk.models.Track
23 | import com.github.odaridavid.zikk.repositories.TrackRepository
24 | import com.github.odaridavid.zikk.utils.convertMediaIdToTrackId
25 | import timber.log.Timber
26 |
27 | /**
28 | * Handles controlling of audio output,bound to a service to ensure playing persists even after
29 | * user leaves the app.
30 | */
31 | internal class TrackPlayer(
32 | val context: Context,
33 | private val trackRepository: TrackRepository
34 | ) : MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener {
35 |
36 | lateinit var mediaPlayer: MediaPlayer
37 | private var delayStart = false
38 |
39 | init {
40 | initPlayer()
41 | }
42 |
43 | private val isInitialized: Boolean
44 | get() = ::mediaPlayer.isInitialized
45 |
46 | val currentPosition: Int
47 | get() {
48 | return if (isInitialized) mediaPlayer.currentPosition else -1
49 | }
50 |
51 | val isPlaying: Boolean
52 | get() {
53 | return if (isInitialized) mediaPlayer.isPlaying else false
54 | }
55 | val duration: Int
56 | get() {
57 | return if (isInitialized) mediaPlayer.duration else -1
58 | }
59 |
60 | private fun initPlayer() {
61 | mediaPlayer = MediaPlayer().apply {
62 | setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK)
63 | setAudioAttributes(
64 | AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
65 | .build()
66 | )
67 | setOnPreparedListener(this@TrackPlayer)
68 | setOnErrorListener(this@TrackPlayer)
69 | }
70 | }
71 |
72 | fun prepare(delayStart: Boolean = false) {
73 | Timber.i("Media Player Preparing")
74 | this.delayStart = delayStart
75 | mediaPlayer.prepareAsync()
76 | }
77 |
78 | fun start() {
79 | Timber.i("Media Player Starting")
80 | mediaPlayer.start()
81 | }
82 |
83 | fun pause() {
84 | Timber.i("Media Player Paused")
85 | mediaPlayer.pause()
86 | }
87 |
88 | fun stop() {
89 | Timber.i("Media Player Stopped")
90 | mediaPlayer.stop()
91 | }
92 |
93 | fun reset() {
94 | Timber.i("Media Player Reset")
95 | mediaPlayer.reset()
96 | }
97 |
98 | fun release() {
99 | Timber.i("Media Player Released")
100 | mediaPlayer.release()
101 | }
102 |
103 | fun setDataSourceFromMediaId(context: Context, mediaId: String) {
104 | val trackId = convertMediaIdToTrackId(mediaId)
105 | if (trackId == 0L) return
106 | val contentUris =
107 | ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, trackId)
108 | mediaPlayer.setDataSource(context, contentUris)
109 | }
110 |
111 | override fun onPrepared(mediaPlayer: MediaPlayer?) {
112 | Timber.i("Media Player Prepared")
113 | if (!delayStart)
114 | start()
115 | }
116 |
117 | override fun onError(mediaPlayer: MediaPlayer?, what: Int, extra: Int): Boolean {
118 | Timber.i("Media Player Error : $what")
119 | reset()
120 | return true
121 | }
122 |
123 | fun getTrackInformationFromMediaId(mediaId: String): Track? {
124 | val trackId = convertMediaIdToTrackId(mediaId)
125 | return trackRepository.loadTrackForId(trackId.toString())
126 | }
127 |
128 | fun getTrackInformationFromTrackId(trackId: Long): Track? {
129 | return trackRepository.loadTrackForId(trackId.toString())
130 | }
131 |
132 |
133 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/ui/TracksAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.ui;
2 |
3 | /**
4 | *
5 | * Copyright 2020 David Odari
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 | * in compliance with the License. You may obtain a copy of the License at
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License
11 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | * or implied. See the License for the specific language governing permissions and limitations under
13 | * the License.
14 | *
15 | **/
16 | import android.graphics.drawable.Animatable2
17 | import android.graphics.drawable.AnimatedVectorDrawable
18 | import android.graphics.drawable.Drawable
19 | import android.os.Build
20 | import android.view.LayoutInflater
21 | import android.view.View
22 | import android.view.ViewGroup
23 | import android.widget.ImageView
24 | import android.widget.TextView
25 | import androidx.recyclerview.widget.RecyclerView
26 | import coil.api.load
27 | import com.github.odaridavid.zikk.R
28 | import com.github.odaridavid.zikk.models.PlayableTrack
29 | import com.github.odaridavid.zikk.utils.invisible
30 | import com.github.odaridavid.zikk.utils.show
31 | import com.github.odaridavid.zikk.utils.versionFrom
32 |
33 | internal class TracksAdapter(val onClick: (String?, Int) -> Unit) :
34 | RecyclerView.Adapter() {
35 |
36 | private lateinit var mediaItems: MutableList
37 |
38 | fun setList(mediaItem: MutableList) {
39 | mediaItems = mediaItem
40 | notifyDataSetChanged()
41 | }
42 |
43 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackViewHolder {
44 | val context = parent.context
45 | val view = LayoutInflater.from(context).inflate(R.layout.item_track, parent, false)
46 | return TrackViewHolder(view)
47 | }
48 |
49 | override fun onBindViewHolder(holder: TrackViewHolder, position: Int) {
50 | holder.bind(mediaItems[position])
51 | }
52 |
53 | override fun onBindViewHolder(
54 | holder: TrackViewHolder,
55 | position: Int,
56 | payloads: MutableList
57 | ) {
58 | if (payloads.isEmpty())
59 | super.onBindViewHolder(holder, position, payloads)
60 | else
61 | holder.setIsPlaying(payloads[0] as Boolean)
62 | }
63 |
64 |
65 | inner class TrackViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
66 |
67 | private val nowPlayingImageView: ImageView =
68 | view.findViewById(R.id.track_now_playing_image_view)
69 |
70 | lateinit var animatedNowPlayingDrawable: AnimatedVectorDrawable
71 |
72 | fun bind(mediaItem: PlayableTrack) {
73 | with(view) {
74 | findViewById(R.id.track_art_image_view).apply {
75 | this.load(mediaItem.icon)
76 | contentDescription = "${mediaItem.title} Album Art"
77 | }
78 | findViewById(R.id.track_title_text_view).apply {
79 | text = mediaItem.title
80 | }
81 | findViewById(R.id.track_artist_text_view).apply {
82 | text = mediaItem.artist
83 | }
84 |
85 | nowPlayingImageView.apply {
86 | setBackgroundResource(R.drawable.now_playing_anim)
87 | animatedNowPlayingDrawable = background as AnimatedVectorDrawable
88 | if (versionFrom(Build.VERSION_CODES.M))
89 | animatedNowPlayingDrawable.registerAnimationCallback(object :
90 | Animatable2.AnimationCallback() {
91 | override fun onAnimationEnd(drawable: Drawable?) {
92 | super.onAnimationEnd(drawable)
93 | animatedNowPlayingDrawable.start()
94 | }
95 | })
96 | }
97 |
98 | setNowPlayingViewVisibility(mediaItem.isPlaying, nowPlayingImageView)
99 |
100 | setOnClickListener {
101 | onClick(mediaItem.mediaId, adapterPosition)
102 | }
103 | }
104 | }
105 |
106 | fun setIsPlaying(isPlaying: Boolean) {
107 | setNowPlayingViewVisibility(isPlaying, nowPlayingImageView)
108 | }
109 |
110 | private fun setNowPlayingViewVisibility(isPlaying: Boolean, showPlaying: ImageView) {
111 | if (isPlaying) {
112 | showPlaying.show()
113 | animatedNowPlayingDrawable.start()
114 | } else {
115 | showPlaying.invisible()
116 | animatedNowPlayingDrawable.stop()
117 | }
118 | }
119 | }
120 |
121 | fun updateIsPlaying(position: Int, payload: Any) {
122 | notifyItemChanged(position, payload)
123 | }
124 |
125 | override fun getItemCount(): Int {
126 | return if (::mediaItems.isInitialized) mediaItems.size else 0
127 | }
128 |
129 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/odaridavid/zikk/repositories/TrackRepository.kt:
--------------------------------------------------------------------------------
1 | package com.github.odaridavid.zikk.repositories
2 |
3 | import android.content.Context
4 | import android.database.Cursor
5 | import android.net.Uri
6 | import android.provider.MediaStore
7 | import com.github.odaridavid.zikk.models.Track
8 |
9 | /**
10 | *
11 | * Copyright 2020 David Odari
12 | *
13 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
14 | * in compliance with the License. You may obtain a copy of the License at
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | * Unless required by applicable law or agreed to in writing, software distributed under the License
17 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
18 | * or implied. See the License for the specific language governing permissions and limitations under
19 | * the License.
20 | *
21 | **/
22 | internal class TrackRepository(
23 | val applicationContext: Context,
24 | private val albumRepository: AlbumRepository
25 | ) {
26 | /**
27 | * Returns a list of tracks after extracting from a cursor
28 | */
29 | fun loadAllTracks(): List