Merge pull request #1 from MuntashirAkon/dev

update
This commit is contained in:
Btxs 2022-05-29 08:24:26 +00:00 committed by GitHub
commit c74b85e63e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
927 changed files with 36709 additions and 65969 deletions

View file

@ -13,7 +13,7 @@ assignees: ''
[ ] Yes [ ] Yes
--> -->
**Have you read the [FAQ](https://www.github.com/h4h13/RetroMusicPlayer/tree/dev/FAQ.md)?** **Have you read the [FAQ](https://github.com/RetroMusicPlayer/RetroMusicPlayer/blob/master/FAQ.md)?**
[Yes/No] [Yes/No]
**Has the bug already been reported? Please search in GitHub issue tab before creating an issue.** **Has the bug already been reported? Please search in GitHub issue tab before creating an issue.**

34
.github/workflows/android.yml vendored Normal file
View file

@ -0,0 +1,34 @@
name: Android CI
on:
push:
branches: [ dev ]
pull_request:
branches: [ dev ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'temurin'
cache: gradle
- name: Generate temporary keystore
run: keytool -genkey -v -storetype pkcs12 -keystore store.p12 -storepass android -alias android -keyalg RSA -keysize 2048 -validity 10000 -dname CN=CI
- name: Write retro.properties
run: |
cat >retro.properties <<EOF
storeFile=$PWD/store.p12
keyAlias=android
storePassword=android
keyPassword=android
EOF
- name: Build with Gradle
run: ./gradlew build

View file

@ -1,76 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at retromusicapp@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

View file

@ -1,7 +1,7 @@
# Contributing # Contributing
## Using the issue tracker ## Using the issue tracker
The [issue tracker](https://github.com/h4h13/RetroMusicPlayer/issues) is the preferred channel for bug reports, feature requests and submitting pull requests, but please follow these rules: The [issue tracker](https://github.com/RetroMusicPlayer/RetroMusicPlayer/issues) is the preferred channel for bug reports, feature requests and submitting pull requests, but please follow these rules:
* Please **do not** derail or troll issues. Keep the discussion on topic and respect the opinions of others. * Please **do not** derail or troll issues. Keep the discussion on topic and respect the opinions of others.
@ -30,6 +30,6 @@ Feature requests are welcome, please make sure to be as detailed as possible and
**Please ask first** before embarking on any significant pull request (e.g. implementing features, refactoring code), otherwise you risk spending a lot of time working on something that developers might not want to merge into the project. To avoid that, you can join the official [Telegram group](https://t.me/retromusicapp) or open an issue. **Please ask first** before embarking on any significant pull request (e.g. implementing features, refactoring code), otherwise you risk spending a lot of time working on something that developers might not want to merge into the project. To avoid that, you can join the official [Telegram group](https://t.me/retromusicapp) or open an issue.
## License ## License
By contributing your code, you agree to license your contribution under the [GNU General Public License](https://github.com/h4h13/RetroMusicPlayer/blob/dev/LICENSE.md). By contributing your code, you agree to license your contribution under the [GNU General Public License](https://github.com/RetroMusicPlayer/RetroMusicPlayer/blob/master/LICENSE.md).
Please note we have a code of conduct, please follow it in all your interactions with the project. Please note we have a code of conduct, please follow it in all your interactions with the project.

190
FAQ.md
View file

@ -1,153 +1,157 @@
#### Q: How do I get the beta version of Retro Music? ## **Q: How do I get the beta version of Retro Music?**
You can opt-in for the beta build by clicking on this link: https://play.google.com/apps/testing/code.name.monkey.retromusic You can opt-in for the beta build by clicking on this link: https://play.google.com/apps/testing/code.name.monkey.retromusic
___
#### Q: How to restore my purchases? ## **Q: How to restore my purchases?**
Make sure to switch and use account in the Play Store app through which you purchased before installing Retro music. The account used to install the app is also used to purchase/restore the pro license. Make sure to switch and use your account in the Play Store app through which you purchased before installing Retro Music. The Google account used to install the app is also used to purchase/restore the pro license.
If you already installed, remove all other accounts except the one with which you purchased premium. Then restore the purchase. If you've already installed the app, remove all other accounts except the one from which you purchased premium, and then restore the purchase.
___
#### Q: How do I use offline synced lyrics? ## Q: **How do I use offline synced lyrics?**
There are two methods for how to get offline synced lyrics. There are three methods for adding offline synced lyrics in Retro Music.
#### Method 1:- ### ***Method 1:-***
##### STEP 1: #### STEP 1:
Find the time stamped lyrics for your songs which don't have lyrics already. A time stamped lyrics looks like this, "[00:04:02] Some lyrics text" for example. Find the time-stamped lyrics for your songs that don't have lyrics already. A time-stamped lyric looks like this, "[00:04:02] Some lyrics text" for example.
##### STEP 2: #### STEP 2:
Copy these time stamped lyrics. Copy these time-stamped lyrics.
##### STEP 3: #### STEP 3:
Open retro music and head to the song synced lyrics editor. Open retro music and head to the song synced lyrics editor.
##### STEP 4: #### STEP 4:
Paste the lyrics there normally and exit the editor Paste the lyrics there normally and exit the editor
##### STEP 5: #### STEP 5:
Open lyrics and you should see your time stamped lyrics there. Open lyrics and you should see your time-stamped lyrics there.
#### Method 2:- ### ***Method 2:-***
##### STEP 1: #### STEP 1:
Find the ".lrc" files for your songs which doesn't have lyrics already. Download the time-stamped lyrics for your songs that don't have lyrics already. You can find ".lrc" files for popular songs at either of the websites given below. A time-stamped lyric looks like this, "[00:04:02] Some lyrics text" for example.
##### STEP 2: #### STEP 2:
A ".lrc" is like a text file which contains the time stamped lyrics for example, "[00:04:02] Some lyrics text" A ".lrc" is like a text file that contains the time-stamped lyrics for example, "[00:04:02] Some lyrics text". Save your time-stamped lyrics are ".lrc" file.
##### STEP 3: #### STEP 3:
Now you have to rename the file you created in this way: <song_name> - <artist_name>.lrc or for better matching copy the <song_name> and the <artist_name> from the tag editor and then rename the file. Now you have to rename the file you created in this way: <song_name> - <artist_name>.lrc or for better matching copy the <song_name> and the <artist_name> from the tag editor and then rename the file.
##### STEP 4: ### STEP 4:
Now paste the LRC files to the following path: /sdcard/Retromusic/lyrics/ Now paste the LRC files to the following path: /sdcard/Retromusic/lyrics/
Here sdcard is your internal storage. Here sdcard is your internal storage.
> If you want to skip to particular time stamp, simply scroll to the time stamp from where you want to start from and a 'Play' icon will appear left to the particular stamp. Tap on play button to play from there. ### ***Method 3:- (Requires third-party app)***
#### STEP 1:
Download automatag or autotagger from Play Store.
#### STEP 2:
Find the time-stamped lyrics for your songs that don't have lyrics already. You can find ".lrc" files for popular songs at either of the websites given below. A time-stamped lyric looks like this, "[00:04:02] Some lyrics text" for example.
#### STEP 3:
Find your song to edit and paste the synced lyrics.
> These apps add those synced lyrics in the music file itself instead of creating a ".lrc file for it."
**Lyrics Website Links:**
- https://www.lyricsify.com/
- https://www.syair.info
#### Q: Why isn't the artist's image downloading? **Some Important Notes:**
Last.fm has disabled the download of artist's images for the time being, whether functionality for this will be restored in future is uncertain. So we have moved to deezer API for artist images which have very less artists in their database and you might not able to see covers on every artist profiles. - If you want to skip to a particular timestamp, simply scroll to the time stamp from where you want to start and a 'Play' icon will appear left to the particular stamp. Tap on the play button to play from there.
- When you save lyrics by pasting lyrics in lyrics editor, close the lyrics and open again for lyrics to show.
- For those who are having difficulty in making the synced lyrics work, we have a short tutorial video on it. Hope this helps you. {[Link in the note or here](https://youtu.be/1oIOTGWhNMY)}
___
#### Q: How do I change the theme? ## **Q: How do I change the theme?**
Settings -> Look and feel -> Select your theme. Settings -> Look and feel -> Select your theme.
___
## **Q: Equalizer is very laggy and unstable or I am getting a "No equalizer found" error. Why?**
- The Retro music in-built equalizer was removed updates ago so the only equalizer you will have by your OEM or Android native equalizer which isn't made by us and have no control over them. So you can report those issues to your OEM so that they can provide a fix in the next updates.
#### Q: Equailizer is very laggy and unstable or I am getting "No equalizer found" error. why? - If you are seeing "No Equalizer Found" in your device, this means your device doesn't have a stock equalizer "MusicFx" Equalizer. You can try using this one. It's made by AEX ROM developers.
The Retro music in-built equalizer was removed updates ago so the only equalizer you will have by your OEM or android which aren't made by us and have no control over them. So you can report those issues to your OEM so that they can provide a fix in next updates.
If you are seeing "No Equalizer Found" in your device, this means your device doesn't have stock equalizer MusicFx Equalizer. You can try using this one. Its made by AEX ROM developers. https://drive.google.com/file/d/1_1bpsn6roeEyElGKikbU39lVKUH8O3xp/view?**usp=drivesdk
https://drive.google.com/file/d/1_1bpsn6roeEyElGKikbU39lVKUH8O3xp/view?usp=drivesdk ___
## **Q: Why aren't last added songs showing?**
#### Q: Why aren't last added songs showing?
Settings -> Other -> Last added playlist interval -> Select an option from the list. Settings -> Other -> Last added playlist interval -> Select an option from the list.
___
## **Q: How do I enable fullscreen lock screen controls?**
#### Q: How do I enable fullscreen lockscreen controls?
Settings -> Personalize -> Fullscreen controls -> Enable (this will only be visible when songs are playing from Retro Music). Settings -> Personalize -> Fullscreen controls -> Enable (this will only be visible when songs are playing from Retro Music).
___
## **Q: Why are my gallery or random pictures showing up as album art?**
#### Q: Why are gallery or random pictures showing up as album art?
Settings -> Images -> Ignore media store covers -> Enable Settings -> Images -> Ignore media store covers -> Enable
___
## **Q: Which file types are supported?**
#### Q: Which file types are supported?
Retro Music uses the native media player that comes with your Android phone, so as long as a file type is supported by your phone, it's supported by Retro Music. Retro Music uses the native media player that comes with your Android phone, so as long as a file type is supported by your phone, it's supported by Retro Music.
___
## **Q: Why is my device slowing down when I'm using the app?**
#### Q: Why is my device slowing down when I'm using the app?
Retro Music is image intensive, it keeps images in the cache for quick loading. Retro Music is image intensive, it keeps images in the cache for quick loading.
___
## **Q: The title "Retro Music" is showing on the top of the app, how can I fix this?**
#### Q: The title "Retro Music" is showing on the top of the app, how can i fix this?
Clear the app's cache and data. Clear the app's cache and data.
___
## **Q: My app is crashing, how do I fix this?** (Sorry, settings have changed internally)
Please try to clear the data of the app. If it doesn't work, reinstalling fresh from the play store should help.
___
#### Q: My app is crashing, how do i fix this? (Sorry, settings have changed internally) ## **Q: Why has all the text gone white/disappeared?**
Please try clear data of the app. If it doesn't work, reinstalling fresh from play store should help.
#### Q: Why has all the text gone white/dissapeared?
Change the theme to Black or Dark and change it back to what you had before. Change the theme to Black or Dark and change it back to what you had before.
___
## **Q: Why some of my songs are not showing in my library?**
- Try checking up if those songs are not less than 30 seconds, if so head to settings -> other -> filter song duration. Put this to zero and see the songs that should start appearing in the library.
#### Q: Why some of my songs are not showing in my library? - If this doesn't work out for you, re-scanning the media folder should help and subsequently rebooting the device to refresh the media store.
Try checking up if those songs are not less than 30 seconds, if so head to settings -> other -> filter song duration. Put this to zero and see the songs should start appearimg in the library.
If this doesn't work out for you, re-scanning the media folder should help and subsquently rebooting the device to refresh media store. - At last, resort, If nothing worked and your audio files are stored in SD card. Try moving them to internal memory then back to SD card.
At last resort, If nothing worked and your audio files are stored in SD card. Try moving them to internal memory then back to SD card. ___
## **Q: Why does my library shows song files twice or no song at all?**
If you are seeing duplication of songs in the library or no songs at all, then it's because of the Media Store issue which got affected by some other app.
#### Q: Why some of my songs are not showing in my library? ***To fix this:***
1. Try checking up if those songs are not less than 30 seconds, if so head to settings -> other -> filter song duration. Put this to zero and see the songs should start appearimg in the library. 1. Head to your device settings
2. If this doesn't work out for you, re-scanning the media folder should help and subsquently rebooting the device to refresh media store. 1. Open up "Apps & notifications" (This name depends from ROM to ROM)
3. At last resort, If nothing worked and your audio files are stored in SD card. Try moving them to internal memory then back to SD card. 1. Find the 'Media storage' app and clear storage (both data and cache) of it.
1. Then open the Retro Music app and manually scan your music from your storage.
#### Q: Why my playlist/playlist songs keep disappearing? 1. Reboot the device to refresh the media store (Not sure if this is necessary)
Playlist/Playlist songs disappearing is based on android media store system. Save those playlist as file(Tap on three dot menu next to available playlist and save as file) and it should get fixed.
**NOTE:** Don't panic when you will open Retro Music and see "Zero" songs there in the library. It's because you cleared Media Store which is responsible for recognising files on your device.
___
#### Q: Why does my library shows song files twice or no song at all? ## **Q: I can't find the folder menu anymore after the latest update?**
If you are seeing duplication of songs in the library or no songs at all, then it's because of Media Store issue which got affected by some other app. Head to settings -> personalise. And select folders from "library categories". If there is no option of folders, tap on reset and select folders.
___
To fix this: ## **Q: After updating the app to the latest version, the font got removed. Why?**
- Retro Music's font has now been replaced with system font, which means the default font your system uses will be used by Retro Music too. It fixes all font-related issues you used to face/are facing in the app.
• Head to your device settings - With the recent Retro Music v5 release, we have a built-in optional font "Manrope font" which you can toggle from Settings > Look & Feel > Toggle "Use manrope font".
• Open up "Apps & notifications" (This name depends from ROM to ROM) - If you think the font looks ugly, then you just need to change the default font from your Android settings (or use any Magisk module). If you can't, there's nothing we can do about it.
___
• Find 'Media storage' app and clear storage (both data and cache) of it. ## **Q: How to export playlist?**
- ***From Retro Music:***
• Then open Retro Music app and manually scan your music from your storage. Head to the playlists tab > tap on the three-dot menu on the playlist you want to export > save as a file.
• Reboot the device to refresh media store (Not sure if this is necessary) - ***From Other Music Players:***
NOTE: Don't panic when you will open Retro Music and see "Zero" songs there in the library. It's because you cleared Media Store which is responsible for recognising files on your device. In your built-in music player, there should be an option to save that playlist as a file. Save them and import them from the file manager by opening it into retro music.
> Note that such playlist must be of your offline music only since retro music is an offline music player, not an online music player. So if your playlist is of online music, it can't be opened on other offline players nor can be exported
___
#### Q: I can't find folder menu anymore after latest update? ## **Q: How to restore/import playlist?**
Head to settings -> personalise. And select folders from "library categories". If there is option of folders, tap on reset and select folders. Retro Music will automatically detect any playlist file when that playlist file is stored in internal storage/Playlist. However, if it doesn't, just open any "File manager" and open that playlist file with Retro Music.
For restoring playlists successfully, the location of songs must be the same in both the "Playlist" file and in your storage. For example, If your music is in "Internal storage/Music" and the playlist file has songs location "Internal storage/Songs". Then it will not be going to work since both these locations are different.
#### Q: After updating the app to latest version, font got removed. Why?
Retro Music's font have now been replaced with system font now, which means the default font your system uses will be used by Retro too. It fixes all font related issues you used to face/are facing in the app.
If you think the font looks ugly, then you just need to change the default font from your Android settings (or use any Magisk module). If you can't, there's nothing we can do about it.
#### Q: How to export playlist:
In your built-in music player, there should be an option to save those playlist as file. Save them and import from file manager by opening it into retro music.
Note that those playlist must be of your offline music only since retro music is offline music player not an online music player. So if your playlist are of online music, it can't be opened on other offline players nor can be exported
#### Q: How to restore/import playlist:
Retro Music will automatically detect any playlist file when that playlist file is stored in InternalStorage/Playlist. However, if it doesn't, just open "file manager" and open that playlist file with Retro Music.
For restoring playlists, the location of songs must be same in both Playlist file and in your storage. For example, your music is in "Internalstorage/Music" and playlist file has songs location "Internalstorage/Songs". Then it will not going to work since both these location are different.
#### Q: Adding songs to playlist or marking them as favourite are making app crash. Why?
It's a known issue with only android 10 with its media store API when songs are in SD card due to introduction of Scoped Storage by Google. The issue have been created on Google Issue Tracker by many users. Many other players which doesn't have this issue are using a custom database for storing playlist. We will soon be implementing a custom database for playlist to fix this issue!
Workaround: You can move all songs to internal storage to fix the issue.
ISSUE link: https://issuetracker.google.com/issues/147619577

View file

@ -2,19 +2,6 @@
Material Design music player for Android music lovers Material Design music player for Android music lovers
## Table of contents
- [Downloads](#downloads)
- [Differences between Metro and RetroMusicPlayer](#differences-between-metro-and-retromusicplayer)
- [Screenshots](#screenshots)
- [App Themes](#app-themes)
- [Player screen](#player-screen)
- [9+ Now playing themes](#9-now-playing-themes)
- [Navigation never made easier](#-navigation-never-made-easier)
- [Colorful](#-colorful)
- [Home](#-home)
- [Included Features](#-included-features)
- [License](#%EF%B8%8F-license)
## Downloads ## Downloads
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" [<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
@ -31,26 +18,35 @@ Material Design music player for Android music lovers
- Bug fixes - Bug fixes
- Minor differences in UI - Minor differences in UI
## Screenshots ## 📱 Screenshots
### App Themes ### App Themes
| <img src="screenshots/home.jpeg" width="200"/> | <img src="screenshots/home_dark.jpeg" width="200"/> | <img src="screenshots/home_black.jpeg" width="200"/> | | <img src="screenshots/home_light.jpg" width="200"/> | <img src="screenshots/home_dark.jpg" width="200"/> | <img src="screenshots/home_black.jpg" width="200"/> |
|:---:|:---:|:---:| |:---:|:---:|:---:|
|Clearly white| Kinda dark | Just black| |Clearly white| Kinda dark | Just black|
### Player screen ### Player screen
| <img src="screenshots/home.jpeg" width="200"/>| <img src="screenshots/list.jpeg" width="200"/>| <img src="screenshots/albums.jpeg" width="200"/>| <img src="screenshots/settings.jpeg" width="200"/>| | <img src="screenshots/home_light.jpg" width="200"/>| <img src="screenshots/songs.jpg" width="200"/>| <img src="screenshots/albums.jpg" width="200"/>| <img src="screenshots/artists.jpg" width="200"/>| <img src="screenshots/settings.jpg" width="200"/>|
|:---:|:---:|:---:|:---:| |:---:|:---:|:---:|:---:|:---:|
| Home | Songs | Albums | Settings | | Home | Songs | Albums | Artists | Settings |
### 9+ Now playing themes ### Synced lyrics screen (Over Cover)
| <img src="screenshots/synced_over_light.jpg" width="200"/>| <img src="screenshots/synced_over_dark.jpg" width="200"/>| <img src="screenshots/synced_over_black.jpg" width="200"/>|
|:---:|:---:|:---:|
| Synced Over Cover light | Synced Over Cover dark | Synced Over Cover black |
| <img src="screenshots/np_normal.jpeg" width="200"/> |<img src="screenshots/np_fit.jpeg" width="200"/>| <img src="screenshots/np_flat.jpeg" width="200"/> | <img src="screenshots/np_color.jpeg" width="200"/> | <img src="screenshots/np_material.jpeg" width="200"/> | ### Synced lyrics screen (Replace Cover)
| <img src="screenshots/synced_replace_light.jpg" width="200"/>| <img src="screenshots/synced_replace_dark.jpg" width="200"/>| <img src="screenshots/synced_replace_black.jpg" width="200"/>|
|:---:|:---:|:---:|
| Synced Replace Cover light | Synced Replace Cover dark | Synced Replace Cover black |
### 10+ Now playing themes
| <img src="screenshots/normal.jpg" width="200"/> |<img src="screenshots/fit.jpg" width="200"/>| <img src="screenshots/flat.jpg" width="200"/> | <img src="screenshots/color.jpg" width="200"/> | <img src="screenshots/material.jpg" width="200"/> |
|:-----: |:-----: |:-----: |:-----: |:-----: | |:-----: |:-----: |:-----: |:-----: |:-----: |
| Normal | Fit | Flat | Color | Material | | Normal | Fit | Flat | Color | Material |
| <img src="screenshots/no_classic.jpeg" width="200"/> |<img src="screenshots/np_adaptive.jpeg" width="200"/>| <img src="screenshots/np_blur.jpeg" width="200"/> | <img src="screenshots/np_tiny.jpeg" width="200"/> | <img src="screenshots/np_peak.jpeg" width="200"/> | | <img src="screenshots/classic.jpg" width="200"/> |<img src="screenshots/adaptive.jpg" width="200"/>| <img src="screenshots/blur.jpg" width="200"/> | <img src="screenshots/tiny.jpg" width="200"/> | <img src="screenshots/peek.jpg" width="200"/> |
|:-----: |:-----: |:-----: |:-----: |:-----: | |:-----: |:-----: |:-----: |:-----: |:-----: |
| Classic | Adaptive | Blur | Tiny | Peak | | Classic | Adaptive | Blur | Tiny | Peek |
## 🧭 Navigation never made easier ## 🧭 Navigation never made easier
Self-explanatory interface without overloaded menus. Self-explanatory interface without overloaded menus.
@ -70,6 +66,10 @@ favorite songs. No other music player has this feature.
- Driving Mode - Driving Mode
- Headset/Bluetooth support - Headset/Bluetooth support
- Music duration filter - Music duration filter
- Android auto support
- Wallpaper accent picker on Android 8.1+
- Material You support on Android 12+
- Monet themed icon support on Android 13+
- Folder support - Play songs by folder - Folder support - Play songs by folder
- Gapless playback - Gapless playback
- Volume controls - Volume controls
@ -89,7 +89,6 @@ favorite songs. No other music player has this feature.
- Smart Auto Playlists - Recently played, most played and history - Smart Auto Playlists - Recently played, most played and history
- Build your playlist on the go - Build your playlist on the go
We are trying our best to bring you the best user experience. The app is regularly being updated for bug fixes and new features. We are trying our best to bring you the best user experience. The app is regularly being updated for bug fixes and new features.
## 🗂️ License ## 🗂️ License

View file

@ -1,23 +1,22 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs.kotlin" apply plugin: "androidx.navigation.safeargs.kotlin"
apply plugin: 'kotlin-parcelize'
android { android {
compileSdkVersion 29 compileSdk 32
buildToolsVersion = '30.0.3' buildToolsVersion = '30.0.3'
defaultConfig { defaultConfig {
minSdkVersion 21 minSdk 21
targetSdkVersion 29 targetSdk 32
renderscriptTargetApi 29 //must match target sdk and build tools
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
applicationId 'io.github.muntashirakon.Music' applicationId 'io.github.muntashirakon.Music'
versionCode 10503 versionCode 10580
versionName '4.0.010' versionName '5.8.5'
multiDexEnabled true multiDexEnabled true
} }
@ -32,15 +31,17 @@ android {
} }
} }
packagingOptions { buildFeatures{
exclude 'META-INF/LICENSE' viewBinding true
exclude 'META-INF/NOTICE'
exclude 'META-INF/java.properties'
} }
lintOptions { packagingOptions {
disable 'MissingTranslation' resources {
disable 'InvalidPackage' excludes += ['META-INF/LICENSE', 'META-INF/NOTICE', 'META-INF/java.properties']
}
}
lint {
abortOnError false abortOnError false
disable 'MissingTranslation', 'InvalidPackage'
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -53,86 +54,78 @@ android {
configurations.all { configurations.all {
resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9' resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9'
} }
androidExtensions {
experimental = true
}
kapt {
generateStubs = true
}
} }
dependencies { dependencies {
implementation project(':appthemehelper') implementation project(':appthemehelper')
implementation 'androidx.multidex:multidex:2.0.1'
implementation "androidx.gridlayout:gridlayout:1.0.0" implementation "androidx.gridlayout:gridlayout:1.0.0"
implementation "androidx.cardview:cardview:1.0.0" implementation "androidx.cardview:cardview:1.0.0"
implementation 'androidx.appcompat:appcompat:1.2.0' implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation 'androidx.annotation:annotation:1.1.0' implementation 'androidx.annotation:annotation:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.preference:preference-ktx:1.1.1' implementation "androidx.preference:preference-ktx:$preference_version"
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.fragment:fragment-ktx:1.2.5'
implementation 'androidx.palette:palette-ktx:1.0.0' implementation 'androidx.palette:palette-ktx:1.0.0'
def nav_version = "2.3.2" implementation "androidx.media:media:1.6.0"
implementation "androidx.navigation:navigation-runtime-ktx:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
def room_version = "2.2.6" implementation "androidx.navigation:navigation-runtime-ktx:$navigation_version"
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
def room_version = '2.4.2'
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version" implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"
def lifecycle_version = "2.2.0" def lifecycle_version = "2.5.0-rc01"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation 'com.google.android.material:material:1.3.0-alpha04' implementation "androidx.core:core-splashscreen:1.0.0-beta02"
def retrofit_version = '2.9.0' implementation "com.google.android.material:material:$mdc_version"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation 'com.google.code.gson:gson:2.9.0'
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0'
def material_dialog_version = "0.9.6.0" def material_dialog_version = "3.3.0"
implementation "com.afollestad.material-dialogs:core:$material_dialog_version" implementation "com.afollestad.material-dialogs:core:$material_dialog_version"
implementation "com.afollestad.material-dialogs:commons:$material_dialog_version" implementation "com.afollestad.material-dialogs:input:$material_dialog_version"
implementation 'com.afollestad:material-cab:0.1.12' implementation "com.afollestad.material-dialogs:color:$material_dialog_version"
def kotlin_coroutines_version = "1.3.8" implementation 'com.afollestad:material-cab:2.0.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
def kotlin_coroutines_version = '1.6.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
def koin_version = "2.1.5" def koin_version = '3.2.0'
implementation "org.koin:koin-core:$koin_version" implementation "io.insert-koin:koin-core:$koin_version"
implementation "org.koin:koin-core-ext:$koin_version" implementation "io.insert-koin:koin-android:$koin_version"
implementation "org.koin:koin-androidx-scope:$koin_version"
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
implementation "org.koin:koin-androidx-fragment:$koin_version"
implementation "org.koin:koin-androidx-ext:$koin_version"
implementation 'com.github.bumptech.glide:glide:3.8.0' def glide_version = '4.13.2'
implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0' implementation "com.github.bumptech.glide:glide:$glide_version"
kapt "com.github.bumptech.glide:compiler:$glide_version"
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0' implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r' implementation 'com.github.bosphere.android-fadingedgelayout:fadingedgelayout:1.0.0'
implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0'
implementation 'com.github.kabouzeid:recyclerview-fastscroll:1.9-kmod'
implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3'
implementation 'com.r0adkll:slidableactivity:2.1.0'
implementation 'com.heinrichreimersoftware:material-intro:1.6'
implementation 'com.github.dhaval2404:imagepicker:1.7.1'
implementation 'org.jsoup:jsoup:1.11.1'
implementation 'me.zhanghai.android.fastscroll:library:1.1.0'
implementation 'me.jorgecastillo:androidcolorx:0.2.0'
implementation 'org.jsoup:jsoup:1.11.1'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
}
apply from: '../spotless.gradle' implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC3'
implementation 'com.github.jetradarmobile:android-snowfall:1.2.1'
implementation "dev.chrisbanes.insetter:insetter:0.6.1"
implementation 'com.github.Adonai:jaudiotagger:2.3.15'
implementation 'com.r0adkll:slidableactivity:2.1.0'
implementation 'com.heinrichreimersoftware:material-intro:2.0.0'
implementation 'com.github.dhaval2404:imagepicker:2.1'
implementation 'me.zhanghai.android.fastscroll:library:1.1.8'
implementation 'cat.ereza:customactivityoncrash:2.3.0'
implementation 'me.tankery.lib:circularSeekBar:1.3.2'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}

View file

@ -16,9 +16,9 @@
# public *; # public *;
#} #}
# Uncomment this to preserve the line number information for # Preserve the line number information for
# debugging stack traces. # debugging stack traces.
#-keepattributes SourceFile,LineNumberTable -keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
@ -34,10 +34,16 @@
# Glide # Glide
-keep public class * implements com.bumptech.glide.module.GlideModule -keep public class * implements com.bumptech.glide.module.GlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { -keep class * extends com.bumptech.glide.module.AppGlideModule {
<init>(...);
}
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES; **[] $VALUES;
public *; public *;
} }
-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {
*** rewind();
}
# OkHttp # OkHttp
-keepattributes Signature -keepattributes Signature
@ -47,16 +53,19 @@
#-dontwarn #-dontwarn
#-ignorewarnings #-ignorewarnings
-dontshrink
-dontobfuscate
#Jaudiotagger
-dontwarn org.jaudiotagger.** -dontwarn org.jaudiotagger.**
-dontwarn org.jcodec.**
-keep class org.jaudiotagger.** { *; } -keep class org.jaudiotagger.** { *; }
-keep class org.jcodec.** { *; }
-keepclassmembers enum * { *; } -keepclassmembers enum * { *; }
-keepattributes *Annotation*, Signature, Exception -keepattributes *Annotation*, Signature, Exception
-keepnames class androidx.navigation.fragment.NavHostFragment -keepnames class androidx.navigation.fragment.NavHostFragment
-keepnames class io.github.muntashirakon.music.model.Home
-keep class * extends androidx.fragment.app.Fragment{} -keep class * extends androidx.fragment.app.Fragment{}
-keepnames class * extends android.os.Parcelable -keepnames class * extends android.os.Parcelable
-keepnames class * extends java.io.Serializable -keepnames class * extends java.io.Serializable
-keep class io.github.muntashirakon.music.network.model.** { *; }
-keep class io.github.muntashirakon.music.model.** { *; }
-keep class com.google.android.material.bottomsheet.** { *; }

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="md3_available">true</bool>
<bool name="allowBackup">false</bool>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Metro Debug</string>
</resources>

View file

@ -6,6 +6,11 @@
<item name="fontFamily">@font/sans</item> <item name="fontFamily">@font/sans</item>
</style> </style>
<style name="TextViewNormalCompress" parent="TextAppearance.MaterialComponents.Caption">
<item name="android:textSize">14sp</item>
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewHeadline4" parent="TextAppearance.MaterialComponents.Headline4"> <style name="TextViewHeadline4" parent="TextAppearance.MaterialComponents.Headline4">
<item name="fontFamily">@font/sans</item> <item name="fontFamily">@font/sans</item>
</style> </style>
@ -89,8 +94,12 @@
<item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:textColor">?android:attr/textColorPrimary</item>
</style> </style>
<style name="circleImageView" parent=""> <style name="circleImageView" parent="ShapeAppearance.MaterialComponents">
<item name="cornerFamily">rounded</item> <item name="cornerSize">40dp</item>
<item name="cornerSize">5%</item> </style>
<style name="BottomSheetItemTextAppearance" parent="Widget.MaterialComponents.BottomNavigationView.Colored">
<item name="android:textSize">13sp</item>
<item name="fontFamily">@font/sans</item>
</style> </style>
</resources> </resources>

View file

@ -4,36 +4,42 @@
package="io.github.muntashirakon.music" package="io.github.muntashirakon.music"
android:installLocation="auto"> android:installLocation="auto">
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission
<uses-permission android:name="android.permission.INTERNET" /> android:name="android.permission.WRITE_EXTERNAL_STORAGE"
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission <uses-permission
android:name="android.permission.WRITE_SETTINGS" android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" /> tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<application <application
android:name=".App" android:name=".App"
android:allowBackup="true" android:allowBackup="@bool/allowBackup"
android:configChanges="locale|layoutDirection" android:configChanges="locale|layoutDirection"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:requestLegacyExternalStorage="true" android:restoreAnyVersion="true"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.RetroMusic.FollowSystem" android:theme="@style/Theme.RetroMusic.FollowSystem"
android:usesCleartextTraffic="false" android:usesCleartextTraffic="true"
tools:ignore="AllowBackup,GoogleAppIndexingWarning" tools:ignore="AllowBackup,GoogleAppIndexingWarning"
tools:targetApi="q"> tools:targetApi="q">
<activity <activity
android:name=".activities.MainActivity" android:name=".activities.MainActivity"
android:label="@string/app_name" android:exported="true"
android:launchMode="singleTop"
android:theme="@style/SplashTheme"> android:theme="@style/SplashTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -105,20 +111,52 @@
<data android:mimeType="vnd.android.cursor.dir/audio" /> <data android:mimeType="vnd.android.cursor.dir/audio" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".activities.PlayingQueueActivity" />
<activity android:name=".activities.SettingsActivity" /> <activity android:name=".activities.SettingsActivity" />
<activity android:name=".activities.tageditor.AlbumTagEditorActivity" /> <activity android:name=".activities.tageditor.AlbumTagEditorActivity" />
<activity android:name=".activities.tageditor.SongTagEditorActivity" /> <activity android:name=".activities.tageditor.SongTagEditorActivity" />
<activity android:name=".activities.LyricsActivity" />
<activity android:name=".activities.UserInfoActivity" />
<activity android:name=".activities.LicenseActivity" /> <activity android:name=".activities.LicenseActivity" />
<activity android:name=".activities.WhatsNewActivity" />
<activity android:name=".activities.bugreport.BugReportActivity" /> <activity android:name=".activities.bugreport.BugReportActivity" />
<activity android:name=".activities.ShareInstagramStory" /> <activity android:name=".activities.ShareInstagramStory" />
<activity android:name=".activities.DriveModeActivity" /> <activity android:name=".activities.DriveModeActivity" />
<activity android:name=".activities.PermissionActivity" /> <activity android:name=".activities.PermissionActivity" />
<activity android:name=".activities.LockScreenActivity" /> <activity android:name=".activities.LockScreenActivity" />
<activity android:name=".activities.saf.SAFRequestActivity" /> <activity android:name=".activities.saf.SAFRequestActivity" />
<activity
android:name=".fragments.backup.RestoreActivity"
android:excludeFromRecents="false"
android:exported="true"
android:label="@string/restore"
android:theme="@style/Theme.RetroMusic.Dialog">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="application/octet-stream" />
<data android:mimeType="application/x-zip-compressed" />
<data android:mimeType="application/zip" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="*/*" />
<!--
Work around Android's ugly primitive PatternMatcher
implementation that can't cope with finding a . early in
the path unless it's explicitly matched.
-->
<data android:host="*" />
<data android:pathPattern=".*\\.rmbak" />
<data android:pathPattern=".*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
</intent-filter>
</activity>
<activity <activity
android:name=".appshortcuts.AppShortcutLauncherActivity" android:name=".appshortcuts.AppShortcutLauncherActivity"
@ -129,6 +167,14 @@
android:name=".activities.saf.SAFGuideActivity" android:name=".activities.saf.SAFGuideActivity"
android:theme="@style/Theme.Intro" /> android:theme="@style/Theme.Intro" />
<activity
android:name=".activities.ErrorActivity"
android:exported="true">
<intent-filter>
<action android:name="cat.ereza.customactivityoncrash.RESTART" />
</intent-filter>
</activity>
<provider <provider
android:name=".misc.GenericFileProvider" android:name=".misc.GenericFileProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"
@ -139,7 +185,6 @@
android:resource="@xml/provider_paths" /> android:resource="@xml/provider_paths" />
</provider> </provider>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}" android:authorities="${applicationId}"
@ -150,13 +195,17 @@
android:resource="@xml/provider_paths" /> android:resource="@xml/provider_paths" />
</provider> </provider>
<receiver android:name=".service.MediaButtonIntentReceiver"> <receiver
android:name="androidx.media.session.MediaButtonReceiver"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".appwidgets.BootReceiver"> <receiver
android:name=".appwidgets.BootReceiver"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" /> <action android:name="android.intent.action.QUICKBOOT_POWERON" />
@ -165,7 +214,7 @@
<receiver <receiver
android:name=".appwidgets.AppWidgetBig" android:name=".appwidgets.AppWidgetBig"
android:exported="false" android:exported="true"
android:label="@string/app_widget_big_name"> android:label="@string/app_widget_big_name">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -178,7 +227,7 @@
<receiver <receiver
android:name=".appwidgets.AppWidgetClassic" android:name=".appwidgets.AppWidgetClassic"
android:exported="false" android:exported="true"
android:label="@string/app_widget_classic_name"> android:label="@string/app_widget_classic_name">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -190,7 +239,7 @@
</receiver> </receiver>
<receiver <receiver
android:name=".appwidgets.AppWidgetSmall" android:name=".appwidgets.AppWidgetSmall"
android:exported="false" android:exported="true"
android:label="@string/app_widget_small_name"> android:label="@string/app_widget_small_name">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -202,7 +251,7 @@
</receiver> </receiver>
<receiver <receiver
android:name=".appwidgets.AppWidgetText" android:name=".appwidgets.AppWidgetText"
android:exported="false" android:exported="true"
android:label="@string/app_widget_text_name"> android:label="@string/app_widget_text_name">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -214,7 +263,7 @@
</receiver> </receiver>
<receiver <receiver
android:name=".appwidgets.AppWidgetCard" android:name=".appwidgets.AppWidgetCard"
android:exported="false" android:exported="true"
android:label="@string/app_widget_card_name"> android:label="@string/app_widget_card_name">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -224,16 +273,43 @@
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/app_widget_card_info" /> android:resource="@xml/app_widget_card_info" />
</receiver> </receiver>
<receiver
android:name=".appwidgets.AppWidgetMD3"
android:exported="true"
android:label="@string/app_widget_md3_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/app_widget_md3_info" />
</receiver>
<receiver
android:name=".appwidgets.AppWidgetCircle"
android:exported="true"
android:label="@string/app_widget_circle_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/app_widget_circle_info" />
</receiver>
<service <service
android:name=".service.MusicService" android:name=".service.MusicService"
android:enabled="true" android:enabled="true"
android:exported="true" android:exported="true"
android:label="@string/app_name" android:foregroundServiceType="mediaPlayback"
tools:ignore="ExportedService"> android:label="@string/app_name">
<intent-filter> <intent-filter>
<action android:name="android.media.browse.MediaBrowserService" /> <action android:name="android.media.browse.MediaBrowserService" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</service> </service>
<meta-data <meta-data
@ -244,12 +320,23 @@
android:name="com.lge.support.SPLIT_WINDOW" android:name="com.lge.support.SPLIT_WINDOW"
android:value="true" /> android:value="true" />
<!-- Android Auto -->
<meta-data <meta-data
android:name="io.github.muntashirakon.music.glide.RetroMusicGlideModule" android:name="com.google.android.gms.car.application"
android:value="GlideModule" /> android:resource="@xml/automotive_app_desc" />
<meta-data <meta-data
android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule" android:name="com.google.android.gms.car.application.theme"
android:value="GlideModule" /> android:resource="@style/CarTheme" />
<meta-data
android:name="com.google.android.gms.car.notification.SmallIcon"
android:resource="@drawable/ic_notification" />
</application> </application>
<!--
This is not that important, it's just here so that we can query equalizer package
and check if it's present on A11+ because of Package visibility restrictions.
-->
<queries>
<package android:name="com.android.musicfx" />
</queries>
</manifest> </manifest>

View file

@ -1,32 +0,0 @@
[
{
"name": "Hemanth Savarala",
"summary": "Lead Developer & Designer",
"link": "https://github.com/h4h13",
"image": "https://i.imgur.com/AoVs9oj.jpg"
},
{
"name": "Lennart Glamann",
"summary": "Play Store Banner & Images",
"link": "https://t.me/FlixbusLennart",
"image": "https://i.imgur.com/Q5Nsx1R.jpg"
},
{
"name": "Daksh P. Jain",
"summary": "Support Representative & Moderator",
"link": "https://daksh.eu.org",
"image": "https://i.imgur.com/fnYpg65.jpg"
},
{
"name": "Milind Goel",
"summary": "Support Representative & Moderator",
"link": "https://t.me/MilindGoel15",
"image": "https://i.imgur.com/Bz4De21_d.jpg"
},
{
"name": "Haythem Gataa",
"summary": "App Logo Designer",
"link": "https://dribbble.com/haythemgataa",
"image": "https://i.imgur.com/g5RuIZq.jpg"
}
]

View file

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<style media="screen" type="text/css">
* {
word-wrap: break-word;
}
{style-placeholder}
a {
color: {link-color};
}
a:active {
color: {link-color-active};
}
ol {
list-style-position: inside;
padding-left: 0;
padding-right: 0;
}
li {
padding-top: 8px;
}
</style>
</head>
<body>
<p><b><a href="https://github.com/kabouzeid/Phonograph" title="Phonograph"> Phonograph</a></b> by
Karim Abou Zeid</p>
<p><b><a href="http://developer.android.com/tools/support-library/index.html"
title="AOSP Support Libraries">AOSP Support Libraries</a></b> by AOSP contributors</p>
<p><b><a href="https://github.com/bumptech/glide" title="Glide"> Glide</a></b> by Sam Judd</p>
<p><b><a href="https://github.com/square/retrofit" title="Retrofit"> Retrofit</a></b> by Square team
</p>
<p><b><a href="http://square.github.io/okhttp/" title="OkHttp"> OkHttp</a></b> by Square team</p>
<p><b><a href="https://github.com/InsertKoinIO/koin"
title="Koin">Koin</a></b> by Arnaud Giuliani</p>
<p><b><a href="https://github.com/afollestad" title="Material Dialogs"> Material Dialogs and Cab</a></b>
by Aidan Michael Follestad</p>
<p><b><a href="https://github.com/afollestad/material-cab" title="Material Contextual Action Bar">
Material Contextual Action Bar</a></b> by Aidan Michael Follestad</p>
<p><b><a href="https://github.com/h6ah4i/android-advancedrecyclerview"
title="Advanced RecyclerView"> Advanced RecyclerView</a></b> by Haruki Hasegawa</p>
<p><b><a href="https://github.com/Ereza/CustomActivityOnCrash"
title="Custom Activity on Crash">Custom Activity on Crash</a></b> by Eduard Ereza Martínez
</p>
<p><b><a href="https://github.com/NanoHttpd/nanohttpd"
title="NanoHttpd">NanoHttpd</a></b> by NanoHttpd Team</p>
<p><b><a href="https://github.com/tankery/CircularSeekBar"
title="Circular Seekbar">Circular Seekbar</a></b> by Tankery</p>
<p><b><a href="https://github.com/Kaned1as/jaudiotagger"
title="jAudioTagger">jAudioTagger</a></b> by Kanedias</p>
<p><b><a href="https://github.com/zhanghai/AndroidFastScroll"
title="Android Fast Scroll">Android Fast Scroll</a></b> by Zhang Hai</p>
<p><b><a href="https://github.com/Dhaval2404/ImagePicker"
title="Image Picker">Image Picker</a></b> by Dhaval Patel</p>
<p><b><a href="https://github.com/heinrichreimer/material-intro"
title="Material Intro">Material Intro</a></b> by Jan Heinrich Reimer</p>
<p><b><a href="https://github.com/r0adkll/Slidr"
title="Slidr">Slidr</a></b> by Drew Heavner</p>
<p><b><a href="https://github.com/bosphere/Android-FadingEdgeLayout"
title="FadingEdgeLayout">FadingEdgeLayout</a></b> by bosphere</p>
<p><b><a href="https://github.com/yshrsmz/KeyboardVisibilityEvent"
title="KeyboardVisibilityEvent">KeyboardVisibilityEvent</a></b> by Yasuhiro SHIMIZU</p>
<p><b><a href="https://github.com/JetradarMobile/android-snowfall"
title="android-snowfall">android-snowfall</a></b> by Jetradar Mobile</p>
<p><b><a href="https://github.com/chrisbanes/insetter"
title="Insetter">Insetter</a></b> by Chris Banes</p>
<p><b><a href="https://materialdesignicons.com" title="Icons"> Icons</a></b> by Austin Andrews</p>
<p><b><a href="https://www.techjuice.pk" title="City wallpaper"> Material Design City Wallpaper</a></b>
</p>
</body>
</html>

View file

@ -1,63 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<style media="screen" type="text/css">
* {
word-wrap: break-word;
}
{style-placeholder}
a {
color: {link-color};
}
a:active {
color: {link-color-active};
}
ol {
list-style-position: inside;
padding-left: 0;
padding-right: 0;
}
li {
padding-top: 8px;
}
</style>
</head>
<body>
<p><b><a href="https://github.com/kabouzeid/Phonograph" title="Phonograph"> Phonograph</a></b> by
Karim Abou Zeid</p>
<p><b><a href="https://github.com/afollestad" title="Material Dialogs"> Material Dialogs and Cab</a></b>
by Aidan Michael Follestad</p>
<p><b><a href="http://developer.android.com/tools/support-library/index.html"
title="AOSP Support Libraries"> AOSP Support Libraries</a></b>by AOSP contributors</p>
<p><b><a href="https://github.com/bumptech/glide" title="Glide"> Glide</a></b> by Sam Judd</p>
<p><b><a href="https://github.com/square/retrofit" title="Retrofit"> Retrofit</a></b> by Square team
</p>
<p><b><a href="https://github.com/afollestad/material-cab" title="Material Contextual Action Bar">
Material Contextual Action Bar</a></b> by Aidan Michael Follestad</p>
<p><b><a href="http://square.github.io/okhttp/" title="OkHttp"> OkHttp</a></b> by Square team</p>
<p><b><a href="https://github.com/hdodenhof/CircleImageView" title="CircleImageView">
CircleImageView</a></b> by Henning Dodenhof</p>
<p><b><a href="https://github.com/DreaminginCodeZH/MaterialProgressBar" title="MaterialProgressBar">
MaterialProgressBar</a></b> by Zhang Hai</p>
<p><b><a href="https://github.com/anjlab/android-inapp-billing-v3"
title="Android In-App Billing v3 Library"> Android In-App Billing v3 Library</a></b> by
Henning Dodenhof</p>
<p><b><a href="https://github.com/h6ah4i/android-advancedrecyclerview"
title="Advanced RecyclerView"> Advanced RecyclerView</a></b> by Haruki Hasegawa</p>
<p><b><a href="https://github.com/ksoichiro/Android-ObservableScrollView"
title="Android-ObservableScrollView"> Android-ObservableScrollView</a></b> by Soichiro
Kashima</p>
<p><b><a href="https://materialdesignicons.com" title="Icons"> Icons</a></b> by Austin Andrews</p>
<p><b><a href="https://www.techjuice.pk" title="City wallpaper"> Material Design City Wallpaper</a></b>
</p>
</body>
</html>

View file

@ -6,22 +6,23 @@
word-wrap: break-word; word-wrap: break-word;
} }
body { div {
padding-left: 1rem; margin: 20px 10px;
padding-right: 1rem; padding: 10px;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
} }
h2 { h2 {
margin-block-end: 0rem; margin-block-end: 0rem;
margin-block-start: 0rem; margin-block-start: 0rem;
display: inline-block;
vertical-align: center;
} }
li { li {
font-size: 0.85rem; font-size: 0.85rem;
padding-top: 0.5rem; padding: 0.5rem 0;
padding-left: 0;
padding-right: 0;
color: rgba(0, 0, 0, 0.8);
} }
ul { ul {
@ -36,7 +37,6 @@
span { span {
font-size: 0.7rem; font-size: 0.7rem;
line-height: 0.7rem;
} }
h5 { h5 {
@ -44,39 +44,348 @@
margin-block-end: 0.5rem; margin-block-end: 0.5rem;
} }
h3 span { h3 {
border-radius: 0.2rem; margin: 10px 0px;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.3rem;
padding-bottom: 0.3rem;
font-size: 1rem; font-size: 1rem;
} }
.tag {
border-radius: 5px;
margin-left: 10px;
padding: 5px;
display: inline-block;
vertical-align: top;
}
{style-placeholder} {style-placeholder}
</style> </style>
</head> </head>
<body> <body>
<div>
<h5>May 25, 2022</h5>
<h2>v5.8.5</h2>
<h3>What's New</h3>
<ul>
<li>Display song images along in the artist and album details pages</li>
<li>Removed the Internet permissions and all the associated features</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed crashing occurs during changing screen orientation</li>
</ul>
</div>
<div>
<h5>May 13, 2022</h5>
<h2>v5.8.4</h2>
<h3>What's New</h3>
<ul>
<li>Added a toggle to enable/disable swipe down to dismiss mini player</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Playback speed and pitch not working when CrossFade is enabled</li>
<li>Fixed crash when adding folders to blacklist</li>
<li>Fix bugs in MD3 theme</li>
</ul>
</div>
<div>
<h5>May 07, 2022</h5>
<h2>v5.8.3</h2>
<h3>What's New</h3>
<ul>
<li>Added a new MD3 now playing theme</li>
<li>Swipe down to dismiss Mini player</li>
<li>Add support for Just Black with Material You</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed sharing of files from SD Card</li>
<li>Fix ChromeCast crash and bugs</li>
<li>Fix Audio Crossfade</li>
<li>Tried to fix incorrect song data in notification</li>
</ul>
</div>
<div>
<h5>April 8, 2022</h5>
<h2>v5.8.0</h2>
<h3>What's New</h3>
<ul>
<li>Updated translations</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Classic Notification crash</li>
<li>Fixed crash when clicking on Playlist in the Search Tab</li>
<li>Fixed settings change not reflecting immediately</li>
<li>Fixed shuffle</li>
<li>Fixed Song duration not visible in Card & Blur card themes</li>
<li>Fixed some Album skip styles</li>
<li>Minor bug fixes & UI improvements</li>
</ul>
</div>
<div>
<h5>March 13, 2022</h5>
<h2>v5.7.3</h2>
<h3>What's New</h3>
<ul>
<li>Added adaptive color in Material now playing theme</li>
<li>Added an option to share crash report</li>
<li>Added options to clear, pause history</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Adapt Wallpaper accent for better readability</li>
<li>Optimized search</li>
<li>Made sleep timer precise</li>
</ul>
</div>
<div>
<h5>February 13, 2022</h5>
<h2>v5.7.2<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Animated splash screen on Android 12 devices</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed crash when removing song from Playing queue</li>
<li>Fixed lyrics editing crash</li>
<li>Fixed shuffle button not working</li>
<li>Fixed crash on song deletion</li>
</ul>
</div>
<div>
<h5>February 1, 2022</h5>
<h2>v5.7.1<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added option to disable changing song by swiping anywhere on the now playing screen</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Playlist save on A11+</li>
<li>Fixed Just Black theme</li>
<li>Fixed some UI issues</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Improvements to search</li>
</ul>
</div>
<div>
<h5>January 25, 2022</h5>
<h2>v5.7.0<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added Android Auto</li>
<li>Added accent color extraction on Android 8.1+ devices</li>
<li>Added new Circle widget</li>
<li>Added Collapsing appbar to library tabs with an option to switch back to simple appbar
</li>
<li>Added Search tab</li>
<li>Added option to use circular play button</li>
<li>Added lyrics editing on A11+ devices again</li>
<li>Added Long Press to forward, rewind current song</li>
<li>Added ability to set Playback speed and pitch</li>
<li>Added option to show lyrics over Cover</li>
<li>Added option to keep screen on when showing lyrics</li>
<li>Added option to switch to Manrope font</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Gapless Playback</li>
<li>Fixed Shuffle FAB going behind Mini Player</li>
<li>Fixed crashes on Pre-marshmallow devices</li>
<li>Blacklisted songs can't be played after opening from outside the app</li>
<li>Fixed various small bugs and some minor improvements</li>
</ul>
</div>
<div>
<h5>January 1, 2021</h5>
<h2>v5.6.1</h2>
<h3>Fixed</h3>
<ul>
<li>Fixed artist covers not updating and showing album cover images.</li>
<li>Fixed FAB's not visible (Shuffle, Save, etc.)</li>
<li>Fixed a crash when a Song is deleted in Artist Details</li>
<li>Fixed Snowfall effect</li>
<li>Fixed empty notification when queue is cleared</li>
</ul>
</div>
<div>
<h5>December 25, 2021</h5>
<h2>v5.6.0<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added Artwork editing for songs</li>
<li>Circle theme has album art now</li>
<li>Upgraded tag editor library, we should support reading tags of more formats now e.g.
opus.
</li>
<li>Added Snowfall effect</li>
<li>Crossfade effect for background when Song is changed for Blur, Blur card themes</li>
<li>Album art is hidden when Show lyrics is enabled</li>
<li>Added fastscroll in Playlists tab</li>
<li>Added Chooser to choose what to restore</li>
<li>Hide BottomNavigation when only one tab is selected in Library Categories(This was
already there but when changing screens it was getting visible)
</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Ringtone crash</li>
<li>Fixed blank album cover bug</li>
<li>Fixed bottom navigation visible in Playing Queue</li>
<li>Fixed lockscreen dragging glitch</li>
<li>Fixed incorrect colors when no cover art is available</li>
<li>Fixed favorite not updating when song is changed</li>
<li>Fixed playlist not getting created & playlist creation crash with same name</li>
<li>Fixed a bug in "Plain" Now playing theme where onClick event is consumed by the views
behind the bottom sheet
</li>
</ul>
</div>
<div>
<h5>December 6, 2021</h5>
<h2>v5.4.2<span class="tag"><i>Beta</i></span></h2>
<h3>Fixed</h3>
<ul>
<li>Bug Fixes</li>
</ul>
</div>
<div>
<h5>December 1, 2021</h5>
<h2>v5.4.1<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added file selection from system file picker for restore</li>
<li>Added Grid size selection for Playlists</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Enable Material You by default on Android 12</li>
<li>Reworked Grid Style saving</li>
<li>Improved Playlist preview images</li>
<li>UI improvements</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Fixed File deletion on Android 10</li>
<li>Fixed Sleep Timer crash</li>
<li>Fixed Bottom Toolbar not clickable in now playing when Shuffle is clicked</li>
<li>Fixed Album Artist sort order</li>
<li>Fixed a crash when clicking the "Clear All" button in the Blacklist folder selection
</li>
<li>Fixed Continuous crashes on A12 when the song ends</li>
<li>Fixed New Music Mix multiple clicks crash</li>
</ul>
</div>
<div>
<h5>November 22, 2021</h5>
<h2>v5.4.0<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li><b>Material You</b></li>
<li>Going Edge-to-Edge</li>
<li>Added Backup & restore</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Improved Animations</li>
<li>Improved Crossfade</li>
</ul>
</div>
<div>
<h5>September 06, 2021</h5>
<h2>v5.0.0</h2>
<h3>What's New</h3>
<ul>
<li>Added ability to Remember last tab</li>
<li>Added Whitelisting songs</li>
<li>You can now browse SDCard from Folders Tab</li>
</ul>
</div>
<div>
<h5>August 22, 2021</h5>
<h2>v4.4.0<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added Crossfade</li>
<li>Multi-select in Album and Artist Details</li>
<li>Albums now show Album Artists instead of artist of first song</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Playlist Preview Images</li>
</ul>
</div>
<div>
<h5>August 11, 2021</h5>
<h2>v4.2.30<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Revamped Playlist Tab</li>
<li>Revamped Genres Tab</li>
<li>Added support for embedded Synced Lyrics</li>
<li>Added animated icons</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Language Switching</li>
<li>Fixed some reported bugs</li>
</ul>
</div>
<div>
<h5>July 18, 2021</h5>
<h2>v4.2.020<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added ChromeCast Support</li>
<li>Added Lyrics Editor for Normal and Synced Lyrics</li>
<li>Added Ripple Animation for Color Theme</li>
<li>Added Drag to seek in Tiny Theme</li>
<li>Added Playlist Order</li>
<li>Added Search Filters</li>
<li>Added Audio Fade</li>
<li>Synced Lyrics in all Themes</li>
<li>Swipe anywhere to change song</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Fixed Navigate by Album Artist</li>
<li>Changed New Music Mix Actions</li>
<li>Improved Animations</li>
<li>And some minor bug fixes and improvements</li>
</ul>
</div>
<div>
<h5>October 12, 2020</h5>
<h2>v4.0.10</h2>
<h3>What's New</h3>
<ul>
<li>Re-built from scratch using MVVM Architecture and JetPack Components</li>
<li>New Material Design icon</li>
<li>Implemented a custom database for playlists</li>
<li>Added new Material Design motions</li>
<li>Bug fixes & performance improvements</li>
<li>Revamped Home tab UI</li>
<li>Android 11 support</li>
<li>And more!</li>
</ul>
</div>
<div>
<h5>April 30, 2020</h5> <h5>April 30, 2020</h5>
<h2>v3.5.110</h2> <h2>v3.5.110<span class="tag"><i>Beta</i></span></h2>
<span class="tag"><i>Beta version</i></span> <h3>What's New</h3>
<h3><span class="colorHeader">What's New</span></h3>
<ul> <ul>
<li>Changed profile form image to icon</li> <li>Changed profile form image to icon</li>
<li>New what's new screen</li> <li>New what's new screen</li>
<li>Added In-App language changer, where you can select language</li> <li>Added In-App language changer, where you can select language</li>
</ul> </ul>
<h3><span class="colorHeader">Improved</span></h3> <h3>Improved</h3>
<ul> <ul>
<li>Improved loading of Songs, Albums, Artists, Genres, Playlists</li> <li>Improved loading of Songs, Albums, Artists, Genres, Playlists</li>
</ul> </ul>
<!--<h3><span class="colorHeader">Bug fixes</span></h3> </div>
<ul>
<li></li>
</ul>-->
<p>*If you face any UI related issues you clear app data and cache, if itsnot working try to
uninstall and install
again. </p>
</body> </body>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

View file

@ -13,14 +13,19 @@
*/ */
package io.github.muntashirakon.music package io.github.muntashirakon.music
import androidx.multidex.MultiDexApplication import android.app.Application
import cat.ereza.customactivityoncrash.config.CaocConfig
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.activities.ErrorActivity
import io.github.muntashirakon.music.appshortcuts.DynamicShortcutManager import io.github.muntashirakon.music.appshortcuts.DynamicShortcutManager
import io.github.muntashirakon.music.helper.WallpaperAccentManager
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
class App : MultiDexApplication() { class App : Application() {
private val wallpaperAccentManager = WallpaperAccentManager(this)
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -37,9 +42,18 @@ class App : MultiDexApplication() {
.coloredNavigationBar(true) .coloredNavigationBar(true)
.commit() .commit()
} }
wallpaperAccentManager.init()
if (VersionUtils.hasNougatMR()) if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).initDynamicShortcuts() DynamicShortcutManager(this).initDynamicShortcuts()
// setting Error activity
CaocConfig.Builder.create().errorActivity(ErrorActivity::class.java).apply()
}
override fun onTerminate() {
super.onTerminate()
wallpaperAccentManager.release()
} }
companion object { companion object {

View file

@ -18,40 +18,37 @@ import android.provider.BaseColumns
import android.provider.MediaStore import android.provider.MediaStore
object Constants { object Constants {
const val RATE_ON_GOOGLE_PLAY = const val TRANSLATE = "https://crowdin.com/project/retromusicplayer"
"https://play.google.com/store/apps/details?id=code.name.monkey.retromusic"
const val TRANSLATE = "https://github.com/h4h13/RetroMusicPlayer"
const val GITHUB_PROJECT = "https://github.com/MuntashirAkon/Metro" const val GITHUB_PROJECT = "https://github.com/MuntashirAkon/Metro"
const val TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog" const val TELEGRAM_CHANGE_LOG = "https://t.me/AppManagerChannel"
const val USER_PROFILE = "profile.jpg" const val USER_PROFILE = "profile.jpg"
const val USER_BANNER = "banner.jpg" const val USER_BANNER = "banner.jpg"
const val APP_INSTAGRAM_LINK = "https://www.instagram.com/retromusicapp/" const val FAQ_LINK = "https://github.com/MuntashirAkon/Metro/blob/master/FAQ.md"
const val APP_TELEGRAM_LINK = "https://t.me/retromusicapp/"
const val APP_TWITTER_LINK = "https://twitter.com/retromusicapp"
const val FAQ_LINK = "https://github.com/h4h13/RetroMusicPlayer/blob/master/FAQ.md"
const val PINTEREST = "https://in.pinterest.com/retromusicapp/"
const val AUDIO_SCROBBLER_URL = "https://ws.audioscrobbler.com/2.0/"
const val IS_MUSIC = const val IS_MUSIC =
MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''" MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"
const val DATA = "_data"
@Suppress("Deprecation")
val baseProjection = arrayOf( val baseProjection = arrayOf(
BaseColumns._ID, // 0 BaseColumns._ID, // 0
MediaStore.Audio.AudioColumns.TITLE, // 1 MediaStore.Audio.AudioColumns.TITLE, // 1
MediaStore.Audio.AudioColumns.TRACK, // 2 MediaStore.Audio.AudioColumns.TRACK, // 2
MediaStore.Audio.AudioColumns.YEAR, // 3 MediaStore.Audio.AudioColumns.YEAR, // 3
MediaStore.Audio.AudioColumns.DURATION, // 4 MediaStore.Audio.AudioColumns.DURATION, // 4
MediaStore.Audio.AudioColumns.DATA, // 5 DATA, // 5
MediaStore.Audio.AudioColumns.DATE_MODIFIED, // 6 MediaStore.Audio.AudioColumns.DATE_MODIFIED, // 6
MediaStore.Audio.AudioColumns.ALBUM_ID, // 7 MediaStore.Audio.AudioColumns.ALBUM_ID, // 7
MediaStore.Audio.AudioColumns.ALBUM, // 8 MediaStore.Audio.AudioColumns.ALBUM, // 8
MediaStore.Audio.AudioColumns.ARTIST_ID, // 9 MediaStore.Audio.AudioColumns.ARTIST_ID, // 9
MediaStore.Audio.AudioColumns.ARTIST, // 10 MediaStore.Audio.AudioColumns.ARTIST, // 10
MediaStore.Audio.AudioColumns.COMPOSER, // 11 MediaStore.Audio.AudioColumns.COMPOSER, // 11
"album_artist" // 12 ALBUM_ARTIST // 12
) )
const val NUMBER_OF_TOP_TRACKS = 99 const val NUMBER_OF_TOP_TRACKS = 99
} }
const val EXTRA_PLAYLIST_TYPE = "type" const val EXTRA_PLAYLIST_TYPE = "type"
const val EXTRA_GENRE = "extra_genre" const val EXTRA_GENRE = "extra_genre"
const val EXTRA_PLAYLIST = "extra_playlist" const val EXTRA_PLAYLIST = "extra_playlist"
@ -70,7 +67,7 @@ const val NOW_PLAYING_SCREEN_ID = "now_playing_screen_id"
const val CAROUSEL_EFFECT = "carousel_effect" const val CAROUSEL_EFFECT = "carousel_effect"
const val COLORED_NOTIFICATION = "colored_notification" const val COLORED_NOTIFICATION = "colored_notification"
const val CLASSIC_NOTIFICATION = "classic_notification" const val CLASSIC_NOTIFICATION = "classic_notification"
const val GAP_LESS_PLAYBACK = "gap_less_playback" const val GAP_LESS_PLAYBACK = "gapless_playback"
const val ALBUM_ART_ON_LOCK_SCREEN = "album_art_on_lock_screen" const val ALBUM_ART_ON_LOCK_SCREEN = "album_art_on_lock_screen"
const val BLURRED_ALBUM_ART = "blurred_album_art" const val BLURRED_ALBUM_ART = "blurred_album_art"
const val NEW_BLUR_AMOUNT = "new_blur_amount" const val NEW_BLUR_AMOUNT = "new_blur_amount"
@ -116,6 +113,8 @@ const val ALBUM_GRID_SIZE_LAND = "album_grid_size_land"
const val SONG_GRID_SIZE_LAND = "song_grid_size_land" const val SONG_GRID_SIZE_LAND = "song_grid_size_land"
const val ARTIST_GRID_SIZE = "artist_grid_size" const val ARTIST_GRID_SIZE = "artist_grid_size"
const val ARTIST_GRID_SIZE_LAND = "artist_grid_size_land" const val ARTIST_GRID_SIZE_LAND = "artist_grid_size_land"
const val PLAYLIST_GRID_SIZE = "playlist_grid_size"
const val PLAYLIST_GRID_SIZE_LAND = "playlist_grid_size_land"
const val COLORED_APP_SHORTCUTS = "colored_app_shortcuts" const val COLORED_APP_SHORTCUTS = "colored_app_shortcuts"
const val AUDIO_DUCKING = "audio_ducking" const val AUDIO_DUCKING = "audio_ducking"
const val LAST_ADDED_CUTOFF = "last_added_interval" const val LAST_ADDED_CUTOFF = "last_added_interval"
@ -123,12 +122,13 @@ const val LAST_SLEEP_TIMER_VALUE = "last_sleep_timer_value"
const val NEXT_SLEEP_TIMER_ELAPSED_REALTIME = "next_sleep_timer_elapsed_real_time" const val NEXT_SLEEP_TIMER_ELAPSED_REALTIME = "next_sleep_timer_elapsed_real_time"
const val IGNORE_MEDIA_STORE_ARTWORK = "ignore_media_store_artwork" const val IGNORE_MEDIA_STORE_ARTWORK = "ignore_media_store_artwork"
const val LAST_CHANGELOG_VERSION = "last_changelog_version" const val LAST_CHANGELOG_VERSION = "last_changelog_version"
const val AUTO_DOWNLOAD_IMAGES_POLICY = "auto_download_images_policy"
const val START_DIRECTORY = "start_directory" const val START_DIRECTORY = "start_directory"
const val RECENTLY_PLAYED_CUTOFF = "recently_played_interval" const val RECENTLY_PLAYED_CUTOFF = "recently_played_interval"
const val LOCK_SCREEN = "lock_screen" const val LOCK_SCREEN = "lock_screen"
const val ALBUM_ARTISTS_ONLY = "album_artists_only" const val ALBUM_ARTISTS_ONLY = "album_artists_only"
const val ALBUM_ARTIST = "album_artist"
const val ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order" const val ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order"
const val ARTIST_DETAIL_SONG_SORT_ORDER = "artist_detail_song_sort_order"
const val LYRICS_OPTIONS = "lyrics_tab_position" const val LYRICS_OPTIONS = "lyrics_tab_position"
const val CHOOSE_EQUALIZER = "choose_equalizer" const val CHOOSE_EQUALIZER = "choose_equalizer"
const val EQUALIZER = "equalizer" const val EQUALIZER = "equalizer"
@ -137,3 +137,25 @@ const val SONG_GRID_STYLE = "song_grid_style"
const val PAUSE_ON_ZERO_VOLUME = "pause_on_zero_volume" const val PAUSE_ON_ZERO_VOLUME = "pause_on_zero_volume"
const val FILTER_SONG = "filter_song" const val FILTER_SONG = "filter_song"
const val EXPAND_NOW_PLAYING_PANEL = "expand_now_playing_panel" const val EXPAND_NOW_PLAYING_PANEL = "expand_now_playing_panel"
const val EXTRA_ARTIST_NAME = "extra_artist_name"
const val TOGGLE_SUGGESTIONS = "toggle_suggestions"
const val AUDIO_FADE_DURATION = "audio_fade_duration"
const val CROSS_FADE_DURATION = "cross_fade_duration"
const val SHOW_LYRICS = "show_lyrics"
const val REMEMBER_LAST_TAB = "remember_last_tab"
const val LAST_USED_TAB = "last_used_tab"
const val WHITELIST_MUSIC = "whitelist_music"
const val MATERIAL_YOU = "material_you"
const val SNOWFALL = "snowfall"
const val LYRICS_TYPE = "lyrics_type"
const val PLAYBACK_SPEED = "playback_speed"
const val PLAYBACK_PITCH = "playback_pitch"
const val CUSTOM_FONT = "custom_font"
const val APPBAR_MODE = "appbar_mode"
const val WALLPAPER_ACCENT = "wallpaper_accent"
const val SCREEN_ON_LYRICS = "screen_on_lyrics"
const val CIRCLE_PLAY_BUTTON = "circle_play_button"
const val SWIPE_ANYWHERE_NOW_PLAYING = "swipe_anywhere_now_playing"
const val PAUSE_HISTORY = "pause_history"
const val MANAGE_AUDIO_FOCUS = "manage_audio_focus"
const val SWIPE_DOWN_DISMISS = "swipe_to_dismiss"

View file

@ -1,41 +0,0 @@
package io.github.muntashirakon.music;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.LocaleList;
import code.name.monkey.appthemehelper.util.VersionUtils;
import java.util.Locale;
public class LanguageContextWrapper extends ContextWrapper {
public LanguageContextWrapper(Context base) {
super(base);
}
public static LanguageContextWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (VersionUtils.INSTANCE.hasNougatMR()) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (VersionUtils.INSTANCE.hasLollipop()) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new LanguageContextWrapper(context);
}
}

View file

@ -0,0 +1,25 @@
package io.github.muntashirakon.music
import android.content.Context
import android.content.ContextWrapper
import android.os.LocaleList
import code.name.monkey.appthemehelper.util.VersionUtils.hasNougatMR
import java.util.*
class LanguageContextWrapper(base: Context?) : ContextWrapper(base) {
companion object {
fun wrap(context: Context?, newLocale: Locale?): LanguageContextWrapper {
if (context == null) return LanguageContextWrapper(context)
val configuration = context.resources.configuration
if (hasNougatMR()) {
configuration.setLocale(newLocale)
val localeList = LocaleList(newLocale)
LocaleList.setDefault(localeList)
configuration.setLocales(localeList)
} else {
configuration.setLocale(newLocale)
}
return LanguageContextWrapper(context.createConfigurationContext(configuration))
}
}
}

View file

@ -3,6 +3,7 @@ package io.github.muntashirakon.music
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import io.github.muntashirakon.music.auto.AutoMusicProvider
import io.github.muntashirakon.music.db.BlackListStoreDao import io.github.muntashirakon.music.db.BlackListStoreDao
import io.github.muntashirakon.music.db.BlackListStoreEntity import io.github.muntashirakon.music.db.BlackListStoreEntity
import io.github.muntashirakon.music.db.PlaylistWithSongs import io.github.muntashirakon.music.db.PlaylistWithSongs
@ -13,10 +14,6 @@ import io.github.muntashirakon.music.fragments.artists.ArtistDetailsViewModel
import io.github.muntashirakon.music.fragments.genres.GenreDetailsViewModel import io.github.muntashirakon.music.fragments.genres.GenreDetailsViewModel
import io.github.muntashirakon.music.fragments.playlists.PlaylistDetailsViewModel import io.github.muntashirakon.music.fragments.playlists.PlaylistDetailsViewModel
import io.github.muntashirakon.music.model.Genre import io.github.muntashirakon.music.model.Genre
import io.github.muntashirakon.music.network.provideDefaultCache
import io.github.muntashirakon.music.network.provideLastFmRest
import io.github.muntashirakon.music.network.provideLastFmRetrofit
import io.github.muntashirakon.music.network.provideOkHttp
import io.github.muntashirakon.music.repository.* import io.github.muntashirakon.music.repository.*
import io.github.muntashirakon.music.util.FilePathUtil import io.github.muntashirakon.music.util.FilePathUtil
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
@ -27,22 +24,6 @@ import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.bind import org.koin.dsl.bind
import org.koin.dsl.module import org.koin.dsl.module
val networkModule = module {
factory {
provideDefaultCache()
}
factory {
provideOkHttp(get(), get())
}
single {
provideLastFmRetrofit(get())
}
single {
provideLastFmRest(get())
}
}
private val roomModule = module { private val roomModule = module {
single { single {
@ -85,6 +66,19 @@ private val roomModule = module {
RealRoomRepository(get(), get(), get(), get(), get()) RealRoomRepository(get(), get(), get(), get(), get())
} bind RoomRepository::class } bind RoomRepository::class
} }
private val autoModule = module {
single {
AutoMusicProvider(
androidContext(),
get(),
get(),
get(),
get(),
get(),
get()
)
}
}
private val mainModule = module { private val mainModule = module {
single { single {
androidContext().contentResolver androidContext().contentResolver
@ -103,8 +97,6 @@ private val dataModule = module {
get(), get(),
get(), get(),
get(), get(),
get(),
get(),
) )
} bind Repository::class } bind Repository::class
@ -149,9 +141,6 @@ private val dataModule = module {
get() get()
) )
} }
single {
RealLocalDataRepository(get())
} bind LocalDataRepository::class
} }
private val viewModules = module { private val viewModules = module {
@ -167,10 +156,11 @@ private val viewModules = module {
) )
} }
viewModel { (artistId: Long) -> viewModel { (artistId: Long?, artistName: String?) ->
ArtistDetailsViewModel( ArtistDetailsViewModel(
get(), get(),
artistId artistId,
artistName
) )
} }
@ -189,4 +179,4 @@ private val viewModules = module {
} }
} }
val appModules = listOf(mainModule, dataModule, viewModules, networkModule, roomModule) val appModules = listOf(mainModule, dataModule, autoModule, viewModules, roomModule)

View file

@ -1,46 +0,0 @@
package io.github.muntashirakon.music
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class PeekingLinearLayoutManager : LinearLayoutManager {
@JvmOverloads
constructor(
context: Context?,
@RecyclerView.Orientation orientation: Int = RecyclerView.VERTICAL,
reverseLayout: Boolean = false
) : super(context, orientation, reverseLayout)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(
context,
attrs,
defStyleAttr,
defStyleRes
)
override fun generateDefaultLayoutParams() =
scaledLayoutParams(super.generateDefaultLayoutParams())
override fun generateLayoutParams(lp: ViewGroup.LayoutParams?) =
scaledLayoutParams(super.generateLayoutParams(lp))
override fun generateLayoutParams(c: Context?, attrs: AttributeSet?) =
scaledLayoutParams(super.generateLayoutParams(c, attrs))
private fun scaledLayoutParams(layoutParams: RecyclerView.LayoutParams) =
layoutParams.apply {
when (orientation) {
HORIZONTAL -> width = (horizontalSpace * ratio).toInt()
VERTICAL -> height = (verticalSpace * ratio).toInt()
}
}
private val horizontalSpace get() = width - paddingStart - paddingEnd
private val verticalSpace get() = height - paddingTop - paddingBottom
private val ratio = 0.8f // change to 0.7f for 70%
}

View file

@ -1,35 +0,0 @@
package io.github.muntashirakon.music;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import org.jetbrains.annotations.NotNull;
public class RetroBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> {
private static final String TAG = "RetroBottomSheetBehavior";
private boolean allowDragging = true;
public RetroBottomSheetBehavior() {}
public RetroBottomSheetBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setAllowDragging(boolean allowDragging) {
this.allowDragging = allowDragging;
}
@Override
public boolean onInterceptTouchEvent(
@NotNull CoordinatorLayout parent, @NotNull V child, @NotNull MotionEvent event) {
if (!allowDragging) {
return false;
}
return super.onInterceptTouchEvent(parent, child, event);
}
}

View file

@ -15,32 +15,37 @@
package io.github.muntashirakon.music.activities package io.github.muntashirakon.music.activities
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Bundle import android.os.Bundle
import android.view.animation.LinearInterpolator import android.view.animation.LinearInterpolator
import android.widget.SeekBar import android.widget.SeekBar
import code.name.monkey.appthemehelper.ThemeStore import androidx.lifecycle.lifecycleScope
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity
import io.github.muntashirakon.music.databinding.ActivityDriveModeBinding
import io.github.muntashirakon.music.db.toSongEntity
import io.github.muntashirakon.music.extensions.accentColor
import io.github.muntashirakon.music.extensions.drawAboveSystemBars
import io.github.muntashirakon.music.fragments.base.AbsPlayerControlsFragment import io.github.muntashirakon.music.fragments.base.AbsPlayerControlsFragment
import io.github.muntashirakon.music.glide.BlurTransformation import io.github.muntashirakon.music.glide.BlurTransformation
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.SongGlideRequest import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.helper.MusicProgressViewUpdateHelper import io.github.muntashirakon.music.helper.MusicProgressViewUpdateHelper
import io.github.muntashirakon.music.helper.MusicProgressViewUpdateHelper.Callback import io.github.muntashirakon.music.helper.MusicProgressViewUpdateHelper.Callback
import io.github.muntashirakon.music.helper.PlayPauseButtonOnClickHandler import io.github.muntashirakon.music.helper.PlayPauseButtonOnClickHandler
import io.github.muntashirakon.music.misc.SimpleOnSeekbarChangeListener import io.github.muntashirakon.music.misc.SimpleOnSeekbarChangeListener
import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.repository.RealRepository
import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.service.MusicService
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_drive_mode.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject
/** /**
* Created by hemanths on 2020-02-02. * Created by hemanths on 2020-02-02.
@ -48,21 +53,24 @@ import kotlinx.coroutines.withContext
class DriveModeActivity : AbsMusicServiceActivity(), Callback { class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private lateinit var binding: ActivityDriveModeBinding
private var lastPlaybackControlsColor: Int = Color.GRAY private var lastPlaybackControlsColor: Int = Color.GRAY
private var lastDisabledPlaybackControlsColor: Int = Color.GRAY private var lastDisabledPlaybackControlsColor: Int = Color.GRAY
private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper
private val repository: RealRepository by inject()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_drive_mode) binding = ActivityDriveModeBinding.inflate(layoutInflater)
setContentView(binding.root)
setUpMusicControllers() setUpMusicControllers()
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
lastPlaybackControlsColor = ThemeStore.accentColor(this) lastPlaybackControlsColor = accentColor()
close.setOnClickListener { binding.close.setOnClickListener {
onBackPressed() onBackPressed()
} }
binding.repeatButton.drawAboveSystemBars()
} }
private fun setUpMusicControllers() { private fun setUpMusicControllers() {
@ -75,26 +83,37 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
} }
private fun setupFavouriteToggle() { private fun setupFavouriteToggle() {
songFavourite.setOnClickListener { binding.songFavourite.setOnClickListener {
MusicUtil.toggleFavorite( toggleFavorite(MusicPlayerRemote.currentSong)
this@DriveModeActivity,
MusicPlayerRemote.currentSong
)
} }
} }
private fun toggleFavourite() { private fun toggleFavorite(song: Song) {
CoroutineScope(Dispatchers.IO).launch { lifecycleScope.launch(Dispatchers.IO) {
val isFavourite = val playlist = repository.favoritePlaylist()
MusicUtil.isFavorite(this@DriveModeActivity, MusicPlayerRemote.currentSong) val songEntity = song.toSongEntity(playlist.playListId)
val isFavorite = repository.isSongFavorite(song.id)
if (isFavorite) {
repository.removeSongFromPlaylist(songEntity)
} else {
repository.insertSongs(listOf(song.toSongEntity(playlist.playListId)))
}
sendBroadcast(Intent(MusicService.FAVORITE_STATE_CHANGED))
}
}
private fun updateFavorite() {
lifecycleScope.launch(Dispatchers.IO) {
val isFavorite: Boolean =
repository.isSongFavorite(MusicPlayerRemote.currentSong.id)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
songFavourite.setImageResource(if (isFavourite) R.drawable.ic_favorite else R.drawable.ic_favorite_border) binding.songFavourite.setImageResource(if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border)
} }
} }
} }
private fun setUpProgressSlider() { private fun setUpProgressSlider() {
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) { if (fromUser) {
MusicPlayerRemote.seekTo(progress) MusicPlayerRemote.seekTo(progress)
@ -118,21 +137,20 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
} }
private fun setUpPrevNext() { private fun setUpPrevNext() {
binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() }
nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } binding.previousButton.setOnClickListener { MusicPlayerRemote.back() }
previousButton.setOnClickListener { MusicPlayerRemote.back() }
} }
private fun setUpShuffleButton() { private fun setUpShuffleButton() {
shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() }
} }
private fun setUpRepeatButton() { private fun setUpRepeatButton() {
repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() }
} }
private fun setUpPlayPauseFab() { private fun setUpPlayPauseFab() {
playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler())
} }
override fun onRepeatModeChanged() { override fun onRepeatModeChanged() {
@ -156,24 +174,24 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
updateSong() updateSong()
updateRepeatState() updateRepeatState()
updateShuffleState() updateShuffleState()
toggleFavourite() updateFavorite()
} }
private fun updatePlayPauseDrawableState() { private fun updatePlayPauseDrawableState() {
if (MusicPlayerRemote.isPlaying) { if (MusicPlayerRemote.isPlaying) {
playPauseButton.setImageResource(R.drawable.ic_pause) binding.playPauseButton.setImageResource(R.drawable.ic_pause)
} else { } else {
playPauseButton.setImageResource(R.drawable.ic_play_arrow) binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow)
} }
} }
fun updateShuffleState() { fun updateShuffleState() {
when (MusicPlayerRemote.shuffleMode) { when (MusicPlayerRemote.shuffleMode) {
MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter(
lastPlaybackControlsColor, lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
else -> shuffleButton.setColorFilter( else -> binding.shuffleButton.setColorFilter(
lastDisabledPlaybackControlsColor, lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
@ -183,19 +201,25 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private fun updateRepeatState() { private fun updateRepeatState() {
when (MusicPlayerRemote.repeatMode) { when (MusicPlayerRemote.repeatMode) {
MusicService.REPEAT_MODE_NONE -> { MusicService.REPEAT_MODE_NONE -> {
repeatButton.setImageResource(R.drawable.ic_repeat) binding.repeatButton.setImageResource(R.drawable.ic_repeat)
repeatButton.setColorFilter( binding.repeatButton.setColorFilter(
lastDisabledPlaybackControlsColor, lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
} }
MusicService.REPEAT_MODE_ALL -> { MusicService.REPEAT_MODE_ALL -> {
repeatButton.setImageResource(R.drawable.ic_repeat) binding.repeatButton.setImageResource(R.drawable.ic_repeat)
repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) binding.repeatButton.setColorFilter(
lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
MusicService.REPEAT_MODE_THIS -> { MusicService.REPEAT_MODE_THIS -> {
repeatButton.setImageResource(R.drawable.ic_repeat_one) binding.repeatButton.setImageResource(R.drawable.ic_repeat_one)
repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) binding.repeatButton.setColorFilter(
lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
} }
} }
@ -203,35 +227,36 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged() super.onPlayingMetaChanged()
updateSong() updateSong()
toggleFavourite() updateFavorite()
}
override fun onFavoriteStateChanged() {
super.onFavoriteStateChanged()
updateFavorite()
} }
private fun updateSong() { private fun updateSong() {
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
songTitle.text = song.title binding.songTitle.text = song.title
songText.text = song.artistName binding.songText.text = song.artistName
SongGlideRequest.Builder.from(Glide.with(this), song) GlideApp.with(this)
.checkIgnoreMediaStore(this) .load(RetroGlideExtension.getSongModel(song))
.generatePalette(this) .songCoverOptions(song)
.build()
.transform(BlurTransformation.Builder(this).build()) .transform(BlurTransformation.Builder(this).build())
.into(object : RetroMusicColoredTarget(image) { .into(binding.image)
override fun onColorReady(colors: MediaNotificationProcessor) {
}
})
} }
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total binding.progressSlider.max = total
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress)
animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
animator.interpolator = LinearInterpolator() animator.interpolator = LinearInterpolator()
animator.start() animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
} }
} }

View file

@ -0,0 +1,80 @@
package io.github.muntashirakon.music.activities
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import cat.ereza.customactivityoncrash.CustomActivityOnCrash
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.util.FileUtils.createFile
import io.github.muntashirakon.music.util.Share.shareFile
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
class ErrorActivity : AppCompatActivity() {
private val dayFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
private val ReportPrefix = "bug_report-"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.customactivityoncrash_default_error_activity)
val restartButton =
findViewById<Button>(R.id.customactivityoncrash_error_activity_restart_button)
val config = CustomActivityOnCrash.getConfigFromIntent(intent)
if (config == null) {
finish()
return
}
restartButton.setText(R.string.customactivityoncrash_error_activity_restart_app)
restartButton.setOnClickListener {
CustomActivityOnCrash.restartApplication(
this@ErrorActivity,
config
)
}
val moreInfoButton =
findViewById<Button>(R.id.customactivityoncrash_error_activity_more_info_button)
moreInfoButton.setOnClickListener { //We retrieve all the error data and show it
AlertDialog.Builder(this@ErrorActivity)
.setTitle(R.string.customactivityoncrash_error_activity_error_details_title)
.setMessage(
CustomActivityOnCrash.getAllErrorDetailsFromIntent(
this@ErrorActivity,
intent
)
)
.setPositiveButton(
R.string.customactivityoncrash_error_activity_error_details_close,
null
)
.setNeutralButton(
R.string.customactivityoncrash_error_activity_error_details_share
) { _, _ ->
val bugReport = createFile(
context = this,
"Bug Report",
"$ReportPrefix${dayFormat.format(Date())}",
CustomActivityOnCrash.getAllErrorDetailsFromIntent(
this@ErrorActivity,
intent
), ".txt"
)
shareFile(this, bugReport)
}
.show()
}
val errorActivityDrawableId = config.errorDrawable
val errorImageView =
findViewById<ImageView>(R.id.customactivityoncrash_error_activity_image)
if (errorActivityDrawableId != null) {
errorImageView.setImageResource(
errorActivityDrawableId
)
}
}
}

View file

@ -1,103 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package io.github.muntashirakon.music.activities;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MenuItem;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.appthemehelper.util.ColorUtil;
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper;
import io.github.muntashirakon.music.R;
import io.github.muntashirakon.music.activities.base.AbsBaseActivity;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import org.jetbrains.annotations.Nullable;
/** Created by hemanths on 2019-09-27. */
public class LicenseActivity extends AbsBaseActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
setDrawUnderStatusBar();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_license);
setStatusbarColorAuto();
setNavigationbarColorAuto();
setLightNavigationBar(true);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ToolbarContentTintHelper.colorBackButton(toolbar);
toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface));
WebView webView = findViewById(R.id.license);
try {
StringBuilder buf = new StringBuilder();
InputStream json = getAssets().open("oldindex.html");
BufferedReader in = new BufferedReader(new InputStreamReader(json, StandardCharsets.UTF_8));
String str;
while ((str = in.readLine()) != null) {
buf.append(str);
}
in.close();
// Inject color values for WebView body background and links
final boolean isDark = ATHUtil.INSTANCE.isWindowBackgroundDark(this);
final String backgroundColor =
colorToCSS(
ATHUtil.INSTANCE.resolveColor(
this, R.attr.colorSurface, Color.parseColor(isDark ? "#424242" : "#ffffff")));
final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000"));
final String changeLog =
buf.toString()
.replace(
"{style-placeholder}",
String.format(
"body { background-color: %s; color: %s; }", backgroundColor, contentColor))
.replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this)))
.replace(
"{link-color-active}",
colorToCSS(
ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this))));
webView.loadData(changeLog, "text/html", "UTF-8");
} catch (Throwable e) {
webView.loadData(
"<h1>Unable to load</h1><p>" + e.getLocalizedMessage() + "</p>", "text/html", "UTF-8");
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
private String colorToCSS(int color) {
return String.format(
"rgb(%d, %d, %d)",
Color.red(color),
Color.green(color),
Color.blue(color)); // on API 29, WebView doesn't load with hex colors
}
}

View file

@ -0,0 +1,94 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package io.github.muntashirakon.music.activities
import android.graphics.Color
import android.os.Bundle
import android.view.MenuItem
import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark
import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import io.github.muntashirakon.music.activities.base.AbsThemeActivity
import io.github.muntashirakon.music.databinding.ActivityLicenseBinding
import io.github.muntashirakon.music.extensions.accentColor
import io.github.muntashirakon.music.extensions.drawAboveSystemBars
import io.github.muntashirakon.music.extensions.surfaceColor
import java.io.BufferedReader
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
/** Created by hemanths on 2019-09-27. */
class LicenseActivity : AbsThemeActivity() {
private lateinit var binding: ActivityLicenseBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLicenseBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
try {
val buf = StringBuilder()
val json = assets.open("license.html")
BufferedReader(InputStreamReader(json, StandardCharsets.UTF_8)).use { br ->
var str: String?
while (br.readLine().also { str = it } != null) {
buf.append(str)
}
}
// Inject color values for WebView body background and links
val isDark = isWindowBackgroundDark(this)
val backgroundColor = colorToCSS(
surfaceColor(Color.parseColor(if (isDark) "#424242" else "#ffffff"))
)
val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000"))
val changeLog = buf.toString()
.replace(
"{style-placeholder}", String.format(
"body { background-color: %s; color: %s; }", backgroundColor, contentColor
)
)
.replace("{link-color}", colorToCSS(accentColor()))
.replace(
"{link-color-active}",
colorToCSS(
lightenColor(accentColor())
)
)
binding.license.loadData(changeLog, "text/html", "UTF-8")
} catch (e: Throwable) {
binding.license.loadData(
"<h1>Unable to load</h1><p>" + e.localizedMessage + "</p>", "text/html", "UTF-8"
)
}
binding.license.drawAboveSystemBars()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
private fun colorToCSS(color: Int): String {
return String.format(
"rgb(%d, %d, %d)",
Color.red(color),
Color.green(color),
Color.blue(color)
) // on API 29, WebView doesn't load with hex colors
}
}

View file

@ -15,40 +15,38 @@
package io.github.muntashirakon.music.activities package io.github.muntashirakon.music.activities
import android.app.KeyguardManager import android.app.KeyguardManager
import android.content.Context
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.view.WindowManager import android.view.WindowManager
import androidx.core.view.ViewCompat import androidx.core.content.getSystemService
import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity
import io.github.muntashirakon.music.databinding.ActivityLockScreenBinding
import io.github.muntashirakon.music.extensions.hideStatusBar
import io.github.muntashirakon.music.extensions.setTaskDescriptionColorAuto
import io.github.muntashirakon.music.extensions.whichFragment import io.github.muntashirakon.music.extensions.whichFragment
import io.github.muntashirakon.music.fragments.player.lockscreen.LockScreenControlsFragment import io.github.muntashirakon.music.fragments.player.lockscreen.LockScreenControlsFragment
import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
import io.github.muntashirakon.music.glide.SongGlideRequest
import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import com.r0adkll.slidr.Slidr import com.r0adkll.slidr.Slidr
import com.r0adkll.slidr.model.SlidrConfig import com.r0adkll.slidr.model.SlidrConfig
import com.r0adkll.slidr.model.SlidrListener import com.r0adkll.slidr.model.SlidrListener
import com.r0adkll.slidr.model.SlidrPosition import com.r0adkll.slidr.model.SlidrPosition
import kotlinx.android.synthetic.main.activity_lock_screen.*
class LockScreenActivity : AbsMusicServiceActivity() { class LockScreenActivity : AbsMusicServiceActivity() {
private lateinit var binding: ActivityLockScreenBinding
private var fragment: LockScreenControlsFragment? = null private var fragment: LockScreenControlsFragment? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
lockScreenInit() lockScreenInit()
setContentView(R.layout.activity_lock_screen) binding = ActivityLockScreenBinding.inflate(layoutInflater)
setContentView(binding.root)
hideStatusBar() hideStatusBar()
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
setLightNavigationBar(true)
val config = SlidrConfig.Builder().listener(object : SlidrListener { val config = SlidrConfig.Builder().listener(object : SlidrListener {
override fun onSlideStateChanged(state: Int) { override fun onSlideStateChanged(state: Int) {
@ -61,10 +59,10 @@ class LockScreenActivity : AbsMusicServiceActivity() {
} }
override fun onSlideClosed(): Boolean { override fun onSlideClosed(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (VersionUtils.hasOreo()) {
val keyguardManager = val keyguardManager =
getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager getSystemService<KeyguardManager>()
keyguardManager.requestDismissKeyguard(this@LockScreenActivity, null) keyguardManager?.requestDismissKeyguard(this@LockScreenActivity, null)
} }
finish() finish()
return true return true
@ -75,18 +73,19 @@ class LockScreenActivity : AbsMusicServiceActivity() {
fragment = whichFragment<LockScreenControlsFragment>(R.id.playback_controls_fragment) fragment = whichFragment<LockScreenControlsFragment>(R.id.playback_controls_fragment)
findViewById<View>(R.id.slide).apply { binding.slide.apply {
translationY = 100f translationY = 100f
alpha = 0f alpha = 0f
ViewCompat.animate(this).translationY(0f).alpha(1f).setDuration(1500).start() animate().translationY(0f).alpha(1f).setDuration(1500).start()
} }
} }
@Suppress("Deprecation")
private fun lockScreenInit() { private fun lockScreenInit() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { if (VersionUtils.hasOreoMR1()) {
setShowWhenLocked(true) setShowWhenLocked(true)
val keyguardManager: KeyguardManager = getSystemService(KeyguardManager::class.java) val keyguardManager = getSystemService<KeyguardManager>()
keyguardManager.requestDismissKeyguard(this, null) keyguardManager?.requestDismissKeyguard(this, null)
} else { } else {
this.window.addFlags( this.window.addFlags(
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
@ -107,9 +106,12 @@ class LockScreenActivity : AbsMusicServiceActivity() {
private fun updateSongs() { private fun updateSongs() {
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
SongGlideRequest.Builder.from(Glide.with(this), song).checkIgnoreMediaStore(this) GlideApp.with(this)
.generatePalette(this).build().dontAnimate() .asBitmapPalette()
.into(object : RetroMusicColoredTarget(image) { .songCoverOptions(song)
.load(RetroGlideExtension.getSongModel(song))
.dontAnimate()
.into(object : RetroMusicColoredTarget(binding.image) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
fragment?.setColor(colors) fragment?.setColor(colors)
} }

View file

@ -1,156 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package io.github.muntashirakon.music.activities
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.WindowManager
import androidx.core.view.ViewCompat
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity
import io.github.muntashirakon.music.extensions.surfaceColor
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.helper.MusicProgressViewUpdateHelper
import io.github.muntashirakon.music.lyrics.LrcView
import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.LyricUtil
import io.github.muntashirakon.music.util.RetroUtil
import com.google.android.material.color.MaterialColors
import com.google.android.material.transition.platform.MaterialArcMotion
import com.google.android.material.transition.platform.MaterialContainerTransform
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
import kotlinx.android.synthetic.main.activity_lyrics.*
class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper.Callback {
private lateinit var updateHelper: MusicProgressViewUpdateHelper
private lateinit var song: Song
private val googleSearchLrcUrl: String
get() {
var baseUrl = "http://www.google.com/search?"
var query = song.title + "+" + song.artistName
query = "q=" + query.replace(" ", "+") + " .lrc"
baseUrl += query
return baseUrl
}
private fun buildContainerTransform(): MaterialContainerTransform {
val transform = MaterialContainerTransform()
transform.setAllContainerColors(
MaterialColors.getColor(findViewById(R.id.container), R.attr.colorSurface)
)
transform.addTarget(R.id.container)
transform.duration = 300
return transform
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_lyrics)
ViewCompat.setTransitionName(container, "lyrics")
setStatusbarColorAuto()
setTaskDescriptionColorAuto()
setNavigationbarColorAuto()
setupWakelock()
toolbar.setBackgroundColor(surfaceColor())
ToolbarContentTintHelper.colorBackButton(toolbar)
setSupportActionBar(toolbar)
updateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
setupLyricsView()
}
private fun setupLyricsView() {
lyricsView.apply {
setCurrentColor(ThemeStore.accentColor(context))
setTimeTextColor(ThemeStore.accentColor(context))
setTimelineColor(ThemeStore.accentColor(context))
setTimelineTextColor(ThemeStore.accentColor(context))
setDraggable(true, LrcView.OnPlayClickListener {
MusicPlayerRemote.seekTo(it.toInt())
return@OnPlayClickListener true
})
}
}
override fun onResume() {
super.onResume()
updateHelper.start()
}
override fun onPause() {
super.onPause()
updateHelper.stop()
}
override fun onUpdateProgressViews(progress: Int, total: Int) {
lyricsView.updateTime(progress.toLong())
}
private fun loadLRCLyrics() {
lyricsView.setLabel("Empty")
val song = MusicPlayerRemote.currentSong
if (LyricUtil.isLrcOriginalFileExist(song.data)) {
lyricsView.loadLrc(LyricUtil.getLocalLyricOriginalFile(song.data))
} else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
lyricsView.loadLrc(LyricUtil.getLocalLyricFile(song.title, song.artistName))
}
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
updateTitleSong()
loadLRCLyrics()
}
override fun onServiceConnected() {
super.onServiceConnected()
updateTitleSong()
loadLRCLyrics()
}
private fun updateTitleSong() {
song = MusicPlayerRemote.currentSong
toolbar.title = song.title
toolbar.subtitle = song.artistName
}
private fun setupWakelock() {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_search, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
finish()
return true
}
if (item.itemId == R.id.action_search) {
RetroUtil.openUrl(this, googleSearchLrcUrl)
}
return super.onOptionsItemSelected(item)
}
}

View file

@ -20,40 +20,16 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import android.view.View
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.ui.NavigationUI import androidx.navigation.contains
import io.github.muntashirakon.music.ADAPTIVE_COLOR_APP import androidx.navigation.ui.setupWithNavController
import io.github.muntashirakon.music.ALBUM_COVER_STYLE import io.github.muntashirakon.music.*
import io.github.muntashirakon.music.ALBUM_COVER_TRANSFORM
import io.github.muntashirakon.music.BANNER_IMAGE_PATH
import io.github.muntashirakon.music.BLACK_THEME
import io.github.muntashirakon.music.CAROUSEL_EFFECT
import io.github.muntashirakon.music.CIRCULAR_ALBUM_ART
import io.github.muntashirakon.music.DESATURATED_COLOR
import io.github.muntashirakon.music.EXTRA_SONG_INFO
import io.github.muntashirakon.music.GENERAL_THEME
import io.github.muntashirakon.music.HOME_ARTIST_GRID_STYLE
import io.github.muntashirakon.music.KEEP_SCREEN_ON
import io.github.muntashirakon.music.LANGUAGE_NAME
import io.github.muntashirakon.music.LIBRARY_CATEGORIES
import io.github.muntashirakon.music.NOW_PLAYING_SCREEN_ID
import io.github.muntashirakon.music.PROFILE_IMAGE_PATH
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.ROUND_CORNERS
import io.github.muntashirakon.music.TAB_TEXT_MODE
import io.github.muntashirakon.music.TOGGLE_ADD_CONTROLS
import io.github.muntashirakon.music.TOGGLE_FULL_SCREEN
import io.github.muntashirakon.music.TOGGLE_GENRE
import io.github.muntashirakon.music.TOGGLE_HOME_BANNER
import io.github.muntashirakon.music.TOGGLE_SEPARATE_LINE
import io.github.muntashirakon.music.TOGGLE_VOLUME
import io.github.muntashirakon.music.USER_NAME
import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity
import io.github.muntashirakon.music.extensions.extra import io.github.muntashirakon.music.databinding.SlidingMusicPanelLayoutBinding
import io.github.muntashirakon.music.extensions.findNavController import io.github.muntashirakon.music.extensions.*
import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.helper.SearchQueryHelper.getSongs import io.github.muntashirakon.music.helper.SearchQueryHelper.getSongs
import io.github.muntashirakon.music.interfaces.IScrollHelper
import io.github.muntashirakon.music.model.CategoryInfo import io.github.muntashirakon.music.model.CategoryInfo
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.repository.PlaylistSongsLoader import io.github.muntashirakon.music.repository.PlaylistSongsLoader
@ -69,16 +45,12 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
const val EXPAND_PANEL = "expand_panel" const val EXPAND_PANEL = "expand_panel"
} }
override fun createContentView(): View { override fun createContentView(): SlidingMusicPanelLayoutBinding {
return wrapSlidingMusicPanel() return wrapSlidingMusicPanel()
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setLightNavigationBar(true)
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
hideStatusBar() hideStatusBar()
updateTabs() updateTabs()
@ -87,6 +59,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
if (!hasPermissions()) { if (!hasPermissions()) {
findNavController(R.id.fragment_container).navigate(R.id.permissionFragment) findNavController(R.id.fragment_container).navigate(R.id.permissionFragment)
} }
WhatsNewFragment.showChangeLog(this)
} }
private fun setupNavigationController() { private fun setupNavigationController() {
@ -96,23 +69,75 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
val categoryInfo: CategoryInfo = PreferenceUtil.libraryCategory.first { it.visible } val categoryInfo: CategoryInfo = PreferenceUtil.libraryCategory.first { it.visible }
if (categoryInfo.visible) { if (categoryInfo.visible) {
navGraph.startDestination = categoryInfo.category.id if (!navGraph.contains(PreferenceUtil.lastTab)) PreferenceUtil.lastTab =
categoryInfo.category.id
navGraph.setStartDestination(
if (PreferenceUtil.rememberLastTab) {
PreferenceUtil.lastTab.let {
if (it == 0) {
categoryInfo.category.id
} else {
it
}
}
} else categoryInfo.category.id
)
} }
navController.graph = navGraph navController.graph = navGraph
NavigationUI.setupWithNavController(getBottomNavigationView(), navController) bottomNavigationView.setupWithNavController(navController)
// Scroll Fragment to top
bottomNavigationView.setOnItemReselectedListener {
currentFragment(R.id.fragment_container).apply {
if (this is IScrollHelper) {
scrollToTop()
}
}
}
navController.addOnDestinationChangedListener { _, destination, _ ->
if (destination.id == navGraph.startDestinationId) {
currentFragment(R.id.fragment_container)?.enterTransition = null
}
when (destination.id) {
R.id.action_home, R.id.action_song, R.id.action_album, R.id.action_artist, R.id.action_folder, R.id.action_playlist, R.id.action_genre, R.id.action_search -> {
// Save the last tab
if (PreferenceUtil.rememberLastTab) {
saveTab(destination.id)
}
// Show Bottom Navigation Bar
setBottomNavVisibility(visible = true, animate = true)
}
R.id.playing_queue_fragment -> {
setBottomNavVisibility(visible = false, hideBottomSheet = true)
}
else -> setBottomNavVisibility(
visible = false,
animate = true
) // Hide Bottom Navigation Bar
}
}
}
private fun saveTab(id: Int) {
PreferenceUtil.lastTab = id
} }
override fun onSupportNavigateUp(): Boolean = override fun onSupportNavigateUp(): Boolean =
findNavController(R.id.fragment_container).navigateUp() findNavController(R.id.fragment_container).navigateUp()
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
val expand = intent?.extra<Boolean>(EXPAND_PANEL)?.value ?: false
if (expand && PreferenceUtil.isExpandPanel) {
fromNotification = true
slidingPanel.bringToFront()
expandPanel()
intent?.removeExtra(EXPAND_PANEL)
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
PreferenceUtil.registerOnSharedPreferenceChangedListener(this) PreferenceUtil.registerOnSharedPreferenceChangedListener(this)
val expand = extra<Boolean>(EXPAND_PANEL).value ?: false
if (expand && PreferenceUtil.isExpandPanel) {
expandPanel()
intent.removeExtra(EXPAND_PANEL)
}
} }
override fun onDestroy() { override fun onDestroy() {
@ -121,7 +146,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == GENERAL_THEME || key == BLACK_THEME || key == ADAPTIVE_COLOR_APP || key == USER_NAME || key == TOGGLE_FULL_SCREEN || key == TOGGLE_VOLUME || key == ROUND_CORNERS || key == CAROUSEL_EFFECT || key == NOW_PLAYING_SCREEN_ID || key == TOGGLE_GENRE || key == BANNER_IMAGE_PATH || key == PROFILE_IMAGE_PATH || key == CIRCULAR_ALBUM_ART || key == KEEP_SCREEN_ON || key == TOGGLE_SEPARATE_LINE || key == TOGGLE_HOME_BANNER || key == TOGGLE_ADD_CONTROLS || key == ALBUM_COVER_STYLE || key == HOME_ARTIST_GRID_STYLE || key == ALBUM_COVER_TRANSFORM || key == DESATURATED_COLOR || key == EXTRA_SONG_INFO || key == TAB_TEXT_MODE || key == LANGUAGE_NAME || key == LIBRARY_CATEGORIES) { if (key == GENERAL_THEME || key == MATERIAL_YOU || key == WALLPAPER_ACCENT || key == BLACK_THEME || key == ADAPTIVE_COLOR_APP || key == USER_NAME || key == TOGGLE_FULL_SCREEN || key == TOGGLE_VOLUME || key == ROUND_CORNERS || key == CAROUSEL_EFFECT || key == NOW_PLAYING_SCREEN_ID || key == TOGGLE_GENRE || key == BANNER_IMAGE_PATH || key == PROFILE_IMAGE_PATH || key == CIRCULAR_ALBUM_ART || key == KEEP_SCREEN_ON || key == TOGGLE_SEPARATE_LINE || key == TOGGLE_HOME_BANNER || key == TOGGLE_ADD_CONTROLS || key == ALBUM_COVER_STYLE || key == HOME_ARTIST_GRID_STYLE || key == ALBUM_COVER_TRANSFORM || key == DESATURATED_COLOR || key == EXTRA_SONG_INFO || key == TAB_TEXT_MODE || key == LANGUAGE_NAME || key == LIBRARY_CATEGORIES || key == CUSTOM_FONT || key == APPBAR_MODE || key == CIRCLE_PLAY_BUTTON || key == SWIPE_DOWN_DISMISS) {
postRecreate() postRecreate()
} }
} }
@ -149,7 +174,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
handled = true handled = true
} }
if (uri != null && uri.toString().isNotEmpty()) { if (uri != null && uri.toString().isNotEmpty()) {
MusicPlayerRemote.playFromUri(uri) MusicPlayerRemote.playFromUri(this@MainActivity, uri)
handled = true handled = true
} else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) { } else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) {
val id = parseLongFromIntent(intent, "playlistId", "playlist") val id = parseLongFromIntent(intent, "playlistId", "playlist")

View file

@ -14,42 +14,61 @@
*/ */
package io.github.muntashirakon.music.activities package io.github.muntashirakon.music.activities
import android.Manifest
import android.Manifest.permission.BLUETOOTH_CONNECT
import android.content.Intent import android.content.Intent
import android.net.Uri import android.content.pm.PackageManager
import android.content.res.ColorStateList
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import androidx.core.text.HtmlCompat import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.net.toUri
import androidx.core.text.parseAsHtml
import androidx.core.view.isVisible
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity
import io.github.muntashirakon.music.extensions.accentBackgroundColor import io.github.muntashirakon.music.databinding.ActivityPermissionBinding
import io.github.muntashirakon.music.extensions.show import io.github.muntashirakon.music.extensions.*
import io.github.muntashirakon.music.util.RingtoneManager
import kotlinx.android.synthetic.main.activity_permission.*
class PermissionActivity : AbsMusicServiceActivity() { class PermissionActivity : AbsMusicServiceActivity() {
private lateinit var binding: ActivityPermissionBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView((R.layout.activity_permission)) binding = ActivityPermissionBinding.inflate(layoutInflater)
setStatusbarColorAuto() setContentView(binding.root)
setNavigationbarColorAuto() setStatusBarColorAuto()
setLightNavigationBar(true)
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
setupTitle() setupTitle()
storagePermission.setButtonClick { binding.storagePermission.setButtonClick {
requestPermissions() requestPermissions()
} }
if (VersionUtils.hasMarshmallow()) audioPermission.show() if (VersionUtils.hasMarshmallow()) {
audioPermission.setButtonClick { binding.audioPermission.show()
if (RingtoneManager.requiresDialog(this@PermissionActivity)) { binding.audioPermission.setButtonClick {
if (!hasAudioPermission()) {
val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS) val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
intent.data = Uri.parse("package:" + applicationContext.packageName) intent.data = ("package:" + applicationContext.packageName).toUri()
startActivity(intent) startActivity(intent)
} }
} }
finish.accentBackgroundColor() }
finish.setOnClickListener {
if (VersionUtils.hasS()) {
binding.bluetoothPermission.show()
binding.bluetoothPermission.setButtonClick {
ActivityCompat.requestPermissions(this,
arrayOf(BLUETOOTH_CONNECT),
PERMISSION_REQUEST)
}
}
binding.finish.accentBackgroundColor()
binding.finish.setOnClickListener {
if (hasPermissions()) { if (hasPermissions()) {
startActivity( startActivity(
Intent(this, MainActivity::class.java).addFlags( Intent(this, MainActivity::class.java).addFlags(
@ -63,10 +82,55 @@ class PermissionActivity : AbsMusicServiceActivity() {
} }
private fun setupTitle() { private fun setupTitle() {
val appName = HtmlCompat.fromHtml( val appName =
"Hello there! <br>Welcome to <b>Metro</b>", getString(R.string.message_welcome,
HtmlCompat.FROM_HTML_MODE_COMPACT "<b>Metro</b>")
) .parseAsHtml()
appNameText.text = appName binding.appNameText.text = appName
}
override fun onResume() {
super.onResume()
binding.finish.isEnabled = hasStoragePermission()
if (hasStoragePermission()) {
binding.storagePermission.checkImage.isVisible = true
binding.storagePermission.checkImage.imageTintList =
ColorStateList.valueOf(accentColor())
}
if (VersionUtils.hasMarshmallow()) {
if (hasAudioPermission()) {
binding.audioPermission.checkImage.isVisible = true
binding.audioPermission.checkImage.imageTintList =
ColorStateList.valueOf(accentColor())
}
}
if (VersionUtils.hasS()) {
if (hasBluetoothPermission()) {
binding.bluetoothPermission.checkImage.isVisible = true
binding.bluetoothPermission.checkImage.imageTintList =
ColorStateList.valueOf(accentColor())
}
}
}
private fun hasStoragePermission(): Boolean {
return ActivityCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
}
@RequiresApi(Build.VERSION_CODES.S)
private fun hasBluetoothPermission(): Boolean {
return ActivityCompat.checkSelfPermission(this,
BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED
}
@RequiresApi(Build.VERSION_CODES.M)
private fun hasAudioPermission(): Boolean {
return Settings.System.canWrite(this)
}
override fun onBackPressed() {
super.onBackPressed()
finishAffinity()
} }
} }

View file

@ -1,202 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package io.github.muntashirakon.music.activities
import android.content.res.ColorStateList
import android.os.Bundle
import android.view.MenuItem
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity
import io.github.muntashirakon.music.adapter.song.PlayingQueueAdapter
import io.github.muntashirakon.music.extensions.accentColor
import io.github.muntashirakon.music.extensions.surfaceColor
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.ThemedFastScroller
import kotlinx.android.synthetic.main.activity_playing_queue.*
open class PlayingQueueActivity : AbsMusicServiceActivity() {
private var wrappedAdapter: RecyclerView.Adapter<*>? = null
private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
private var playingQueueAdapter: PlayingQueueAdapter? = null
private lateinit var linearLayoutManager: LinearLayoutManager
private fun getUpNextAndQueueTime(): String {
val duration = MusicPlayerRemote.getQueueDurationMillis(MusicPlayerRemote.position)
return MusicUtil.buildInfoString(
resources.getString(R.string.up_next),
MusicUtil.getReadableDurationString(duration)
)
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_playing_queue)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setupToolbar()
setUpRecyclerView()
clearQueue.setOnClickListener {
MusicPlayerRemote.clearQueue()
}
checkForPadding()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun setUpRecyclerView() {
recyclerViewTouchActionGuardManager = RecyclerViewTouchActionGuardManager()
recyclerViewDragDropManager = RecyclerViewDragDropManager()
recyclerViewSwipeManager = RecyclerViewSwipeManager()
val animator = DraggableItemAnimator()
animator.supportsChangeAnimations = false
playingQueueAdapter = PlayingQueueAdapter(
this,
MusicPlayerRemote.playingQueue.toMutableList(),
MusicPlayerRemote.position,
R.layout.item_queue
)
wrappedAdapter = recyclerViewDragDropManager?.createWrappedAdapter(playingQueueAdapter!!)
wrappedAdapter = wrappedAdapter?.let { recyclerViewSwipeManager?.createWrappedAdapter(it) }
linearLayoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = linearLayoutManager
recyclerView.adapter = wrappedAdapter
recyclerView.itemAnimator = animator
recyclerViewTouchActionGuardManager?.attachRecyclerView(recyclerView)
recyclerViewDragDropManager?.attachRecyclerView(recyclerView)
recyclerViewSwipeManager?.attachRecyclerView(recyclerView)
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
clearQueue.shrink()
} else if (dy < 0) {
clearQueue.extend()
}
}
})
ThemedFastScroller.create(recyclerView)
}
private fun checkForPadding() {
}
override fun onQueueChanged() {
if (MusicPlayerRemote.playingQueue.isEmpty()) {
finish()
return
}
checkForPadding()
updateQueue()
updateCurrentSong()
}
override fun onMediaStoreChanged() {
updateQueue()
updateCurrentSong()
}
private fun updateCurrentSong() {
toolbar.subtitle = getUpNextAndQueueTime()
}
override fun onPlayingMetaChanged() {
updateQueuePosition()
}
private fun updateQueuePosition() {
playingQueueAdapter?.setCurrent(MusicPlayerRemote.position)
resetToCurrentPosition()
toolbar.subtitle = getUpNextAndQueueTime()
}
private fun updateQueue() {
playingQueueAdapter?.swapDataSet(MusicPlayerRemote.playingQueue, MusicPlayerRemote.position)
resetToCurrentPosition()
}
private fun resetToCurrentPosition() {
recyclerView.stopScroll()
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
}
override fun onPause() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.cancelDrag()
}
super.onPause()
}
override fun onDestroy() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.release()
recyclerViewDragDropManager = null
}
if (recyclerViewSwipeManager != null) {
recyclerViewSwipeManager?.release()
recyclerViewSwipeManager = null
}
if (wrappedAdapter != null) {
WrapperAdapterUtils.releaseAll(wrappedAdapter)
wrappedAdapter = null
}
playingQueueAdapter = null
super.onDestroy()
}
private fun setupToolbar() {
toolbar.subtitle = getUpNextAndQueueTime()
toolbar.setBackgroundColor(surfaceColor())
setSupportActionBar(toolbar)
clearQueue.backgroundTintList = ColorStateList.valueOf(accentColor())
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor()))
).apply {
clearQueue.setTextColor(this)
clearQueue.iconTint = this
}
}
}

View file

@ -14,37 +14,44 @@
*/ */
package io.github.muntashirakon.music.activities package io.github.muntashirakon.music.activities
import android.Manifest.permission.BLUETOOTH_CONNECT
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import com.afollestad.materialdialogs.color.ColorChooserDialog
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.base.AbsBaseActivity import io.github.muntashirakon.music.activities.base.AbsBaseActivity
import io.github.muntashirakon.music.appshortcuts.DynamicShortcutManager import io.github.muntashirakon.music.appshortcuts.DynamicShortcutManager
import io.github.muntashirakon.music.extensions.applyToolbar import io.github.muntashirakon.music.databinding.ActivitySettingsBinding
import io.github.muntashirakon.music.extensions.findNavController import io.github.muntashirakon.music.extensions.*
import kotlinx.android.synthetic.main.activity_settings.* import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.color.ColorCallback
class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback { class SettingsActivity : AbsBaseActivity(), ColorCallback, OnThemeChangedListener {
private lateinit var binding: ActivitySettingsBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar() val mSavedInstanceState = extra<Bundle>(TAG).value ?: savedInstanceState
super.onCreate(savedInstanceState) super.onCreate(mSavedInstanceState)
setContentView(R.layout.activity_settings) binding = ActivitySettingsBinding.inflate(layoutInflater)
setStatusbarColorAuto() setContentView(binding.root)
setNavigationbarColorAuto()
setLightNavigationBar(true)
setupToolbar() setupToolbar()
setPermissionDeniedMessage(getString(R.string.permission_bluetooth_denied))
}
override fun onResume() {
super.onResume()
setNavigationBarColorPreOreo(surfaceColor())
} }
private fun setupToolbar() { private fun setupToolbar() {
setTitle(R.string.action_settings) applyToolbar(binding.toolbar)
applyToolbar(toolbar)
val navController: NavController = findNavController(R.id.contentFrame) val navController: NavController = findNavController(R.id.contentFrame)
navController.addOnDestinationChangedListener { _, _, _ -> navController.addOnDestinationChangedListener { _, _, _ ->
toolbar.title = navController.currentDestination?.let { getStringFromDestination(it) } binding.collapsingToolbarLayout.title =
navController.currentDestination?.let { getStringFromDestination(it) }
} }
} }
@ -59,6 +66,7 @@ class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback {
R.id.personalizeSettingsFragment -> R.string.personalize R.id.personalizeSettingsFragment -> R.string.personalize
R.id.themeSettingsFragment -> R.string.general_settings_title R.id.themeSettingsFragment -> R.string.general_settings_title
R.id.aboutActivity -> R.string.action_about R.id.aboutActivity -> R.string.action_about
R.id.backup_fragment -> R.string.backup_restore_title
else -> R.id.action_settings else -> R.id.action_settings
} }
return getString(idRes) return getString(idRes)
@ -68,24 +76,47 @@ class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback {
return findNavController(R.id.contentFrame).navigateUp() || super.onSupportNavigateUp() return findNavController(R.id.contentFrame).navigateUp() || super.onSupportNavigateUp()
} }
override fun onColorSelection(dialog: ColorChooserDialog, selectedColor: Int) {
when (dialog.title) {
R.string.accent_color -> {
ThemeStore.editTheme(this).accentColor(selectedColor).commit()
if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).updateDynamicShortcuts()
}
}
recreate()
}
override fun onColorChooserDismissed(dialog: ColorChooserDialog) {
}
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) { if (item.itemId == android.R.id.home) {
onBackPressed() onBackPressed()
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
override fun getPermissionsToRequest(): Array<String> {
return if (VersionUtils.hasS()) {
arrayOf(BLUETOOTH_CONNECT)
} else {
arrayOf()
}
}
override fun invoke(dialog: MaterialDialog, color: Int) {
ThemeStore.editTheme(this).accentColor(color).commit()
if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).updateDynamicShortcuts()
restart()
}
override fun onThemeValuesChanged() {
restart()
}
private fun restart() {
val savedInstanceState = Bundle().apply {
onSaveInstanceState(this)
}
finish()
val intent = Intent(this, this::class.java).putExtra(TAG, savedInstanceState)
startActivity(intent)
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
}
companion object {
val TAG: String = SettingsActivity::class.java.simpleName
}
}
interface OnThemeChangedListener {
fun onThemeValuesChanged()
} }

View file

@ -18,23 +18,24 @@ import android.content.res.ColorStateList
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore.Images.Media import android.provider.MediaStore.Images.Media
import android.view.MenuItem import android.view.MenuItem
import androidx.core.net.toUri
import androidx.core.view.drawToBitmap import androidx.core.view.drawToBitmap
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.base.AbsBaseActivity import io.github.muntashirakon.music.activities.base.AbsBaseActivity
import io.github.muntashirakon.music.databinding.ActivityShareInstagramBinding
import io.github.muntashirakon.music.extensions.accentColor
import io.github.muntashirakon.music.extensions.setLightStatusBar
import io.github.muntashirakon.music.extensions.setStatusBarColor
import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
import io.github.muntashirakon.music.glide.SongGlideRequest
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.Share import io.github.muntashirakon.music.util.Share
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_share_instagram.*
/** /**
* Created by hemanths on 2020-02-02. * Created by hemanths on 2020-02-02.
@ -42,6 +43,8 @@ import kotlinx.android.synthetic.main.activity_share_instagram.*
class ShareInstagramStory : AbsBaseActivity() { class ShareInstagramStory : AbsBaseActivity() {
private lateinit var binding: ActivityShareInstagramBinding
companion object { companion object {
const val EXTRA_SONG = "extra_song" const val EXTRA_SONG = "extra_song"
} }
@ -55,61 +58,60 @@ class ShareInstagramStory : AbsBaseActivity() {
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_share_instagram) binding = ActivityShareInstagramBinding.inflate(layoutInflater)
setStatusbarColor(Color.TRANSPARENT) setContentView(binding.root)
setNavigationbarColor(Color.BLACK) setStatusBarColor(Color.TRANSPARENT)
toolbar.setBackgroundColor(Color.TRANSPARENT) binding.toolbar.setBackgroundColor(Color.TRANSPARENT)
setSupportActionBar(toolbar) setSupportActionBar(binding.toolbar)
val song = intent.extras?.getParcelable<Song>(EXTRA_SONG) val song = intent.extras?.getParcelable<Song>(EXTRA_SONG)
song?.let { songFinal -> song?.let { songFinal ->
SongGlideRequest.Builder.from(Glide.with(this), songFinal) GlideApp.with(this)
.checkIgnoreMediaStore(this@ShareInstagramStory) .asBitmapPalette()
.generatePalette(this@ShareInstagramStory) .songCoverOptions(songFinal)
.build() .load(RetroGlideExtension.getSongModel(songFinal))
.into(object : RetroMusicColoredTarget(image) { .into(object : RetroMusicColoredTarget(binding.image) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
val isColorLight = ColorUtil.isColorLight(colors.backgroundColor) val isColorLight = ColorUtil.isColorLight(colors.backgroundColor)
setColors(isColorLight, colors.backgroundColor) setColors(isColorLight, colors.backgroundColor)
} }
}) })
shareTitle.text = songFinal.title binding.shareTitle.text = songFinal.title
shareText.text = songFinal.artistName binding.shareText.text = songFinal.artistName
shareButton.setOnClickListener { binding.shareButton.setOnClickListener {
val path: String = Media.insertImage( val path: String = Media.insertImage(
contentResolver, contentResolver,
mainContent.drawToBitmap(Bitmap.Config.ARGB_8888), binding.mainContent.drawToBitmap(Bitmap.Config.ARGB_8888),
"Design", null "Design", null
) )
val uri = Uri.parse(path)
Share.shareStoryToSocial( Share.shareStoryToSocial(
this@ShareInstagramStory, this@ShareInstagramStory,
uri path.toUri()
) )
} }
} }
shareButton.setTextColor( binding.shareButton.setTextColor(
MaterialValueHelper.getPrimaryTextColor( MaterialValueHelper.getPrimaryTextColor(
this, this,
ColorUtil.isColorLight(ThemeStore.accentColor(this)) ColorUtil.isColorLight(accentColor())
) )
) )
shareButton.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this)) binding.shareButton.backgroundTintList =
ColorStateList.valueOf(accentColor())
} }
private fun setColors(colorLight: Boolean, color: Int) { private fun setColors(colorLight: Boolean, color: Int) {
setLightStatusbar(colorLight) setLightStatusBar(colorLight)
toolbar.setTitleTextColor( binding.toolbar.setTitleTextColor(
MaterialValueHelper.getPrimaryTextColor( MaterialValueHelper.getPrimaryTextColor(
this@ShareInstagramStory, this@ShareInstagramStory,
colorLight colorLight
) )
) )
toolbar.navigationIcon?.setTintList( binding.toolbar.navigationIcon?.setTintList(
ColorStateList.valueOf( ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor( MaterialValueHelper.getPrimaryTextColor(
this@ShareInstagramStory, this@ShareInstagramStory,
@ -117,7 +119,7 @@ class ShareInstagramStory : AbsBaseActivity() {
) )
) )
) )
mainContent.background = binding.mainContent.background =
GradientDrawable( GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM, GradientDrawable.Orientation.TOP_BOTTOM,
intArrayOf(color, Color.BLACK) intArrayOf(color, Color.BLACK)

View file

@ -1,229 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package io.github.muntashirakon.music.activities
import android.app.Activity
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.text.TextUtils
import android.view.MenuItem
import android.widget.Toast
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import io.github.muntashirakon.music.Constants.USER_BANNER
import io.github.muntashirakon.music.Constants.USER_PROFILE
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.base.AbsBaseActivity
import io.github.muntashirakon.music.extensions.accentColor
import io.github.muntashirakon.music.extensions.applyToolbar
import io.github.muntashirakon.music.glide.ProfileBannerGlideRequest
import io.github.muntashirakon.music.glide.UserProfileGlideRequest
import io.github.muntashirakon.music.util.ImageUtil
import io.github.muntashirakon.music.util.PreferenceUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.github.dhaval2404.imagepicker.ImagePicker
import com.github.dhaval2404.imagepicker.constant.ImageProvider
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import kotlinx.android.synthetic.main.activity_user_info.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class UserInfoActivity : AbsBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_info)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
applyToolbar(toolbar)
nameContainer.accentColor()
name.setText(PreferenceUtil.userName)
userImage.setOnClickListener {
pickNewPhoto()
}
bannerImage.setOnClickListener {
selectBannerImage()
}
next.setOnClickListener {
val nameString = name.text.toString().trim { it <= ' ' }
if (TextUtils.isEmpty(nameString)) {
Toast.makeText(this, "Umm you're name can't be empty!", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
PreferenceUtil.userName = nameString
setResult(Activity.RESULT_OK)
finish()
}
val textColor =
MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor()))
next.backgroundTintList = ColorStateList.valueOf(accentColor())
next.iconTint = ColorStateList.valueOf(textColor)
next.setTextColor(textColor)
loadProfile()
}
private fun loadProfile() {
bannerImage?.let {
ProfileBannerGlideRequest.Builder.from(
Glide.with(this),
ProfileBannerGlideRequest.getBannerModel()
).build().into(it)
}
UserProfileGlideRequest.Builder.from(
Glide.with(this),
UserProfileGlideRequest.getUserModel()
).build().into(userImage)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
}
return super.onOptionsItemSelected(item)
}
private fun selectBannerImage() {
ImagePicker.with(this)
.compress(1440)
.provider(ImageProvider.GALLERY)
.crop(16f, 9f)
.start(PICK_BANNER_REQUEST)
}
private fun pickNewPhoto() {
ImagePicker.with(this)
.provider(ImageProvider.GALLERY)
.cropSquare()
.compress(1440)
.start(PICK_IMAGE_REQUEST)
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && requestCode == PICK_IMAGE_REQUEST) {
val fileUri = data?.data
fileUri?.let { setAndSaveUserImage(it) }
} else if (resultCode == Activity.RESULT_OK && requestCode == PICK_BANNER_REQUEST) {
val fileUri = data?.data
fileUri?.let { setAndSaveBannerImage(it) }
} else if (resultCode == ImagePicker.RESULT_ERROR) {
Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Task Cancelled", Toast.LENGTH_SHORT).show()
}
}
private fun setAndSaveBannerImage(fileUri: Uri) {
Glide.with(this)
.load(fileUri)
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(object : RequestListener<Any, Bitmap> {
override fun onException(
e: java.lang.Exception?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Bitmap?,
model: Any?,
target: Target<Bitmap>?,
isFromMemoryCache: Boolean,
isFirstResource: Boolean
): Boolean {
resource?.let { saveImage(it, USER_BANNER) }
return false
}
})
.into(bannerImage)
}
private fun saveImage(bitmap: Bitmap, fileName: String) {
CoroutineScope(Dispatchers.IO).launch {
val appDir = applicationContext.filesDir
val file = File(appDir, fileName)
var successful = false
try {
val os = BufferedOutputStream(FileOutputStream(file))
successful = ImageUtil.resizeBitmap(bitmap, 2048)
.compress(Bitmap.CompressFormat.WEBP, 100, os)
withContext(Dispatchers.IO) { os.close() }
} catch (e: IOException) {
e.printStackTrace()
}
if (successful) {
withContext(Dispatchers.Main) {
Toast.makeText(this@UserInfoActivity, "Updated", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun setAndSaveUserImage(fileUri: Uri) {
Glide.with(this)
.load(fileUri)
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(object : RequestListener<Any, Bitmap> {
override fun onException(
e: java.lang.Exception?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Bitmap?,
model: Any?,
target: Target<Bitmap>?,
isFromMemoryCache: Boolean,
isFirstResource: Boolean
): Boolean {
resource?.let { saveImage(it, USER_PROFILE) }
return false
}
})
.into(userImage)
}
companion object {
private const val PICK_IMAGE_REQUEST = 9002
private const val PICK_BANNER_REQUEST = 9004
}
}

View file

@ -1,111 +0,0 @@
package io.github.muntashirakon.music.activities;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.appthemehelper.util.ColorUtil;
import code.name.monkey.appthemehelper.util.MaterialValueHelper;
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper;
import io.github.muntashirakon.music.R;
import io.github.muntashirakon.music.activities.base.AbsBaseActivity;
import io.github.muntashirakon.music.util.PreferenceUtil;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
public class WhatsNewActivity extends AbsBaseActivity {
private static String colorToCSS(int color) {
return String.format(
Locale.getDefault(),
"rgba(%d, %d, %d, %d)",
Color.red(color),
Color.green(color),
Color.blue(color),
Color.alpha(color)); // on API 29, WebView doesn't load with hex colors
}
private static void setChangelogRead(@NonNull Context context) {
try {
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
int currentVersion = pInfo.versionCode;
PreferenceUtil.INSTANCE.setLastVersion(currentVersion);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
setDrawUnderStatusBar();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_whats_new);
setStatusbarColorAuto();
setNavigationbarColorAuto();
setTaskDescriptionColorAuto();
WebView webView = findViewById(R.id.webView);
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface));
toolbar.setNavigationOnClickListener(v -> onBackPressed());
ToolbarContentTintHelper.colorBackButton(toolbar);
try {
StringBuilder buf = new StringBuilder();
InputStream json = getAssets().open("retro-changelog.html");
BufferedReader in = new BufferedReader(new InputStreamReader(json, StandardCharsets.UTF_8));
String str;
while ((str = in.readLine()) != null) {
buf.append(str);
}
in.close();
// Inject color values for WebView body background and links
final boolean isDark = ATHUtil.INSTANCE.isWindowBackgroundDark(this);
final int accentColor = ThemeStore.Companion.accentColor(this);
final String backgroundColor =
colorToCSS(
ATHUtil.INSTANCE.resolveColor(
this, R.attr.colorSurface, Color.parseColor(isDark ? "#424242" : "#ffffff")));
final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000"));
final String textColor = colorToCSS(Color.parseColor(isDark ? "#60FFFFFF" : "#80000000"));
final String accentColorString = colorToCSS(ThemeStore.Companion.accentColor(this));
final String accentTextColor =
colorToCSS(
MaterialValueHelper.getPrimaryTextColor(
this, ColorUtil.INSTANCE.isColorLight(accentColor)));
final String changeLog =
buf.toString()
.replace(
"{style-placeholder}",
String.format(
"body { background-color: %s; color: %s; } li {color: %s;} .colorHeader {background-color: %s; color: %s;} .tag {color: %s;}",
backgroundColor,
contentColor,
textColor,
accentColorString,
accentTextColor,
accentColorString))
.replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this)))
.replace(
"{link-color-active}",
colorToCSS(
ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this))));
webView.loadData(changeLog, "text/html", "UTF-8");
} catch (Throwable e) {
webView.loadData(
"<h1>Unable to load</h1><p>" + e.getLocalizedMessage() + "</p>", "text/html", "UTF-8");
}
setChangelogRead(this);
}
}

View file

@ -0,0 +1,138 @@
package io.github.muntashirakon.music.activities
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.pm.PackageInfoCompat
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.FragmentActivity
import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark
import code.name.monkey.appthemehelper.util.ColorUtil.isColorLight
import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor
import code.name.monkey.appthemehelper.util.MaterialValueHelper.getPrimaryTextColor
import io.github.muntashirakon.music.BuildConfig
import io.github.muntashirakon.music.Constants
import io.github.muntashirakon.music.databinding.FragmentWhatsNewBinding
import io.github.muntashirakon.music.extensions.accentColor
import io.github.muntashirakon.music.extensions.openUrl
import io.github.muntashirakon.music.util.PreferenceUtil.lastVersion
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import java.nio.charset.StandardCharsets
import java.util.*
class WhatsNewFragment : BottomSheetDialogFragment() {
private var _binding: FragmentWhatsNewBinding? = null
val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentWhatsNewBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
try {
val buf = StringBuilder()
val stream= requireContext().assets.open("retro-changelog.html")
stream.reader(StandardCharsets.UTF_8).buffered().use { br ->
var str: String?
while (br.readLine().also { str = it } != null) {
buf.append(str)
}
}
// Inject color values for WebView body background and links
val isDark = isWindowBackgroundDark(requireContext())
val accentColor = accentColor()
binding.webView.setBackgroundColor(0)
val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000"))
val textColor = colorToCSS(Color.parseColor(if (isDark) "#60FFFFFF" else "#80000000"))
val accentColorString = colorToCSS(accentColor())
val cardBackgroundColor =
colorToCSS(Color.parseColor(if (isDark) "#353535" else "#ffffff"))
val accentTextColor = colorToCSS(
getPrimaryTextColor(
requireContext(), isColorLight(accentColor)
)
)
val changeLog = buf.toString()
.replace(
"{style-placeholder}",
"body { color: $contentColor; } li {color: $textColor;} h3 {color: $accentColorString;} .tag {background-color: $accentColorString; color: $accentTextColor; } div{background-color: $cardBackgroundColor;}"
)
.replace("{link-color}", colorToCSS(accentColor()))
.replace(
"{link-color-active}",
colorToCSS(
lightenColor(accentColor())
)
)
binding.webView.loadData(changeLog, "text/html", "UTF-8")
} catch (e: Throwable) {
binding.webView.loadData(
"<h1>Unable to load</h1><p>" + e.localizedMessage + "</p>", "text/html", "UTF-8"
)
}
setChangelogRead(requireContext())
binding.tgFab.setOnClickListener {
openUrl(Constants.TELEGRAM_CHANGE_LOG)
}
binding.tgFab.accentColor()
binding.tgFab.shrink()
binding.container.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int ->
val dy = scrollY - oldScrollY
if (dy > 0) {
binding.tgFab.shrink()
} else if (dy < 0) {
binding.tgFab.extend()
}
}
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
companion object {
const val TAG = "WhatsNewFragment"
private fun colorToCSS(color: Int): String {
return String.format(
Locale.getDefault(),
"rgba(%d, %d, %d, %d)",
Color.red(color),
Color.green(color),
Color.blue(color),
Color.alpha(color)
) // on API 29, WebView doesn't load with hex colors
}
private fun setChangelogRead(context: Context) {
try {
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
val currentVersion = PackageInfoCompat.getLongVersionCode(pInfo)
lastVersion = currentVersion
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
}
fun showChangeLog(activity: FragmentActivity) {
val pInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
val currentVersion = PackageInfoCompat.getLongVersionCode(pInfo)
if (currentVersion > lastVersion && !BuildConfig.DEBUG) {
val changelogBottomSheet = WhatsNewFragment()
changelogBottomSheet.show(activity.supportFragmentManager, TAG)
}
}
}
}

View file

@ -17,16 +17,21 @@ package io.github.muntashirakon.music.activities.base
import android.Manifest import android.Manifest
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Rect
import android.media.AudioManager import android.media.AudioManager
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import code.name.monkey.appthemehelper.ThemeStore import androidx.core.content.getSystemService
import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.extensions.accentColor
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
abstract class AbsBaseActivity : AbsThemeActivity() { abstract class AbsBaseActivity : AbsThemeActivity() {
@ -57,19 +62,12 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
permissionDeniedMessage = null permissionDeniedMessage = null
} }
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
if (!hasPermissions()) {
// requestPermissions()
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
val hasPermissions = hasPermissions() val hasPermissions = hasPermissions()
if (hasPermissions != hadPermissions) { if (hasPermissions != hadPermissions) {
hadPermissions = hasPermissions hadPermissions = hasPermissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (VersionUtils.hasMarshmallow()) {
onHasPermissionsChanged(hasPermissions) onHasPermissionsChanged(hasPermissions)
} }
} }
@ -92,26 +90,24 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
} }
protected open fun requestPermissions() { protected open fun requestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST)
requestPermissions(permissions, PERMISSION_REQUEST)
}
} }
protected fun hasPermissions(): Boolean { protected fun hasPermissions(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
for (permission in permissions) { for (permission in permissions) {
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(this,
permission) != PackageManager.PERMISSION_GRANTED
) {
return false return false
} }
} }
}
return true return true
} }
override fun onRequestPermissionsResult( override fun onRequestPermissionsResult(
requestCode: Int, requestCode: Int,
permissions: Array<String>, permissions: Array<String>,
grantResults: IntArray grantResults: IntArray,
) { ) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST) { if (requestCode == PERMISSION_REQUEST) {
@ -128,14 +124,31 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
Snackbar.LENGTH_INDEFINITE Snackbar.LENGTH_INDEFINITE
) )
.setAction(R.string.action_grant) { requestPermissions() } .setAction(R.string.action_grant) { requestPermissions() }
.setActionTextColor(ThemeStore.accentColor(this)).show() .setActionTextColor(accentColor()).show()
} else if (ActivityCompat.shouldShowRequestPermissionRationale(
this@AbsBaseActivity, Manifest.permission.BLUETOOTH_CONNECT
)
) {
// User has deny from permission dialog
Snackbar.make(
snackBarContainer,
R.string.permission_bluetooth_denied,
Snackbar.LENGTH_INDEFINITE
)
.setAction(R.string.action_grant) {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.BLUETOOTH_CONNECT),
PERMISSION_REQUEST)
}
.setActionTextColor(accentColor()).show()
} else { } else {
// User has deny permission and checked never show permission dialog so you can redirect to Application settings page // User has deny permission and checked never show permission dialog so you can redirect to Application settings page
Snackbar.make( Snackbar.make(
snackBarContainer, snackBarContainer,
permissionDeniedMessage!!, permissionDeniedMessage!!,
Snackbar.LENGTH_INDEFINITE Snackbar.LENGTH_INDEFINITE
).setAction(R.string.action_settings) { )
.setAction(R.string.action_settings) {
val intent = Intent() val intent = Intent()
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts( val uri = Uri.fromParts(
@ -145,7 +158,7 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
) )
intent.data = uri intent.data = uri
startActivity(intent) startActivity(intent)
}.setActionTextColor(ThemeStore.accentColor(this)).show() }.setActionTextColor(accentColor()).show()
} }
return return
} }
@ -158,4 +171,23 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
companion object { companion object {
const val PERMISSION_REQUEST = 100 const val PERMISSION_REQUEST = 100
} }
// this lets keyboard close when clicked in background
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
val v = currentFocus
if (v is EditText) {
val outRect = Rect()
v.getGlobalVisibleRect(outRect)
if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
v.clearFocus()
getSystemService<InputMethodManager>()?.hideSoftInputFromWindow(
v.windowToken,
0
)
}
}
}
return super.dispatchTouchEvent(event)
}
} }

View file

@ -15,26 +15,28 @@
package io.github.muntashirakon.music.activities.base package io.github.muntashirakon.music.activities.base
import android.Manifest import android.Manifest
import android.content.BroadcastReceiver import android.content.*
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.ServiceConnection
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.db.toPlayCount import io.github.muntashirakon.music.db.toPlayCount
import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.interfaces.IMusicServiceEventListener import io.github.muntashirakon.music.interfaces.IMusicServiceEventListener
import io.github.muntashirakon.music.repository.RealRepository import io.github.muntashirakon.music.repository.RealRepository
import io.github.muntashirakon.music.service.MusicService.* import io.github.muntashirakon.music.service.MusicService.Companion.FAVORITE_STATE_CHANGED
import java.lang.ref.WeakReference import io.github.muntashirakon.music.service.MusicService.Companion.MEDIA_STORE_CHANGED
import java.util.* import io.github.muntashirakon.music.service.MusicService.Companion.META_CHANGED
import io.github.muntashirakon.music.service.MusicService.Companion.PLAY_STATE_CHANGED
import io.github.muntashirakon.music.service.MusicService.Companion.QUEUE_CHANGED
import io.github.muntashirakon.music.service.MusicService.Companion.REPEAT_MODE_CHANGED
import io.github.muntashirakon.music.service.MusicService.Companion.SHUFFLE_MODE_CHANGED
import io.github.muntashirakon.music.util.PreferenceUtil
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import java.lang.ref.WeakReference
abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventListener { abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventListener {
@ -123,8 +125,11 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
if (entity != null) { if (entity != null) {
repository.updateHistorySong(MusicPlayerRemote.currentSong) repository.updateHistorySong(MusicPlayerRemote.currentSong)
} else { } else {
// Check whether pause history option is ON or OFF
if (!PreferenceUtil.pauseHistory) {
repository.addSongToHistory(MusicPlayerRemote.currentSong) repository.addSongToHistory(MusicPlayerRemote.currentSong)
} }
}
val songs = repository.checkSongExistInPlayCount(MusicPlayerRemote.currentSong.id) val songs = repository.checkSongExistInPlayCount(MusicPlayerRemote.currentSong.id)
if (songs.isNotEmpty()) { if (songs.isNotEmpty()) {
repository.updateSongInPlayCount(songs.first().apply { repository.updateSongInPlayCount(songs.first().apply {
@ -166,6 +171,12 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
} }
} }
override fun onFavoriteStateChanged() {
for (listener in mMusicServiceEventListeners) {
listener.onFavoriteStateChanged()
}
}
override fun onHasPermissionsChanged(hasPermissions: Boolean) { override fun onHasPermissionsChanged(hasPermissions: Boolean) {
super.onHasPermissionsChanged(hasPermissions) super.onHasPermissionsChanged(hasPermissions)
val intent = Intent(MEDIA_STORE_CHANGED) val intent = Intent(MEDIA_STORE_CHANGED)
@ -178,11 +189,11 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
} }
override fun getPermissionsToRequest(): Array<String> { override fun getPermissionsToRequest(): Array<String> {
return arrayOf( return mutableListOf(Manifest.permission.READ_EXTERNAL_STORAGE).apply {
Manifest.permission.READ_EXTERNAL_STORAGE, if (!VersionUtils.hasQ()) {
Manifest.permission.WRITE_EXTERNAL_STORAGE, add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
Manifest.permission.BLUETOOTH }
) }.toTypedArray()
} }
private class MusicStateReceiver(activity: AbsMusicServiceActivity) : BroadcastReceiver() { private class MusicStateReceiver(activity: AbsMusicServiceActivity) : BroadcastReceiver() {
@ -194,7 +205,8 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
val activity = reference.get() val activity = reference.get()
if (activity != null && action != null) { if (activity != null && action != null) {
when (action) { when (action) {
FAVORITE_STATE_CHANGED, META_CHANGED -> activity.onPlayingMetaChanged() FAVORITE_STATE_CHANGED -> activity.onFavoriteStateChanged()
META_CHANGED -> activity.onPlayingMetaChanged()
QUEUE_CHANGED -> activity.onQueueChanged() QUEUE_CHANGED -> activity.onQueueChanged()
PLAY_STATE_CHANGED -> activity.onPlayStateChanged() PLAY_STATE_CHANGED -> activity.onPlayStateChanged()
REPEAT_MODE_CHANGED -> activity.onRepeatModeChanged() REPEAT_MODE_CHANGED -> activity.onRepeatModeChanged()

View file

@ -14,27 +14,29 @@
*/ */
package io.github.muntashirakon.music.activities.base package io.github.muntashirakon.music.activities.base
import android.annotation.SuppressLint import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.view.animation.PathInterpolator
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.core.view.ViewCompat import androidx.core.animation.doOnEnd
import androidx.core.view.isVisible import androidx.core.view.*
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.commit import androidx.fragment.app.commit
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.appthemehelper.util.ColorUtil
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.RetroBottomSheetBehavior import io.github.muntashirakon.music.databinding.SlidingMusicPanelLayoutBinding
import io.github.muntashirakon.music.extensions.* import io.github.muntashirakon.music.extensions.*
import io.github.muntashirakon.music.fragments.LibraryViewModel import io.github.muntashirakon.music.fragments.LibraryViewModel
import io.github.muntashirakon.music.fragments.MiniPlayerFragment
import io.github.muntashirakon.music.fragments.NowPlayingScreen import io.github.muntashirakon.music.fragments.NowPlayingScreen
import io.github.muntashirakon.music.fragments.NowPlayingScreen.* import io.github.muntashirakon.music.fragments.NowPlayingScreen.*
import io.github.muntashirakon.music.fragments.base.AbsPlayerFragment import io.github.muntashirakon.music.fragments.base.AbsPlayerFragment
import io.github.muntashirakon.music.fragments.other.MiniPlayerFragment
import io.github.muntashirakon.music.fragments.player.adaptive.AdaptiveFragment import io.github.muntashirakon.music.fragments.player.adaptive.AdaptiveFragment
import io.github.muntashirakon.music.fragments.player.blur.BlurPlayerFragment import io.github.muntashirakon.music.fragments.player.blur.BlurPlayerFragment
import io.github.muntashirakon.music.fragments.player.card.CardFragment import io.github.muntashirakon.music.fragments.player.card.CardFragment
@ -47,58 +49,85 @@ import io.github.muntashirakon.music.fragments.player.flat.FlatPlayerFragment
import io.github.muntashirakon.music.fragments.player.full.FullPlayerFragment import io.github.muntashirakon.music.fragments.player.full.FullPlayerFragment
import io.github.muntashirakon.music.fragments.player.gradient.GradientPlayerFragment import io.github.muntashirakon.music.fragments.player.gradient.GradientPlayerFragment
import io.github.muntashirakon.music.fragments.player.material.MaterialFragment import io.github.muntashirakon.music.fragments.player.material.MaterialFragment
import io.github.muntashirakon.music.fragments.player.md3.MD3PlayerFragment
import io.github.muntashirakon.music.fragments.player.normal.PlayerFragment import io.github.muntashirakon.music.fragments.player.normal.PlayerFragment
import io.github.muntashirakon.music.fragments.player.peak.PeakPlayerFragment import io.github.muntashirakon.music.fragments.player.peek.PeekPlayerFragment
import io.github.muntashirakon.music.fragments.player.plain.PlainPlayerFragment import io.github.muntashirakon.music.fragments.player.plain.PlainPlayerFragment
import io.github.muntashirakon.music.fragments.player.simple.SimplePlayerFragment import io.github.muntashirakon.music.fragments.player.simple.SimplePlayerFragment
import io.github.muntashirakon.music.fragments.player.tiny.TinyPlayerFragment import io.github.muntashirakon.music.fragments.player.tiny.TinyPlayerFragment
import io.github.muntashirakon.music.fragments.queue.PlayingQueueFragment
import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.model.CategoryInfo import io.github.muntashirakon.music.model.CategoryInfo
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.views.BottomNavigationBarTinted import io.github.muntashirakon.music.util.ViewUtil
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.* import com.google.android.material.bottomsheet.BottomSheetBehavior.*
import kotlinx.android.synthetic.main.sliding_music_panel_layout.*
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
companion object { companion object {
val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName
} }
var fromNotification = false
private var windowInsets: WindowInsetsCompat? = null
protected val libraryViewModel by viewModel<LibraryViewModel>() protected val libraryViewModel by viewModel<LibraryViewModel>()
private lateinit var bottomSheetBehavior: RetroBottomSheetBehavior<FrameLayout> private lateinit var bottomSheetBehavior: BottomSheetBehavior<FrameLayout>
private var playerFragment: AbsPlayerFragment? = null private var playerFragment: AbsPlayerFragment? = null
private var miniPlayerFragment: MiniPlayerFragment? = null private var miniPlayerFragment: MiniPlayerFragment? = null
private var nowPlayingScreen: NowPlayingScreen? = null private var nowPlayingScreen: NowPlayingScreen? = null
private var navigationBarColor: Int = 0
private var taskColor: Int = 0 private var taskColor: Int = 0
private var lightStatusBar: Boolean = false
private var lightNavigationBar: Boolean = false
private var paletteColor: Int = Color.WHITE private var paletteColor: Int = Color.WHITE
protected abstract fun createContentView(): View private var navigationBarColor = 0
protected abstract fun createContentView(): SlidingMusicPanelLayoutBinding
private val panelState: Int private val panelState: Int
get() = bottomSheetBehavior.state get() = bottomSheetBehavior.state
private lateinit var binding: SlidingMusicPanelLayoutBinding
private var isInOneTabMode = false
private var navigationBarColorAnimator: ValueAnimator? = null
private val argbEvaluator: ArgbEvaluator = ArgbEvaluator()
private val bottomSheetCallbackList = object : BottomSheetCallback() { private val bottomSheetCallbackList = object : BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) { override fun onSlide(bottomSheet: View, slideOffset: Float) {
setMiniPlayerAlphaProgress(slideOffset) setMiniPlayerAlphaProgress(slideOffset)
dimBackground.show() navigationBarColorAnimator?.cancel()
dimBackground.alpha = slideOffset setNavigationBarColorPreOreo(
argbEvaluator.evaluate(
slideOffset,
surfaceColor(),
navigationBarColor
) as Int
)
} }
override fun onStateChanged(bottomSheet: View, newState: Int) { override fun onStateChanged(bottomSheet: View, newState: Int) {
when (newState) { when (newState) {
STATE_EXPANDED -> { STATE_EXPANDED -> {
onPanelExpanded() onPanelExpanded()
if (PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics) {
keepScreenOn(true)
}
} }
STATE_COLLAPSED -> { STATE_COLLAPSED -> {
onPanelCollapsed() onPanelCollapsed()
dimBackground.hide() if ((PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics) || !PreferenceUtil.isScreenOnEnabled) {
keepScreenOn(false)
}
}
STATE_SETTLING, STATE_DRAGGING -> {
if (fromNotification) {
binding.bottomNavigationView.bringToFront()
fromNotification = false
}
}
STATE_HIDDEN -> {
MusicPlayerRemote.clearQueue()
} }
else -> { else -> {
println("Do something") println("Do a flip")
} }
} }
} }
@ -108,23 +137,31 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(createContentView()) binding = createContentView()
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _, insets ->
windowInsets = insets
insets
}
chooseFragmentForTheme() chooseFragmentForTheme()
setupSlidingUpPanel() setupSlidingUpPanel()
setupBottomSheet() setupBottomSheet()
updateColor() updateColor()
if (!PreferenceUtil.materialYou) {
val themeColor = resolveColor(android.R.attr.windowBackground, Color.GRAY) binding.slidingPanel.backgroundTintList = ColorStateList.valueOf(darkAccentColor())
dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f)) bottomNavigationView.backgroundTintList = ColorStateList.valueOf(darkAccentColor())
dimBackground.setOnClickListener {
println("dimBackground")
collapsePanel()
} }
navigationBarColor = surfaceColor()
} }
private fun setupBottomSheet() { private fun setupBottomSheet() {
bottomSheetBehavior = from(slidingPanel) as RetroBottomSheetBehavior bottomSheetBehavior = from(binding.slidingPanel)
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallbackList) bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallbackList)
bottomSheetBehavior.isHideable = PreferenceUtil.swipeDownToDismiss
setMiniPlayerAlphaProgress(0F)
} }
override fun onResume() { override fun onResume() {
@ -142,55 +179,69 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallbackList) bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallbackList)
} }
@SuppressLint("InflateParams") protected fun wrapSlidingMusicPanel(): SlidingMusicPanelLayoutBinding {
protected fun wrapSlidingMusicPanel(): View { return SlidingMusicPanelLayoutBinding.inflate(layoutInflater)
val slidingMusicPanelLayout =
layoutInflater.inflate(R.layout.sliding_music_panel_layout, null)
val contentContainer: ViewGroup =
slidingMusicPanelLayout.findViewById(R.id.mainContentFrame)
layoutInflater.inflate(R.layout.activity_main_content, contentContainer)
return slidingMusicPanelLayout
} }
fun collapsePanel() { fun collapsePanel() {
bottomSheetBehavior.state = STATE_COLLAPSED bottomSheetBehavior.state = STATE_COLLAPSED
setMiniPlayerAlphaProgress(0f)
} }
fun expandPanel() { fun expandPanel() {
bottomSheetBehavior.state = STATE_EXPANDED bottomSheetBehavior.state = STATE_EXPANDED
setMiniPlayerAlphaProgress(1f)
} }
private fun setMiniPlayerAlphaProgress(progress: Float) { private fun setMiniPlayerAlphaProgress(progress: Float) {
if (progress < 0) return
val alpha = 1 - progress val alpha = 1 - progress
miniPlayerFragment?.view?.alpha = alpha miniPlayerFragment?.view?.alpha = 1 - (progress / 0.2F)
miniPlayerFragment?.view?.visibility = if (alpha == 0f) View.GONE else View.VISIBLE miniPlayerFragment?.view?.isGone = alpha == 0f
bottomNavigationView.translationY = progress * 500 binding.bottomNavigationView.translationY = progress * 500
bottomNavigationView.alpha = alpha binding.bottomNavigationView.alpha = alpha
binding.playerFragmentContainer.alpha = (progress - 0.2F) / 0.2F
}
private fun animateNavigationBarColor(color: Int) {
if (VersionUtils.hasOreo()) return
navigationBarColorAnimator?.cancel()
navigationBarColorAnimator = ValueAnimator
.ofArgb(window.navigationBarColor, color).apply {
duration = ViewUtil.RETRO_MUSIC_ANIM_TIME.toLong()
interpolator = PathInterpolator(0.4f, 0f, 1f, 1f)
addUpdateListener { animation: ValueAnimator ->
setNavigationBarColorPreOreo(
animation.animatedValue as Int
)
}
start()
}
} }
open fun onPanelCollapsed() { open fun onPanelCollapsed() {
setMiniPlayerAlphaProgress(0F)
// restore values // restore values
super.setLightStatusbar(lightStatusBar) animateNavigationBarColor(surfaceColor())
super.setTaskDescriptionColor(taskColor) setLightStatusBarAuto()
super.setNavigationbarColor(navigationBarColor) setLightNavigationBarAuto()
super.setLightNavigationBar(lightNavigationBar) setTaskDescriptionColor(taskColor)
playerFragment?.onHide()
} }
open fun onPanelExpanded() { open fun onPanelExpanded() {
setMiniPlayerAlphaProgress(1F)
onPaletteColorChanged() onPaletteColorChanged()
playerFragment?.onShow()
} }
private fun setupSlidingUpPanel() { private fun setupSlidingUpPanel() {
slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object : binding.slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener { ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() { override fun onGlobalLayout() {
slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this) binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
if (nowPlayingScreen != Peak) { if (nowPlayingScreen != Peek) {
val params = slidingPanel.layoutParams as ViewGroup.LayoutParams binding.slidingPanel.updateLayoutParams<ViewGroup.LayoutParams> {
params.height = ViewGroup.LayoutParams.MATCH_PARENT height = ViewGroup.LayoutParams.MATCH_PARENT
slidingPanel.layoutParams = params }
} }
when (panelState) { when (panelState) {
STATE_EXPANDED -> onPanelExpanded() STATE_EXPANDED -> onPanelExpanded()
@ -203,26 +254,30 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
}) })
} }
fun getBottomNavigationView(): BottomNavigationBarTinted { val bottomNavigationView get() = binding.bottomNavigationView
return bottomNavigationView
} val slidingPanel get() = binding.slidingPanel
override fun onServiceConnected() { override fun onServiceConnected() {
super.onServiceConnected() super.onServiceConnected()
if (MusicPlayerRemote.playingQueue.isNotEmpty()) { if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object : binding.slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener { ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() { override fun onGlobalLayout() {
slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this) binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
hideBottomBar(false) hideBottomSheet(false)
} }
}) })
} // don't call hideBottomBar(true) here as it causes a bug with the SlidingUpPanelLayout } // don't call hideBottomSheet(true) here as it causes a bug with the SlidingUpPanelLayout
} }
override fun onQueueChanged() { override fun onQueueChanged() {
super.onQueueChanged() super.onQueueChanged()
hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty()) // Mini player should be hidden in Playing Queue
// it may pop up if hideBottomSheet is called
if (currentFragment(R.id.fragment_container) !is PlayingQueueFragment) {
hideBottomSheet(MusicPlayerRemote.playingQueue.isEmpty())
}
} }
override fun onBackPressed() { override fun onBackPressed() {
@ -235,127 +290,156 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
collapsePanel() collapsePanel()
return true return true
} }
return false return false
} }
private fun onPaletteColorChanged() { private fun onPaletteColorChanged() {
if (panelState == STATE_EXPANDED) { if (panelState == STATE_EXPANDED) {
super.setTaskDescriptionColor(paletteColor) navigationBarColor = surfaceColor()
val isColorLight = ColorUtil.isColorLight(paletteColor) setTaskDescColor(paletteColor)
if (PreferenceUtil.isAdaptiveColor && (nowPlayingScreen == Normal || nowPlayingScreen == Flat)) { val isColorLight = paletteColor.isColorLight
super.setLightNavigationBar(true) if (PreferenceUtil.isAdaptiveColor && (nowPlayingScreen == Normal || nowPlayingScreen == Flat || nowPlayingScreen == Material)) {
super.setLightStatusbar(isColorLight) setLightNavigationBar(true)
setLightStatusBar(isColorLight)
} else if (nowPlayingScreen == Card || nowPlayingScreen == Blur || nowPlayingScreen == BlurCard) { } else if (nowPlayingScreen == Card || nowPlayingScreen == Blur || nowPlayingScreen == BlurCard) {
super.setLightStatusbar(false) animateNavigationBarColor(Color.BLACK)
super.setLightNavigationBar(true) navigationBarColor = Color.BLACK
super.setNavigationbarColor(Color.BLACK) setLightStatusBar(false)
setLightNavigationBar(true)
} else if (nowPlayingScreen == Color || nowPlayingScreen == Tiny || nowPlayingScreen == Gradient) { } else if (nowPlayingScreen == Color || nowPlayingScreen == Tiny || nowPlayingScreen == Gradient) {
super.setNavigationbarColor(paletteColor) animateNavigationBarColor(paletteColor)
super.setLightNavigationBar(isColorLight) navigationBarColor = paletteColor
super.setLightStatusbar(isColorLight) setLightNavigationBar(isColorLight)
setLightStatusBar(isColorLight)
} else if (nowPlayingScreen == Full) { } else if (nowPlayingScreen == Full) {
super.setNavigationbarColor(paletteColor) animateNavigationBarColor(paletteColor)
super.setLightNavigationBar(isColorLight) navigationBarColor = paletteColor
super.setLightStatusbar(false) setLightNavigationBar(isColorLight)
setLightStatusBar(false)
} else if (nowPlayingScreen == Classic) { } else if (nowPlayingScreen == Classic) {
super.setLightStatusbar(false) setLightStatusBar(false)
} else if (nowPlayingScreen == Fit) { } else if (nowPlayingScreen == Fit) {
super.setLightStatusbar(false) setLightStatusBar(false)
} else {
super.setLightStatusbar(
ColorUtil.isColorLight(
ATHUtil.resolveColor(
this,
android.R.attr.windowBackground
)
)
)
super.setLightNavigationBar(true)
} }
} }
} }
override fun setLightStatusbar(enabled: Boolean) { private fun setTaskDescColor(color: Int) {
lightStatusBar = enabled
if (panelState == STATE_COLLAPSED) {
super.setLightStatusbar(enabled)
}
}
override fun setLightNavigationBar(enabled: Boolean) {
lightNavigationBar = enabled
if (panelState == STATE_COLLAPSED) {
super.setLightNavigationBar(enabled)
}
}
override fun setNavigationbarColor(color: Int) {
navigationBarColor = color
if (panelState == STATE_COLLAPSED) {
super.setNavigationbarColor(color)
}
}
override fun setTaskDescriptionColor(color: Int) {
taskColor = color taskColor = color
if (panelState == STATE_COLLAPSED) { if (panelState == STATE_COLLAPSED) {
super.setTaskDescriptionColor(color) setTaskDescriptionColor(color)
} }
} }
fun updateTabs() { fun updateTabs() {
bottomNavigationView.menu.clear() binding.bottomNavigationView.menu.clear()
val currentTabs: List<CategoryInfo> = PreferenceUtil.libraryCategory val currentTabs: List<CategoryInfo> = PreferenceUtil.libraryCategory
for (tab in currentTabs) { for (tab in currentTabs) {
if (tab.visible) { if (tab.visible) {
val menu = tab.category val menu = tab.category
bottomNavigationView.menu.add(0, menu.id, 0, menu.stringRes).setIcon(menu.icon) binding.bottomNavigationView.menu.add(0, menu.id, 0, menu.stringRes)
.setIcon(menu.icon)
} }
} }
if (bottomNavigationView.menu.size() == 1) { if (binding.bottomNavigationView.menu.size() == 1) {
bottomNavigationView.hide() isInOneTabMode = true
binding.bottomNavigationView.hide()
} }
} }
private fun updateColor() { private fun updateColor() {
libraryViewModel.paletteColor.observe(this, { color -> libraryViewModel.paletteColor.observe(this) { color ->
this.paletteColor = color this.paletteColor = color
onPaletteColorChanged() onPaletteColorChanged()
}) }
} }
fun setBottomBarVisibility(visible: Boolean) { fun setBottomNavVisibility(
bottomNavigationView.isVisible = visible visible: Boolean,
hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty()) animate: Boolean = false,
hideBottomSheet: Boolean = MusicPlayerRemote.playingQueue.isEmpty(),
) {
if (!ViewCompat.isLaidOut(bottomNavigationView)) {
return
}
if (isInOneTabMode) {
hideBottomSheet(
hide = hideBottomSheet,
animate = animate,
isBottomNavVisible = false
)
return
}
val mAnimate = animate && bottomSheetBehavior.state == STATE_COLLAPSED
if (mAnimate) {
if (visible) {
binding.bottomNavigationView.bringToFront()
binding.bottomNavigationView.show()
} else {
binding.bottomNavigationView.hide()
}
} else {
binding.bottomNavigationView.isVisible = false
if (visible && bottomSheetBehavior.state != STATE_EXPANDED) {
binding.bottomNavigationView.bringToFront()
}
}
hideBottomSheet(
hide = hideBottomSheet,
animate = animate,
isBottomNavVisible = visible
)
} }
private fun hideBottomBar(hide: Boolean) { fun hideBottomSheet(
val heightOfBar = dip(R.dimen.mini_player_height) hide: Boolean,
val heightOfBarWithTabs = heightOfBar * 2 animate: Boolean = false,
val isVisible = bottomNavigationView.isVisible isBottomNavVisible: Boolean = bottomNavigationView.isVisible,
) {
val heightOfBar =
windowInsets.safeGetBottomInsets() +
if (MusicPlayerRemote.isCasting) dip(R.dimen.cast_mini_player_height) else dip(R.dimen.mini_player_height)
val heightOfBarWithTabs = heightOfBar + dip(R.dimen.bottom_nav_height)
if (hide) { if (hide) {
bottomSheetBehavior.isHideable = true bottomSheetBehavior.peekHeight = -windowInsets.safeGetBottomInsets()
bottomSheetBehavior.peekHeight = 0 bottomSheetBehavior.state = STATE_COLLAPSED
ViewCompat.setElevation(slidingPanel, 0f) libraryViewModel.setFabMargin(
ViewCompat.setElevation(bottomNavigationView, 10f) this,
collapsePanel() if (isBottomNavVisible) dip(R.dimen.bottom_nav_height) else 0
)
} else { } else {
if (MusicPlayerRemote.playingQueue.isNotEmpty()) { if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
bottomSheetBehavior.isHideable = false binding.slidingPanel.elevation = 0F
ViewCompat.setElevation(slidingPanel, 10f) binding.bottomNavigationView.elevation = 5F
ViewCompat.setElevation(bottomNavigationView, 10f) if (isBottomNavVisible) {
if (isVisible) {
println("List") println("List")
bottomSheetBehavior.peekHeight = heightOfBarWithTabs - 22 if (animate) {
bottomSheetBehavior.peekHeightAnimate(heightOfBarWithTabs)
} else {
bottomSheetBehavior.peekHeight = heightOfBarWithTabs
}
libraryViewModel.setFabMargin(this, dip(R.dimen.mini_player_height_expanded))
} else { } else {
println("Details") println("Details")
if (animate) {
bottomSheetBehavior.peekHeightAnimate(heightOfBar).doOnEnd {
binding.slidingPanel.bringToFront()
}
} else {
bottomSheetBehavior.peekHeight = heightOfBar bottomSheetBehavior.peekHeight = heightOfBar
binding.slidingPanel.bringToFront()
}
libraryViewModel.setFabMargin(this, dip(R.dimen.mini_player_height))
} }
} }
} }
} }
fun setAllowDragging(allowDragging: Boolean) {
bottomSheetBehavior.isDraggable = allowDragging
hideBottomSheet(false)
}
private fun chooseFragmentForTheme() { private fun chooseFragmentForTheme() {
nowPlayingScreen = PreferenceUtil.nowPlayingScreen nowPlayingScreen = PreferenceUtil.nowPlayingScreen
@ -374,9 +458,10 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
Color -> ColorFragment() Color -> ColorFragment()
Gradient -> GradientPlayerFragment() Gradient -> GradientPlayerFragment()
Tiny -> TinyPlayerFragment() Tiny -> TinyPlayerFragment()
Peak -> PeakPlayerFragment() Peek -> PeekPlayerFragment()
Circle -> CirclePlayerFragment() Circle -> CirclePlayerFragment()
Classic -> ClassicPlayerFragment() Classic -> ClassicPlayerFragment()
MD3 -> MD3PlayerFragment()
else -> PlayerFragment() else -> PlayerFragment()
} // must implement AbsPlayerFragment } // must implement AbsPlayerFragment
supportFragmentManager.commit { supportFragmentManager.commit {

View file

@ -15,52 +15,53 @@
package io.github.muntashirakon.music.activities.base package io.github.muntashirakon.music.activities.base
import android.content.Context import android.content.Context
import android.graphics.Color import android.content.res.Resources
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper
import android.view.KeyEvent import android.view.KeyEvent
import android.view.View import android.view.View
import android.view.WindowManager
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
import code.name.monkey.appthemehelper.ATH import androidx.core.os.ConfigurationCompat
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.common.ATHToolbarActivity import code.name.monkey.appthemehelper.common.ATHToolbarActivity
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialDialogsUtil
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.LanguageContextWrapper import io.github.muntashirakon.music.LanguageContextWrapper
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.extensions.*
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil import io.github.muntashirakon.music.util.theme.getNightMode
import io.github.muntashirakon.music.util.theme.ThemeManager import io.github.muntashirakon.music.util.theme.getThemeResValue
import java.util.* import java.util.*
abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable { abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
private val handler = Handler() private val handler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
updateTheme() updateTheme()
hideStatusBar() hideStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setImmersiveFullscreen() setEdgeToEdgeOrImmersive()
registerSystemUiVisibility() registerSystemUiVisibility()
toggleScreenOn() toggleScreenOn()
MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this) setLightNavigationBarAuto()
setLightStatusBarAuto(surfaceColor())
if (VersionUtils.hasQ()) {
window.decorView.isForceDarkAllowed = false
}
} }
private fun updateTheme() { private fun updateTheme() {
setTheme(ThemeManager.getThemeResValue(this)) setTheme(getThemeResValue())
setDefaultNightMode(ThemeManager.getNightMode(this)) if (PreferenceUtil.materialYou) {
setDefaultNightMode(getNightMode())
} }
private fun toggleScreenOn() { if (PreferenceUtil.isCustomFont) {
if (PreferenceUtil.isScreenOnEnabled) { setTheme(R.style.FontThemeOverlay)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) }
} else { if (PreferenceUtil.circlePlayButton) {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) setTheme(R.style.CircleFABOverlay)
} }
} }
@ -75,92 +76,6 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
} }
} }
fun hideStatusBar() {
hideStatusBar(PreferenceUtil.isFullScreenMode)
}
private fun hideStatusBar(fullscreen: Boolean) {
val statusBar = window.decorView.rootView.findViewById<View>(R.id.status_bar)
if (statusBar != null) {
statusBar.visibility = if (fullscreen) View.GONE else View.VISIBLE
}
}
fun setDrawUnderStatusBar() {
RetroUtil.setAllowDrawUnderStatusBar(window)
}
fun setDrawUnderNavigationBar() {
RetroUtil.setAllowDrawUnderNavigationBar(window)
}
/**
* This will set the color of the view with the id "status_bar" on KitKat and Lollipop. On
* Lollipop if no such view is found it will set the statusbar color using the native method.
*
* @param color the new statusbar color (will be shifted down on Lollipop and above)
*/
fun setStatusbarColor(color: Int) {
val statusBar = window.decorView.rootView.findViewById<View>(R.id.status_bar)
if (statusBar != null) {
when {
VersionUtils.hasMarshmallow() -> statusBar.setBackgroundColor(color)
VersionUtils.hasLollipop() -> statusBar.setBackgroundColor(
ColorUtil.darkenColor(
color
)
)
else -> statusBar.setBackgroundColor(color)
}
} else {
when {
VersionUtils.hasMarshmallow() -> window.statusBarColor = color
else -> window.statusBarColor = ColorUtil.darkenColor(color)
}
}
setLightStatusbarAuto(ATHUtil.resolveColor(this, R.attr.colorSurface))
}
fun setStatusbarColorAuto() {
// we don't want to use statusbar color because we are doing the color darkening on our own to support KitKat
setStatusbarColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setLightStatusbarAuto(ATHUtil.resolveColor(this, R.attr.colorSurface))
}
open fun setTaskDescriptionColor(@ColorInt color: Int) {
ATH.setTaskDescriptionColor(this, color)
}
fun setTaskDescriptionColorAuto() {
setTaskDescriptionColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
}
open fun setNavigationbarColor(color: Int) {
if (ThemeStore.coloredNavigationBar(this)) {
ATH.setNavigationbarColor(this, color)
} else {
ATH.setNavigationbarColor(this, Color.BLACK)
}
}
fun setNavigationbarColorAuto() {
setNavigationbarColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
}
open fun setLightStatusbar(enabled: Boolean) {
ATH.setLightStatusbar(this, enabled)
}
fun setLightStatusbarAuto(bgColor: Int) {
setLightStatusbar(ColorUtil.isColorLight(bgColor))
}
open fun setLightNavigationBar(enabled: Boolean) {
if (!ATHUtil.isWindowBackgroundDark(this) and ThemeStore.coloredNavigationBar(this)) {
ATH.setLightNavigationbar(this, enabled)
}
}
private fun registerSystemUiVisibility() { private fun registerSystemUiVisibility() {
val decorView = window.decorView val decorView = window.decorView
decorView.setOnSystemUiVisibilityChangeListener { visibility -> decorView.setOnSystemUiVisibilityChangeListener { visibility ->
@ -175,19 +90,6 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
decorView.setOnSystemUiVisibilityChangeListener(null) decorView.setOnSystemUiVisibilityChangeListener(null)
} }
private fun setImmersiveFullscreen() {
val flags =
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
if (PreferenceUtil.isFullScreenMode) {
window.decorView.systemUiVisibility = flags
}
}
private fun exitFullscreen() {
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
}
override fun run() { override fun run() {
setImmersiveFullscreen() setImmersiveFullscreen()
} }
@ -213,8 +115,12 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
override fun attachBaseContext(newBase: Context?) { override fun attachBaseContext(newBase: Context?) {
val code = PreferenceUtil.languageCode val code = PreferenceUtil.languageCode
if (code != "auto") { val locale = if (code == "auto") {
super.attachBaseContext(LanguageContextWrapper.wrap(newBase, Locale(code))) // Get the device default locale
} else super.attachBaseContext(newBase) ConfigurationCompat.getLocales(Resources.getSystem().configuration)[0]
} else {
Locale.forLanguageTag(code)
}
super.attachBaseContext(LanguageContextWrapper.wrap(newBase, locale))
} }
} }

View file

@ -14,229 +14,70 @@
*/ */
package io.github.muntashirakon.music.activities.bugreport package io.github.muntashirakon.music.activities.bugreport
import android.app.Activity
import android.app.Dialog
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils
import android.view.MenuItem import android.view.MenuItem
import android.view.inputmethod.EditorInfo import androidx.core.content.getSystemService
import android.widget.Toast import androidx.core.net.toUri
import androidx.annotation.StringDef
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.base.AbsThemeActivity import io.github.muntashirakon.music.activities.base.AbsThemeActivity
import io.github.muntashirakon.music.activities.bugreport.model.DeviceInfo import io.github.muntashirakon.music.activities.bugreport.model.DeviceInfo
import io.github.muntashirakon.music.activities.bugreport.model.Report import io.github.muntashirakon.music.databinding.ActivityBugReportBinding
import io.github.muntashirakon.music.activities.bugreport.model.github.ExtraInfo import io.github.muntashirakon.music.extensions.accentColor
import io.github.muntashirakon.music.activities.bugreport.model.github.GithubLogin import io.github.muntashirakon.music.extensions.setTaskDescriptionColorAuto
import io.github.muntashirakon.music.activities.bugreport.model.github.GithubTarget import io.github.muntashirakon.music.extensions.showToast
import io.github.muntashirakon.music.misc.DialogAsyncTask
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.textfield.TextInputLayout
import java.io.IOException
import kotlinx.android.synthetic.main.activity_bug_report.*
import kotlinx.android.synthetic.main.bug_report_card_device_info.*
import kotlinx.android.synthetic.main.bug_report_card_report.*
import org.eclipse.egit.github.core.Issue
import org.eclipse.egit.github.core.client.GitHubClient
import org.eclipse.egit.github.core.client.RequestException
import org.eclipse.egit.github.core.service.IssueService
private const val RESULT_SUCCESS = "RESULT_OK"
private const val RESULT_BAD_CREDENTIALS = "RESULT_BAD_CREDENTIALS"
private const val RESULT_INVALID_TOKEN = "RESULT_INVALID_TOKEN"
private const val RESULT_ISSUES_NOT_ENABLED = "RESULT_ISSUES_NOT_ENABLED"
private const val RESULT_UNKNOWN = "RESULT_UNKNOWN"
@StringDef(
RESULT_SUCCESS,
RESULT_BAD_CREDENTIALS,
RESULT_INVALID_TOKEN,
RESULT_ISSUES_NOT_ENABLED,
RESULT_UNKNOWN
)
@Retention(AnnotationRetention.SOURCE)
private annotation class Result
open class BugReportActivity : AbsThemeActivity() { open class BugReportActivity : AbsThemeActivity() {
private lateinit var binding: ActivityBugReportBinding
private var deviceInfo: DeviceInfo? = null private var deviceInfo: DeviceInfo? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bug_report) binding = ActivityBugReportBinding.inflate(layoutInflater)
setStatusbarColorAuto() setContentView(binding.root)
setNavigationbarColorAuto()
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
initViews() initViews()
if (TextUtils.isEmpty(title)) setTitle(R.string.report_an_issue) if (title.isNullOrEmpty()) setTitle(R.string.report_an_issue)
deviceInfo = DeviceInfo(this) deviceInfo = DeviceInfo(this)
airTextDeviceInfo.text = deviceInfo.toString() binding.cardDeviceInfo.airTextDeviceInfo.text = deviceInfo.toString()
} }
private fun initViews() { private fun initViews() {
val accentColor = ThemeStore.accentColor(this) val accentColor = accentColor()
val primaryColor = ATHUtil.resolveColor(this, R.attr.colorSurface) setSupportActionBar(binding.toolbar)
toolbar.setBackgroundColor(primaryColor) ToolbarContentTintHelper.colorBackButton(binding.toolbar)
setSupportActionBar(toolbar)
ToolbarContentTintHelper.colorBackButton(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
TintHelper.setTintAuto(optionUseAccount, accentColor, false)
optionUseAccount?.setOnClickListener {
inputTitle.isEnabled = true
inputDescription.isEnabled = true
inputUsername.isEnabled = true
inputPassword.isEnabled = true
optionAnonymous.isChecked = false binding.cardDeviceInfo.airTextDeviceInfo.setOnClickListener { copyDeviceInfoToClipBoard() }
sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton?) {
super.onHidden(fab)
sendFab.setImageResource(R.drawable.ic_send)
sendFab.show()
}
})
}
TintHelper.setTintAuto(optionAnonymous, accentColor, false)
optionAnonymous.setOnClickListener {
inputTitle.isEnabled = false
inputDescription.isEnabled = false
inputUsername.isEnabled = false
inputPassword.isEnabled = false
optionUseAccount.isChecked = false TintHelper.setTintAuto(binding.sendFab, accentColor, true)
sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() { binding.sendFab.setOnClickListener { reportIssue() }
override fun onHidden(fab: FloatingActionButton?) {
super.onHidden(fab)
sendFab.setImageResource(R.drawable.ic_open_in_browser)
sendFab.show()
}
})
}
inputPassword.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEND) {
reportIssue()
return@setOnEditorActionListener true
}
false
}
airTextDeviceInfo.setOnClickListener { copyDeviceInfoToClipBoard() }
TintHelper.setTintAuto(sendFab, accentColor, true)
sendFab.setOnClickListener { reportIssue() }
MaterialUtil.setTint(inputLayoutTitle, false)
MaterialUtil.setTint(inputLayoutDescription, false)
MaterialUtil.setTint(inputLayoutUsername, false)
MaterialUtil.setTint(inputLayoutPassword, false)
} }
private fun reportIssue() { private fun reportIssue() {
if (optionUseAccount.isChecked) {
if (!validateInput()) return
val username = inputUsername.text.toString()
val password = inputPassword.text.toString()
sendBugReport(GithubLogin(username, password))
} else {
copyDeviceInfoToClipBoard() copyDeviceInfoToClipBoard()
val i = Intent(Intent.ACTION_VIEW) val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(ISSUE_TRACKER_LINK) i.data = ISSUE_TRACKER_LINK.toUri()
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(i) startActivity(i)
} }
}
private fun copyDeviceInfoToClipBoard() { private fun copyDeviceInfoToClipBoard() {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipboard = getSystemService<ClipboardManager>()
val clip = ClipData.newPlainText(getString(R.string.device_info), deviceInfo?.toMarkdown()) val clip = ClipData.newPlainText(getString(R.string.device_info), deviceInfo?.toMarkdown())
clipboard.setPrimaryClip(clip) clipboard?.setPrimaryClip(clip)
Toast.makeText( showToast(R.string.copied_device_info_to_clipboard)
this@BugReportActivity,
R.string.copied_device_info_to_clipboard,
Toast.LENGTH_LONG
).show()
} }
private fun validateInput(): Boolean {
var hasErrors = false
if (optionUseAccount.isChecked) {
if (TextUtils.isEmpty(inputUsername.text)) {
setError(inputLayoutUsername, R.string.bug_report_no_username)
hasErrors = true
} else {
removeError(inputLayoutUsername)
}
if (TextUtils.isEmpty(inputPassword.text)) {
setError(inputLayoutPassword, R.string.bug_report_no_password)
hasErrors = true
} else {
removeError(inputLayoutPassword)
}
}
if (TextUtils.isEmpty(inputTitle.text)) {
setError(inputLayoutTitle, R.string.bug_report_no_title)
hasErrors = true
} else {
removeError(inputLayoutTitle)
}
if (TextUtils.isEmpty(inputDescription.text)) {
setError(inputLayoutDescription, R.string.bug_report_no_description)
hasErrors = true
} else {
removeError(inputLayoutDescription)
}
return !hasErrors
}
private fun setError(editTextLayout: TextInputLayout, @StringRes errorRes: Int) {
editTextLayout.error = getString(errorRes)
}
private fun removeError(editTextLayout: TextInputLayout) {
editTextLayout.error = null
}
private fun sendBugReport(login: GithubLogin) {
if (!validateInput()) return
val bugTitle = inputTitle.text.toString()
val bugDescription = inputDescription.text.toString()
val extraInfo = ExtraInfo()
onSaveExtraInfo()
val report = Report(bugTitle, bugDescription, deviceInfo, extraInfo)
val target = GithubTarget("h4h13", "RetroMusicPlayer")
ReportIssueAsyncTask.report(this, report, target, login)
}
private fun onSaveExtraInfo() {}
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) { if (item.itemId == android.R.id.home) {
onBackPressed() onBackPressed()
@ -244,99 +85,14 @@ open class BugReportActivity : AbsThemeActivity() {
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
private class ReportIssueAsyncTask private constructor(
activity: Activity,
private val report: Report,
private val target: GithubTarget,
private val login: GithubLogin
) : DialogAsyncTask<Void, Void, String>(activity) {
override fun createDialog(context: Context): Dialog {
return AlertDialog.Builder(context).show()
}
@Result
override fun doInBackground(vararg params: Void): String {
val client: GitHubClient = if (login.shouldUseApiToken()) {
GitHubClient().setOAuth2Token(login.apiToken)
} else {
GitHubClient().setCredentials(login.username, login.password)
}
val issue = Issue().setTitle(report.title).setBody(report.description)
try {
IssueService(client).createIssue(target.username, target.repository, issue)
return RESULT_SUCCESS
} catch (e: RequestException) {
return when (e.status) {
STATUS_BAD_CREDENTIALS -> {
if (login.shouldUseApiToken()) RESULT_INVALID_TOKEN else RESULT_BAD_CREDENTIALS
}
STATUS_ISSUES_NOT_ENABLED -> RESULT_ISSUES_NOT_ENABLED
else -> {
e.printStackTrace()
RESULT_UNKNOWN
}
}
} catch (e: IOException) {
e.printStackTrace()
return RESULT_UNKNOWN
}
}
override fun onPostExecute(@Result result: String) {
super.onPostExecute(result)
val context = context ?: return
when (result) {
RESULT_SUCCESS -> tryToFinishActivity()
RESULT_BAD_CREDENTIALS -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_wrong_credentials)
.setPositiveButton(android.R.string.ok, null)
.show()
RESULT_INVALID_TOKEN -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_invalid_token)
.setPositiveButton(android.R.string.ok, null).show()
RESULT_ISSUES_NOT_ENABLED -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_issues_not_available)
.setPositiveButton(android.R.string.ok, null)
else -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_unknown)
.setPositiveButton(android.R.string.ok) { _, _ -> tryToFinishActivity() }
.setNegativeButton(android.R.string.cancel) { _, _ -> tryToFinishActivity() }
}
}
private fun tryToFinishActivity() { private fun tryToFinishActivity() {
val context = context if (!isFinishing) {
if (context is Activity && !context.isFinishing) { finish()
context.finish()
} }
} }
companion object { companion object {
private const val ISSUE_TRACKER_LINK =
fun report( "https://github.com/MuntashirAkon/Metro/issues/new"
activity: Activity,
report: Report,
target: GithubTarget,
login: GithubLogin
) {
ReportIssueAsyncTask(activity, report, target, login).execute()
}
}
}
companion object {
private const val STATUS_BAD_CREDENTIALS = 401
private const val STATUS_ISSUES_NOT_ENABLED = 410
private const val ISSUE_TRACKER_LINK = "https://github.com/h4h13/RetroMusicPlayer"
} }
} }

View file

@ -1,205 +0,0 @@
package io.github.muntashirakon.music.activities.bugreport.model;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.IntRange;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Locale;
import io.github.muntashirakon.music.util.PreferenceUtil;
public class DeviceInfo {
@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
private final String[] abis =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
? Build.SUPPORTED_ABIS
: new String[] {Build.CPU_ABI, Build.CPU_ABI2};
@SuppressLint("NewApi")
private final String[] abis32Bits =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? Build.SUPPORTED_32_BIT_ABIS : null;
@SuppressLint("NewApi")
private final String[] abis64Bits =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? Build.SUPPORTED_64_BIT_ABIS : null;
private final String baseTheme;
private final String brand = Build.BRAND;
private final String buildID = Build.DISPLAY;
private final String buildVersion = Build.VERSION.INCREMENTAL;
private final String device = Build.DEVICE;
private final String hardware = Build.HARDWARE;
private final boolean isAdaptive;
private final String manufacturer = Build.MANUFACTURER;
private final String model = Build.MODEL;
private final String nowPlayingTheme;
private final String product = Build.PRODUCT;
private final String releaseVersion = Build.VERSION.RELEASE;
@IntRange(from = 0)
private final int sdkVersion = Build.VERSION.SDK_INT;
private final int versionCode;
private final String versionName;
private final String selectedLang;
public DeviceInfo(@NotNull Context context) {
PackageInfo packageInfo;
try {
packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
packageInfo = null;
}
if (packageInfo != null) {
versionCode = packageInfo.versionCode;
versionName = packageInfo.versionName;
} else {
versionCode = -1;
versionName = null;
}
baseTheme = PreferenceUtil.INSTANCE.getBaseTheme();
nowPlayingTheme =
context.getString(PreferenceUtil.INSTANCE.getNowPlayingScreen().getTitleRes());
isAdaptive = PreferenceUtil.INSTANCE.isAdaptiveColor();
selectedLang = PreferenceUtil.INSTANCE.getLanguageCode();
}
public String toMarkdown() {
return "Device info:\n"
+ "---\n"
+ "<table>\n"
+ "<tr><td><b>App version</b></td><td>"
+ versionName
+ "</td></tr>\n"
+ "<tr><td>App version code</td><td>"
+ versionCode
+ "</td></tr>\n"
+ "<tr><td>Android build version</td><td>"
+ buildVersion
+ "</td></tr>\n"
+ "<tr><td>Android release version</td><td>"
+ releaseVersion
+ "</td></tr>\n"
+ "<tr><td>Android SDK version</td><td>"
+ sdkVersion
+ "</td></tr>\n"
+ "<tr><td>Android build ID</td><td>"
+ buildID
+ "</td></tr>\n"
+ "<tr><td>Device brand</td><td>"
+ brand
+ "</td></tr>\n"
+ "<tr><td>Device manufacturer</td><td>"
+ manufacturer
+ "</td></tr>\n"
+ "<tr><td>Device name</td><td>"
+ device
+ "</td></tr>\n"
+ "<tr><td>Device model</td><td>"
+ model
+ "</td></tr>\n"
+ "<tr><td>Device product name</td><td>"
+ product
+ "</td></tr>\n"
+ "<tr><td>Device hardware name</td><td>"
+ hardware
+ "</td></tr>\n"
+ "<tr><td>ABIs</td><td>"
+ Arrays.toString(abis)
+ "</td></tr>\n"
+ "<tr><td>ABIs (32bit)</td><td>"
+ Arrays.toString(abis32Bits)
+ "</td></tr>\n"
+ "<tr><td>ABIs (64bit)</td><td>"
+ Arrays.toString(abis64Bits)
+ "</td></tr>\n"
+ "<tr><td>Language</td><td>"
+ selectedLang
+ "</td></tr>\n"
+ "</table>\n";
}
@NotNull
@Override
public String toString() {
return "App version: "
+ versionName
+ "\n"
+ "App version code: "
+ versionCode
+ "\n"
+ "Android build version: "
+ buildVersion
+ "\n"
+ "Android release version: "
+ releaseVersion
+ "\n"
+ "Android SDK version: "
+ sdkVersion
+ "\n"
+ "Android build ID: "
+ buildID
+ "\n"
+ "Device brand: "
+ brand
+ "\n"
+ "Device manufacturer: "
+ manufacturer
+ "\n"
+ "Device name: "
+ device
+ "\n"
+ "Device model: "
+ model
+ "\n"
+ "Device product name: "
+ product
+ "\n"
+ "Device hardware name: "
+ hardware
+ "\n"
+ "ABIs: "
+ Arrays.toString(abis)
+ "\n"
+ "ABIs (32bit): "
+ Arrays.toString(abis32Bits)
+ "\n"
+ "ABIs (64bit): "
+ Arrays.toString(abis64Bits)
+ "\n"
+ "Base theme: "
+ baseTheme
+ "\n"
+ "Now playing theme: "
+ nowPlayingTheme
+ "\n"
+ "Adaptive: "
+ isAdaptive
+ "\n"
+ "System language: "
+ Locale.getDefault().toLanguageTag()
+ "\n"
+ "In-App Language: "
+ selectedLang;
}
}

View file

@ -0,0 +1,111 @@
package io.github.muntashirakon.music.activities.bugreport.model
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.annotation.IntRange
import androidx.core.content.pm.PackageInfoCompat
import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.PreferenceUtil.isAdaptiveColor
import io.github.muntashirakon.music.util.PreferenceUtil.languageCode
import io.github.muntashirakon.music.util.PreferenceUtil.nowPlayingScreen
import java.util.*
class DeviceInfo(context: Context) {
@SuppressLint("NewApi")
private val abis = Build.SUPPORTED_ABIS
@SuppressLint("NewApi")
private val abis32Bits = Build.SUPPORTED_32_BIT_ABIS
@SuppressLint("NewApi")
private val abis64Bits = Build.SUPPORTED_64_BIT_ABIS
private val baseTheme: String
private val brand = Build.BRAND
private val buildID = Build.DISPLAY
private val buildVersion = Build.VERSION.INCREMENTAL
private val device = Build.DEVICE
private val hardware = Build.HARDWARE
private val isAdaptive: Boolean
private val manufacturer = Build.MANUFACTURER
private val model = Build.MODEL
private val nowPlayingTheme: String
private val product = Build.PRODUCT
private val releaseVersion = Build.VERSION.RELEASE
@IntRange(from = 0)
private val sdkVersion = Build.VERSION.SDK_INT
private var versionCode = 0L
private var versionName: String? = null
private val selectedLang: String
fun toMarkdown(): String {
return """
Device info:
---
<table>
<tr><td><b>App version</b></td><td>$versionName</td></tr>
<tr><td>App version code</td><td>$versionCode</td></tr>
<tr><td>Android build version</td><td>$buildVersion</td></tr>
<tr><td>Android release version</td><td>$releaseVersion</td></tr>
<tr><td>Android SDK version</td><td>$sdkVersion</td></tr>
<tr><td>Android build ID</td><td>$buildID</td></tr>
<tr><td>Device brand</td><td>$brand</td></tr>
<tr><td>Device manufacturer</td><td>$manufacturer</td></tr>
<tr><td>Device name</td><td>$device</td></tr>
<tr><td>Device model</td><td>$model</td></tr>
<tr><td>Device product name</td><td>$product</td></tr>
<tr><td>Device hardware name</td><td>$hardware</td></tr>
<tr><td>ABIs</td><td>${Arrays.toString(abis)}</td></tr>
<tr><td>ABIs (32bit)</td><td>${Arrays.toString(abis32Bits)}</td></tr>
<tr><td>ABIs (64bit)</td><td>${Arrays.toString(abis64Bits)}</td></tr>
<tr><td>Language</td><td>$selectedLang</td></tr>
</table>
""".trimIndent()
}
override fun toString(): String {
return """
App version: $versionName
App version code: $versionCode
Android build version: $buildVersion
Android release version: $releaseVersion
Android SDK version: $sdkVersion
Android build ID: $buildID
Device brand: $brand
Device manufacturer: $manufacturer
Device name: $device
Device model: $model
Device product name: $product
Device hardware name: $hardware
ABIs: ${Arrays.toString(abis)}
ABIs (32bit): ${Arrays.toString(abis32Bits)}
ABIs (64bit): ${Arrays.toString(abis64Bits)}
Base theme: $baseTheme
Now playing theme: $nowPlayingTheme
Adaptive: $isAdaptive
System language: ${Locale.getDefault().toLanguageTag()}
In-App Language: $selectedLang
""".trimIndent()
}
init {
val packageInfo = try {
context.packageManager.getPackageInfo(context.packageName, 0)
} catch (e: PackageManager.NameNotFoundException) {
null
}
if (packageInfo != null) {
versionCode = PackageInfoCompat.getLongVersionCode(packageInfo)
versionName = packageInfo.versionName
} else {
versionCode = -1
versionName = null
}
baseTheme = PreferenceUtil.baseTheme
nowPlayingTheme = context.getString(nowPlayingScreen.titleRes)
isAdaptive = isAdaptiveColor
selectedLang = languageCode
}
}

View file

@ -1,34 +0,0 @@
package io.github.muntashirakon.music.activities.bugreport.model;
import io.github.muntashirakon.music.activities.bugreport.model.github.ExtraInfo;
public class Report {
private final String description;
private final DeviceInfo deviceInfo;
private final ExtraInfo extraInfo;
private final String title;
public Report(String title, String description, DeviceInfo deviceInfo, ExtraInfo extraInfo) {
this.title = title;
this.description = description;
this.deviceInfo = deviceInfo;
this.extraInfo = extraInfo;
}
public String getDescription() {
return description
+ "\n\n"
+ "-\n\n"
+ deviceInfo.toMarkdown()
+ "\n\n"
+ extraInfo.toMarkdown();
}
public String getTitle() {
return title;
}
}

View file

@ -1,61 +0,0 @@
package io.github.muntashirakon.music.activities.bugreport.model.github;
import java.util.LinkedHashMap;
import java.util.Map;
public class ExtraInfo {
private final Map<String, String> extraInfo = new LinkedHashMap<>();
public void put(String key, String value) {
extraInfo.put(key, value);
}
public void put(String key, boolean value) {
extraInfo.put(key, Boolean.toString(value));
}
public void put(String key, double value) {
extraInfo.put(key, Double.toString(value));
}
public void put(String key, float value) {
extraInfo.put(key, Float.toString(value));
}
public void put(String key, long value) {
extraInfo.put(key, Long.toString(value));
}
public void put(String key, int value) {
extraInfo.put(key, Integer.toString(value));
}
public void put(String key, Object value) {
extraInfo.put(key, String.valueOf(value));
}
public void remove(String key) {
extraInfo.remove(key);
}
public String toMarkdown() {
if (extraInfo.isEmpty()) {
return "";
}
StringBuilder output = new StringBuilder();
output.append("Extra info:\n" + "---\n" + "<table>\n");
for (String key : extraInfo.keySet()) {
output
.append("<tr><td>")
.append(key)
.append("</td><td>")
.append(extraInfo.get(key))
.append("</td></tr>\n");
}
output.append("</table>\n");
return output.toString();
}
}

View file

@ -1,40 +0,0 @@
package io.github.muntashirakon.music.activities.bugreport.model.github;
import android.text.TextUtils;
public class GithubLogin {
private final String apiToken;
private final String password;
private final String username;
public GithubLogin(String username, String password) {
this.username = username;
this.password = password;
this.apiToken = null;
}
public GithubLogin(String apiToken) {
this.username = null;
this.password = null;
this.apiToken = apiToken;
}
public String getApiToken() {
return apiToken;
}
public String getPassword() {
return password;
}
public String getUsername() {
return username;
}
public boolean shouldUseApiToken() {
return TextUtils.isEmpty(username) || TextUtils.isEmpty(password);
}
}

View file

@ -1,21 +0,0 @@
package io.github.muntashirakon.music.activities.bugreport.model.github;
public class GithubTarget {
private final String repository;
private final String username;
public GithubTarget(String username, String repository) {
this.username = username;
this.repository = repository;
}
public String getRepository() {
return repository;
}
public String getUsername() {
return username;
}
}

View file

@ -16,11 +16,14 @@ package io.github.muntashirakon.music.activities.saf;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import io.github.muntashirakon.music.R;
import com.heinrichreimersoftware.materialintro.app.IntroActivity; import com.heinrichreimersoftware.materialintro.app.IntroActivity;
import com.heinrichreimersoftware.materialintro.slide.SimpleSlide; import com.heinrichreimersoftware.materialintro.slide.SimpleSlide;
import io.github.muntashirakon.music.R;
/** Created by hemanths on 2019-07-31. */ /** Created by hemanths on 2019-07-31. */
public class SAFGuideActivity extends IntroActivity { public class SAFGuideActivity extends IntroActivity {

View file

@ -21,27 +21,37 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore
import android.util.Log import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import android.view.animation.OvershootInterpolator import android.view.animation.OvershootInterpolator
import android.widget.ImageView
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.R.drawable import io.github.muntashirakon.music.R.drawable
import io.github.muntashirakon.music.activities.base.AbsBaseActivity import io.github.muntashirakon.music.activities.base.AbsBaseActivity
import io.github.muntashirakon.music.activities.saf.SAFGuideActivity import io.github.muntashirakon.music.activities.saf.SAFGuideActivity
import io.github.muntashirakon.music.extensions.accentColor import io.github.muntashirakon.music.extensions.accentColor
import io.github.muntashirakon.music.extensions.colorButtons
import io.github.muntashirakon.music.extensions.hideSoftKeyboard
import io.github.muntashirakon.music.extensions.setTaskDescriptionColorAuto
import io.github.muntashirakon.music.model.ArtworkInfo import io.github.muntashirakon.music.model.ArtworkInfo
import io.github.muntashirakon.music.model.LoadingInfo import io.github.muntashirakon.music.model.AudioTagInfo
import io.github.muntashirakon.music.repository.Repository import io.github.muntashirakon.music.repository.Repository
import io.github.muntashirakon.music.util.RetroUtil
import io.github.muntashirakon.music.util.SAFUtil import io.github.muntashirakon.music.util.SAFUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.android.synthetic.main.activity_album_tag_editor.* import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jaudiotagger.audio.AudioFile import org.jaudiotagger.audio.AudioFile
import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
@ -49,20 +59,27 @@ import org.koin.android.ext.android.inject
import java.io.File import java.io.File
import java.util.* import java.util.*
abstract class AbsTagEditorActivity : AbsBaseActivity() { abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
abstract val editorImage: ImageView
val repository by inject<Repository>() val repository by inject<Repository>()
lateinit var saveFab: MaterialButton lateinit var saveFab: MaterialButton
protected var id: Long = 0 protected var id: Long = 0
private set private set
private var paletteColorPrimary: Int = 0 private var paletteColorPrimary: Int = 0
private var isInNoImageMode: Boolean = false
private var songPaths: List<String>? = null private var songPaths: List<String>? = null
private var savedSongPaths: List<String>? = null private var savedSongPaths: List<String>? = null
private val currentSongPath: String? = null private val currentSongPath: String? = null
private var savedTags: Map<FieldKey, String>? = null private var savedTags: Map<FieldKey, String>? = null
private var savedArtworkInfo: ArtworkInfo? = null private var savedArtworkInfo: ArtworkInfo? = null
protected abstract val contentViewLayout: Int private var _binding: VB? = null
protected val binding: VB get() = _binding!!
private var cacheFiles = listOf<File>()
abstract val bindingInflater: (LayoutInflater) -> VB
private lateinit var launcher: ActivityResultLauncher<IntentSenderRequest>
protected abstract fun loadImageFromFile(selectedFile: Uri?) protected abstract fun loadImageFromFile(selectedFile: Uri?)
protected val show: AlertDialog protected val show: AlertDialog
@ -76,7 +93,9 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
2 -> deleteImage() 2 -> deleteImage()
} }
} }
.setNegativeButton(R.string.action_cancel, null)
.show() .show()
.colorButtons()
internal val albumArtist: String? internal val albumArtist: String?
get() { get() {
@ -158,6 +177,15 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
} }
} }
protected val discNumber: String?
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.DISC_NO)
} catch (ignored: Exception) {
null
}
}
protected val lyrics: String? protected val lyrics: String?
get() { get() {
return try { return try {
@ -187,9 +215,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(contentViewLayout) _binding = bindingInflater.invoke(layoutInflater)
setStatusbarColorAuto() setContentView(binding.root)
setNavigationbarColorAuto()
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
saveFab = findViewById(R.id.saveTags) saveFab = findViewById(R.id.saveTags)
@ -201,6 +228,11 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
finish() finish()
} }
setUpViews() setUpViews()
launcher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
writeToFiles(getSongUris(), cacheFiles)
}
}
} }
private fun setUpViews() { private fun setUpViews() {
@ -217,7 +249,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
getString(R.string.web_search), getString(R.string.web_search),
getString(R.string.remove_cover) getString(R.string.remove_cover)
) )
editorImage?.setOnClickListener { show } editorImage.setOnClickListener { show }
} }
private fun startImagePicker() { private fun startImagePicker() {
@ -259,6 +291,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
protected abstract fun getSongPaths(): List<String> protected abstract fun getSongPaths(): List<String>
protected abstract fun getSongUris(): List<Uri>
protected fun searchWebFor(vararg keys: String) { protected fun searchWebFor(vararg keys: String) {
val stringBuilder = StringBuilder() val stringBuilder = StringBuilder()
for (key in keys) { for (key in keys) {
@ -282,20 +316,6 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
protected fun setNoImageMode() {
isInNoImageMode = true
imageContainer?.visibility = View.GONE
editorImage?.visibility = View.GONE
editorImage?.isEnabled = false
setColors(
intent.getIntExtra(
EXTRA_PALETTE,
ATHUtil.resolveColor(this, R.attr.colorPrimary)
)
)
}
protected fun dataChanged() { protected fun dataChanged() {
showFab() showFab()
} }
@ -329,27 +349,57 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
fieldKeyValueMap: Map<FieldKey, String>, fieldKeyValueMap: Map<FieldKey, String>,
artworkInfo: ArtworkInfo? artworkInfo: ArtworkInfo?
) { ) {
RetroUtil.hideSoftKeyboard(this) hideSoftKeyboard()
hideFab() hideFab()
println(fieldKeyValueMap) println(fieldKeyValueMap)
WriteTagsAsyncTask(this).execute( GlobalScope.launch {
LoadingInfo( if (VersionUtils.hasR()) {
cacheFiles = TagWriter.writeTagsToFilesR(
this@AbsTagEditorActivity, AudioTagInfo(
songPaths,
fieldKeyValueMap,
artworkInfo
)
)
val pendingIntent = MediaStore.createWriteRequest(contentResolver, getSongUris())
launcher.launch(IntentSenderRequest.Builder(pendingIntent).build())
} else {
TagWriter.writeTagsToFiles(
this@AbsTagEditorActivity, AudioTagInfo(
songPaths, songPaths,
fieldKeyValueMap, fieldKeyValueMap,
artworkInfo artworkInfo
) )
) )
} }
}
}
private fun writeTags(paths: List<String>?) { private fun writeTags(paths: List<String>?) {
WriteTagsAsyncTask(this).execute( GlobalScope.launch {
LoadingInfo( if (VersionUtils.hasR()) {
cacheFiles = TagWriter.writeTagsToFilesR(
this@AbsTagEditorActivity, AudioTagInfo(
paths, paths,
savedTags, savedTags,
savedArtworkInfo savedArtworkInfo
) )
) )
val pendingIntent = MediaStore.createWriteRequest(contentResolver, getSongUris())
launcher.launch(IntentSenderRequest.Builder(pendingIntent).build())
} else {
TagWriter.writeTagsToFiles(
this@AbsTagEditorActivity, AudioTagInfo(
paths,
savedTags,
savedArtworkInfo
)
)
}
}
} }
@ -388,9 +438,30 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
} }
} }
private fun writeToFiles(songUris: List<Uri>, cacheFiles: List<File>) {
if (cacheFiles.size == songUris.size) {
for (i in cacheFiles.indices) {
contentResolver.openOutputStream(songUris[i])?.use { output ->
cacheFiles[i].inputStream().use { input ->
input.copyTo(output)
}
}
}
}
lifecycleScope.launch {
TagWriter.scan(this@AbsTagEditorActivity, getSongPaths())
}
}
override fun onDestroy() {
super.onDestroy()
// Delete Cache Files
cacheFiles.forEach { file ->
file.delete()
}
}
companion object { companion object {
const val EXTRA_ID = "extra_id" const val EXTRA_ID = "extra_id"
const val EXTRA_PALETTE = "extra_palette" const val EXTRA_PALETTE = "extra_palette"
private val TAG = AbsTagEditorActivity::class.java.simpleName private val TAG = AbsTagEditorActivity::class.java.simpleName

View file

@ -22,33 +22,34 @@ import android.graphics.Color
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.transition.Slide import android.transition.Slide
import android.view.LayoutInflater
import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
import code.name.monkey.appthemehelper.util.ATHUtil import androidx.core.widget.doAfterTextChanged
import code.name.monkey.appthemehelper.util.MaterialUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.extensions.appHandleColor import io.github.muntashirakon.music.databinding.ActivityAlbumTagEditorBinding
import io.github.muntashirakon.music.glide.palette.BitmapPaletteTranscoder import io.github.muntashirakon.music.extensions.*
import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper
import io.github.muntashirakon.music.model.ArtworkInfo import io.github.muntashirakon.music.model.ArtworkInfo
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.ImageUtil import io.github.muntashirakon.music.util.ImageUtil
import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.RetroColorUtil.generatePalette import io.github.muntashirakon.music.util.RetroColorUtil.generatePalette
import io.github.muntashirakon.music.util.RetroColorUtil.getColor import io.github.muntashirakon.music.util.RetroColorUtil.getColor
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.target.ImageViewTarget
import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.transition.Transition
import java.util.* import com.google.android.material.shape.MaterialShapeDrawable
import kotlinx.android.synthetic.main.activity_album_tag_editor.*
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
import java.util.*
class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBinding>() {
override val contentViewLayout: Int override val bindingInflater: (LayoutInflater) -> ActivityAlbumTagEditorBinding =
get() = R.layout.activity_album_tag_editor ActivityAlbumTagEditorBinding::inflate
private fun windowEnterTransition() { private fun windowEnterTransition() {
val slide = Slide() val slide = Slide()
@ -60,54 +61,19 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
window.enterTransition = slide window.enterTransition = slide
} }
override fun loadImageFromFile(selectedFile: Uri?) {
Glide.with(this@AlbumTagEditorActivity).load(selectedFile).asBitmap()
.transcode(BitmapPaletteTranscoder(this), BitmapPaletteWrapper::class.java)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : SimpleTarget<BitmapPaletteWrapper>() {
override fun onResourceReady(
resource: BitmapPaletteWrapper?,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>?
) {
getColor(resource?.palette, Color.TRANSPARENT)
albumArtBitmap = resource?.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
setImageBitmap(
albumArtBitmap,
getColor(
resource?.palette,
ATHUtil.resolveColor(
this@AlbumTagEditorActivity,
R.attr.defaultFooterColor
)
)
)
deleteAlbumArt = false
dataChanged()
setResult(Activity.RESULT_OK)
}
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable)
Toast.makeText(this@AlbumTagEditorActivity, e.toString(), Toast.LENGTH_LONG)
.show()
}
})
}
private var albumArtBitmap: Bitmap? = null private var albumArtBitmap: Bitmap? = null
private var deleteAlbumArt: Boolean = false private var deleteAlbumArt: Boolean = false
private fun setupToolbar() { private fun setupToolbar() {
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface)) setSupportActionBar(binding.toolbar)
setSupportActionBar(toolbar) binding.appBarLayout?.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(this)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
window.sharedElementsUseOverlay = true window.sharedElementsUseOverlay = true
imageContainer?.transitionName = getString(R.string.transition_album_art) binding.imageContainer.transitionName = getString(R.string.transition_album_art)
windowEnterTransition() windowEnterTransition()
setUpViews() setUpViews()
setupToolbar() setupToolbar()
@ -116,22 +82,23 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
private fun setUpViews() { private fun setUpViews() {
fillViewsWithFileTags() fillViewsWithFileTags()
MaterialUtil.setTint(yearContainer, false) binding.yearContainer.setTint(false)
MaterialUtil.setTint(genreContainer, false) binding.genreContainer.setTint(false)
MaterialUtil.setTint(albumTitleContainer, false) binding.albumTitleContainer.setTint(false)
MaterialUtil.setTint(albumArtistContainer, false) binding.albumArtistContainer.setTint(false)
albumText.appHandleColor().addTextChangedListener(this) binding.albumText.appHandleColor().doAfterTextChanged { dataChanged() }
albumArtistText.appHandleColor().addTextChangedListener(this) binding.albumArtistText.appHandleColor().doAfterTextChanged { dataChanged() }
genreTitle.appHandleColor().addTextChangedListener(this) binding.genreTitle.appHandleColor().doAfterTextChanged { dataChanged() }
yearTitle.appHandleColor().addTextChangedListener(this) binding.yearTitle.appHandleColor().doAfterTextChanged { dataChanged() }
} }
private fun fillViewsWithFileTags() { private fun fillViewsWithFileTags() {
albumText.setText(albumTitle) binding.albumText.setText(albumTitle)
albumArtistText.setText(albumArtistName) binding.albumArtistText.setText(albumArtistName)
genreTitle.setText(genreName) binding.genreTitle.setText(genreName)
yearTitle.setText(songYear) binding.yearTitle.setText(songYear)
println(albumTitle + albumArtistName)
} }
override fun loadCurrentImage() { override fun loadCurrentImage() {
@ -140,41 +107,68 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
bitmap, bitmap,
getColor( getColor(
generatePalette(bitmap), generatePalette(bitmap),
ATHUtil.resolveColor(this, R.attr.defaultFooterColor) defaultFooterColor()
) )
) )
deleteAlbumArt = false deleteAlbumArt = false
} }
private fun toastLoadingFailed() { private fun toastLoadingFailed() {
Toast.makeText( showToast(R.string.could_not_download_album_cover)
this@AlbumTagEditorActivity,
R.string.could_not_download_album_cover,
Toast.LENGTH_SHORT
).show()
} }
override fun searchImageOnWeb() { override fun searchImageOnWeb() {
searchWebFor(albumText.text.toString(), albumArtistText.text.toString()) searchWebFor(binding.albumText.text.toString(), binding.albumArtistText.text.toString())
} }
override fun deleteImage() { override fun deleteImage() {
setImageBitmap( setImageBitmap(
BitmapFactory.decodeResource(resources, R.drawable.default_audio_art), BitmapFactory.decodeResource(resources, R.drawable.default_audio_art),
ATHUtil.resolveColor(this, R.attr.defaultFooterColor) defaultFooterColor()
) )
deleteAlbumArt = true deleteAlbumArt = true
dataChanged() dataChanged()
} }
override fun loadImageFromFile(selectedFile: Uri?) {
GlideApp.with(this@AlbumTagEditorActivity).asBitmapPalette().load(selectedFile)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
transition: Transition<in BitmapPaletteWrapper>?
) {
getColor(resource.palette, Color.TRANSPARENT)
albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
setImageBitmap(
albumArtBitmap,
getColor(
resource.palette,
defaultFooterColor()
)
)
deleteAlbumArt = false
dataChanged()
setResult(Activity.RESULT_OK)
}
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
showToast(R.string.error_load_failed, Toast.LENGTH_LONG)
}
override fun setResource(resource: BitmapPaletteWrapper?) {}
})
}
override fun save() { override fun save() {
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java) val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.ALBUM] = albumText.text.toString() fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString()
// android seems not to recognize album_artist field so we additionally write the normal artist field // android seems not to recognize album_artist field so we additionally write the normal artist field
fieldKeyValueMap[FieldKey.ARTIST] = albumArtistText.text.toString() fieldKeyValueMap[FieldKey.ARTIST] = binding.albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = albumArtistText.text.toString() fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.GENRE] = genreTitle.text.toString() fieldKeyValueMap[FieldKey.GENRE] = binding.genreTitle.text.toString()
fieldKeyValueMap[FieldKey.YEAR] = yearTitle.text.toString() fieldKeyValueMap[FieldKey.YEAR] = binding.yearTitle.text.toString()
writeValuesToFiles( writeValuesToFiles(
fieldKeyValueMap, fieldKeyValueMap,
@ -191,20 +185,28 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
.map(Song::data) .map(Song::data)
} }
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { override fun getSongUris(): List<Uri> = repository.albumById(id).songs.map {
} MusicUtil.getSongFileUri(it.id)
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable) {
dataChanged()
} }
override fun setColors(color: Int) { override fun setColors(color: Int) {
super.setColors(color) super.setColors(color)
saveFab.backgroundTintList = ColorStateList.valueOf(color) saveFab.backgroundTintList = ColorStateList.valueOf(color)
saveFab.backgroundTintList = ColorStateList.valueOf(color)
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this,
color.isColorLight
)
).also {
saveFab.iconTint = it
saveFab.setTextColor(it)
} }
}
override val editorImage: ImageView
get() = binding.editorImage
companion object { companion object {

View file

@ -14,110 +14,196 @@
*/ */
package io.github.muntashirakon.music.activities.tageditor package io.github.muntashirakon.music.activities.tageditor
import android.annotation.SuppressLint
import android.app.Activity
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.view.LayoutInflater
import android.text.TextWatcher import android.widget.ImageView
import code.name.monkey.appthemehelper.util.ATHUtil import android.widget.Toast
import code.name.monkey.appthemehelper.util.MaterialUtil import androidx.core.widget.doAfterTextChanged
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.extensions.appHandleColor import io.github.muntashirakon.music.databinding.ActivitySongTagEditorBinding
import io.github.muntashirakon.music.extensions.*
import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper
import io.github.muntashirakon.music.model.ArtworkInfo
import io.github.muntashirakon.music.repository.SongRepository import io.github.muntashirakon.music.repository.SongRepository
import kotlinx.android.synthetic.main.activity_song_tag_editor.* import io.github.muntashirakon.music.util.ImageUtil
import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.RetroColorUtil
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.target.ImageViewTarget
import com.bumptech.glide.request.transition.Transition
import com.google.android.material.shape.MaterialShapeDrawable
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import java.util.* import java.util.*
class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher { class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>() {
override val bindingInflater: (LayoutInflater) -> ActivitySongTagEditorBinding =
ActivitySongTagEditorBinding::inflate
override val contentViewLayout: Int
get() = R.layout.activity_song_tag_editor
private val songRepository by inject<SongRepository>() private val songRepository by inject<SongRepository>()
private var albumArtBitmap: Bitmap? = null
private var deleteAlbumArt: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setNoImageMode()
setUpViews() setUpViews()
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface)) setSupportActionBar(binding.toolbar)
setSupportActionBar(toolbar) binding.appBarLayout?.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(this)
} }
@SuppressLint("ClickableViewAccessibility")
private fun setUpViews() { private fun setUpViews() {
fillViewsWithFileTags() fillViewsWithFileTags()
MaterialUtil.setTint(songTextContainer, false) binding.songTextContainer.setTint(false)
MaterialUtil.setTint(composerContainer, false) binding.composerContainer.setTint(false)
MaterialUtil.setTint(albumTextContainer, false) binding.albumTextContainer.setTint(false)
MaterialUtil.setTint(artistContainer, false) binding.artistContainer.setTint(false)
MaterialUtil.setTint(albumArtistContainer, false) binding.albumArtistContainer.setTint(false)
MaterialUtil.setTint(yearContainer, false) binding.yearContainer.setTint(false)
MaterialUtil.setTint(genreContainer, false) binding.genreContainer.setTint(false)
MaterialUtil.setTint(trackNumberContainer, false) binding.trackNumberContainer.setTint(false)
MaterialUtil.setTint(lyricsContainer, false) binding.discNumberContainer.setTint(false)
binding.lyricsContainer.setTint(false)
songText.appHandleColor().addTextChangedListener(this) binding.songText.appHandleColor().doAfterTextChanged { dataChanged() }
albumText.appHandleColor().addTextChangedListener(this) binding.albumText.appHandleColor().doAfterTextChanged { dataChanged() }
albumArtistText.appHandleColor().addTextChangedListener(this) binding.albumArtistText.appHandleColor().doAfterTextChanged { dataChanged() }
artistText.appHandleColor().addTextChangedListener(this) binding.artistText.appHandleColor().doAfterTextChanged { dataChanged() }
genreText.appHandleColor().addTextChangedListener(this) binding.genreText.appHandleColor().doAfterTextChanged { dataChanged() }
yearText.appHandleColor().addTextChangedListener(this) binding.yearText.appHandleColor().doAfterTextChanged { dataChanged() }
trackNumberText.appHandleColor().addTextChangedListener(this) binding.trackNumberText.appHandleColor().doAfterTextChanged { dataChanged() }
lyricsText.appHandleColor().addTextChangedListener(this) binding.discNumberText.appHandleColor().doAfterTextChanged { dataChanged() }
songComposerText.appHandleColor().addTextChangedListener(this) binding.lyricsText.appHandleColor().doAfterTextChanged { dataChanged() }
binding.songComposerText.appHandleColor().doAfterTextChanged { dataChanged() }
} }
private fun fillViewsWithFileTags() { private fun fillViewsWithFileTags() {
songText.setText(songTitle) binding.songText.setText(songTitle)
albumArtistText.setText(albumArtist) binding.albumArtistText.setText(albumArtist)
albumText.setText(albumTitle) binding.albumText.setText(albumTitle)
artistText.setText(artistName) binding.artistText.setText(artistName)
genreText.setText(genreName) binding.genreText.setText(genreName)
yearText.setText(songYear) binding.yearText.setText(songYear)
trackNumberText.setText(trackNumber) binding.trackNumberText.setText(trackNumber)
lyricsText.setText(lyrics) binding.discNumberText.setText(discNumber)
songComposerText.setText(composer) binding.lyricsText.setText(lyrics)
binding.songComposerText.setText(composer)
println(songTitle + songYear)
} }
override fun loadCurrentImage() { override fun loadCurrentImage() {
val bitmap = albumArt
setImageBitmap(
bitmap,
RetroColorUtil.getColor(
RetroColorUtil.generatePalette(bitmap),
defaultFooterColor()
)
)
deleteAlbumArt = false
} }
override fun searchImageOnWeb() { override fun searchImageOnWeb() {
searchWebFor(binding.songText.text.toString(), binding.artistText.text.toString())
} }
override fun deleteImage() { override fun deleteImage() {
setImageBitmap(
BitmapFactory.decodeResource(resources, R.drawable.default_audio_art),
defaultFooterColor()
)
deleteAlbumArt = true
dataChanged()
}
override fun setColors(color: Int) {
super.setColors(color)
saveFab.backgroundTintList = ColorStateList.valueOf(color)
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this,
color.isColorLight
)
).also {
saveFab.iconTint = it
saveFab.setTextColor(it)
}
} }
override fun save() { override fun save() {
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java) val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.TITLE] = songText.text.toString() fieldKeyValueMap[FieldKey.TITLE] = binding.songText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM] = albumText.text.toString() fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString()
fieldKeyValueMap[FieldKey.ARTIST] = artistText.text.toString() fieldKeyValueMap[FieldKey.ARTIST] = binding.artistText.text.toString()
fieldKeyValueMap[FieldKey.GENRE] = genreText.text.toString() fieldKeyValueMap[FieldKey.GENRE] = binding.genreText.text.toString()
fieldKeyValueMap[FieldKey.YEAR] = yearText.text.toString() fieldKeyValueMap[FieldKey.YEAR] = binding.yearText.text.toString()
fieldKeyValueMap[FieldKey.TRACK] = trackNumberText.text.toString() fieldKeyValueMap[FieldKey.TRACK] = binding.trackNumberText.text.toString()
fieldKeyValueMap[FieldKey.LYRICS] = lyricsText.text.toString() fieldKeyValueMap[FieldKey.DISC_NO] = binding.discNumberText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = albumArtistText.text.toString() fieldKeyValueMap[FieldKey.LYRICS] = binding.lyricsText.text.toString()
fieldKeyValueMap[FieldKey.COMPOSER] = songComposerText.text.toString() fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString()
writeValuesToFiles(fieldKeyValueMap, null) fieldKeyValueMap[FieldKey.COMPOSER] = binding.songComposerText.text.toString()
writeValuesToFiles(
fieldKeyValueMap, when {
deleteAlbumArt -> ArtworkInfo(id, null)
albumArtBitmap == null -> null
else -> ArtworkInfo(id, albumArtBitmap!!)
}
)
} }
override fun getSongPaths(): List<String> = listOf(songRepository.song(id).data) override fun getSongPaths(): List<String> = listOf(songRepository.song(id).data)
override fun getSongUris(): List<Uri> = listOf(MusicUtil.getSongFileUri(id))
override fun loadImageFromFile(selectedFile: Uri?) { override fun loadImageFromFile(selectedFile: Uri?) {
} GlideApp.with(this@SongTagEditorActivity).asBitmapPalette().load(selectedFile)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { .into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
} override fun onResourceReady(
resource: BitmapPaletteWrapper,
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { transition: Transition<in BitmapPaletteWrapper>?
} ) {
RetroColorUtil.getColor(resource.palette, Color.TRANSPARENT)
override fun afterTextChanged(s: Editable) { albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
setImageBitmap(
albumArtBitmap,
RetroColorUtil.getColor(
resource.palette,
defaultFooterColor()
)
)
deleteAlbumArt = false
dataChanged() dataChanged()
setResult(Activity.RESULT_OK)
}
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
showToast(R.string.error_load_failed, Toast.LENGTH_LONG)
}
override fun setResource(resource: BitmapPaletteWrapper?) {}
})
} }
companion object { companion object {
val TAG: String = SongTagEditorActivity::class.java.simpleName val TAG: String = SongTagEditorActivity::class.java.simpleName
} }
override val editorImage: ImageView
get() = binding.editorImage
} }

View file

@ -0,0 +1,201 @@
package io.github.muntashirakon.music.activities.tageditor
import android.app.Activity
import android.content.Context
import android.graphics.Bitmap
import android.media.MediaScannerConnection
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import io.github.muntashirakon.music.extensions.showToast
import io.github.muntashirakon.music.misc.UpdateToastMediaScannerCompletionListener
import io.github.muntashirakon.music.model.AudioTagInfo
import io.github.muntashirakon.music.util.MusicUtil.createAlbumArtFile
import io.github.muntashirakon.music.util.MusicUtil.deleteAlbumArt
import io.github.muntashirakon.music.util.MusicUtil.insertAlbumArt
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.audio.exceptions.CannotReadException
import org.jaudiotagger.audio.exceptions.CannotWriteException
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException
import org.jaudiotagger.tag.TagException
import org.jaudiotagger.tag.images.AndroidArtwork
import org.jaudiotagger.tag.images.Artwork
import java.io.File
import java.io.IOException
class TagWriter {
companion object {
suspend fun scan(context: Context, toBeScanned: List<String?>?) {
if (toBeScanned == null || toBeScanned.isEmpty()) {
Log.i("scan", "scan: Empty")
context.showToast( "Scan file from folder")
return
}
MediaScannerConnection.scanFile(
context,
toBeScanned.toTypedArray(),
null,
withContext(Dispatchers.Main) {
if (context is Activity) UpdateToastMediaScannerCompletionListener(
context, toBeScanned
) else null
}
)
}
suspend fun writeTagsToFiles(context: Context, info: AudioTagInfo) {
withContext(Dispatchers.IO) {
runCatching {
var artwork: Artwork? = null
var albumArtFile: File? = null
if (info.artworkInfo?.artwork != null) {
try {
albumArtFile = createAlbumArtFile(context).canonicalFile
info.artworkInfo.artwork.compress(
Bitmap.CompressFormat.JPEG,
100,
albumArtFile.outputStream()
)
artwork = AndroidArtwork.createArtworkFromFile(albumArtFile)
} catch (e: IOException) {
e.printStackTrace()
}
}
var wroteArtwork = false
var deletedArtwork = false
for (filePath in info.filePaths!!) {
try {
val audioFile = AudioFileIO.read(File(filePath))
val tag = audioFile.tagOrCreateAndSetDefault
if (info.fieldKeyValueMap != null) {
for ((key, value) in info.fieldKeyValueMap) {
try {
tag.setField(key, value)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
if (info.artworkInfo != null) {
if (info.artworkInfo.artwork == null) {
tag.deleteArtworkField()
deletedArtwork = true
} else if (artwork != null) {
tag.deleteArtworkField()
tag.setField(artwork)
wroteArtwork = true
}
}
audioFile.commit()
} catch (e: CannotReadException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
} catch (e: CannotWriteException) {
e.printStackTrace()
} catch (e: TagException) {
e.printStackTrace()
} catch (e: ReadOnlyFileException) {
e.printStackTrace()
} catch (e: InvalidAudioFrameException) {
e.printStackTrace()
}
}
if (wroteArtwork) {
insertAlbumArt(context, info.artworkInfo!!.albumId, albumArtFile!!.path)
} else if (deletedArtwork) {
deleteAlbumArt(context, info.artworkInfo!!.albumId)
}
scan(context, info.filePaths)
}.onFailure {
it.printStackTrace()
}
}
}
@RequiresApi(Build.VERSION_CODES.R)
suspend fun writeTagsToFilesR(context: Context, info: AudioTagInfo): List<File> =
withContext(Dispatchers.IO) {
val cacheFiles = mutableListOf<File>()
runCatching {
var artwork: Artwork? = null
var albumArtFile: File? = null
if (info.artworkInfo?.artwork != null) {
try {
albumArtFile = createAlbumArtFile(context).canonicalFile
info.artworkInfo.artwork.compress(
Bitmap.CompressFormat.JPEG,
100,
albumArtFile.outputStream()
)
artwork = AndroidArtwork.createArtworkFromFile(albumArtFile)
} catch (e: IOException) {
e.printStackTrace()
}
}
var wroteArtwork = false
var deletedArtwork = false
for (filePath in info.filePaths!!) {
try {
val originFile = File(filePath)
val cacheFile = File(context.cacheDir, originFile.name)
cacheFiles.add(cacheFile)
originFile.inputStream().use { input ->
cacheFile.outputStream().use { output ->
input.copyTo(output)
}
}
val audioFile = AudioFileIO.read(cacheFile)
val tag = audioFile.tagOrCreateAndSetDefault
if (info.fieldKeyValueMap != null) {
for ((key, value) in info.fieldKeyValueMap) {
try {
tag.setField(key, value)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
if (info.artworkInfo != null) {
if (info.artworkInfo.artwork == null) {
tag.deleteArtworkField()
deletedArtwork = true
} else if (artwork != null) {
tag.deleteArtworkField()
tag.setField(artwork)
wroteArtwork = true
}
}
audioFile.commit()
} catch (e: CannotReadException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
} catch (e: CannotWriteException) {
e.printStackTrace()
} catch (e: TagException) {
e.printStackTrace()
} catch (e: ReadOnlyFileException) {
e.printStackTrace()
} catch (e: InvalidAudioFrameException) {
e.printStackTrace()
}
}
if (wroteArtwork) {
insertAlbumArt(context, info.artworkInfo!!.albumId, albumArtFile!!.path)
} else if (deletedArtwork) {
deleteAlbumArt(context, info.artworkInfo!!.albumId)
}
}.onFailure {
it.printStackTrace()
}
cacheFiles
}
}
}

View file

@ -1,152 +0,0 @@
package io.github.muntashirakon.music.activities.tageditor;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.media.MediaScannerConnection;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.afollestad.materialdialogs.MaterialDialog;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.CannotWriteException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.Tag;
import org.jaudiotagger.tag.TagException;
import org.jaudiotagger.tag.images.Artwork;
import org.jaudiotagger.tag.images.ArtworkFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import io.github.muntashirakon.music.R;
import io.github.muntashirakon.music.misc.DialogAsyncTask;
import io.github.muntashirakon.music.misc.UpdateToastMediaScannerCompletionListener;
import io.github.muntashirakon.music.model.LoadingInfo;
import io.github.muntashirakon.music.util.MusicUtil;
public class WriteTagsAsyncTask extends DialogAsyncTask<LoadingInfo, Integer, List<String>> {
public WriteTagsAsyncTask(Context context) {
super(context);
}
@Override
protected List<String> doInBackground(LoadingInfo... params) {
try {
LoadingInfo info = params[0];
Artwork artwork = null;
File albumArtFile = null;
if (info.getArtworkInfo() != null && info.getArtworkInfo().getArtwork() != null) {
try {
albumArtFile = MusicUtil.INSTANCE.createAlbumArtFile().getCanonicalFile();
info.getArtworkInfo().getArtwork().compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile));
artwork = ArtworkFactory.createArtworkFromFile(albumArtFile);
} catch (IOException e) {
e.printStackTrace();
}
}
int counter = 0;
boolean wroteArtwork = false;
boolean deletedArtwork = false;
for (String filePath : info.getFilePaths()) {
publishProgress(++counter, info.getFilePaths().size());
try {
AudioFile audioFile = AudioFileIO.read(new File(filePath));
Tag tag = audioFile.getTagOrCreateAndSetDefault();
if (info.getFieldKeyValueMap() != null) {
for (Map.Entry<FieldKey, String> entry : info.getFieldKeyValueMap().entrySet()) {
try {
tag.setField(entry.getKey(), entry.getValue());
} catch (Exception e) {
e.printStackTrace();
}
}
}
if (info.getArtworkInfo() != null) {
if (info.getArtworkInfo().getArtwork() == null) {
tag.deleteArtworkField();
deletedArtwork = true;
} else if (artwork != null) {
tag.deleteArtworkField();
tag.setField(artwork);
wroteArtwork = true;
}
}
audioFile.commit();
} catch (@NonNull CannotReadException | IOException | CannotWriteException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
e.printStackTrace();
}
}
Context context = getContext();
if (context != null) {
if (wroteArtwork) {
MusicUtil.INSTANCE.
insertAlbumArt(context, info.getArtworkInfo().getAlbumId(), albumArtFile.getPath());
} else if (deletedArtwork) {
MusicUtil.INSTANCE.deleteAlbumArt(context, info.getArtworkInfo().getAlbumId());
}
}
return info.getFilePaths();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
protected void onPostExecute(List<String> toBeScanned) {
super.onPostExecute(toBeScanned);
scan(toBeScanned);
}
@Override
protected void onCancelled(List<String> toBeScanned) {
super.onCancelled(toBeScanned);
scan(toBeScanned);
}
private void scan(List<String> toBeScanned) {
Context context = getContext();
if (toBeScanned == null || toBeScanned.isEmpty()) {
Log.i("scan", "scan: Empty");
Toast.makeText(context, "Scan file from folder", Toast.LENGTH_SHORT).show();
return;
}
MediaScannerConnection.scanFile(context, toBeScanned.toArray(new String[0]), null, context instanceof Activity ? new UpdateToastMediaScannerCompletionListener((Activity) context, toBeScanned) : null);
}
@Override
protected Dialog createDialog(@NonNull Context context) {
return new MaterialDialog.Builder(context)
.title(R.string.saving_changes)
.cancelable(false)
.progress(false, 0)
.build();
}
@Override
protected void onProgressUpdate(@NonNull Dialog dialog, Integer... values) {
super.onProgressUpdate(dialog, values);
((MaterialDialog) dialog).setMaxProgress(values[1]);
((MaterialDialog) dialog).setProgress(values[0]);
}
}

View file

@ -1,140 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package io.github.muntashirakon.music.adapter;
import android.annotation.SuppressLint;
import android.content.res.ColorStateList;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import code.name.monkey.appthemehelper.ThemeStore;
import io.github.muntashirakon.music.R;
import io.github.muntashirakon.music.model.CategoryInfo;
import io.github.muntashirakon.music.util.SwipeAndDragHelper;
import com.google.android.material.checkbox.MaterialCheckBox;
import java.util.List;
public class CategoryInfoAdapter extends RecyclerView.Adapter<CategoryInfoAdapter.ViewHolder>
implements SwipeAndDragHelper.ActionCompletionContract {
private List<CategoryInfo> categoryInfos;
private ItemTouchHelper touchHelper;
public CategoryInfoAdapter() {
SwipeAndDragHelper swipeAndDragHelper = new SwipeAndDragHelper(this);
touchHelper = new ItemTouchHelper(swipeAndDragHelper);
}
public void attachToRecyclerView(RecyclerView recyclerView) {
touchHelper.attachToRecyclerView(recyclerView);
}
@NonNull
public List<CategoryInfo> getCategoryInfos() {
return categoryInfos;
}
public void setCategoryInfos(@NonNull List<CategoryInfo> categoryInfos) {
this.categoryInfos = categoryInfos;
notifyDataSetChanged();
}
@Override
public int getItemCount() {
return categoryInfos.size();
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onBindViewHolder(@NonNull CategoryInfoAdapter.ViewHolder holder, int position) {
CategoryInfo categoryInfo = categoryInfos.get(position);
holder.checkBox.setChecked(categoryInfo.isVisible());
holder.title.setText(
holder.title.getResources().getString(categoryInfo.getCategory().getStringRes()));
holder.itemView.setOnClickListener(
v -> {
if (!(categoryInfo.isVisible() && isLastCheckedCategory(categoryInfo))) {
categoryInfo.setVisible(!categoryInfo.isVisible());
holder.checkBox.setChecked(categoryInfo.isVisible());
} else {
Toast.makeText(
holder.itemView.getContext(),
R.string.you_have_to_select_at_least_one_category,
Toast.LENGTH_SHORT)
.show();
}
});
holder.dragView.setOnTouchListener(
(view, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
touchHelper.startDrag(holder);
}
return false;
});
}
@Override
@NonNull
public CategoryInfoAdapter.ViewHolder onCreateViewHolder(
@NonNull ViewGroup parent, int viewType) {
View view =
LayoutInflater.from(parent.getContext())
.inflate(R.layout.preference_dialog_library_categories_listitem, parent, false);
return new ViewHolder(view);
}
@Override
public void onViewMoved(int oldPosition, int newPosition) {
CategoryInfo categoryInfo = categoryInfos.get(oldPosition);
categoryInfos.remove(oldPosition);
categoryInfos.add(newPosition, categoryInfo);
notifyItemMoved(oldPosition, newPosition);
}
private boolean isLastCheckedCategory(CategoryInfo categoryInfo) {
if (categoryInfo.isVisible()) {
for (CategoryInfo c : categoryInfos) {
if (c != categoryInfo && c.isVisible()) {
return false;
}
}
}
return true;
}
static class ViewHolder extends RecyclerView.ViewHolder {
private MaterialCheckBox checkBox;
private View dragView;
private TextView title;
ViewHolder(View view) {
super(view);
checkBox = view.findViewById(R.id.checkbox);
checkBox.setButtonTintList(
ColorStateList.valueOf(ThemeStore.Companion.accentColor(checkBox.getContext())));
title = view.findViewById(R.id.title);
dragView = view.findViewById(R.id.drag_view);
}
}
}

View file

@ -0,0 +1,116 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package io.github.muntashirakon.music.adapter
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.databinding.PreferenceDialogLibraryCategoriesListitemBinding
import io.github.muntashirakon.music.extensions.showToast
import io.github.muntashirakon.music.model.CategoryInfo
import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.SwipeAndDragHelper
import io.github.muntashirakon.music.util.SwipeAndDragHelper.ActionCompletionContract
class CategoryInfoAdapter : RecyclerView.Adapter<CategoryInfoAdapter.ViewHolder>(),
ActionCompletionContract {
var categoryInfos: MutableList<CategoryInfo> =
PreferenceUtil.libraryCategory.toMutableList()
@SuppressLint("NotifyDataSetChanged")
set(value) {
field = value
notifyDataSetChanged()
}
private val touchHelper: ItemTouchHelper
fun attachToRecyclerView(recyclerView: RecyclerView?) {
touchHelper.attachToRecyclerView(recyclerView)
}
override fun getItemCount(): Int {
return categoryInfos.size
}
@SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val categoryInfo = categoryInfos[position]
holder.binding.checkbox.isChecked = categoryInfo.visible
holder.binding.title.text =
holder.binding.title.resources.getString(categoryInfo.category.stringRes)
holder.itemView.setOnClickListener {
if (!(categoryInfo.visible && isLastCheckedCategory(categoryInfo))) {
categoryInfo.visible = !categoryInfo.visible
holder.binding.checkbox.isChecked = categoryInfo.visible
} else {
holder.itemView.context.showToast(R.string.you_have_to_select_at_least_one_category)
}
}
holder.binding.dragView.setOnTouchListener { _: View?, event: MotionEvent ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
touchHelper.startDrag(holder)
}
false
}
}
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int
): ViewHolder {
return ViewHolder(
PreferenceDialogLibraryCategoriesListitemBinding.inflate(
LayoutInflater.from(
parent.context
), parent, false
)
)
}
override fun onViewMoved(oldPosition: Int, newPosition: Int) {
val categoryInfo = categoryInfos[oldPosition]
categoryInfos.removeAt(oldPosition)
categoryInfos.add(newPosition, categoryInfo)
notifyItemMoved(oldPosition, newPosition)
}
private fun isLastCheckedCategory(categoryInfo: CategoryInfo): Boolean {
if (categoryInfo.visible) {
for (c in categoryInfos) {
if (c !== categoryInfo && c.visible) {
return false
}
}
}
return true
}
class ViewHolder(val binding: PreferenceDialogLibraryCategoriesListitemBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.checkbox.buttonTintList =
ColorStateList.valueOf(accentColor(binding.checkbox.context))
}
}
init {
val swipeAndDragHelper = SwipeAndDragHelper(this)
touchHelper = ItemTouchHelper(swipeAndDragHelper)
}
}

View file

@ -1,97 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package io.github.muntashirakon.music.adapter
import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.model.Contributor
import io.github.muntashirakon.music.util.RetroUtil.openUrl
import io.github.muntashirakon.music.views.RetroShapeableImageView
import com.bumptech.glide.Glide
class ContributorAdapter(
private var contributors: List<Contributor>
) : RecyclerView.Adapter<ContributorAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return if (viewType == HEADER) {
ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_contributor_header,
parent,
false
)
)
} else ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_contributor,
parent,
false
)
)
}
companion object {
const val HEADER: Int = 0
const val ITEM: Int = 1
}
override fun getItemViewType(position: Int): Int {
return if (position == 0) {
HEADER
} else {
ITEM
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val contributor = contributors[position]
holder.bindData(contributor)
holder.itemView.setOnClickListener {
openUrl(it?.context as Activity, contributors[position].link)
}
}
override fun getItemCount(): Int {
return contributors.size
}
fun swapData(it: List<Contributor>) {
contributors = it
notifyDataSetChanged()
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.findViewById(R.id.title)
val text: TextView = itemView.findViewById(R.id.text)
val image: RetroShapeableImageView = itemView.findViewById(R.id.icon)
internal fun bindData(contributor: Contributor) {
title.text = contributor.name
text.text = contributor.summary
Glide.with(image.context)
.load(contributor.image)
.error(R.drawable.ic_account)
.placeholder(R.drawable.ic_account)
.dontAnimate()
.into(image)
}
}
}

View file

@ -14,16 +14,22 @@
*/ */
package io.github.muntashirakon.music.adapter package io.github.muntashirakon.music.adapter
import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.ViewCompat import android.view.ViewOutlineProvider
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder import io.github.muntashirakon.music.databinding.ItemGenreBinding
import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
import io.github.muntashirakon.music.interfaces.IGenreClickListener import io.github.muntashirakon.music.interfaces.IGenreClickListener
import io.github.muntashirakon.music.model.Genre import io.github.muntashirakon.music.model.Genre
import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import java.util.* import java.util.*
/** /**
@ -33,37 +39,72 @@ import java.util.*
class GenreAdapter( class GenreAdapter(
private val activity: FragmentActivity, private val activity: FragmentActivity,
var dataSet: List<Genre>, var dataSet: List<Genre>,
private val mItemLayoutRes: Int,
private val listener: IGenreClickListener private val listener: IGenreClickListener
) : RecyclerView.Adapter<GenreAdapter.ViewHolder>() { ) : RecyclerView.Adapter<GenreAdapter.ViewHolder>() {
init {
this.setHasStableIds(true)
}
override fun getItemId(position: Int): Long {
return dataSet[position].id
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(activity).inflate(mItemLayoutRes, parent, false)) return ViewHolder(ItemGenreBinding.inflate(LayoutInflater.from(activity), parent, false))
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val genre = dataSet[position] val genre = dataSet[position]
holder.title?.text = genre.name holder.binding.title.text = genre.name
holder.text?.text = String.format( holder.binding.text.text = String.format(
Locale.getDefault(), Locale.getDefault(),
"%d %s", "%d %s",
genre.songCount, genre.songCount,
if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(R.string.song) if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(R.string.song)
) )
loadGenreImage(genre, holder)
}
private fun loadGenreImage(genre: Genre, holder: GenreAdapter.ViewHolder) {
val genreSong = MusicUtil.songByGenre(genre.id)
GlideApp.with(activity)
.asBitmapPalette()
.load(RetroGlideExtension.getSongModel(genreSong))
.songCoverOptions(genreSong)
.into(object : RetroMusicColoredTarget(holder.binding.image) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(holder, colors)
}
})
// Just for a bit of shadow around image
holder.binding.image.outlineProvider = ViewOutlineProvider.BOUNDS
}
private fun setColors(holder: ViewHolder, color: MediaNotificationProcessor) {
holder.binding.imageContainerCard.setCardBackgroundColor(color.backgroundColor)
holder.binding.title.setTextColor(color.primaryTextColor)
holder.binding.text.setTextColor(color.secondaryTextColor)
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
return dataSet.size return dataSet.size
} }
@SuppressLint("NotifyDataSetChanged")
fun swapDataSet(list: List<Genre>) { fun swapDataSet(list: List<Genre>) {
dataSet = list dataSet = list
notifyDataSetChanged() notifyDataSetChanged()
} }
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { inner class ViewHolder(val binding: ItemGenreBinding) : RecyclerView.ViewHolder(binding.root),
View.OnClickListener {
override fun onClick(v: View?) { override fun onClick(v: View?) {
ViewCompat.setTransitionName(itemView, "genre")
listener.onClickGenre(dataSet[layoutPosition], itemView) listener.onClickGenre(dataSet[layoutPosition], itemView)
} }
init {
itemView.setOnClickListener(this)
}
} }
} }

View file

@ -14,35 +14,30 @@
*/ */
package io.github.muntashirakon.music.adapter package io.github.muntashirakon.music.adapter
import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.fragment.app.findFragment
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil
import io.github.muntashirakon.music.* import io.github.muntashirakon.music.*
import io.github.muntashirakon.music.adapter.album.AlbumAdapter import io.github.muntashirakon.music.adapter.album.AlbumAdapter
import io.github.muntashirakon.music.adapter.artist.ArtistAdapter import io.github.muntashirakon.music.adapter.artist.ArtistAdapter
import io.github.muntashirakon.music.adapter.song.SongAdapter import io.github.muntashirakon.music.adapter.song.SongAdapter
import io.github.muntashirakon.music.extensions.hide import io.github.muntashirakon.music.fragments.home.HomeFragment
import io.github.muntashirakon.music.glide.SongGlideRequest
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.interfaces.IAlbumClickListener import io.github.muntashirakon.music.interfaces.IAlbumClickListener
import io.github.muntashirakon.music.interfaces.IArtistClickListener import io.github.muntashirakon.music.interfaces.IArtistClickListener
import io.github.muntashirakon.music.interfaces.IGenreClickListener import io.github.muntashirakon.music.interfaces.IGenreClickListener
import io.github.muntashirakon.music.model.* import io.github.muntashirakon.music.model.*
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import com.bumptech.glide.Glide
import com.google.android.material.card.MaterialCardView
class HomeAdapter( class HomeAdapter(
private val activity: AppCompatActivity private val activity: AppCompatActivity
@ -60,17 +55,10 @@ class HomeAdapter(
LayoutInflater.from(activity).inflate(R.layout.section_recycler_view, parent, false) LayoutInflater.from(activity).inflate(R.layout.section_recycler_view, parent, false)
return when (viewType) { return when (viewType) {
RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout) RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout)
GENRES -> GenreViewHolder(layout)
FAVOURITES -> PlaylistViewHolder(layout) FAVOURITES -> PlaylistViewHolder(layout)
TOP_ALBUMS, RECENT_ALBUMS -> AlbumViewHolder(layout) TOP_ALBUMS, RECENT_ALBUMS -> AlbumViewHolder(layout)
else -> { else -> {
SuggestionsViewHolder( ArtistViewHolder(layout)
LayoutInflater.from(activity).inflate(
R.layout.item_suggestions,
parent,
false
)
)
} }
} }
} }
@ -82,6 +70,7 @@ class HomeAdapter(
val viewHolder = holder as AlbumViewHolder val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(home) viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener { viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment, R.id.detailListFragment,
bundleOf("type" to RECENT_ALBUMS) bundleOf("type" to RECENT_ALBUMS)
@ -92,6 +81,7 @@ class HomeAdapter(
val viewHolder = holder as AlbumViewHolder val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(home) viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener { viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment, R.id.detailListFragment,
bundleOf("type" to TOP_ALBUMS) bundleOf("type" to TOP_ALBUMS)
@ -102,6 +92,7 @@ class HomeAdapter(
val viewHolder = holder as ArtistViewHolder val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(home) viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener { viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment, R.id.detailListFragment,
bundleOf("type" to RECENT_ARTISTS) bundleOf("type" to RECENT_ARTISTS)
@ -112,32 +103,24 @@ class HomeAdapter(
val viewHolder = holder as ArtistViewHolder val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(home) viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener { viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment, R.id.detailListFragment,
bundleOf("type" to TOP_ARTISTS) bundleOf("type" to TOP_ARTISTS)
) )
} }
} }
SUGGESTIONS -> {
val viewHolder = holder as SuggestionsViewHolder
viewHolder.bindView(home)
}
FAVOURITES -> { FAVOURITES -> {
val viewHolder = holder as PlaylistViewHolder val viewHolder = holder as PlaylistViewHolder
viewHolder.bindView(home) viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener { viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment, R.id.detailListFragment,
bundleOf("type" to FAVOURITES) bundleOf("type" to FAVOURITES)
) )
} }
} }
GENRES -> {
val viewHolder = holder as GenreViewHolder
viewHolder.bind(home)
}
PLAYLISTS -> {
}
} }
} }
@ -145,6 +128,7 @@ class HomeAdapter(
return list.size return list.size
} }
@SuppressLint("NotifyDataSetChanged")
fun swapData(sections: List<Home>) { fun swapData(sections: List<Home>) {
list = sections list = sections
notifyDataSetChanged() notifyDataSetChanged()
@ -170,36 +154,6 @@ class HomeAdapter(
} }
} }
private inner class SuggestionsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val images = listOf(
R.id.image1,
R.id.image2,
R.id.image3,
R.id.image4,
R.id.image5,
R.id.image6,
R.id.image7,
R.id.image8
)
fun bindView(home: Home) {
val color = ThemeStore.accentColor(activity)
itemView.findViewById<TextView>(R.id.message).setTextColor(color)
itemView.findViewById<MaterialCardView>(R.id.card6).apply {
setCardBackgroundColor(ColorUtil.withAlpha(color, 0.12f))
}
images.forEachIndexed { index, id ->
itemView.findViewById<View>(id).setOnClickListener {
MusicPlayerRemote.playNext(home.arrayList[index] as Song)
}
SongGlideRequest.Builder.from(Glide.with(activity), home.arrayList[index] as Song)
.asBitmap()
.build()
.into(itemView.findViewById(id))
}
}
}
private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) { private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(home: Home) { fun bindView(home: Home) {
title.setText(home.titleRes) title.setText(home.titleRes)
@ -207,7 +161,7 @@ class HomeAdapter(
val songAdapter = SongAdapter( val songAdapter = SongAdapter(
activity, activity,
home.arrayList as MutableList<Song>, home.arrayList as MutableList<Song>,
R.layout.item_album_card, null R.layout.item_favourite_card, null
) )
layoutManager = linearLayoutManager() layoutManager = linearLayoutManager()
adapter = songAdapter adapter = songAdapter
@ -215,23 +169,6 @@ class HomeAdapter(
} }
} }
private inner class GenreViewHolder(itemView: View) : AbsHomeViewItem(itemView) {
fun bind(home: Home) {
arrow.hide()
title.setText(home.titleRes)
val genreAdapter = GenreAdapter(
activity,
home.arrayList as List<Genre>,
R.layout.item_grid_genre,
this@HomeAdapter
)
recyclerView.apply {
layoutManager = GridLayoutManager(activity, 3, GridLayoutManager.HORIZONTAL, false)
adapter = genreAdapter
}
}
}
open class AbsHomeViewItem(itemView: View) : RecyclerView.ViewHolder(itemView) { open class AbsHomeViewItem(itemView: View) : RecyclerView.ViewHolder(itemView) {
val recyclerView: RecyclerView = itemView.findViewById(R.id.recyclerView) val recyclerView: RecyclerView = itemView.findViewById(R.id.recyclerView)
val title: AppCompatTextView = itemView.findViewById(R.id.title) val title: AppCompatTextView = itemView.findViewById(R.id.title)
@ -257,7 +194,7 @@ class HomeAdapter(
bundleOf(EXTRA_ARTIST_ID to artistId), bundleOf(EXTRA_ARTIST_ID to artistId),
null, null,
FragmentNavigatorExtras( FragmentNavigatorExtras(
view to "artist" view to artistId.toString()
) )
) )
} }
@ -268,7 +205,7 @@ class HomeAdapter(
bundleOf(EXTRA_ALBUM_ID to albumId), bundleOf(EXTRA_ALBUM_ID to albumId),
null, null,
FragmentNavigatorExtras( FragmentNavigatorExtras(
view to "album" view to albumId.toString()
) )
) )
} }

View file

@ -14,6 +14,7 @@
*/ */
package io.github.muntashirakon.music.adapter package io.github.muntashirakon.music.adapter
import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -27,17 +28,16 @@ import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import io.github.muntashirakon.music.* import io.github.muntashirakon.music.*
import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder
import io.github.muntashirakon.music.db.PlaylistEntity
import io.github.muntashirakon.music.db.PlaylistWithSongs import io.github.muntashirakon.music.db.PlaylistWithSongs
import io.github.muntashirakon.music.glide.AlbumGlideRequest import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.ArtistGlideRequest import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.helper.menu.SongMenuHelper import io.github.muntashirakon.music.helper.menu.SongMenuHelper
import io.github.muntashirakon.music.model.* import io.github.muntashirakon.music.model.Album
import io.github.muntashirakon.music.model.smartplaylist.AbsSmartPlaylist import io.github.muntashirakon.music.model.Artist
import io.github.muntashirakon.music.repository.PlaylistSongsLoader import io.github.muntashirakon.music.model.Genre
import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import com.bumptech.glide.Glide
import java.util.* import java.util.*
class SearchAdapter( class SearchAdapter(
@ -45,6 +45,7 @@ class SearchAdapter(
private var dataSet: List<Any> private var dataSet: List<Any>
) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() { ) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
@SuppressLint("NotifyDataSetChanged")
fun swapDataSet(dataSet: List<Any>) { fun swapDataSet(dataSet: List<Any>) {
this.dataSet = dataSet this.dataSet = dataSet
notifyDataSetChanged() notifyDataSetChanged()
@ -52,9 +53,9 @@ class SearchAdapter(
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
if (dataSet[position] is Album) return ALBUM if (dataSet[position] is Album) return ALBUM
if (dataSet[position] is Artist) return ARTIST if (dataSet[position] is Artist) return if ((dataSet[position] as Artist).isAlbumArtist) ALBUM_ARTIST else ARTIST
if (dataSet[position] is Genre) return GENRE if (dataSet[position] is Genre) return GENRE
if (dataSet[position] is PlaylistEntity) return PLAYLIST if (dataSet[position] is PlaylistWithSongs) return PLAYLIST
return if (dataSet[position] is Song) SONG else HEADER return if (dataSet[position] is Song) SONG else HEADER
} }
@ -66,6 +67,14 @@ class SearchAdapter(
false false
), viewType ), viewType
) )
else if (viewType == ALBUM || viewType == ARTIST || viewType== ALBUM_ARTIST)
ViewHolder(
LayoutInflater.from(activity).inflate(
R.layout.item_list_big,
parent,
false
), viewType
)
else else
ViewHolder( ViewHolder(
LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false), LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false),
@ -80,21 +89,23 @@ class SearchAdapter(
val album = dataSet[position] as Album val album = dataSet[position] as Album
holder.title?.text = album.title holder.title?.text = album.title
holder.text?.text = album.artistName holder.text?.text = album.artistName
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) GlideApp.with(activity).asDrawable().albumCoverOptions(album.safeGetFirstSong()).load(RetroGlideExtension.getSongModel(album.safeGetFirstSong()))
.checkIgnoreMediaStore().build().into(holder.image) .into(holder.image!!)
} }
ARTIST -> { ARTIST -> {
holder.imageTextContainer?.isVisible = true holder.imageTextContainer?.isVisible = true
val artist = dataSet[position] as Artist val artist = dataSet[position] as Artist
holder.title?.text = artist.name holder.title?.text = artist.name
holder.text?.text = MusicUtil.getArtistInfoString(activity, artist) holder.text?.text = MusicUtil.getArtistInfoString(activity, artist)
ArtistGlideRequest.Builder.from(Glide.with(activity), artist).build() GlideApp.with(activity).asDrawable().artistImageOptions(artist).load(
.into(holder.image) RetroGlideExtension.getArtistModel(artist)).into(holder.image!!)
} }
SONG -> { SONG -> {
holder.imageTextContainer?.isVisible = true
val song = dataSet[position] as Song val song = dataSet[position] as Song
holder.title?.text = song.title holder.title?.text = song.title
holder.text?.text = song.albumName holder.text?.text = song.albumName
GlideApp.with(activity).asDrawable().songCoverOptions(song).load(RetroGlideExtension.getSongModel(song)).into(holder.image!!)
} }
GENRE -> { GENRE -> {
val genre = dataSet[position] as Genre val genre = dataSet[position] as Genre
@ -109,10 +120,18 @@ class SearchAdapter(
) )
} }
PLAYLIST -> { PLAYLIST -> {
val playlist = dataSet[position] as PlaylistEntity val playlist = dataSet[position] as PlaylistWithSongs
holder.title?.text = playlist.playlistName holder.title?.text = playlist.playlistEntity.playlistName
//holder.text?.text = MusicUtil.playlistInfoString(activity, playlist.songs) //holder.text?.text = MusicUtil.playlistInfoString(activity, playlist.songs)
} }
ALBUM_ARTIST -> {
holder.imageTextContainer?.isVisible = true
val artist = dataSet[position] as Artist
holder.title?.text = artist.name
holder.text?.text = MusicUtil.getArtistInfoString(activity, artist)
GlideApp.with(activity).asDrawable().artistImageOptions(artist).load(
RetroGlideExtension.getArtistModel(artist)).into(holder.image!!)
}
else -> { else -> {
holder.title?.text = dataSet[position].toString() holder.title?.text = dataSet[position].toString()
holder.title?.setTextColor(ThemeStore.accentColor(activity)) holder.title?.setTextColor(ThemeStore.accentColor(activity))
@ -120,16 +139,6 @@ class SearchAdapter(
} }
} }
private fun getSongs(playlist: Playlist): List<Song> {
val songs = mutableListOf<Song>()
if (playlist is AbsSmartPlaylist) {
songs.addAll(playlist.getSongs())
} else {
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
}
return songs
}
override fun getItemCount(): Int { override fun getItemCount(): Int {
return dataSet.size return dataSet.size
} }
@ -140,13 +149,13 @@ class SearchAdapter(
imageTextContainer?.isInvisible = true imageTextContainer?.isInvisible = true
if (itemViewType == SONG) { if (itemViewType == SONG) {
imageTextContainer?.isGone = true imageTextContainer?.isGone = true
menu?.visibility = View.VISIBLE menu?.isVisible = true
menu?.setOnClickListener(object : SongMenuHelper.OnClickSongMenu(activity) { menu?.setOnClickListener(object : SongMenuHelper.OnClickSongMenu(activity) {
override val song: Song override val song: Song
get() = dataSet[layoutPosition] as Song get() = dataSet[layoutPosition] as Song
}) })
} else { } else {
menu?.visibility = View.GONE menu?.isVisible = false
} }
when (itemViewType) { when (itemViewType) {
@ -154,7 +163,7 @@ class SearchAdapter(
ARTIST -> setImageTransitionName(activity.getString(R.string.transition_artist_image)) ARTIST -> setImageTransitionName(activity.getString(R.string.transition_artist_image))
else -> { else -> {
val container = itemView.findViewById<View>(R.id.imageContainer) val container = itemView.findViewById<View>(R.id.imageContainer)
container?.visibility = View.GONE container?.isVisible = false
} }
} }
} }
@ -174,6 +183,12 @@ class SearchAdapter(
bundleOf(EXTRA_ARTIST_ID to (item as Artist).id) bundleOf(EXTRA_ARTIST_ID to (item as Artist).id)
) )
} }
ALBUM_ARTIST ->{
activity.findNavController(R.id.fragment_container).navigate(
R.id.albumArtistDetailsFragment,
bundleOf(EXTRA_ARTIST_NAME to (item as Artist).name)
)
}
GENRE -> { GENRE -> {
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.genreDetailsFragment, R.id.genreDetailsFragment,
@ -187,9 +202,8 @@ class SearchAdapter(
) )
} }
SONG -> { SONG -> {
val playList = mutableListOf<Song>() MusicPlayerRemote.playNext(item as Song)
playList.add(item as Song) MusicPlayerRemote.playNextSong()
MusicPlayerRemote.openQueue(playList, 0, true)
} }
} }
} }
@ -202,5 +216,6 @@ class SearchAdapter(
private const val SONG = 3 private const val SONG = 3
private const val GENRE = 4 private const val GENRE = 4
private const val PLAYLIST = 5 private const val PLAYLIST = 5
private const val ALBUM_ARTIST = 6
} }
} }

View file

@ -20,30 +20,32 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter
import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder
import io.github.muntashirakon.music.extensions.getTintedDrawable
import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.glide.audiocover.AudioFileCover import io.github.muntashirakon.music.glide.audiocover.AudioFileCover
import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.interfaces.ICabHolder
import io.github.muntashirakon.music.interfaces.ICallbacks import io.github.muntashirakon.music.interfaces.ICallbacks
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.RetroUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.signature.MediaStoreSignature import com.bumptech.glide.signature.MediaStoreSignature
import me.zhanghai.android.fastscroll.PopupTextProvider
import java.io.File import java.io.File
import java.text.DecimalFormat import java.text.DecimalFormat
import kotlin.math.log10 import kotlin.math.log10
import kotlin.math.pow import kotlin.math.pow
import me.zhanghai.android.fastscroll.PopupTextProvider
class SongFileAdapter( class SongFileAdapter(
private val activity: AppCompatActivity, override val activity: AppCompatActivity,
private var dataSet: List<File>, private var dataSet: List<File>,
private val itemLayoutRes: Int, private val itemLayoutRes: Int,
private val iCallbacks: ICallbacks?, private val iCallbacks: ICallbacks?,
iCabHolder: ICabHolder? iCabHolder: ICabHolder?,
) : AbsMultiSelectAdapter<SongFileAdapter.ViewHolder, File>( ) : AbsMultiSelectAdapter<SongFileAdapter.ViewHolder, File>(
activity, iCabHolder, R.menu.menu_media_selection activity, iCabHolder, R.menu.menu_media_selection
), PopupTextProvider { ), PopupTextProvider {
@ -77,7 +79,7 @@ class SongFileAdapter(
if (holder.itemViewType == FILE) { if (holder.itemViewType == FILE) {
holder.text?.text = getFileText(file) holder.text?.text = getFileText(file)
} else { } else {
holder.text?.visibility = View.GONE holder.text?.isVisible = false
} }
} }
@ -108,17 +110,15 @@ class SongFileAdapter(
) )
) )
} else { } else {
val error = RetroUtil.getTintedVectorDrawable( val error = activity.getTintedDrawable(R.drawable.ic_file_music, iconColor)
activity, R.drawable.ic_file_music, iconColor GlideApp.with(activity)
)
Glide.with(activity)
.load(AudioFileCover(file.path)) .load(AudioFileCover(file.path))
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.error(error) .error(error)
.placeholder(error) .placeholder(error)
.animate(android.R.anim.fade_in) .transition(RetroGlideExtension.getDefaultTransition())
.signature(MediaStoreSignature("", file.lastModified(), 0)) .signature(MediaStoreSignature("", file.lastModified(), 0))
.into(holder.image) .into(holder.image!!)
} }
} }
@ -126,12 +126,12 @@ class SongFileAdapter(
return dataSet.size return dataSet.size
} }
override fun getIdentifier(position: Int): File? { override fun getIdentifier(position: Int): File {
return dataSet[position] return dataSet[position]
} }
override fun getName(`object`: File): String { override fun getName(model: File): String {
return getFileTitle(`object`) return getFileTitle(model)
} }
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<File>) { override fun onMultipleItemAction(menuItem: MenuItem, selection: List<File>) {
@ -140,7 +140,7 @@ class SongFileAdapter(
} }
override fun getPopupText(position: Int): String { override fun getPopupText(position: Int): String {
return getSectionName(position) return if (position >= dataSet.lastIndex) "" else getSectionName(position)
} }
private fun getSectionName(position: Int): String { private fun getSectionName(position: Int): String {

View file

@ -0,0 +1,55 @@
package io.github.muntashirakon.music.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import io.github.muntashirakon.music.R
import java.io.File
class StorageAdapter(
val storageList: List<Storage>,
val storageClickListener: StorageClickListener
) :
RecyclerView.Adapter<StorageAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_storage,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindData(storageList[position])
}
override fun getItemCount(): Int {
return storageList.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.findViewById(R.id.title)
fun bindData(storage: Storage) {
title.text = storage.title
}
init {
itemView.setOnClickListener { storageClickListener.onStorageClicked(storageList[bindingAdapterPosition]) }
}
}
}
interface StorageClickListener {
fun onStorageClicked(storage: Storage)
}
class Storage {
lateinit var title: String
lateinit var file: File
}

View file

@ -1,66 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package io.github.muntashirakon.music.adapter
import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.extensions.hide
import io.github.muntashirakon.music.model.Contributor
import io.github.muntashirakon.music.util.RetroUtil
import io.github.muntashirakon.music.views.RetroShapeableImageView
class TranslatorsAdapter(
private var contributors: List<Contributor>
) : RecyclerView.Adapter<TranslatorsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_contributor,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val contributor = contributors[position]
holder.bindData(contributor)
holder.itemView.setOnClickListener {
RetroUtil.openUrl(it?.context as Activity, contributors[position].link)
}
}
override fun getItemCount(): Int {
return contributors.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.findViewById(R.id.title)
val text: TextView = itemView.findViewById(R.id.text)
val image: RetroShapeableImageView = itemView.findViewById(R.id.icon)
internal fun bindData(contributor: Contributor) {
title.text = contributor.name
text.text = contributor.summary
image.hide()
}
}
}

View file

@ -19,12 +19,13 @@ import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.ViewCompat import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter
import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder
import io.github.muntashirakon.music.glide.AlbumGlideRequest import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
import io.github.muntashirakon.music.helper.SortOrder import io.github.muntashirakon.music.helper.SortOrder
import io.github.muntashirakon.music.helper.menu.SongsMenuHelper import io.github.muntashirakon.music.helper.menu.SongsMenuHelper
@ -35,11 +36,10 @@ import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import me.zhanghai.android.fastscroll.PopupTextProvider import me.zhanghai.android.fastscroll.PopupTextProvider
open class AlbumAdapter( open class AlbumAdapter(
val activity: FragmentActivity, override val activity: FragmentActivity,
var dataSet: List<Album>, var dataSet: List<Album>,
var itemLayoutRes: Int, var itemLayoutRes: Int,
iCabHolder: ICabHolder?, iCabHolder: ICabHolder?,
@ -68,12 +68,18 @@ open class AlbumAdapter(
return ViewHolder(view) return ViewHolder(view)
} }
private fun getAlbumTitle(album: Album): String? { private fun getAlbumTitle(album: Album): String {
return album.title return album.title
} }
protected open fun getAlbumText(album: Album): String? { protected open fun getAlbumText(album: Album): String? {
return album.artistName return album.albumArtist.let {
if (it.isNullOrEmpty()) {
album.artistName
} else {
it
}
}
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
@ -82,6 +88,13 @@ open class AlbumAdapter(
holder.itemView.isActivated = isChecked holder.itemView.isActivated = isChecked
holder.title?.text = getAlbumTitle(album) holder.title?.text = getAlbumTitle(album)
holder.text?.text = getAlbumText(album) holder.text?.text = getAlbumText(album)
// Check if imageContainer exists so we can have a smooth transition without
// CardView clipping, if it doesn't exist in current layout set transition name to image instead.
if (holder.imageContainer != null) {
holder.imageContainer?.transitionName = album.id.toString()
} else {
holder.image?.transitionName = album.id.toString()
}
loadAlbumCover(album, holder) loadAlbumCover(album, holder)
} }
@ -92,17 +105,17 @@ open class AlbumAdapter(
holder.paletteColorContainer?.setBackgroundColor(color.backgroundColor) holder.paletteColorContainer?.setBackgroundColor(color.backgroundColor)
} }
holder.mask?.backgroundTintList = ColorStateList.valueOf(color.primaryTextColor) holder.mask?.backgroundTintList = ColorStateList.valueOf(color.primaryTextColor)
holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor) } holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor)
}
protected open fun loadAlbumCover(album: Album, holder: ViewHolder) { protected open fun loadAlbumCover(album: Album, holder: ViewHolder) {
if (holder.image == null) { if (holder.image == null) {
return return
} }
val song = album.safeGetFirstSong()
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) GlideApp.with(activity).asBitmapPalette().albumCoverOptions(song)
.checkIgnoreMediaStore() //.checkIgnoreMediaStore()
.generatePalette(activity) .load(RetroGlideExtension.getSongModel(song))
.build()
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder) setColors(colors, holder)
@ -122,8 +135,8 @@ open class AlbumAdapter(
return dataSet[position] return dataSet[position]
} }
override fun getName(album: Album): String { override fun getName(model: Album): String {
return album.title!! return model.title
} }
override fun onMultipleItemAction( override fun onMultipleItemAction(
@ -150,7 +163,7 @@ open class AlbumAdapter(
when (PreferenceUtil.albumSortOrder) { when (PreferenceUtil.albumSortOrder) {
SortOrder.AlbumSortOrder.ALBUM_A_Z, SortOrder.AlbumSortOrder.ALBUM_Z_A -> sectionName = SortOrder.AlbumSortOrder.ALBUM_A_Z, SortOrder.AlbumSortOrder.ALBUM_Z_A -> sectionName =
dataSet[position].title dataSet[position].title
SortOrder.AlbumSortOrder.ALBUM_ARTIST -> sectionName = dataSet[position].artistName SortOrder.AlbumSortOrder.ALBUM_ARTIST -> sectionName = dataSet[position].albumArtist
SortOrder.AlbumSortOrder.ALBUM_YEAR -> return MusicUtil.getYearString( SortOrder.AlbumSortOrder.ALBUM_YEAR -> return MusicUtil.getYearString(
dataSet[position].year dataSet[position].year
) )
@ -161,8 +174,7 @@ open class AlbumAdapter(
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init { init {
setImageTransitionName("Album") menu?.isVisible = false
menu?.visibility = View.GONE
} }
override fun onClick(v: View?) { override fun onClick(v: View?) {
@ -171,16 +183,13 @@ open class AlbumAdapter(
toggleChecked(layoutPosition) toggleChecked(layoutPosition)
} else { } else {
image?.let { image?.let {
ViewCompat.setTransitionName(it, "album") listener?.onAlbumClick(dataSet[layoutPosition].id, imageContainer ?: it)
listener?.onAlbumClick(dataSet[layoutPosition].id, it)
} }
} }
} }
override fun onLongClick(v: View?): Boolean { override fun onLongClick(v: View?): Boolean {
toggleChecked(layoutPosition) return toggleChecked(layoutPosition)
return super.onLongClick(v)
} }
} }

View file

@ -19,22 +19,24 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import androidx.core.view.ViewCompat import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.MainActivity
import io.github.muntashirakon.music.fragments.AlbumCoverStyle import io.github.muntashirakon.music.fragments.AlbumCoverStyle
import io.github.muntashirakon.music.fragments.NowPlayingScreen.* import io.github.muntashirakon.music.fragments.NowPlayingScreen.*
import io.github.muntashirakon.music.fragments.base.goToLyrics
import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
import io.github.muntashirakon.music.glide.SongGlideRequest
import io.github.muntashirakon.music.misc.CustomFragmentStatePagerAdapter import io.github.muntashirakon.music.misc.CustomFragmentStatePagerAdapter
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.NavigationUtil
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -83,12 +85,12 @@ class AlbumCoverPagerAdapter(
class AlbumCoverFragment : Fragment() { class AlbumCoverFragment : Fragment() {
private lateinit var albumCover: ImageView
private var isColorReady: Boolean = false private var isColorReady: Boolean = false
private lateinit var color: MediaNotificationProcessor private lateinit var color: MediaNotificationProcessor
private lateinit var song: Song private lateinit var song: Song
private var colorReceiver: ColorReceiver? = null private var colorReceiver: ColorReceiver? = null
private var request: Int = 0 private var request: Int = 0
private val mainActivity get() = activity as MainActivity
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -103,11 +105,11 @@ class AlbumCoverPagerAdapter(
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false) val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false)
ViewCompat.setTransitionName(view, "lyrics")
albumCover = view.findViewById(R.id.player_image)
view.setOnClickListener { view.setOnClickListener {
if (mainActivity.getBottomSheetBehavior().state == STATE_EXPANDED) {
showLyricsDialog() showLyricsDialog()
} }
}
return view return view
} }
@ -122,7 +124,7 @@ class AlbumCoverPagerAdapter(
setTitle(song.title) setTitle(song.title)
setMessage(if (data.isNullOrEmpty()) "No lyrics found" else data) setMessage(if (data.isNullOrEmpty()) "No lyrics found" else data)
setNegativeButton(R.string.synced_lyrics) { _, _ -> setNegativeButton(R.string.synced_lyrics) { _, _ ->
NavigationUtil.goToLyrics(requireActivity()) goToLyrics(requireActivity())
} }
show() show()
} }
@ -133,6 +135,7 @@ class AlbumCoverPagerAdapter(
private fun getLayoutWithPlayerTheme(): Int { private fun getLayoutWithPlayerTheme(): Int {
return when (PreferenceUtil.nowPlayingScreen) { return when (PreferenceUtil.nowPlayingScreen) {
Card, Fit, Tiny, Classic, Gradient, Full -> R.layout.fragment_album_full_cover Card, Fit, Tiny, Classic, Gradient, Full -> R.layout.fragment_album_full_cover
Peek -> R.layout.fragment_peek_album_cover
else -> { else -> {
if (PreferenceUtil.isCarouselEffect) { if (PreferenceUtil.isCarouselEffect) {
R.layout.fragment_album_carousel_cover R.layout.fragment_album_carousel_cover
@ -142,7 +145,6 @@ class AlbumCoverPagerAdapter(
AlbumCoverStyle.Flat -> R.layout.fragment_album_flat_cover AlbumCoverStyle.Flat -> R.layout.fragment_album_flat_cover
AlbumCoverStyle.Circle -> R.layout.fragment_album_circle_cover AlbumCoverStyle.Circle -> R.layout.fragment_album_circle_cover
AlbumCoverStyle.Card -> R.layout.fragment_album_card_cover AlbumCoverStyle.Card -> R.layout.fragment_album_card_cover
AlbumCoverStyle.Material -> R.layout.fragment_album_material_cover
AlbumCoverStyle.Full -> R.layout.fragment_album_full_cover AlbumCoverStyle.Full -> R.layout.fragment_album_full_cover
AlbumCoverStyle.FullCard -> R.layout.fragment_album_full_card_cover AlbumCoverStyle.FullCard -> R.layout.fragment_album_full_card_cover
} }
@ -153,7 +155,7 @@ class AlbumCoverPagerAdapter(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
loadAlbumCover() loadAlbumCover(albumCover = view.findViewById(R.id.player_image))
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -161,10 +163,11 @@ class AlbumCoverPagerAdapter(
colorReceiver = null colorReceiver = null
} }
private fun loadAlbumCover() { private fun loadAlbumCover(albumCover: ImageView) {
SongGlideRequest.Builder.from(Glide.with(requireContext()), song) GlideApp.with(this).asBitmapPalette().songCoverOptions(song)
.checkIgnoreMediaStore(requireContext()) //.checkIgnoreMediaStore()
.generatePalette(requireContext()).build() .load(RetroGlideExtension.getSongModel(song))
.dontAnimate()
.into(object : RetroMusicColoredTarget(albumCover) { .into(object : RetroMusicColoredTarget(albumCover) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
setColor(colors) setColor(colors)
@ -200,9 +203,7 @@ class AlbumCoverPagerAdapter(
fun newInstance(song: Song): AlbumCoverFragment { fun newInstance(song: Song): AlbumCoverFragment {
val frag = AlbumCoverFragment() val frag = AlbumCoverFragment()
val args = Bundle() frag.arguments = bundleOf(SONG_ARG to song)
args.putParcelable(SONG_ARG, song)
frag.arguments = args
return frag return frag
} }
} }

View file

@ -17,7 +17,8 @@ package io.github.muntashirakon.music.adapter.album
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import io.github.muntashirakon.music.glide.AlbumGlideRequest import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
import io.github.muntashirakon.music.helper.HorizontalAdapterHelper import io.github.muntashirakon.music.helper.HorizontalAdapterHelper
import io.github.muntashirakon.music.interfaces.IAlbumClickListener import io.github.muntashirakon.music.interfaces.IAlbumClickListener
@ -25,7 +26,6 @@ import io.github.muntashirakon.music.interfaces.ICabHolder
import io.github.muntashirakon.music.model.Album import io.github.muntashirakon.music.model.Album
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
class HorizontalAlbumAdapter( class HorizontalAlbumAdapter(
activity: FragmentActivity, activity: FragmentActivity,
@ -49,10 +49,8 @@ class HorizontalAlbumAdapter(
override fun loadAlbumCover(album: Album, holder: ViewHolder) { override fun loadAlbumCover(album: Album, holder: ViewHolder) {
if (holder.image == null) return if (holder.image == null) return
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) GlideApp.with(activity).asBitmapPalette().albumCoverOptions(album.safeGetFirstSong())
.checkIgnoreMediaStore() .load(RetroGlideExtension.getSongModel(album.safeGetFirstSong()))
.generatePalette(activity)
.build()
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder) setColors(colors, holder)
@ -60,7 +58,7 @@ class HorizontalAlbumAdapter(
}) })
} }
override fun getAlbumText(album: Album): String? { override fun getAlbumText(album: Album): String {
return MusicUtil.getYearString(album.year) return MusicUtil.getYearString(album.year)
} }

View file

@ -14,48 +14,55 @@
*/ */
package io.github.muntashirakon.music.adapter.artist package io.github.muntashirakon.music.adapter.artist
import android.annotation.SuppressLint
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.content.res.Resources import android.content.res.Resources
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.ViewCompat import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter
import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder
import io.github.muntashirakon.music.extensions.hide import io.github.muntashirakon.music.extensions.hide
import io.github.muntashirakon.music.glide.ArtistGlideRequest import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
import io.github.muntashirakon.music.helper.menu.SongsMenuHelper import io.github.muntashirakon.music.helper.menu.SongsMenuHelper
import io.github.muntashirakon.music.interfaces.IAlbumArtistClickListener
import io.github.muntashirakon.music.interfaces.IArtistClickListener import io.github.muntashirakon.music.interfaces.IArtistClickListener
import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.interfaces.ICabHolder
import io.github.muntashirakon.music.model.Artist import io.github.muntashirakon.music.model.Artist
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import java.util.*
import me.zhanghai.android.fastscroll.PopupTextProvider import me.zhanghai.android.fastscroll.PopupTextProvider
class ArtistAdapter( class ArtistAdapter(
val activity: FragmentActivity, override val activity: FragmentActivity,
var dataSet: List<Artist>, var dataSet: List<Artist>,
var itemLayoutRes: Int, var itemLayoutRes: Int,
val ICabHolder: ICabHolder?, val ICabHolder: ICabHolder?,
val IArtistClickListener: IArtistClickListener val IArtistClickListener: IArtistClickListener,
val IAlbumArtistClickListener: IAlbumArtistClickListener? = null
) : AbsMultiSelectAdapter<ArtistAdapter.ViewHolder, Artist>( ) : AbsMultiSelectAdapter<ArtistAdapter.ViewHolder, Artist>(
activity, ICabHolder, R.menu.menu_media_selection activity, ICabHolder, R.menu.menu_media_selection
), PopupTextProvider { ), PopupTextProvider {
var albumArtistsOnly = false
init { init {
this.setHasStableIds(true) this.setHasStableIds(true)
} }
@SuppressLint("NotifyDataSetChanged")
fun swapDataSet(dataSet: List<Artist>) { fun swapDataSet(dataSet: List<Artist>) {
this.dataSet = dataSet this.dataSet = dataSet
notifyDataSetChanged() notifyDataSetChanged()
albumArtistsOnly = PreferenceUtil.albumArtistsOnly
} }
override fun getItemId(position: Int): Long { override fun getItemId(position: Int): Long {
@ -82,6 +89,13 @@ class ArtistAdapter(
holder.itemView.isActivated = isChecked holder.itemView.isActivated = isChecked
holder.title?.text = artist.name holder.title?.text = artist.name
holder.text?.hide() holder.text?.hide()
val transitionName =
if (albumArtistsOnly) artist.name else artist.id.toString()
if (holder.imageContainer != null) {
holder.imageContainer?.transitionName = transitionName
} else {
holder.image?.transitionName = transitionName
}
loadArtistImage(artist, holder) loadArtistImage(artist, holder)
} }
@ -98,9 +112,11 @@ class ArtistAdapter(
if (holder.image == null) { if (holder.image == null) {
return return
} }
ArtistGlideRequest.Builder.from(Glide.with(activity), artist) GlideApp.with(activity)
.generatePalette(activity) .asBitmapPalette()
.build() .load(RetroGlideExtension.getArtistModel(artist))
.artistImageOptions(artist)
.transition(RetroGlideExtension.getDefaultTransition())
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder) setColors(colors, holder)
@ -112,12 +128,12 @@ class ArtistAdapter(
return dataSet.size return dataSet.size
} }
override fun getIdentifier(position: Int): Artist? { override fun getIdentifier(position: Int): Artist {
return dataSet[position] return dataSet[position]
} }
override fun getName(artist: Artist): String { override fun getName(model: Artist): String {
return artist.name return model.name
} }
override fun onMultipleItemAction( override fun onMultipleItemAction(
@ -146,7 +162,7 @@ class ArtistAdapter(
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init { init {
menu?.visibility = View.GONE menu?.isVisible = false
} }
override fun onClick(v: View?) { override fun onClick(v: View?) {
@ -154,16 +170,19 @@ class ArtistAdapter(
if (isInQuickSelectMode) { if (isInQuickSelectMode) {
toggleChecked(layoutPosition) toggleChecked(layoutPosition)
} else { } else {
val artist = dataSet[layoutPosition]
image?.let { image?.let {
ViewCompat.setTransitionName(it, "artist") if (albumArtistsOnly && IAlbumArtistClickListener != null) {
IArtistClickListener.onArtist(dataSet[layoutPosition].id, it) IAlbumArtistClickListener.onAlbumArtist(artist.name, imageContainer ?: it)
} else {
IArtistClickListener.onArtist(artist.id, imageContainer ?: it)
}
} }
} }
} }
override fun onLongClick(v: View?): Boolean { override fun onLongClick(v: View?): Boolean {
toggleChecked(layoutPosition) return toggleChecked(layoutPosition)
return super.onLongClick(v)
} }
} }
} }

View file

@ -0,0 +1,65 @@
package io.github.muntashirakon.music.adapter.backup
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.databinding.ItemListBackupBinding
import java.io.File
class BackupAdapter(
val activity: FragmentActivity,
var dataSet: MutableList<File>,
val backupClickedListener: BackupClickedListener
) : RecyclerView.Adapter<BackupAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
ItemListBackupBinding.inflate(LayoutInflater.from(activity), parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.binding.title.text = dataSet[position].nameWithoutExtension
}
override fun getItemCount(): Int = dataSet.size
@SuppressLint("NotifyDataSetChanged")
fun swapDataset(dataSet: List<File>) {
this.dataSet = ArrayList(dataSet)
notifyDataSetChanged()
}
inner class ViewHolder(val binding: ItemListBackupBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.menu.setOnClickListener { view ->
val popupMenu = PopupMenu(activity, view)
popupMenu.inflate(R.menu.menu_backup)
popupMenu.setOnMenuItemClickListener { menuItem ->
return@setOnMenuItemClickListener backupClickedListener.onBackupMenuClicked(
dataSet[bindingAdapterPosition],
menuItem
)
}
popupMenu.show()
}
itemView.setOnClickListener {
backupClickedListener.onBackupClicked(dataSet[bindingAdapterPosition])
}
}
}
interface BackupClickedListener {
fun onBackupClicked(file: File)
fun onBackupMenuClicked(file: File, menuItem: MenuItem): Boolean
}
}

View file

@ -1,129 +0,0 @@
package io.github.muntashirakon.music.adapter.base;
import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.MenuRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import io.github.muntashirakon.music.R;
import io.github.muntashirakon.music.interfaces.ICabHolder;
import com.afollestad.materialcab.MaterialCab;
import java.util.ArrayList;
import java.util.List;
public abstract class AbsMultiSelectAdapter<V extends RecyclerView.ViewHolder, I>
extends RecyclerView.Adapter<V> implements MaterialCab.Callback {
@Nullable private final ICabHolder ICabHolder;
private final Context context;
private MaterialCab cab;
private List<I> checked;
private int menuRes;
public AbsMultiSelectAdapter(
@NonNull Context context, @Nullable ICabHolder ICabHolder, @MenuRes int menuRes) {
this.ICabHolder = ICabHolder;
checked = new ArrayList<>();
this.menuRes = menuRes;
this.context = context;
}
@Override
public boolean onCabCreated(MaterialCab materialCab, Menu menu) {
return true;
}
@Override
public boolean onCabFinished(MaterialCab materialCab) {
clearChecked();
return true;
}
@Override
public boolean onCabItemClicked(MenuItem menuItem) {
if (menuItem.getItemId() == R.id.action_multi_select_adapter_check_all) {
checkAll();
} else {
onMultipleItemAction(menuItem, new ArrayList<>(checked));
cab.finish();
clearChecked();
}
return true;
}
protected void checkAll() {
if (ICabHolder != null) {
checked.clear();
for (int i = 0; i < getItemCount(); i++) {
I identifier = getIdentifier(i);
if (identifier != null) {
checked.add(identifier);
}
}
notifyDataSetChanged();
updateCab();
}
}
@Nullable
protected abstract I getIdentifier(int position);
protected String getName(I object) {
return object.toString();
}
protected boolean isChecked(I identifier) {
return checked.contains(identifier);
}
protected boolean isInQuickSelectMode() {
return cab != null && cab.isActive();
}
protected abstract void onMultipleItemAction(MenuItem menuItem, List<I> selection);
protected void setMultiSelectMenuRes(@MenuRes int menuRes) {
this.menuRes = menuRes;
}
protected boolean toggleChecked(final int position) {
if (ICabHolder != null) {
I identifier = getIdentifier(position);
if (identifier == null) {
return false;
}
if (!checked.remove(identifier)) {
checked.add(identifier);
}
notifyItemChanged(position);
updateCab();
return true;
}
return false;
}
private void clearChecked() {
checked.clear();
notifyDataSetChanged();
}
private void updateCab() {
if (ICabHolder != null) {
if (cab == null || !cab.isActive()) {
cab = ICabHolder.openCab(menuRes, this);
}
final int size = checked.size();
if (size <= 0) {
cab.finish();
} else if (size == 1) {
cab.setTitle(getName(checked.get(0)));
} else {
cab.setTitle(context.getString(R.string.x_selected, size));
}
}
}
}

View file

@ -0,0 +1,125 @@
package io.github.muntashirakon.music.adapter.base
import android.annotation.SuppressLint
import android.graphics.Color
import android.view.Menu
import android.view.MenuItem
import androidx.annotation.MenuRes
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.extensions.surfaceColor
import io.github.muntashirakon.music.interfaces.ICabCallback
import io.github.muntashirakon.music.interfaces.ICabHolder
import io.github.muntashirakon.music.util.RetroColorUtil
import com.afollestad.materialcab.attached.AttachedCab
import com.afollestad.materialcab.attached.destroy
import com.afollestad.materialcab.attached.isActive
abstract class AbsMultiSelectAdapter<V : RecyclerView.ViewHolder?, I>(
open val activity: FragmentActivity, private val ICabHolder: ICabHolder?, @MenuRes menuRes: Int
) : RecyclerView.Adapter<V>(), ICabCallback {
private var cab: AttachedCab? = null
private val checked: MutableList<I>
private var menuRes: Int
override fun onCabCreated(cab: AttachedCab, menu: Menu): Boolean {
activity.window.statusBarColor =
RetroColorUtil.shiftBackgroundColor(activity.surfaceColor())
return true
}
override fun onCabFinished(cab: AttachedCab): Boolean {
clearChecked()
activity.window.statusBarColor = when {
VersionUtils.hasMarshmallow() -> Color.TRANSPARENT
else -> Color.BLACK
}
return true
}
override fun onCabItemClicked(item: MenuItem): Boolean {
if (item.itemId == R.id.action_multi_select_adapter_check_all) {
checkAll()
} else {
onMultipleItemAction(item, ArrayList(checked))
cab?.destroy()
clearChecked()
}
return true
}
private fun checkAll() {
if (ICabHolder != null) {
checked.clear()
for (i in 0 until itemCount) {
val identifier = getIdentifier(i)
if (identifier != null) {
checked.add(identifier)
}
}
notifyDataSetChanged()
updateCab()
}
}
protected abstract fun getIdentifier(position: Int): I?
protected abstract fun getName(model: I): String?
protected fun isChecked(identifier: I): Boolean {
return checked.contains(identifier)
}
protected val isInQuickSelectMode: Boolean
get() = cab != null && cab!!.isActive()
protected abstract fun onMultipleItemAction(menuItem: MenuItem, selection: List<I>)
protected fun setMultiSelectMenuRes(@MenuRes menuRes: Int) {
this.menuRes = menuRes
}
protected fun toggleChecked(position: Int): Boolean {
if (ICabHolder != null) {
val identifier = getIdentifier(position) ?: return false
if (!checked.remove(identifier)) {
checked.add(identifier)
}
notifyItemChanged(position)
updateCab()
return true
}
return false
}
private fun clearChecked() {
checked.clear()
notifyDataSetChanged()
}
@SuppressLint("StringFormatInvalid", "StringFormatMatches")
private fun updateCab() {
if (ICabHolder != null) {
if (cab == null || !cab!!.isActive()) {
cab = ICabHolder.openCab(menuRes, this)
}
val size = checked.size
when {
size <= 0 -> {
cab?.destroy()
}
size == 1 -> {
cab?.title(literal = getName(checked[0]))
}
else -> {
cab?.title(literal = activity.getString(R.string.x_selected, size))
}
}
}
}
init {
checked = ArrayList()
this.menuRes = menuRes
}
}

View file

@ -16,19 +16,19 @@ package io.github.muntashirakon.music.adapter.base;
import android.graphics.Color; import android.graphics.Color;
import android.view.View; import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.AppCompatImageView;
import androidx.recyclerview.widget.RecyclerView;
import io.github.muntashirakon.music.R;
import com.google.android.material.card.MaterialCardView; import com.google.android.material.card.MaterialCardView;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder; import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder;
import io.github.muntashirakon.music.R;
public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHolder public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHolder
implements View.OnLongClickListener, View.OnClickListener { implements View.OnLongClickListener, View.OnClickListener {
@ -41,15 +41,12 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
@Nullable @Nullable
public ImageView image; public ImageView image;
@Nullable
public ImageView artistImage;
@Nullable
public ImageView playerImage;
@Nullable @Nullable
public MaterialCardView imageContainerCard; public MaterialCardView imageContainerCard;
@Nullable
public FrameLayout imageContainer;
@Nullable @Nullable
public TextView imageText; public TextView imageText;
@ -65,10 +62,6 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
@Nullable @Nullable
public View paletteColorContainer; public View paletteColorContainer;
@Nullable
public RecyclerView recyclerView;
@Nullable @Nullable
public TextView text; public TextView text;
@ -88,18 +81,16 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
text2 = itemView.findViewById(R.id.text2); text2 = itemView.findViewById(R.id.text2);
image = itemView.findViewById(R.id.image); image = itemView.findViewById(R.id.image);
artistImage = itemView.findViewById(R.id.artistImage);
playerImage = itemView.findViewById(R.id.player_image);
time = itemView.findViewById(R.id.time); time = itemView.findViewById(R.id.time);
imageText = itemView.findViewById(R.id.imageText); imageText = itemView.findViewById(R.id.imageText);
imageTextContainer = itemView.findViewById(R.id.imageTextContainer); imageTextContainer = itemView.findViewById(R.id.imageTextContainer);
imageContainerCard = itemView.findViewById(R.id.imageContainerCard); imageContainerCard = itemView.findViewById(R.id.imageContainerCard);
imageContainer = itemView.findViewById(R.id.imageContainer);
menu = itemView.findViewById(R.id.menu); menu = itemView.findViewById(R.id.menu);
dragView = itemView.findViewById(R.id.drag_view); dragView = itemView.findViewById(R.id.drag_view);
paletteColorContainer = itemView.findViewById(R.id.paletteColorContainer); paletteColorContainer = itemView.findViewById(R.id.paletteColorContainer);
recyclerView = itemView.findViewById(R.id.recycler_view);
mask = itemView.findViewById(R.id.mask); mask = itemView.findViewById(R.id.mask);
dummyContainer = itemView.findViewById(R.id.dummy_view); dummyContainer = itemView.findViewById(R.id.dummy_view);

View file

@ -15,42 +15,36 @@
package io.github.muntashirakon.music.adapter.playlist package io.github.muntashirakon.music.adapter.playlist
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.view.ViewCompat import androidx.core.view.isGone
import androidx.core.view.setPadding
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.TintHelper
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter
import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder
import io.github.muntashirakon.music.db.PlaylistEntity import io.github.muntashirakon.music.db.PlaylistEntity
import io.github.muntashirakon.music.db.PlaylistWithSongs import io.github.muntashirakon.music.db.PlaylistWithSongs
import io.github.muntashirakon.music.db.SongEntity
import io.github.muntashirakon.music.db.toSongs import io.github.muntashirakon.music.db.toSongs
import io.github.muntashirakon.music.extensions.hide import io.github.muntashirakon.music.extensions.dipToPix
import io.github.muntashirakon.music.extensions.show import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.playlistPreview.PlaylistPreview
import io.github.muntashirakon.music.helper.SortOrder.PlaylistSortOrder
import io.github.muntashirakon.music.helper.menu.PlaylistMenuHelper import io.github.muntashirakon.music.helper.menu.PlaylistMenuHelper
import io.github.muntashirakon.music.helper.menu.SongsMenuHelper import io.github.muntashirakon.music.helper.menu.SongsMenuHelper
import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.interfaces.ICabHolder
import io.github.muntashirakon.music.interfaces.IPlaylistClickListener import io.github.muntashirakon.music.interfaces.IPlaylistClickListener
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.AutoGeneratedPlaylistBitmap
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import kotlinx.coroutines.Dispatchers.IO import io.github.muntashirakon.music.util.PreferenceUtil
import kotlinx.coroutines.Dispatchers.Main import me.zhanghai.android.fastscroll.PopupTextProvider
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class PlaylistAdapter( class PlaylistAdapter(
private val activity: FragmentActivity, override val activity: FragmentActivity,
private var dataSet: List<PlaylistWithSongs>, var dataSet: List<PlaylistWithSongs>,
private var itemLayoutRes: Int, private var itemLayoutRes: Int,
ICabHolder: ICabHolder?, ICabHolder: ICabHolder?,
private val listener: IPlaylistClickListener private val listener: IPlaylistClickListener
@ -58,7 +52,7 @@ class PlaylistAdapter(
activity, activity,
ICabHolder, ICabHolder,
R.menu.menu_playlists_selection R.menu.menu_playlists_selection
) { ), PopupTextProvider {
init { init {
setHasStableIds(true) setHasStableIds(true)
@ -83,44 +77,51 @@ class PlaylistAdapter(
} }
private fun getPlaylistTitle(playlist: PlaylistEntity): String { private fun getPlaylistTitle(playlist: PlaylistEntity): String {
return if (TextUtils.isEmpty(playlist.playlistName)) "-" else playlist.playlistName return playlist.playlistName.ifEmpty { "-" }
} }
private fun getPlaylistText(playlist: PlaylistWithSongs): String { private fun getPlaylistText(playlist: PlaylistWithSongs): String {
return MusicUtil.getPlaylistInfoString(activity, playlist.songs.toSongs()) return MusicUtil.getPlaylistInfoString(activity, playlist.songs.toSongs())
} }
override fun getPopupText(position: Int): String {
val sectionName: String = when (PreferenceUtil.playlistSortOrder) {
PlaylistSortOrder.PLAYLIST_A_Z, PlaylistSortOrder.PLAYLIST_Z_A -> dataSet[position].playlistEntity.playlistName
PlaylistSortOrder.PLAYLIST_SONG_COUNT, PlaylistSortOrder.PLAYLIST_SONG_COUNT_DESC -> dataSet[position].songs.size.toString()
else -> {
return ""
}
}
return MusicUtil.getSectionName(sectionName)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val playlist = dataSet[position] val playlist = dataSet[position]
holder.itemView.isActivated = isChecked(playlist) holder.itemView.isActivated = isChecked(playlist)
holder.title?.text = getPlaylistTitle(playlist.playlistEntity) holder.title?.text = getPlaylistTitle(playlist.playlistEntity)
holder.text?.text = getPlaylistText(playlist) holder.text?.text = getPlaylistText(playlist)
holder.image?.setImageDrawable(getIconRes()) holder.menu?.isGone = isChecked(playlist)
val isChecked = isChecked(playlist) GlideApp.with(activity)
if (isChecked) { .load(
holder.menu?.hide() if (itemLayoutRes == R.layout.item_list) {
} else { holder.image?.setPadding(activity.dipToPix(8F).toInt())
holder.menu?.show() R.drawable.ic_playlist_play
} } else PlaylistPreview(playlist)
//playlistBitmapLoader(activity, holder, playlist)
}
private fun getIconRes(): Drawable = TintHelper.createTintedDrawable(
activity,
R.drawable.ic_playlist_play,
ATHUtil.resolveColor(activity, R.attr.colorControlNormal)
) )
.playlistOptions()
.into(holder.image!!)
}
override fun getItemCount(): Int { override fun getItemCount(): Int {
return dataSet.size return dataSet.size
} }
override fun getIdentifier(position: Int): PlaylistWithSongs? { override fun getIdentifier(position: Int): PlaylistWithSongs {
return dataSet[position] return dataSet[position]
} }
override fun getName(playlist: PlaylistWithSongs): String { override fun getName(model: PlaylistWithSongs): String {
return playlist.playlistEntity.playlistName return model.playlistEntity.playlistName
} }
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<PlaylistWithSongs>) { override fun onMultipleItemAction(menuItem: MenuItem, selection: List<PlaylistWithSongs>) {
@ -141,18 +142,8 @@ class PlaylistAdapter(
return songs return songs
} }
private fun getSongs(playlist: PlaylistWithSongs): List<SongEntity> =
mutableListOf<SongEntity>().apply {
addAll(playlist.songs)
}
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init { init {
image?.apply {
val iconPadding =
activity.resources.getDimensionPixelSize(R.dimen.list_item_image_icon_padding)
setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
}
menu?.setOnClickListener { view -> menu?.setOnClickListener { view ->
val popupMenu = PopupMenu(activity, view) val popupMenu = PopupMenu(activity, view)
popupMenu.inflate(R.menu.menu_item_playlist) popupMenu.inflate(R.menu.menu_item_playlist)
@ -172,7 +163,7 @@ class PlaylistAdapter(
if (isInQuickSelectMode) { if (isInQuickSelectMode) {
toggleChecked(layoutPosition) toggleChecked(layoutPosition)
} else { } else {
ViewCompat.setTransitionName(itemView, "playlist") itemView.transitionName = "playlist"
listener.onPlaylistClick(dataSet[layoutPosition], itemView) listener.onPlaylistClick(dataSet[layoutPosition], itemView)
} }
} }
@ -183,37 +174,6 @@ class PlaylistAdapter(
} }
} }
private fun playlistBitmapLoader(
activity: FragmentActivity,
viewHolder: ViewHolder,
playlist: PlaylistWithSongs
) {
activity.lifecycleScope.launch(IO) {
val songs = playlist.songs.toSongs()
val bitmap = AutoGeneratedPlaylistBitmap.getBitmap(activity, songs, false, false)
withContext(Main) { viewHolder.image?.setImageBitmap(bitmap) }
}
/*
override fun doInBackground(vararg params: Void?): Bitmap {
val songs = playlist.songs.toSongs()
return AutoGeneratedPlaylistBitmap.getBitmap(activity, songs, false, false)
}
override fun onPostExecute(result: Bitmap?) {
super.onPostExecute(result)
viewHolder.image?.setImageBitmap(result)
val color = RetroColorUtil.getColor(
RetroColorUtil.generatePalette(
result
),
ATHUtil.resolveColor(activity, R.attr.colorSurface)
)
viewHolder.paletteColorContainer?.setBackgroundColor(color)
}*/
}
companion object { companion object {
val TAG: String = PlaylistAdapter::class.java.simpleName val TAG: String = PlaylistAdapter::class.java.simpleName
} }

View file

@ -16,106 +16,96 @@ package io.github.muntashirakon.music.adapter.song
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.R.menu
import io.github.muntashirakon.music.db.PlaylistEntity import io.github.muntashirakon.music.db.PlaylistEntity
import io.github.muntashirakon.music.db.toSongEntity import io.github.muntashirakon.music.db.toSongEntity
import io.github.muntashirakon.music.db.toSongs import io.github.muntashirakon.music.db.toSongsEntity
import io.github.muntashirakon.music.dialogs.RemoveSongFromPlaylistDialog import io.github.muntashirakon.music.dialogs.RemoveSongFromPlaylistDialog
import io.github.muntashirakon.music.extensions.accentColor
import io.github.muntashirakon.music.extensions.accentOutlineColor
import io.github.muntashirakon.music.fragments.LibraryViewModel
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.interfaces.ICabHolder
import io.github.muntashirakon.music.model.PlaylistSong
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.ViewUtil import com.google.android.material.button.MaterialButton
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.viewModel
class OrderablePlaylistSongAdapter( class OrderablePlaylistSongAdapter(
private val playlist: PlaylistEntity, private val playlist: PlaylistEntity,
activity: FragmentActivity, activity: FragmentActivity,
dataSet: ArrayList<Song>, dataSet: MutableList<Song>,
itemLayoutRes: Int, itemLayoutRes: Int,
ICabHolder: ICabHolder?, ICabHolder: ICabHolder?,
private val onMoveItemListener: OnMoveItemListener? ) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, ICabHolder),
) : SongAdapter( DraggableItemAdapter<OrderablePlaylistSongAdapter.ViewHolder> {
activity,
dataSet, val libraryViewModel: LibraryViewModel by activity.viewModel()
itemLayoutRes,
ICabHolder
), DraggableItemAdapter<OrderablePlaylistSongAdapter.ViewHolder> {
init { init {
setMultiSelectMenuRes(menu.menu_playlists_songs_selection) this.setHasStableIds(true)
this.setMultiSelectMenuRes(R.menu.menu_playlists_songs_selection)
}
override fun getItemId(position: Int): Long {
// requires static value, it means need to keep the same value
// even if the item position has been changed.
return if (position != 0) {
dataSet[position - 1].id
} else {
-1
}
} }
override fun createViewHolder(view: View): SongAdapter.ViewHolder { override fun createViewHolder(view: View): SongAdapter.ViewHolder {
return ViewHolder(view) return ViewHolder(view)
} }
override fun getItemId(position: Int): Long { override fun getItemViewType(position: Int): Int {
var positionFinal = position return if (position == 0) OFFSET_ITEM else SONG
positionFinal-- }
var long: Long = 0 override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) {
if (positionFinal < 0) { if (holder.itemViewType == OFFSET_ITEM) {
long = -2 val viewHolder = holder as ViewHolder
viewHolder.playAction?.let {
it.setOnClickListener {
MusicPlayerRemote.openQueue(dataSet, 0, true)
}
it.accentOutlineColor()
}
viewHolder.shuffleAction?.let {
it.setOnClickListener {
MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
}
it.accentColor()
}
} else { } else {
if (dataSet[positionFinal] is PlaylistSong) { super.onBindViewHolder(holder, position - 1)
long = (dataSet[positionFinal] as PlaylistSong).idInPlayList.toLong()
} }
} }
return long
}
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) { override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) {
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.action_remove_from_playlist -> { R.id.action_remove_from_playlist -> RemoveSongFromPlaylistDialog.create(
RemoveSongFromPlaylistDialog.create(selection.toSongs(playlist.playListId)) selection.toSongsEntity(
playlist
)
)
.show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST") .show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
return else -> super.onMultipleItemAction(menuItem, selection)
}
}
super.onMultipleItemAction(menuItem, selection)
}
override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean {
return onMoveItemListener != null && position > 0 && (ViewUtil.hitTest(
holder.dragView!!, x, y
) || ViewUtil.hitTest(holder.image!!, x, y))
}
override fun onGetItemDraggableRange(holder: ViewHolder, position: Int): ItemDraggableRange {
return ItemDraggableRange(1, dataSet.size)
}
override fun onMoveItem(fromPosition: Int, toPosition: Int) {
if (onMoveItemListener != null && fromPosition != toPosition) {
onMoveItemListener.onMoveItem(fromPosition - 1, toPosition - 1)
} }
} }
override fun onCheckCanDrop(draggingPosition: Int, dropPosition: Int): Boolean { inner class ViewHolder(itemView: View) : AbsOffsetSongAdapter.ViewHolder(itemView) {
return dropPosition > 0 val playAction: MaterialButton? = itemView.findViewById(R.id.playAction)
} val shuffleAction: MaterialButton? = itemView.findViewById(R.id.shuffleAction)
override fun onItemDragStarted(position: Int) {
notifyDataSetChanged()
}
override fun onItemDragFinished(fromPosition: Int, toPosition: Int, result: Boolean) {
notifyDataSetChanged()
}
interface OnMoveItemListener {
fun onMoveItem(fromPosition: Int, toPosition: Int)
}
inner class ViewHolder(itemView: View) : SongAdapter.ViewHolder(itemView),
DraggableItemViewHolder {
@DraggableItemStateFlags
private var mDragStateFlags: Int = 0
override var songMenuRes: Int override var songMenuRes: Int
get() = R.menu.menu_item_playlist_song get() = R.menu.menu_item_playlist_song
@ -123,16 +113,6 @@ class OrderablePlaylistSongAdapter(
super.songMenuRes = value super.songMenuRes = value
} }
init {
if (dragView != null) {
if (onMoveItemListener != null) {
dragView?.visibility = View.VISIBLE
} else {
dragView?.visibility = View.GONE
}
}
}
override fun onSongMenuItemClick(item: MenuItem): Boolean { override fun onSongMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_remove_from_playlist -> { R.id.action_remove_from_playlist -> {
@ -144,13 +124,49 @@ class OrderablePlaylistSongAdapter(
return super.onSongMenuItemClick(item) return super.onSongMenuItemClick(item)
} }
@DraggableItemStateFlags init {
override fun getDragStateFlags(): Int { dragView?.isVisible = true
return mDragStateFlags }
} }
override fun setDragStateFlags(@DraggableItemStateFlags flags: Int) { override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean {
mDragStateFlags = flags if (dataSet.size == 0 or 1 || isInQuickSelectMode) {
return false
}
val dragHandle = holder.dragView ?: return false
val handleWidth = dragHandle.width
val handleHeight = dragHandle.height
val handleLeft = dragHandle.left
val handleTop = dragHandle.top
return (x >= handleLeft && x < handleLeft + handleWidth &&
y >= handleTop && y < handleTop + handleHeight) && position != 0
}
override fun onMoveItem(fromPosition: Int, toPosition: Int) {
dataSet.add(toPosition - 1, dataSet.removeAt(fromPosition - 1))
}
override fun onGetItemDraggableRange(holder: ViewHolder, position: Int): ItemDraggableRange {
return ItemDraggableRange(1, itemCount - 1)
}
override fun onCheckCanDrop(draggingPosition: Int, dropPosition: Int): Boolean {
return true
}
override fun onItemDragStarted(position: Int) {
notifyDataSetChanged()
}
override fun onItemDragFinished(fromPosition: Int, toPosition: Int, result: Boolean) {
notifyDataSetChanged()
}
fun saveSongs(playlistEntity: PlaylistEntity) {
activity.lifecycleScope.launch(Dispatchers.IO) {
libraryViewModel.insertSongs(dataSet.toSongsEntity(playlistEntity))
} }
} }
} }

View file

@ -16,11 +16,11 @@ package io.github.muntashirakon.music.adapter.song
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.SongGlideRequest import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.helper.MusicPlayerRemote.isPlaying import io.github.muntashirakon.music.helper.MusicPlayerRemote.isPlaying
import io.github.muntashirakon.music.helper.MusicPlayerRemote.playNextSong import io.github.muntashirakon.music.helper.MusicPlayerRemote.playNextSong
@ -28,8 +28,6 @@ import io.github.muntashirakon.music.helper.MusicPlayerRemote.removeFromQueue
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.ViewUtil import io.github.muntashirakon.music.util.ViewUtil
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags
@ -41,7 +39,7 @@ import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAct
import me.zhanghai.android.fastscroll.PopupTextProvider import me.zhanghai.android.fastscroll.PopupTextProvider
class PlayingQueueAdapter( class PlayingQueueAdapter(
activity: AppCompatActivity, activity: FragmentActivity,
dataSet: MutableList<Song>, dataSet: MutableList<Song>,
private var current: Int, private var current: Int,
itemLayoutRes: Int itemLayoutRes: Int
@ -79,14 +77,10 @@ class PlayingQueueAdapter(
if (holder.image == null) { if (holder.image == null) {
return return
} }
SongGlideRequest.Builder.from(Glide.with(activity), song) GlideApp.with(activity)
.checkIgnoreMediaStore(activity) .load(RetroGlideExtension.getSongModel(song))
.generatePalette(activity).build() .songCoverOptions(song)
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(holder.image!!)
override fun onColorReady(colors: MediaNotificationProcessor) {
//setColors(colors, holder)
}
})
} }
fun swapDataSet(dataSet: List<Song>, position: Int) { fun swapDataSet(dataSet: List<Song>, position: Int) {
@ -156,7 +150,7 @@ class PlayingQueueAdapter(
} }
init { init {
dragView?.visibility = View.VISIBLE dragView?.isVisible = true
} }
override fun onSongMenuItemClick(item: MenuItem): Boolean { override fun onSongMenuItemClick(item: MenuItem): Boolean {
@ -190,7 +184,7 @@ class PlayingQueueAdapter(
private const val UP_NEXT = 2 private const val UP_NEXT = 2
} }
override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int): SwipeResultAction? { override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int): SwipeResultAction {
return if (result == SwipeableItemConstants.RESULT_CANCELED) { return if (result == SwipeableItemConstants.RESULT_CANCELED) {
SwipeResultActionDefault() SwipeResultActionDefault()
} else { } else {

View file

@ -1,63 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package io.github.muntashirakon.music.adapter.song
import android.view.MenuItem
import android.view.View
import androidx.fragment.app.FragmentActivity
import io.github.muntashirakon.music.R
import com.google.android.material.button.MaterialButton
import io.github.muntashirakon.music.db.PlaylistEntity
import io.github.muntashirakon.music.db.toSongEntity
import io.github.muntashirakon.music.dialogs.RemoveSongFromPlaylistDialog
import io.github.muntashirakon.music.interfaces.ICabHolder
import io.github.muntashirakon.music.model.Song
class PlaylistSongAdapter(
private val playlist: PlaylistEntity,
activity: FragmentActivity,
dataSet: MutableList<Song>,
itemLayoutRes: Int,
iCabHolder: ICabHolder?
) : SongAdapter(activity, dataSet, itemLayoutRes, iCabHolder) {
init {
this.setMultiSelectMenuRes(R.menu.menu_cannot_delete_single_songs_playlist_songs_selection)
}
override fun createViewHolder(view: View): SongAdapter.ViewHolder {
return ViewHolder(view)
}
open inner class ViewHolder(itemView: View) : SongAdapter.ViewHolder(itemView) {
override var songMenuRes: Int
get() = R.menu.menu_item_playlist_song
set(value) {
super.songMenuRes = value
}
override fun onSongMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_remove_from_playlist -> {
RemoveSongFromPlaylistDialog.create(song.toSongEntity(playlist.playListId))
.show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
return true
}
}
return super.onSongMenuItemClick(item)
}
}
}

View file

@ -15,14 +15,16 @@
package io.github.muntashirakon.music.adapter.song package io.github.muntashirakon.music.adapter.song
import android.view.View import android.view.View
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import code.name.monkey.appthemehelper.ThemeStore
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.extensions.applyColor import io.github.muntashirakon.music.extensions.accentColor
import io.github.muntashirakon.music.extensions.applyOutlineColor import io.github.muntashirakon.music.extensions.accentOutlineColor
import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.interfaces.ICabHolder
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
class ShuffleButtonSongAdapter( class ShuffleButtonSongAdapter(
@ -32,28 +34,36 @@ class ShuffleButtonSongAdapter(
ICabHolder: ICabHolder? ICabHolder: ICabHolder?
) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, ICabHolder) { ) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, ICabHolder) {
override fun createViewHolder(view: View): SongAdapter.ViewHolder { override fun createViewHolder(view: View): SongAdapter.ViewHolder {
return ViewHolder(view) return ViewHolder(view)
} }
override fun getItemViewType(position: Int): Int {
return if (position == 0) OFFSET_ITEM else SONG
}
override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) { override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) {
if (holder.itemViewType == OFFSET_ITEM) { if (holder.itemViewType == OFFSET_ITEM) {
val color = ThemeStore.accentColor(activity)
val viewHolder = holder as ViewHolder val viewHolder = holder as ViewHolder
viewHolder.playAction?.let { viewHolder.playAction?.let {
it.setOnClickListener { it.setOnClickListener {
MusicPlayerRemote.openQueue(dataSet, 0, true) MusicPlayerRemote.openQueue(dataSet, 0, true)
} }
it.applyOutlineColor(color) it.accentOutlineColor()
} }
viewHolder.shuffleAction?.let { viewHolder.shuffleAction?.let {
it.setOnClickListener { it.setOnClickListener {
MusicPlayerRemote.openAndShuffleQueue(dataSet, true) MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
} }
it.applyColor(color) it.accentColor()
} }
} else { } else {
super.onBindViewHolder(holder, position - 1) super.onBindViewHolder(holder, position - 1)
val landscape = RetroUtil.isLandscape
if ((PreferenceUtil.songGridSize > 2 && !landscape) || (PreferenceUtil.songGridSizeLand > 5 && landscape)) {
holder.menu?.isVisible = false
}
} }
} }
@ -69,4 +79,5 @@ class ShuffleButtonSongAdapter(
super.onClick(v) super.onClick(v)
} }
} }
} }

View file

@ -21,7 +21,6 @@ import androidx.fragment.app.FragmentActivity
import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.interfaces.ICabHolder
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import java.util.*
class SimpleSongAdapter( class SimpleSongAdapter(
context: FragmentActivity, context: FragmentActivity,
@ -45,7 +44,6 @@ class SimpleSongAdapter(
val trackAndTime = (if (fixedTrackNumber > 0) "$fixedTrackNumber | " else "") + val trackAndTime = (if (fixedTrackNumber > 0) "$fixedTrackNumber | " else "") +
MusicUtil.getReadableDurationString(dataSet[position].duration) MusicUtil.getReadableDurationString(dataSet[position].duration)
holder.imageText?.visibility = View.GONE
holder.time?.text = trackAndTime holder.time?.text = trackAndTime
holder.text2?.text = dataSet[position].artistName holder.text2?.text = dataSet[position].artistName
} }

View file

@ -21,6 +21,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.navigation.findNavController import androidx.navigation.findNavController
@ -28,21 +29,20 @@ import io.github.muntashirakon.music.EXTRA_ALBUM_ID
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter
import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder
import io.github.muntashirakon.music.extensions.hide import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.extensions.show import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
import io.github.muntashirakon.music.glide.SongGlideRequest
import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.helper.SortOrder import io.github.muntashirakon.music.helper.SortOrder
import io.github.muntashirakon.music.helper.menu.SongMenuHelper import io.github.muntashirakon.music.helper.menu.SongMenuHelper
import io.github.muntashirakon.music.helper.menu.SongsMenuHelper import io.github.muntashirakon.music.helper.menu.SongsMenuHelper
import io.github.muntashirakon.music.interfaces.ICabCallback
import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.interfaces.ICabHolder
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import com.afollestad.materialcab.MaterialCab
import com.bumptech.glide.Glide
import me.zhanghai.android.fastscroll.PopupTextProvider import me.zhanghai.android.fastscroll.PopupTextProvider
/** /**
@ -50,7 +50,7 @@ import me.zhanghai.android.fastscroll.PopupTextProvider
*/ */
open class SongAdapter( open class SongAdapter(
protected val activity: FragmentActivity, override val activity: FragmentActivity,
var dataSet: MutableList<Song>, var dataSet: MutableList<Song>,
protected var itemLayoutRes: Int, protected var itemLayoutRes: Int,
ICabHolder: ICabHolder?, ICabHolder: ICabHolder?,
@ -59,7 +59,7 @@ open class SongAdapter(
activity, activity,
ICabHolder, ICabHolder,
R.menu.menu_media_selection R.menu.menu_media_selection
), MaterialCab.Callback, PopupTextProvider { ), ICabCallback, PopupTextProvider {
private var showSectionName = true private var showSectionName = true
@ -95,15 +95,15 @@ open class SongAdapter(
val song = dataSet[position] val song = dataSet[position]
val isChecked = isChecked(song) val isChecked = isChecked(song)
holder.itemView.isActivated = isChecked holder.itemView.isActivated = isChecked
if (isChecked) { holder.menu?.isGone = isChecked
holder.menu?.hide()
} else {
holder.menu?.show()
}
holder.title?.text = getSongTitle(song) holder.title?.text = getSongTitle(song)
holder.text?.text = getSongText(song) holder.text?.text = getSongText(song)
holder.text2?.text = getSongText(song) holder.text2?.text = getSongText(song)
loadAlbumCover(song, holder) loadAlbumCover(song, holder)
val landscape = RetroUtil.isLandscape
if ((PreferenceUtil.songGridSize > 2 && !landscape) || (PreferenceUtil.songGridSizeLand > 5 && landscape)) {
holder.menu?.isVisible = false
}
} }
private fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) { private fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) {
@ -120,9 +120,8 @@ open class SongAdapter(
if (holder.image == null) { if (holder.image == null) {
return return
} }
SongGlideRequest.Builder.from(Glide.with(activity), song) GlideApp.with(activity).asBitmapPalette().songCoverOptions(song)
.checkIgnoreMediaStore(activity) .load(RetroGlideExtension.getSongModel(song))
.generatePalette(activity).build()
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder) setColors(colors, holder)
@ -130,15 +129,15 @@ open class SongAdapter(
}) })
} }
private fun getSongTitle(song: Song): String? { private fun getSongTitle(song: Song): String {
return song.title return song.title
} }
private fun getSongText(song: Song): String? { private fun getSongText(song: Song): String {
return song.artistName return song.artistName
} }
private fun getSongText2(song: Song): String? { private fun getSongText2(song: Song): String {
return song.albumName return song.albumName
} }
@ -150,8 +149,8 @@ open class SongAdapter(
return dataSet[position] return dataSet[position]
} }
override fun getName(song: Song): String { override fun getName(model: Song): String {
return song.title return model.title
} }
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) { override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) {
@ -165,6 +164,7 @@ open class SongAdapter(
SortOrder.SongSortOrder.SONG_ARTIST -> dataSet[position].artistName SortOrder.SongSortOrder.SONG_ARTIST -> dataSet[position].artistName
SortOrder.SongSortOrder.SONG_YEAR -> return MusicUtil.getYearString(dataSet[position].year) SortOrder.SongSortOrder.SONG_YEAR -> return MusicUtil.getYearString(dataSet[position].year)
SortOrder.SongSortOrder.COMPOSER -> dataSet[position].composer SortOrder.SongSortOrder.COMPOSER -> dataSet[position].composer
SortOrder.SongSortOrder.SONG_ALBUM_ARTIST -> dataSet[position].albumArtist
else -> { else -> {
return "" return ""
} }

View file

@ -15,18 +15,16 @@
package io.github.muntashirakon.music.appshortcuts package io.github.muntashirakon.music.appshortcuts
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.graphics.drawable.LayerDrawable import android.graphics.drawable.LayerDrawable
import android.os.Build import android.os.Build
import android.util.TypedValue import android.util.TypedValue
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.graphics.drawable.toBitmap
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.extensions.getTintedDrawable
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil
@RequiresApi(Build.VERSION_CODES.N_MR1) @RequiresApi(Build.VERSION_CODES.N_MR1)
object AppShortcutIconGenerator { object AppShortcutIconGenerator {
@ -63,28 +61,17 @@ object AppShortcutIconGenerator {
context: Context, context: Context,
iconId: Int, iconId: Int,
foregroundColor: Int, foregroundColor: Int,
backgroundColor: Int backgroundColor: Int,
): Icon { ): Icon {
// Get and tint foreground and background drawables // Get and tint foreground and background drawables
val vectorDrawable = RetroUtil.getTintedVectorDrawable(context, iconId, foregroundColor) val vectorDrawable = context.getTintedDrawable(iconId, foregroundColor)
val backgroundDrawable = RetroUtil.getTintedVectorDrawable( val backgroundDrawable =
context, R.drawable.ic_app_shortcut_background, backgroundColor context.getTintedDrawable(R.drawable.ic_app_shortcut_background, backgroundColor)
)
// Squash the two drawables together // Squash the two drawables together
val layerDrawable = LayerDrawable(arrayOf(backgroundDrawable, vectorDrawable)) val layerDrawable = LayerDrawable(arrayOf(backgroundDrawable, vectorDrawable))
// Return as an Icon // Return as an Icon
return Icon.createWithBitmap(drawableToBitmap(layerDrawable)) return Icon.createWithBitmap(layerDrawable.toBitmap())
}
private fun drawableToBitmap(drawable: Drawable): Bitmap {
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
} }
} }

View file

@ -17,6 +17,7 @@ package io.github.muntashirakon.music.appshortcuts
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.core.os.bundleOf
import io.github.muntashirakon.music.appshortcuts.shortcuttype.LastAddedShortcutType import io.github.muntashirakon.music.appshortcuts.shortcuttype.LastAddedShortcutType
import io.github.muntashirakon.music.appshortcuts.shortcuttype.ShuffleAllShortcutType import io.github.muntashirakon.music.appshortcuts.shortcuttype.ShuffleAllShortcutType
import io.github.muntashirakon.music.appshortcuts.shortcuttype.TopTracksShortcutType import io.github.muntashirakon.music.appshortcuts.shortcuttype.TopTracksShortcutType
@ -26,7 +27,11 @@ import io.github.muntashirakon.music.model.smartplaylist.LastAddedPlaylist
import io.github.muntashirakon.music.model.smartplaylist.ShuffleAllPlaylist import io.github.muntashirakon.music.model.smartplaylist.ShuffleAllPlaylist
import io.github.muntashirakon.music.model.smartplaylist.TopTracksPlaylist import io.github.muntashirakon.music.model.smartplaylist.TopTracksPlaylist
import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.service.MusicService
import io.github.muntashirakon.music.service.MusicService.* import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_PLAY_PLAYLIST
import io.github.muntashirakon.music.service.MusicService.Companion.INTENT_EXTRA_PLAYLIST
import io.github.muntashirakon.music.service.MusicService.Companion.INTENT_EXTRA_SHUFFLE_MODE
import io.github.muntashirakon.music.service.MusicService.Companion.SHUFFLE_MODE_NONE
import io.github.muntashirakon.music.service.MusicService.Companion.SHUFFLE_MODE_SHUFFLE
class AppShortcutLauncherActivity : Activity() { class AppShortcutLauncherActivity : Activity() {
@ -59,9 +64,10 @@ class AppShortcutLauncherActivity : Activity() {
val intent = Intent(this, MusicService::class.java) val intent = Intent(this, MusicService::class.java)
intent.action = ACTION_PLAY_PLAYLIST intent.action = ACTION_PLAY_PLAYLIST
val bundle = Bundle() val bundle = bundleOf(
bundle.putParcelable(INTENT_EXTRA_PLAYLIST, playlist) INTENT_EXTRA_PLAYLIST to playlist,
bundle.putInt(INTENT_EXTRA_SHUFFLE_MODE, shuffleMode) INTENT_EXTRA_SHUFFLE_MODE to shuffleMode
)
intent.putExtras(bundle) intent.putExtras(bundle)

View file

@ -21,15 +21,15 @@ import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.os.Build import android.os.Build
import androidx.core.content.getSystemService
import io.github.muntashirakon.music.appshortcuts.shortcuttype.LastAddedShortcutType import io.github.muntashirakon.music.appshortcuts.shortcuttype.LastAddedShortcutType
import io.github.muntashirakon.music.appshortcuts.shortcuttype.ShuffleAllShortcutType import io.github.muntashirakon.music.appshortcuts.shortcuttype.ShuffleAllShortcutType
import io.github.muntashirakon.music.appshortcuts.shortcuttype.TopTracksShortcutType import io.github.muntashirakon.music.appshortcuts.shortcuttype.TopTracksShortcutType
import java.util.*
@TargetApi(Build.VERSION_CODES.N_MR1) @TargetApi(Build.VERSION_CODES.N_MR1)
class DynamicShortcutManager(private val context: Context) { class DynamicShortcutManager(private val context: Context) {
private val shortcutManager: ShortcutManager = private val shortcutManager: ShortcutManager? =
this.context.getSystemService(ShortcutManager::class.java) this.context.getSystemService()
private val defaultShortcuts: List<ShortcutInfo> private val defaultShortcuts: List<ShortcutInfo>
get() = listOf( get() = listOf(
@ -40,12 +40,12 @@ class DynamicShortcutManager(private val context: Context) {
fun initDynamicShortcuts() { fun initDynamicShortcuts() {
// if (shortcutManager.dynamicShortcuts.size == 0) { // if (shortcutManager.dynamicShortcuts.size == 0) {
shortcutManager.dynamicShortcuts = defaultShortcuts shortcutManager?.dynamicShortcuts = defaultShortcuts
// } // }
} }
fun updateDynamicShortcuts() { fun updateDynamicShortcuts() {
shortcutManager.updateShortcuts(defaultShortcuts) shortcutManager?.updateShortcuts(defaultShortcuts)
} }
companion object { companion object {
@ -67,7 +67,7 @@ class DynamicShortcutManager(private val context: Context) {
} }
fun reportShortcutUsed(context: Context, shortcutId: String) { fun reportShortcutUsed(context: Context, shortcutId: String) {
context.getSystemService(ShortcutManager::class.java).reportShortcutUsed(shortcutId) context.getSystemService<ShortcutManager>()?.reportShortcutUsed(shortcutId)
} }
} }
} }

View file

@ -19,7 +19,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ShortcutInfo import android.content.pm.ShortcutInfo
import android.os.Build import android.os.Build
import android.os.Bundle import androidx.core.os.bundleOf
import io.github.muntashirakon.music.appshortcuts.AppShortcutLauncherActivity import io.github.muntashirakon.music.appshortcuts.AppShortcutLauncherActivity
@TargetApi(Build.VERSION_CODES.N_MR1) @TargetApi(Build.VERSION_CODES.N_MR1)
@ -36,8 +36,7 @@ abstract class BaseShortcutType(internal var context: Context) {
internal fun getPlaySongsIntent(shortcutType: Long): Intent { internal fun getPlaySongsIntent(shortcutType: Long): Intent {
val intent = Intent(context, AppShortcutLauncherActivity::class.java) val intent = Intent(context, AppShortcutLauncherActivity::class.java)
intent.action = Intent.ACTION_VIEW intent.action = Intent.ACTION_VIEW
val b = Bundle() val b = bundleOf(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE to shortcutType)
b.putLong(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE, shortcutType)
intent.putExtras(b) intent.putExtras(b)
return intent return intent
} }

View file

@ -42,6 +42,6 @@ class LastAddedShortcutType(context: Context) : BaseShortcutType(context) {
companion object { companion object {
val id: String val id: String
get() = BaseShortcutType.ID_PREFIX + "last_added" get() = ID_PREFIX + "last_added"
} }
} }

View file

@ -39,6 +39,6 @@ class TopTracksShortcutType(context: Context) : BaseShortcutType(context) {
companion object { companion object {
val id: String val id: String
get() = BaseShortcutType.ID_PREFIX + "top_tracks" get() = ID_PREFIX + "top_tracks"
} }
} }

View file

@ -20,22 +20,27 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.view.View import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.core.graphics.drawable.toBitmap
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.MainActivity import io.github.muntashirakon.music.activities.MainActivity
import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
import io.github.muntashirakon.music.glide.SongGlideRequest import io.github.muntashirakon.music.extensions.getTintedDrawable
import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.service.MusicService
import io.github.muntashirakon.music.service.MusicService.* import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_REWIND
import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_SKIP
import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil import io.github.muntashirakon.music.util.RetroUtil
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
class AppWidgetBig : BaseAppWidget() { class AppWidgetBig : BaseAppWidget() {
private var target: Target<Bitmap>? = null // for cancellation private var target: Target<Bitmap>? = null // for cancellation
@ -55,31 +60,24 @@ class AppWidgetBig : BaseAppWidget() {
) )
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art) appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_next, createBitmap( R.id.button_next, context.getTintedDrawable(
RetroUtil.getTintedVectorDrawable(
context,
R.drawable.ic_skip_next, R.drawable.ic_skip_next,
MaterialValueHelper.getPrimaryTextColor(context, false) MaterialValueHelper.getPrimaryTextColor(context, false)
)!!, 1f ).toBitmap()
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_prev, createBitmap( R.id.button_prev,
RetroUtil.getTintedVectorDrawable( context.getTintedDrawable(
context,
R.drawable.ic_skip_previous, R.drawable.ic_skip_previous,
MaterialValueHelper.getPrimaryTextColor(context, false) MaterialValueHelper.getPrimaryTextColor(context, false)
)!!, 1f ).toBitmap()
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, BaseAppWidget.Companion.createBitmap( R.id.button_toggle_play_pause,
RetroUtil.getTintedVectorDrawable( context.getTintedDrawable(
context,
R.drawable.ic_play_arrow_white_32dp, R.drawable.ic_play_arrow_white_32dp,
MaterialValueHelper.getPrimaryTextColor(context, false) MaterialValueHelper.getPrimaryTextColor(context, false)
)!!, 1f ).toBitmap()
)
) )
linkButtons(context, appWidgetView) linkButtons(context, appWidgetView)
@ -98,7 +96,7 @@ class AppWidgetBig : BaseAppWidget() {
val song = service.currentSong val song = service.currentSong
// Set the titles and artwork // Set the titles and artwork
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) { if (song.title.isEmpty() && song.artistName.isEmpty()) {
appWidgetView.setViewVisibility( appWidgetView.setViewVisibility(
R.id.media_titles, R.id.media_titles,
View.INVISIBLE View.INVISIBLE
@ -120,33 +118,27 @@ class AppWidgetBig : BaseAppWidget() {
val playPauseRes = val playPauseRes =
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, createBitmap( R.id.button_toggle_play_pause,
RetroUtil.getTintedVectorDrawable( service.getTintedDrawable(
service,
playPauseRes, playPauseRes,
primaryColor primaryColor
)!!, 1f ).toBitmap()
)
) )
// Set prev/next button drawables // Set prev/next button drawables
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_next, createBitmap( R.id.button_next,
RetroUtil.getTintedVectorDrawable( service.getTintedDrawable(
service,
R.drawable.ic_skip_next, R.drawable.ic_skip_next,
primaryColor primaryColor
)!!, 1f ).toBitmap()
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_prev, createBitmap( R.id.button_prev,
RetroUtil.getTintedVectorDrawable( service.getTintedDrawable(
service,
R.drawable.ic_skip_previous, R.drawable.ic_skip_previous,
primaryColor primaryColor
)!!, 1f ).toBitmap()
)
) )
// Link actions buttons to intents // Link actions buttons to intents
@ -154,27 +146,31 @@ class AppWidgetBig : BaseAppWidget() {
// Load the album cover async and push the update on completion // Load the album cover async and push the update on completion
val p = RetroUtil.getScreenSize(service) val p = RetroUtil.getScreenSize(service)
val widgetImageSize = Math.min(p.x, p.y) val widgetImageSize = p.x.coerceAtMost(p.y)
val appContext = service.applicationContext val appContext = service.applicationContext
service.runOnUiThread { service.runOnUiThread {
if (target != null) { if (target != null) {
Glide.clear(target) Glide.with(service).clear(target)
} }
target = SongGlideRequest.Builder.from(Glide.with(appContext), song) target = GlideApp.with(appContext)
.checkIgnoreMediaStore(appContext).asBitmap().build() .asBitmap()
.into(object : SimpleTarget<Bitmap>(widgetImageSize, widgetImageSize) { //.checkIgnoreMediaStore()
.load(RetroGlideExtension.getSongModel(song))
.into(object : CustomTarget<Bitmap>(widgetImageSize, widgetImageSize) {
override fun onResourceReady( override fun onResourceReady(
resource: Bitmap, resource: Bitmap,
glideAnimation: GlideAnimation<in Bitmap> transition: Transition<in Bitmap>?,
) { ) {
update(resource) update(resource)
} }
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable) super.onLoadFailed(errorDrawable)
update(null) update(null)
} }
override fun onLoadCleared(placeholder: Drawable?) {}
private fun update(bitmap: Bitmap?) { private fun update(bitmap: Bitmap?) {
if (bitmap == null) { if (bitmap == null) {
appWidgetView.setImageViewResource( appWidgetView.setImageViewResource(
@ -199,13 +195,17 @@ class AppWidgetBig : BaseAppWidget() {
MainActivity.EXPAND_PANEL, MainActivity.EXPAND_PANEL,
PreferenceUtil.isExpandPanel PreferenceUtil.isExpandPanel
) )
var pendingIntent: PendingIntent
val serviceName = ComponentName(context, MusicService::class.java) val serviceName = ComponentName(context, MusicService::class.java)
// Home // Home
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
pendingIntent = PendingIntent.getActivity(context, 0, action, 0) var pendingIntent =
PendingIntent.getActivity(
context, 0, action, if (VersionUtils.hasMarshmallow())
PendingIntent.FLAG_IMMUTABLE
else 0
)
views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent) views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent)
// Previous track // Previous track

View file

@ -20,24 +20,27 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.view.View import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.core.graphics.drawable.toBitmap
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.MainActivity import io.github.muntashirakon.music.activities.MainActivity
import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
import io.github.muntashirakon.music.glide.SongGlideRequest import io.github.muntashirakon.music.extensions.getTintedDrawable
import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper
import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.service.MusicService
import io.github.muntashirakon.music.service.MusicService.* import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_REWIND
import io.github.muntashirakon.music.util.ImageUtil import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_SKIP
import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
class AppWidgetCard : BaseAppWidget() { class AppWidgetCard : BaseAppWidget() {
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
@ -53,31 +56,25 @@ class AppWidgetCard : BaseAppWidget() {
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art) appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true) val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true)
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_next, createBitmap( R.id.button_next,
RetroUtil.getTintedVectorDrawable( context.getTintedDrawable(
context,
R.drawable.ic_skip_next, R.drawable.ic_skip_next,
secondaryColor secondaryColor
)!!, 1f ).toBitmap()
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_prev, createBitmap( R.id.button_prev,
RetroUtil.getTintedVectorDrawable( context.getTintedDrawable(
context,
R.drawable.ic_skip_previous, R.drawable.ic_skip_previous,
secondaryColor secondaryColor
)!!, 1f ).toBitmap()
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, createBitmap( R.id.button_toggle_play_pause,
RetroUtil.getTintedVectorDrawable( context.getTintedDrawable(
context,
R.drawable.ic_play_arrow_white_32dp, R.drawable.ic_play_arrow_white_32dp,
secondaryColor secondaryColor
)!!, 1f ).toBitmap()
)
) )
linkButtons(context, appWidgetView) linkButtons(context, appWidgetView)
@ -94,7 +91,7 @@ class AppWidgetCard : BaseAppWidget() {
val song = service.currentSong val song = service.currentSong
// Set the titles and artwork // Set the titles and artwork
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) { if (song.title.isEmpty() && song.artistName.isEmpty()) {
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE) appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
} else { } else {
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE) appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
@ -106,33 +103,27 @@ class AppWidgetCard : BaseAppWidget() {
val playPauseRes = val playPauseRes =
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, createBitmap( R.id.button_toggle_play_pause,
RetroUtil.getTintedVectorDrawable( service.getTintedDrawable(
service,
playPauseRes, playPauseRes,
MaterialValueHelper.getSecondaryTextColor(service, true) MaterialValueHelper.getSecondaryTextColor(service, true)
)!!, 1f ).toBitmap()
)
) )
// Set prev/next button drawables // Set prev/next button drawables
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_next, createBitmap( R.id.button_next,
RetroUtil.getTintedVectorDrawable( service.getTintedDrawable(
service,
R.drawable.ic_skip_next, R.drawable.ic_skip_next,
MaterialValueHelper.getSecondaryTextColor(service, true) MaterialValueHelper.getSecondaryTextColor(service, true)
)!!, 1f ).toBitmap()
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_prev, createBitmap( R.id.button_prev,
RetroUtil.getTintedVectorDrawable( service.getTintedDrawable(
service,
R.drawable.ic_skip_previous, R.drawable.ic_skip_previous,
MaterialValueHelper.getSecondaryTextColor(service, true) MaterialValueHelper.getSecondaryTextColor(service, true)
)!!, 1f ).toBitmap()
)
) )
// Link actions buttons to intents // Link actions buttons to intents
@ -150,14 +141,15 @@ class AppWidgetCard : BaseAppWidget() {
// Load the album cover async and push the update on completion // Load the album cover async and push the update on completion
service.runOnUiThread { service.runOnUiThread {
if (target != null) { if (target != null) {
Glide.clear(target) Glide.with(service).clear(target)
} }
target = SongGlideRequest.Builder.from(Glide.with(service), song) target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop() .load(RetroGlideExtension.getSongModel(song))
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) { .centerCrop()
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
override fun onResourceReady( override fun onResourceReady(
resource: BitmapPaletteWrapper, resource: BitmapPaletteWrapper,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper> transition: Transition<in BitmapPaletteWrapper>?,
) { ) {
val palette = resource.palette val palette = resource.palette
update( update(
@ -171,38 +163,31 @@ class AppWidgetCard : BaseAppWidget() {
) )
} }
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable) super.onLoadFailed(errorDrawable)
update(null, MaterialValueHelper.getSecondaryTextColor(service, true)) update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
} }
override fun onLoadCleared(placeholder: Drawable?) {}
private fun update(bitmap: Bitmap?, color: Int) { private fun update(bitmap: Bitmap?, color: Int) {
// Set correct drawable for pause state // Set correct drawable for pause state
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, ImageUtil.createBitmap( R.id.button_toggle_play_pause,
ImageUtil.getTintedVectorDrawable( service.getTintedDrawable(playPauseRes, color).toBitmap()
service, playPauseRes, color
)
)
) )
// Set prev/next button drawables // Set prev/next button drawables
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_next, ImageUtil.createBitmap( R.id.button_next,
ImageUtil.getTintedVectorDrawable( service.getTintedDrawable(R.drawable.ic_skip_next, color).toBitmap()
service, R.drawable.ic_skip_next, color
)
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_prev, ImageUtil.createBitmap( R.id.button_prev,
ImageUtil.getTintedVectorDrawable( service.getTintedDrawable(R.drawable.ic_skip_previous, color).toBitmap()
service, R.drawable.ic_skip_previous, color
)
)
) )
val image = getAlbumArtDrawable(service.resources, bitmap) val image = getAlbumArtDrawable(service, bitmap)
val roundedBitmap = createRoundedBitmap( val roundedBitmap = createRoundedBitmap(
image, imageSize, imageSize, cardRadius, 0F, cardRadius, 0F image, imageSize, imageSize, cardRadius, 0F, cardRadius, 0F
) )
@ -223,13 +208,17 @@ class AppWidgetCard : BaseAppWidget() {
MainActivity.EXPAND_PANEL, MainActivity.EXPAND_PANEL,
PreferenceUtil.isExpandPanel PreferenceUtil.isExpandPanel
) )
var pendingIntent: PendingIntent
val serviceName = ComponentName(context, MusicService::class.java) val serviceName = ComponentName(context, MusicService::class.java)
// Home // Home
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
pendingIntent = PendingIntent.getActivity(context, 0, action, 0) var pendingIntent =
PendingIntent.getActivity(
context, 0, action, if (VersionUtils.hasMarshmallow())
PendingIntent.FLAG_IMMUTABLE
else 0
)
views.setOnClickPendingIntent(R.id.image, pendingIntent) views.setOnClickPendingIntent(R.id.image, pendingIntent)
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent) views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)

View file

@ -0,0 +1,215 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package io.github.muntashirakon.music.appwidgets
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.widget.RemoteViews
import androidx.core.graphics.drawable.toBitmap
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.MainActivity
import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
import io.github.muntashirakon.music.extensions.getTintedDrawable
import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper
import io.github.muntashirakon.music.service.MusicService
import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
import io.github.muntashirakon.music.service.MusicService.Companion.TOGGLE_FAVORITE
import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
class AppWidgetCircle : BaseAppWidget() {
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
/**
* Initialize given widgets to default state, where we launch Music on default click and hide
* actions if service not running.
*/
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_circle)
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true)
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause,
context.getTintedDrawable(
R.drawable.ic_play_arrow,
secondaryColor
).toBitmap()
)
linkButtons(context, appWidgetView)
pushUpdate(context, appWidgetIds, appWidgetView)
}
/**
* Update all active widget instances by pushing changes
*/
override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_circle)
val isPlaying = service.isPlaying
val song = service.currentSong
// Set correct drawable for pause state
val playPauseRes =
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause,
service.getTintedDrawable(
playPauseRes,
MaterialValueHelper.getSecondaryTextColor(service, true)
).toBitmap()
)
val isFavorite = runBlocking(Dispatchers.IO) {
return@runBlocking MusicUtil.repository.isSongFavorite(song.id)
}
val favoriteRes =
if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border
appWidgetView.setImageViewBitmap(
R.id.button_toggle_favorite,
service.getTintedDrawable(
favoriteRes,
MaterialValueHelper.getSecondaryTextColor(service, true)
).toBitmap()
)
// Link actions buttons to intents
linkButtons(service, appWidgetView)
if (imageSize == 0) {
val p = RetroUtil.getScreenSize(service)
imageSize = p.x.coerceAtMost(p.y)
}
// Load the album cover async and push the update on completion
service.runOnUiThread {
if (target != null) {
Glide.with(service).clear(target)
}
target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
.load(RetroGlideExtension.getSongModel(song))
.apply(RequestOptions.circleCropTransform())
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
transition: Transition<in BitmapPaletteWrapper>?,
) {
val palette = resource.palette
update(
resource.bitmap, palette.getVibrantColor(
palette.getMutedColor(
MaterialValueHelper.getSecondaryTextColor(
service, true
)
)
)
)
}
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
}
private fun update(bitmap: Bitmap?, color: Int) {
// Set correct drawable for pause state
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause,
service.getTintedDrawable(
playPauseRes, color
).toBitmap()
)
// Set favorite button drawables
appWidgetView.setImageViewBitmap(
R.id.button_toggle_favorite,
service.getTintedDrawable(
favoriteRes, color
).toBitmap()
)
if (bitmap != null) {
appWidgetView.setImageViewBitmap(R.id.image, bitmap)
}
pushUpdate(service, appWidgetIds, appWidgetView)
}
override fun onLoadCleared(placeholder: Drawable?) {}
})
}
}
/**
* Link up various button actions using [PendingIntent].
*/
private fun linkButtons(context: Context, views: RemoteViews) {
val action = Intent(context, MainActivity::class.java)
.putExtra(
MainActivity.EXPAND_PANEL,
PreferenceUtil.isExpandPanel
)
val serviceName = ComponentName(context, MusicService::class.java)
// Home
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
var pendingIntent =
PendingIntent.getActivity(
context, 0, action, if (VersionUtils.hasMarshmallow())
PendingIntent.FLAG_IMMUTABLE
else 0
)
views.setOnClickPendingIntent(R.id.image, pendingIntent)
// Favorite track
pendingIntent = buildPendingIntent(context, TOGGLE_FAVORITE, serviceName)
views.setOnClickPendingIntent(R.id.button_toggle_favorite, pendingIntent)
// Play and pause
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
}
companion object {
const val NAME = "app_widget_circle"
private var mInstance: AppWidgetCircle? = null
private var imageSize = 0
val instance: AppWidgetCircle
@Synchronized get() {
if (mInstance == null) {
mInstance = AppWidgetCircle()
}
return mInstance!!
}
}
}

View file

@ -21,24 +21,27 @@ import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.view.View import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.core.graphics.drawable.toBitmap
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.MainActivity import io.github.muntashirakon.music.activities.MainActivity
import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
import io.github.muntashirakon.music.glide.SongGlideRequest import io.github.muntashirakon.music.extensions.getTintedDrawable
import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper
import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.service.MusicService
import io.github.muntashirakon.music.service.MusicService.* import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_REWIND
import io.github.muntashirakon.music.util.ImageUtil import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_SKIP
import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
class AppWidgetClassic : BaseAppWidget() { class AppWidgetClassic : BaseAppWidget() {
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
@ -54,33 +57,27 @@ class AppWidgetClassic : BaseAppWidget() {
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art) appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_next, R.id.button_next,
createBitmap(
RetroUtil.getTintedVectorDrawable( context.getTintedDrawable(
context,
R.drawable.ic_skip_next, R.drawable.ic_skip_next,
MaterialValueHelper.getSecondaryTextColor(context, true) MaterialValueHelper.getSecondaryTextColor(context, true)
)!!, 1f ).toBitmap()
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_prev, R.id.button_prev,
createBitmap(
RetroUtil.getTintedVectorDrawable( context.getTintedDrawable(
context,
R.drawable.ic_skip_previous, R.drawable.ic_skip_previous,
MaterialValueHelper.getSecondaryTextColor(context, true) MaterialValueHelper.getSecondaryTextColor(context, true)
)!!, 1f ).toBitmap()
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, R.id.button_toggle_play_pause,
createBitmap(
RetroUtil.getTintedVectorDrawable( context.getTintedDrawable(
context,
R.drawable.ic_play_arrow_white_32dp, R.drawable.ic_play_arrow_white_32dp,
MaterialValueHelper.getSecondaryTextColor(context, true) MaterialValueHelper.getSecondaryTextColor(context, true)
)!!, 1f ).toBitmap()
)
) )
linkButtons(context, appWidgetView) linkButtons(context, appWidgetView)
@ -97,7 +94,7 @@ class AppWidgetClassic : BaseAppWidget() {
val song = service.currentSong val song = service.currentSong
// Set the titles and artwork // Set the titles and artwork
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) { if (song.title.isEmpty() && song.artistName.isEmpty()) {
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE) appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
} else { } else {
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE) appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
@ -120,14 +117,16 @@ class AppWidgetClassic : BaseAppWidget() {
val appContext = service.applicationContext val appContext = service.applicationContext
service.runOnUiThread { service.runOnUiThread {
if (target != null) { if (target != null) {
Glide.clear(target) Glide.with(service).clear(target)
} }
target = SongGlideRequest.Builder.from(Glide.with(service), song) target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop() .load(RetroGlideExtension.getSongModel(song))
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) { //.checkIgnoreMediaStore()
.centerCrop()
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
override fun onResourceReady( override fun onResourceReady(
resource: BitmapPaletteWrapper, resource: BitmapPaletteWrapper,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper> transition: Transition<in BitmapPaletteWrapper>?,
) { ) {
val palette = resource.palette val palette = resource.palette
update( update(
@ -143,49 +142,42 @@ class AppWidgetClassic : BaseAppWidget() {
) )
} }
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable) super.onLoadFailed(errorDrawable)
update(null, Color.WHITE) update(null, Color.WHITE)
} }
override fun onLoadCleared(placeholder: Drawable?) {}
private fun update(bitmap: Bitmap?, color: Int) { private fun update(bitmap: Bitmap?, color: Int) {
// Set correct drawable for pause state // Set correct drawable for pause state
val playPauseRes = val playPauseRes =
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, R.id.button_toggle_play_pause,
ImageUtil.createBitmap( service.getTintedDrawable(
ImageUtil.getTintedVectorDrawable(
service,
playPauseRes, playPauseRes,
color color
) ).toBitmap()
)
) )
// Set prev/next button drawables // Set prev/next button drawables
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_next, R.id.button_next,
ImageUtil.createBitmap( service.getTintedDrawable(
ImageUtil.getTintedVectorDrawable(
service,
R.drawable.ic_skip_next, R.drawable.ic_skip_next,
color color
) ).toBitmap()
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_prev, R.id.button_prev,
ImageUtil.createBitmap( service.getTintedDrawable(
ImageUtil.getTintedVectorDrawable(
service,
R.drawable.ic_skip_previous, R.drawable.ic_skip_previous,
color color
) ).toBitmap()
)
) )
val image = getAlbumArtDrawable(service.resources, bitmap) val image = getAlbumArtDrawable(service, bitmap)
val roundedBitmap = val roundedBitmap =
createRoundedBitmap( createRoundedBitmap(
image, image,
@ -213,13 +205,16 @@ class AppWidgetClassic : BaseAppWidget() {
MainActivity.EXPAND_PANEL, MainActivity.EXPAND_PANEL,
PreferenceUtil.isExpandPanel PreferenceUtil.isExpandPanel
) )
var pendingIntent: PendingIntent
val serviceName = ComponentName(context, MusicService::class.java) val serviceName = ComponentName(context, MusicService::class.java)
// Home // Home
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
pendingIntent = PendingIntent.getActivity(context, 0, action, 0) var pendingIntent = PendingIntent.getActivity(
context, 0, action, if (VersionUtils.hasMarshmallow())
PendingIntent.FLAG_IMMUTABLE
else 0
)
views.setOnClickPendingIntent(R.id.image, pendingIntent) views.setOnClickPendingIntent(R.id.image, pendingIntent)
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent) views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)

View file

@ -0,0 +1,261 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package io.github.muntashirakon.music.appwidgets
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.RemoteViews
import androidx.core.graphics.drawable.toBitmap
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.MainActivity
import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
import io.github.muntashirakon.music.extensions.getTintedDrawable
import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper
import io.github.muntashirakon.music.service.MusicService
import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_REWIND
import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_SKIP
import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
import io.github.muntashirakon.music.util.DensityUtil
import io.github.muntashirakon.music.util.PreferenceUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
class AppWidgetMD3 : BaseAppWidget() {
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
/**
* Initialize given widgets to default state, where we launch Music on default click and hide
* actions if service not running.
*/
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_md3)
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true)
appWidgetView.setImageViewBitmap(
R.id.button_next,
context.getTintedDrawable(
R.drawable.ic_skip_next,
secondaryColor
).toBitmap()
)
appWidgetView.setImageViewBitmap(
R.id.button_prev,
context.getTintedDrawable(
R.drawable.ic_skip_previous,
secondaryColor
).toBitmap()
)
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause,
context.getTintedDrawable(
R.drawable.ic_play_arrow_white_32dp,
secondaryColor
).toBitmap()
)
linkButtons(context, appWidgetView)
pushUpdate(context, appWidgetIds, appWidgetView)
}
/**
* Update all active widget instances by pushing changes
*/
override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_md3)
val isPlaying = service.isPlaying
val song = service.currentSong
// Set the titles and artwork
if (song.title.isEmpty() && song.artistName.isEmpty()) {
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
} else {
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
appWidgetView.setTextViewText(R.id.title, song.title)
appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song))
}
// Set correct drawable for pause state
val playPauseRes =
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause,
service.getTintedDrawable(
playPauseRes,
MaterialValueHelper.getSecondaryTextColor(service, true)
).toBitmap()
)
// Set prev/next button drawables
appWidgetView.setImageViewBitmap(
R.id.button_next,
service.getTintedDrawable(
R.drawable.ic_skip_next,
MaterialValueHelper.getSecondaryTextColor(service, true)
).toBitmap()
)
appWidgetView.setImageViewBitmap(
R.id.button_prev,
service.getTintedDrawable(
R.drawable.ic_skip_previous,
MaterialValueHelper.getSecondaryTextColor(service, true)
).toBitmap()
)
// Link actions buttons to intents
linkButtons(service, appWidgetView)
if (imageSize == 0) {
imageSize =
service.resources.getDimensionPixelSize(R.dimen.app_widget_card_image_size)
}
if (cardRadius == 0f) {
cardRadius =
DensityUtil.dip2px(service, 8F).toFloat()
}
// Load the album cover async and push the update on completion
service.runOnUiThread {
if (target != null) {
Glide.with(service).clear(target)
}
target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
.load(RetroGlideExtension.getSongModel(song))
.centerCrop()
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
transition: Transition<in BitmapPaletteWrapper>?,
) {
val palette = resource.palette
update(
resource.bitmap, palette.getVibrantColor(
palette.getMutedColor(
MaterialValueHelper.getSecondaryTextColor(
service, true
)
)
)
)
}
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
}
override fun onLoadCleared(placeholder: Drawable?) {}
private fun update(bitmap: Bitmap?, color: Int) {
// Set correct drawable for pause state
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause,
service.getTintedDrawable(playPauseRes, color).toBitmap()
)
// Set prev/next button drawables
appWidgetView.setImageViewBitmap(
R.id.button_next,
service.getTintedDrawable(R.drawable.ic_skip_next, color).toBitmap()
)
appWidgetView.setImageViewBitmap(
R.id.button_prev,
service.getTintedDrawable(R.drawable.ic_skip_previous, color).toBitmap()
)
val image = getAlbumArtDrawable(service, bitmap)
val roundedBitmap = createRoundedBitmap(
image,
imageSize,
imageSize,
cardRadius,
cardRadius,
cardRadius,
cardRadius
)
appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap)
pushUpdate(service, appWidgetIds, appWidgetView)
}
})
}
}
/**
* Link up various button actions using [PendingIntent].
*/
private fun linkButtons(context: Context, views: RemoteViews) {
val action = Intent(context, MainActivity::class.java)
.putExtra(
MainActivity.EXPAND_PANEL,
PreferenceUtil.isExpandPanel
)
val serviceName = ComponentName(context, MusicService::class.java)
// Home
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
var pendingIntent =
PendingIntent.getActivity(
context, 0, action, if (VersionUtils.hasMarshmallow())
PendingIntent.FLAG_IMMUTABLE
else 0
)
views.setOnClickPendingIntent(R.id.image, pendingIntent)
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
// Previous track
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
// Play and pause
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
// Next track
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
}
companion object {
const val NAME = "app_widget_md3"
private var mInstance: AppWidgetMD3? = null
private var imageSize = 0
private var cardRadius = 0F
val instance: AppWidgetMD3
@Synchronized get() {
if (mInstance == null) {
mInstance = AppWidgetMD3()
}
return mInstance!!
}
}
}

View file

@ -20,23 +20,27 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.view.View import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.core.graphics.drawable.toBitmap
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.MainActivity import io.github.muntashirakon.music.activities.MainActivity
import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
import io.github.muntashirakon.music.glide.SongGlideRequest import io.github.muntashirakon.music.extensions.getTintedDrawable
import io.github.muntashirakon.music.glide.GlideApp
import io.github.muntashirakon.music.glide.RetroGlideExtension
import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper
import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.service.MusicService
import io.github.muntashirakon.music.service.MusicService.* import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_REWIND
import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_SKIP
import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
class AppWidgetSmall : BaseAppWidget() { class AppWidgetSmall : BaseAppWidget() {
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
@ -52,33 +56,26 @@ class AppWidgetSmall : BaseAppWidget() {
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art) appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_next, R.id.button_next,
createBitmap( context.getTintedDrawable(
RetroUtil.getTintedVectorDrawable(
context,
R.drawable.ic_skip_next, R.drawable.ic_skip_next,
MaterialValueHelper.getSecondaryTextColor(context, true) MaterialValueHelper.getSecondaryTextColor(context, true)
)!!, 1f ).toBitmap()
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_prev, R.id.button_prev,
createBitmap(
RetroUtil.getTintedVectorDrawable( context.getTintedDrawable(
context,
R.drawable.ic_skip_previous, R.drawable.ic_skip_previous,
MaterialValueHelper.getSecondaryTextColor(context, true) MaterialValueHelper.getSecondaryTextColor(context, true)
)!!, 1f ).toBitmap()
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, R.id.button_toggle_play_pause,
createBitmap(
RetroUtil.getTintedVectorDrawable( context.getTintedDrawable(
context,
R.drawable.ic_play_arrow_white_32dp, R.drawable.ic_play_arrow_white_32dp,
MaterialValueHelper.getSecondaryTextColor(context, true) MaterialValueHelper.getSecondaryTextColor(context, true)
)!!, 1f ).toBitmap()
)
) )
linkButtons(context, appWidgetView) linkButtons(context, appWidgetView)
@ -95,10 +92,10 @@ class AppWidgetSmall : BaseAppWidget() {
val song = service.currentSong val song = service.currentSong
// Set the titles and artwork // Set the titles and artwork
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) { if (song.title.isEmpty() && song.artistName.isEmpty()) {
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE) appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
} else { } else {
if (TextUtils.isEmpty(song.title) || TextUtils.isEmpty(song.artistName)) { if (song.title.isEmpty() || song.artistName.isEmpty()) {
appWidgetView.setTextViewText(R.id.text_separator, "") appWidgetView.setTextViewText(R.id.text_separator, "")
} else { } else {
appWidgetView.setTextViewText(R.id.text_separator, "") appWidgetView.setTextViewText(R.id.text_separator, "")
@ -123,14 +120,16 @@ class AppWidgetSmall : BaseAppWidget() {
val appContext = service.applicationContext val appContext = service.applicationContext
service.runOnUiThread { service.runOnUiThread {
if (target != null) { if (target != null) {
Glide.clear(target) Glide.with(service).clear(target)
} }
target = SongGlideRequest.Builder.from(Glide.with(service), song) target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop() //.checkIgnoreMediaStore()
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) { .load(RetroGlideExtension.getSongModel(song))
.centerCrop()
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
override fun onResourceReady( override fun onResourceReady(
resource: BitmapPaletteWrapper, resource: BitmapPaletteWrapper,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper> transition: Transition<in BitmapPaletteWrapper>?,
) { ) {
val palette = resource.palette val palette = resource.palette
update( update(
@ -144,8 +143,12 @@ class AppWidgetSmall : BaseAppWidget() {
) )
} }
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable) super.onLoadFailed(errorDrawable)
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
}
override fun onLoadCleared(placeholder: Drawable?) {
update(null, MaterialValueHelper.getSecondaryTextColor(service, true)) update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
} }
@ -154,30 +157,21 @@ class AppWidgetSmall : BaseAppWidget() {
val playPauseRes = if (isPlaying) R.drawable.ic_pause val playPauseRes = if (isPlaying) R.drawable.ic_pause
else R.drawable.ic_play_arrow_white_32dp else R.drawable.ic_play_arrow_white_32dp
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, createBitmap( R.id.button_toggle_play_pause,
RetroUtil.getTintedVectorDrawable( service.getTintedDrawable(playPauseRes, color).toBitmap()
service, playPauseRes, color
)!!, 1f
)
) )
// Set prev/next button drawables // Set prev/next button drawables
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_next, createBitmap( R.id.button_next,
RetroUtil.getTintedVectorDrawable( service.getTintedDrawable(R.drawable.ic_skip_next, color).toBitmap()
service, R.drawable.ic_skip_next, color
)!!, 1f
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_prev, createBitmap( R.id.button_prev,
RetroUtil.getTintedVectorDrawable( service.getTintedDrawable(R.drawable.ic_skip_previous, color).toBitmap()
service, R.drawable.ic_skip_previous, color
)!!, 1f
)
) )
val image = getAlbumArtDrawable(service.resources, bitmap) val image = getAlbumArtDrawable(service, bitmap)
val roundedBitmap = createRoundedBitmap( val roundedBitmap = createRoundedBitmap(
image, imageSize, imageSize, cardRadius, 0f, 0f, 0f image, imageSize, imageSize, cardRadius, 0f, 0f, 0f
) )
@ -198,13 +192,17 @@ class AppWidgetSmall : BaseAppWidget() {
MainActivity.EXPAND_PANEL, MainActivity.EXPAND_PANEL,
PreferenceUtil.isExpandPanel PreferenceUtil.isExpandPanel
) )
var pendingIntent: PendingIntent
val serviceName = ComponentName(context, MusicService::class.java) val serviceName = ComponentName(context, MusicService::class.java)
// Home // Home
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
pendingIntent = PendingIntent.getActivity(context, 0, action, 0) var pendingIntent =
PendingIntent.getActivity(
context, 0, action, if (VersionUtils.hasMarshmallow())
PendingIntent.FLAG_IMMUTABLE
else 0
)
views.setOnClickPendingIntent(R.id.image, pendingIntent) views.setOnClickPendingIntent(R.id.image, pendingIntent)
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent) views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)

View file

@ -18,49 +18,44 @@ import android.app.PendingIntent
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.text.TextUtils
import android.view.View import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import io.github.muntashirakon.music.App import androidx.core.graphics.drawable.toBitmap
import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.MainActivity import io.github.muntashirakon.music.activities.MainActivity
import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
import io.github.muntashirakon.music.extensions.getTintedDrawable
import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.service.MusicService
import io.github.muntashirakon.music.service.MusicService.* import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_REWIND
import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_SKIP
import io.github.muntashirakon.music.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil
class AppWidgetText : BaseAppWidget() { class AppWidgetText : BaseAppWidget() {
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) { override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_text) val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_text)
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_next, createBitmap( R.id.button_next,
RetroUtil.getTintedVectorDrawable( context.getTintedDrawable(R.drawable.ic_skip_next, ContextCompat.getColor(
context, R.drawable.ic_skip_next, ContextCompat.getColor(
context, R.color.md_white_1000 context, R.color.md_white_1000
) )).toBitmap()
)!!, 1f
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_prev, createBitmap( R.id.button_prev,
RetroUtil.getTintedVectorDrawable( context.getTintedDrawable(R.drawable.ic_skip_previous, ContextCompat.getColor(
context, R.drawable.ic_skip_previous, ContextCompat.getColor(
context, R.color.md_white_1000 context, R.color.md_white_1000
) )
)!!, 1f ).toBitmap()
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, createBitmap( R.id.button_toggle_play_pause,
RetroUtil.getTintedVectorDrawable( context.getTintedDrawable(R.drawable.ic_play_arrow_white_32dp, ContextCompat.getColor(
context, R.drawable.ic_play_arrow_white_32dp, ContextCompat.getColor(
context, R.color.md_white_1000 context, R.color.md_white_1000
) )
)!!, 1f ).toBitmap()
)
) )
appWidgetView.setTextColor( appWidgetView.setTextColor(
@ -83,13 +78,16 @@ class AppWidgetText : BaseAppWidget() {
MainActivity.EXPAND_PANEL, MainActivity.EXPAND_PANEL,
PreferenceUtil.isExpandPanel PreferenceUtil.isExpandPanel
) )
var pendingIntent: PendingIntent
val serviceName = ComponentName(context, MusicService::class.java) val serviceName = ComponentName(context, MusicService::class.java)
// Home // Home
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
pendingIntent = PendingIntent.getActivity(context, 0, action, 0) var pendingIntent = PendingIntent.getActivity(
context, 0, action, if (VersionUtils.hasMarshmallow())
PendingIntent.FLAG_IMMUTABLE
else 0
)
views.setOnClickPendingIntent(R.id.image, pendingIntent) views.setOnClickPendingIntent(R.id.image, pendingIntent)
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent) views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
@ -113,7 +111,7 @@ class AppWidgetText : BaseAppWidget() {
val song = service.currentSong val song = service.currentSong
// Set the titles and artwork // Set the titles and artwork
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) { if (song.title.isEmpty() && song.artistName.isEmpty()) {
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE) appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
} else { } else {
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE) appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
@ -127,35 +125,29 @@ class AppWidgetText : BaseAppWidget() {
val playPauseRes = if (isPlaying) R.drawable.ic_pause val playPauseRes = if (isPlaying) R.drawable.ic_pause
else R.drawable.ic_play_arrow_white_32dp else R.drawable.ic_play_arrow_white_32dp
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, createBitmap( R.id.button_toggle_play_pause,
RetroUtil.getTintedVectorDrawable( service.getTintedDrawable(playPauseRes, ContextCompat.getColor(
App.getContext(), playPauseRes, ContextCompat.getColor( service, R.color.md_white_1000)
App.getContext(), R.color.md_white_1000 ).toBitmap()
)
)!!, 1f
)
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_next, createBitmap( R.id.button_next,
RetroUtil.getTintedVectorDrawable( service.getTintedDrawable(
App.getContext(),
R.drawable.ic_skip_next, R.drawable.ic_skip_next,
ContextCompat.getColor( ContextCompat.getColor(
App.getContext(), R.color.md_white_1000 service,
) R.color.md_white_1000
)!!, 1f
) )
).toBitmap()
) )
appWidgetView.setImageViewBitmap( appWidgetView.setImageViewBitmap(
R.id.button_prev, createBitmap( R.id.button_prev,
RetroUtil.getTintedVectorDrawable( service.getTintedDrawable(
App.getContext(),
R.drawable.ic_skip_previous, R.drawable.ic_skip_previous,
ContextCompat.getColor( ContextCompat.getColor(
App.getContext(), R.color.md_white_1000 service, R.color.md_white_1000
)
)!!, 1f
) )
).toBitmap()
) )
pushUpdate(service.applicationContext, appWidgetIds, appWidgetView) pushUpdate(service.applicationContext, appWidgetIds, appWidgetView)

View file

@ -20,19 +20,20 @@ import android.appwidget.AppWidgetProvider
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Resources
import android.graphics.* import android.graphics.*
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build
import android.text.TextUtils
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import io.github.muntashirakon.music.App import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.service.MusicService
import io.github.muntashirakon.music.service.MusicService.* import io.github.muntashirakon.music.service.MusicService.Companion.APP_WIDGET_UPDATE
import io.github.muntashirakon.music.service.MusicService.Companion.EXTRA_APP_WIDGET_NAME
import io.github.muntashirakon.music.service.MusicService.Companion.FAVORITE_STATE_CHANGED
import io.github.muntashirakon.music.service.MusicService.Companion.META_CHANGED
import io.github.muntashirakon.music.service.MusicService.Companion.PLAY_STATE_CHANGED
abstract class BaseAppWidget : AppWidgetProvider() { abstract class BaseAppWidget : AppWidgetProvider() {
@ -57,7 +58,7 @@ abstract class BaseAppWidget : AppWidgetProvider() {
*/ */
fun notifyChange(service: MusicService, what: String) { fun notifyChange(service: MusicService, what: String) {
if (hasInstances(service)) { if (hasInstances(service)) {
if (META_CHANGED == what || PLAY_STATE_CHANGED == what) { if (META_CHANGED == what || PLAY_STATE_CHANGED == what || FAVORITE_STATE_CHANGED == what) {
performUpdate(service, null) performUpdate(service, null)
} }
} }
@ -96,10 +97,14 @@ abstract class BaseAppWidget : AppWidgetProvider() {
): PendingIntent { ): PendingIntent {
val intent = Intent(action) val intent = Intent(action)
intent.component = serviceName intent.component = serviceName
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { return if (VersionUtils.hasOreo()) {
PendingIntent.getForegroundService(context, 0, intent, 0) PendingIntent.getForegroundService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
} else { } else {
PendingIntent.getService(context, 0, intent, 0) PendingIntent.getService(
context, 0, intent, if (VersionUtils.hasMarshmallow())
PendingIntent.FLAG_IMMUTABLE
else 0
)
} }
} }
@ -107,18 +112,18 @@ abstract class BaseAppWidget : AppWidgetProvider() {
abstract fun performUpdate(service: MusicService, appWidgetIds: IntArray?) abstract fun performUpdate(service: MusicService, appWidgetIds: IntArray?)
protected fun getAlbumArtDrawable(resources: Resources, bitmap: Bitmap?): Drawable { protected fun getAlbumArtDrawable(context: Context, bitmap: Bitmap?): Drawable {
return if (bitmap == null) { return if (bitmap == null) {
ContextCompat.getDrawable(App.getContext(), R.drawable.default_audio_art)!! ContextCompat.getDrawable(context, R.drawable.default_audio_art)!!
} else { } else {
BitmapDrawable(resources, bitmap) BitmapDrawable(context.resources, bitmap)
} }
} }
protected fun getSongArtistAndAlbum(song: Song): String { protected fun getSongArtistAndAlbum(song: Song): String {
val builder = StringBuilder() val builder = StringBuilder()
builder.append(song.artistName) builder.append(song.artistName)
if (!TextUtils.isEmpty(song.artistName) && !TextUtils.isEmpty(song.albumName)) { if (song.artistName.isNotEmpty() && song.albumName.isNotEmpty()) {
builder.append("") builder.append("")
} }
builder.append(song.albumName) builder.append(song.albumName)
@ -162,18 +167,6 @@ abstract class BaseAppWidget : AppWidgetProvider() {
return rounded return rounded
} }
fun createBitmap(drawable: Drawable, sizeMultiplier: Float): Bitmap {
val bitmap = Bitmap.createBitmap(
(drawable.intrinsicWidth * sizeMultiplier).toInt(),
(drawable.intrinsicHeight * sizeMultiplier).toInt(),
Bitmap.Config.ARGB_8888
)
val c = Canvas(bitmap)
drawable.setBounds(0, 0, c.width, c.height)
drawable.draw(c)
return bitmap
}
protected fun composeRoundedRectPath( protected fun composeRoundedRectPath(
rect: RectF, rect: RectF,
tl: Float, tl: Float,

View file

@ -0,0 +1,102 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package io.github.muntashirakon.music.auto;
import androidx.annotation.NonNull;
/**
* Created by Beesham Sarendranauth (Beesham)
*/
public class AutoMediaIDHelper {
// Media IDs used on browseable items of MediaBrowser
public static final String MEDIA_ID_EMPTY_ROOT = "__EMPTY_ROOT__";
public static final String MEDIA_ID_ROOT = "__ROOT__";
public static final String MEDIA_ID_MUSICS_BY_SEARCH = "__BY_SEARCH__"; // TODO
public static final String MEDIA_ID_MUSICS_BY_HISTORY = "__BY_HISTORY__";
public static final String MEDIA_ID_MUSICS_BY_TOP_TRACKS = "__BY_TOP_TRACKS__";
public static final String MEDIA_ID_MUSICS_BY_SUGGESTIONS = "__BY_SUGGESTIONS__";
public static final String MEDIA_ID_MUSICS_BY_PLAYLIST = "__BY_PLAYLIST__";
public static final String MEDIA_ID_MUSICS_BY_ALBUM = "__BY_ALBUM__";
public static final String MEDIA_ID_MUSICS_BY_ARTIST = "__BY_ARTIST__";
public static final String MEDIA_ID_MUSICS_BY_ALBUM_ARTIST = "__BY_ALBUM_ARTIST__";
public static final String MEDIA_ID_MUSICS_BY_GENRE = "__BY_GENRE__";
public static final String MEDIA_ID_MUSICS_BY_SHUFFLE = "__BY_SHUFFLE__";
public static final String MEDIA_ID_MUSICS_BY_QUEUE = "__BY_QUEUE__";
public static final String RECENT_ROOT = "__RECENT__";
private static final String CATEGORY_SEPARATOR = "__/__";
private static final String LEAF_SEPARATOR = "__|__";
/**
* Create a String value that represents a playable or a browsable media.
* <p/>
* Encode the media browseable categories, if any, and the unique music ID, if any,
* into a single String mediaID.
* <p/>
* MediaIDs are of the form <categoryType>__/__<categoryValue>__|__<musicUniqueId>, to make it
* easy to find the category (like genre) that a music was selected from, so we
* can correctly build the playing queue. This is specially useful when
* one music can appear in more than one list, like "by genre -> genre_1"
* and "by artist -> artist_1".
*
* @param mediaID Unique ID for playable items, or null for browseable items.
* @param categories Hierarchy of categories representing this item's browsing parents.
* @return A hierarchy-aware media ID.
*/
public static String createMediaID(String mediaID, String... categories) {
StringBuilder sb = new StringBuilder();
if (categories != null) {
for (int i = 0; i < categories.length; i++) {
if (!isValidCategory(categories[i])) {
throw new IllegalArgumentException("Invalid category: " + categories[i]);
}
sb.append(categories[i]);
if (i < categories.length - 1) {
sb.append(CATEGORY_SEPARATOR);
}
}
}
if (mediaID != null) {
sb.append(LEAF_SEPARATOR).append(mediaID);
}
return sb.toString();
}
public static String extractCategory(@NonNull String mediaID) {
int pos = mediaID.indexOf(LEAF_SEPARATOR);
if (pos >= 0) {
return mediaID.substring(0, pos);
}
return mediaID;
}
public static String extractMusicID(@NonNull String mediaID) {
int pos = mediaID.indexOf(LEAF_SEPARATOR);
if (pos >= 0) {
return mediaID.substring(pos + LEAF_SEPARATOR.length());
}
return null;
}
public static boolean isBrowseable(@NonNull String mediaID) {
return !mediaID.contains(LEAF_SEPARATOR);
}
private static boolean isValidCategory(String category) {
return category == null ||
(!category.contains(CATEGORY_SEPARATOR) && !category.contains(LEAF_SEPARATOR));
}
}

View file

@ -0,0 +1,283 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package io.github.muntashirakon.music.auto
import android.content.Context
import android.content.res.Resources
import android.support.v4.media.MediaBrowserCompat
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.model.CategoryInfo
import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.repository.*
import io.github.muntashirakon.music.service.MusicService
import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.PreferenceUtil
import java.lang.ref.WeakReference
/**
* Created by Beesham Sarendranauth (Beesham)
*/
class AutoMusicProvider(
val mContext: Context,
private val songsRepository: SongRepository,
private val albumsRepository: AlbumRepository,
private val artistsRepository: ArtistRepository,
private val genresRepository: GenreRepository,
private val playlistsRepository: PlaylistRepository,
private val topPlayedRepository: TopPlayedRepository
) {
private var mMusicService: WeakReference<MusicService>? = null
fun setMusicService(service: MusicService) {
mMusicService = WeakReference(service)
}
fun getChildren(mediaId: String?, resources: Resources): List<MediaBrowserCompat.MediaItem> {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
when (mediaId) {
AutoMediaIDHelper.MEDIA_ID_ROOT -> {
mediaItems.addAll(getRootChildren(resources))
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST -> for (playlist in playlistsRepository.playlists()) {
mediaItems.add(
AutoMediaItem.with(mContext)
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST, playlist.id)
.icon(R.drawable.ic_playlist_play)
.title(playlist.name)
.subTitle(playlist.getInfoString(mContext))
.asPlayable()
.build()
)
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM -> for (album in albumsRepository.albums()) {
mediaItems.add(
AutoMediaItem.with(mContext)
.path(mediaId, album.id)
.title(album.title)
.subTitle(album.albumArtist ?: album.artistName)
.icon(MusicUtil.getMediaStoreAlbumCoverUri(album.id))
.asPlayable()
.build()
)
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST -> for (artist in artistsRepository.artists()) {
mediaItems.add(
AutoMediaItem.with(mContext)
.asPlayable()
.path(mediaId, artist.id)
.title(artist.name)
.build()
)
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM_ARTIST -> for (artist in artistsRepository.albumArtists()) {
mediaItems.add(
AutoMediaItem.with(mContext)
.asPlayable()
// we just pass album id here as we don't have album artist id's
.path(mediaId, artist.safeGetFirstAlbum().id)
.title(artist.name)
.build()
)
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE -> for (genre in genresRepository.genres()) {
mediaItems.add(
AutoMediaItem.with(mContext)
.asPlayable()
.path(mediaId, genre.id)
.title(genre.name)
.build()
)
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE ->
mMusicService?.get()?.playingQueue
?.let {
for (song in it) {
mediaItems.add(
AutoMediaItem.with(mContext)
.asPlayable()
.path(mediaId, song.id)
.title(song.title)
.subTitle(song.artistName)
.icon(MusicUtil.getMediaStoreAlbumCoverUri(song.albumId))
.build()
)
}
}
else -> {
getPlaylistChildren(mediaId, mediaItems)
}
}
return mediaItems
}
private fun getPlaylistChildren(
mediaId: String?,
mediaItems: MutableList<MediaBrowserCompat.MediaItem>
) {
val songs = when (mediaId) {
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS -> {
topPlayedRepository.topTracks()
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY -> {
topPlayedRepository.recentlyPlayedTracks()
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS -> {
topPlayedRepository.notRecentlyPlayedTracks().take(8)
}
else -> {
emptyList()
}
}
songs.forEach { song ->
mediaItems.add(
getPlayableSong(mediaId, song)
)
}
}
private fun getRootChildren(resources: Resources): List<MediaBrowserCompat.MediaItem> {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
val libraryCategories = PreferenceUtil.libraryCategory
libraryCategories.forEach {
if (it.visible) {
when (it.category) {
CategoryInfo.Category.Albums -> {
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM)
.gridLayout(true)
.icon(R.drawable.ic_album)
.title(resources.getString(R.string.albums)).build()
)
}
CategoryInfo.Category.Artists -> {
if (PreferenceUtil.albumArtistsOnly) {
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM_ARTIST)
.icon(R.drawable.ic_album_artist)
.title(resources.getString(R.string.album_artist)).build()
)
} else {
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST)
.icon(R.drawable.ic_artist)
.title(resources.getString(R.string.artists)).build()
)
}
}
CategoryInfo.Category.Genres -> {
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE)
.icon(R.drawable.ic_guitar)
.title(resources.getString(R.string.genres)).build()
)
}
CategoryInfo.Category.Playlists -> {
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST)
.icon(R.drawable.ic_playlist_play)
.title(resources.getString(R.string.playlists)).build()
)
}
else -> {
}
}
}
}
mediaItems.add(
AutoMediaItem.with(mContext)
.asPlayable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SHUFFLE)
.icon(R.drawable.ic_shuffle)
.title(resources.getString(R.string.action_shuffle_all))
.subTitle(MusicUtil.getPlaylistInfoString(mContext, songsRepository.songs()))
.build()
)
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE)
.icon(R.drawable.ic_queue_music)
.title(resources.getString(R.string.queue))
.subTitle(MusicUtil.getPlaylistInfoString(mContext, MusicPlayerRemote.playingQueue))
.asBrowsable().build()
)
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS)
.icon(R.drawable.ic_trending_up)
.title(resources.getString(R.string.my_top_tracks))
.subTitle(
MusicUtil.getPlaylistInfoString(
mContext,
topPlayedRepository.topTracks()
)
)
.asBrowsable().build()
)
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS)
.icon(R.drawable.ic_face)
.title(resources.getString(R.string.suggestion_songs))
.subTitle(
MusicUtil.getPlaylistInfoString(
mContext,
topPlayedRepository.notRecentlyPlayedTracks().takeIf {
it.size > 9
} ?: emptyList()
)
)
.asBrowsable().build()
)
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY)
.icon(R.drawable.ic_history)
.title(resources.getString(R.string.history))
.subTitle(
MusicUtil.getPlaylistInfoString(
mContext,
topPlayedRepository.recentlyPlayedTracks()
)
)
.asBrowsable().build()
)
return mediaItems
}
private fun getPlayableSong(mediaId: String?, song: Song): MediaBrowserCompat.MediaItem {
return AutoMediaItem.with(mContext)
.asPlayable()
.path(mediaId, song.id)
.title(song.title)
.subTitle(song.artistName)
.icon(MusicUtil.getMediaStoreAlbumCoverUri(song.albumId))
.build()
}
}

View file

@ -0,0 +1,100 @@
package io.github.muntashirakon.music.auto
import android.content.Context
import android.net.Uri
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaDescriptionCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.core.os.bundleOf
internal object AutoMediaItem {
fun with(context: Context): Builder {
return Builder(context)
}
internal class Builder(private val mContext: Context) {
private var mBuilder: MediaDescriptionCompat.Builder?
private var mFlags = 0
fun path(fullPath: String): Builder {
mBuilder?.setMediaId(fullPath)
return this
}
fun path(path: String?, id: Long): Builder {
return path(AutoMediaIDHelper.createMediaID(id.toString(), path))
}
fun title(title: String): Builder {
mBuilder?.setTitle(title)
return this
}
fun subTitle(subTitle: String): Builder {
mBuilder?.setSubtitle(subTitle)
return this
}
fun icon(uri: Uri?): Builder {
mBuilder?.setIconUri(uri)
return this
}
fun icon(iconDrawableId: Int): Builder {
mBuilder?.setIconBitmap(
ResourcesCompat.getDrawable(
mContext.resources,
iconDrawableId,
mContext.theme
)?.toBitmap()
)
return this
}
fun gridLayout(isGrid: Boolean): Builder {
val hints = bundleOf(
CONTENT_STYLE_SUPPORTED to true,
CONTENT_STYLE_BROWSABLE_HINT to
if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE
else CONTENT_STYLE_LIST_ITEM_HINT_VALUE,
CONTENT_STYLE_PLAYABLE_HINT to
if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE
else CONTENT_STYLE_LIST_ITEM_HINT_VALUE
)
mBuilder?.setExtras(hints)
return this
}
fun asBrowsable(): Builder {
mFlags = mFlags or MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
return this
}
fun asPlayable(): Builder {
mFlags = mFlags or MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
return this
}
fun build(): MediaBrowserCompat.MediaItem {
val result = MediaBrowserCompat.MediaItem(mBuilder!!.build(), mFlags)
mBuilder = null
mFlags = 0
return result
}
init {
mBuilder = MediaDescriptionCompat.Builder()
}
companion object{
// Hints - see https://developer.android.com/training/cars/media#default-content-style
const val CONTENT_STYLE_SUPPORTED = "android.media.browse.CONTENT_STYLE_SUPPORTED"
const val CONTENT_STYLE_BROWSABLE_HINT = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT"
const val CONTENT_STYLE_PLAYABLE_HINT = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT"
const val CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1
const val CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2
}
}
}

View file

@ -26,6 +26,8 @@ interface HistoryDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSongInHistory(historyEntity: HistoryEntity) suspend fun insertSongInHistory(historyEntity: HistoryEntity)
@Query("DELETE FROM HistoryEntity WHERE id= :songId")
fun deleteSongInHistory(songId: Long)
@Query("SELECT * FROM HistoryEntity WHERE id = :songId LIMIT 1") @Query("SELECT * FROM HistoryEntity WHERE id = :songId LIMIT 1")
suspend fun isSongPresentInHistory(songId: Long): HistoryEntity? suspend fun isSongPresentInHistory(songId: Long): HistoryEntity?
@ -37,4 +39,7 @@ interface HistoryDao {
@Query("SELECT * FROM HistoryEntity ORDER BY time_played DESC LIMIT $HISTORY_LIMIT") @Query("SELECT * FROM HistoryEntity ORDER BY time_played DESC LIMIT $HISTORY_LIMIT")
fun observableHistorySongs(): LiveData<List<HistoryEntity>> fun observableHistorySongs(): LiveData<List<HistoryEntity>>
@Query("DELETE FROM HistoryEntity")
suspend fun clearHistory()
} }

View file

@ -26,7 +26,7 @@ interface PlaylistDao {
suspend fun renamePlaylist(playlistId: Long, name: String) suspend fun renamePlaylist(playlistId: Long, name: String)
@Query("SELECT * FROM PlaylistEntity WHERE playlist_name = :name") @Query("SELECT * FROM PlaylistEntity WHERE playlist_name = :name")
fun isPlaylistExists(name: String): List<PlaylistEntity> fun playlist(name: String): List<PlaylistEntity>
@Query("SELECT * FROM PlaylistEntity") @Query("SELECT * FROM PlaylistEntity")
suspend fun playlists(): List<PlaylistEntity> suspend fun playlists(): List<PlaylistEntity>
@ -47,7 +47,7 @@ interface PlaylistDao {
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId") @Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId")
suspend fun isSongExistsInPlaylist(playlistId: Long, songId: Long): List<SongEntity> suspend fun isSongExistsInPlaylist(playlistId: Long, songId: Long): List<SongEntity>
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId") @Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId ORDER BY song_key asc")
fun songsFromPlaylist(playlistId: Long): LiveData<List<SongEntity>> fun songsFromPlaylist(playlistId: Long): LiveData<List<SongEntity>>
@Delete @Delete
@ -64,4 +64,7 @@ interface PlaylistDao {
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId") @Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId")
fun favoritesSongs(playlistId: Long): List<SongEntity> fun favoritesSongs(playlistId: Long): List<SongEntity>
@Query("SELECT EXISTS(SELECT * FROM PlaylistEntity WHERE playlist_id = :playlistId)")
fun checkPlaylistExists(playlistId: Long): LiveData<Boolean>
} }

View file

@ -18,7 +18,7 @@ import android.os.Parcelable
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
@Entity @Entity
@Parcelize @Parcelize

Some files were not shown because too many files have changed in this diff Show more