├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ ├── bug-ui-bug.md
│ ├── feature_request.md
│ └── logic-bug.md
├── .gitignore
├── GET_STARTED.md
├── LICENSE
├── README.md
├── README_ES.MD
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
├── release
│ ├── baselineProfiles
│ │ ├── 0
│ │ │ └── CM_2.8.0.dm
│ │ └── 1
│ │ │ └── CM_2.8.0.dm
│ └── output-metadata.json
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ │ └── com
│ │ │ └── sosauce
│ │ │ └── cutemusic
│ │ │ ├── data
│ │ │ ├── RoomConverters.kt
│ │ │ ├── actions
│ │ │ │ ├── MediaItemActions.kt
│ │ │ │ ├── MetadataActions.kt
│ │ │ │ ├── PlayerActions.kt
│ │ │ │ └── PlaylistActions.kt
│ │ │ ├── datastore
│ │ │ │ ├── DataStore.kt
│ │ │ │ └── SettingsExt.kt
│ │ │ ├── playlist
│ │ │ │ ├── PlaylistDao.kt
│ │ │ │ ├── PlaylistDatabase.kt
│ │ │ │ └── PlaylistState.kt
│ │ │ └── states
│ │ │ │ └── MusicState.kt
│ │ │ ├── di
│ │ │ └── AppModule.kt
│ │ │ ├── domain
│ │ │ ├── model
│ │ │ │ ├── Album.kt
│ │ │ │ ├── Artist.kt
│ │ │ │ ├── Folder.kt
│ │ │ │ ├── Lyrics.kt
│ │ │ │ └── Playlist.kt
│ │ │ └── repository
│ │ │ │ ├── MediaStoreHelper.kt
│ │ │ │ ├── MediaStoreHelperImpl.kt
│ │ │ │ └── SafManager.kt
│ │ │ ├── main
│ │ │ ├── App.kt
│ │ │ ├── AutoPlaybackService.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── PlaybackService.kt
│ │ │ └── quickplay
│ │ │ │ ├── QuickPlayActivity.kt
│ │ │ │ ├── QuickPlayUiState.kt
│ │ │ │ └── QuickPlayViewModel.kt
│ │ │ ├── ui
│ │ │ ├── navigation
│ │ │ │ ├── Navigation.kt
│ │ │ │ └── Screen.kt
│ │ │ ├── screens
│ │ │ │ ├── album
│ │ │ │ │ ├── AlbumDetailsLandscape.kt
│ │ │ │ │ ├── AlbumDetailsScreen.kt
│ │ │ │ │ └── AlbumScreen.kt
│ │ │ │ ├── artist
│ │ │ │ │ ├── ArtistDetails.kt
│ │ │ │ │ ├── ArtistDetailsLandscape.kt
│ │ │ │ │ └── ArtistsScreen.kt
│ │ │ │ ├── lyrics
│ │ │ │ │ └── LyricsView.kt
│ │ │ │ ├── main
│ │ │ │ │ ├── MainScreen.kt
│ │ │ │ │ └── components
│ │ │ │ │ │ └── SortingDropdownMenu.kt
│ │ │ │ ├── metadata
│ │ │ │ │ ├── MetadataEditor.kt
│ │ │ │ │ ├── MetadataState.kt
│ │ │ │ │ └── MetadataViewModel.kt
│ │ │ │ ├── playing
│ │ │ │ │ ├── NowPlaying.kt
│ │ │ │ │ ├── NowPlayingLandscape.kt
│ │ │ │ │ └── components
│ │ │ │ │ │ ├── Artwork.kt
│ │ │ │ │ │ ├── Buttons.kt
│ │ │ │ │ │ ├── CuteSlider.kt
│ │ │ │ │ │ ├── CuteSquigglySlider.kt
│ │ │ │ │ │ ├── CuteTimePicker.kt
│ │ │ │ │ │ ├── QueueSheet.kt
│ │ │ │ │ │ ├── QuickActionsRow.kt
│ │ │ │ │ │ ├── RateAdjustmentDialog.kt
│ │ │ │ │ │ ├── SliderStyle.kt
│ │ │ │ │ │ └── SpeedCard.kt
│ │ │ │ ├── playlists
│ │ │ │ │ ├── CreatePlaylistDialog.kt
│ │ │ │ │ ├── PlaylistDetailsScreen.kt
│ │ │ │ │ ├── PlaylistItem.kt
│ │ │ │ │ ├── PlaylistPicker.kt
│ │ │ │ │ └── PlaylistsScreen.kt
│ │ │ │ └── settings
│ │ │ │ │ ├── SettingsLibrary.kt
│ │ │ │ │ ├── SettingsLookAndFeel.kt
│ │ │ │ │ ├── SettingsNowPlaying.kt
│ │ │ │ │ ├── SettingsScreen.kt
│ │ │ │ │ └── compenents
│ │ │ │ │ ├── AboutCard.kt
│ │ │ │ │ ├── FolderItem.kt
│ │ │ │ │ ├── SettingsCategoryCard.kt
│ │ │ │ │ ├── SettingsComponents.kt
│ │ │ │ │ ├── SettingsScreens.kt
│ │ │ │ │ └── Switches.kt
│ │ │ ├── shared_components
│ │ │ │ ├── AnimatedIconButton.kt
│ │ │ │ ├── CuteDropdownMenuItem.kt
│ │ │ │ ├── CuteNavigationButton.kt
│ │ │ │ ├── CuteText.kt
│ │ │ │ ├── LazyRowWithScrollButton.kt
│ │ │ │ ├── MusicDetailsDialog.kt
│ │ │ │ ├── MusicListItem.kt
│ │ │ │ ├── MusicViewModel.kt
│ │ │ │ ├── PlaylistViewModel.kt
│ │ │ │ ├── ScreenSelection.kt
│ │ │ │ ├── Searchbar.kt
│ │ │ │ ├── SelectedBar.kt
│ │ │ │ └── ThreadDivider.kt
│ │ │ ├── theme
│ │ │ │ ├── Color.kt
│ │ │ │ └── Theme.kt
│ │ │ └── widgets
│ │ │ │ ├── MusicWidget.kt
│ │ │ │ ├── WidgetActions.kt
│ │ │ │ └── WidgetBroadcastReceiver.kt
│ │ │ └── utils
│ │ │ ├── Constants.kt
│ │ │ ├── Extensions.kt
│ │ │ └── ImageUtils.kt
│ └── res
│ │ ├── drawable
│ │ ├── add_emoji_rounded.xml
│ │ ├── add_photo_rounded.xml
│ │ ├── artist_rounded.xml
│ │ ├── bedtime_outlined.xml
│ │ ├── carousel.xml
│ │ ├── classic_slider.xml
│ │ ├── dark_mode.xml
│ │ ├── edit_rounded.xml
│ │ ├── export.xml
│ │ ├── folder_rounded.xml
│ │ ├── grid_view.xml
│ │ ├── ic_launcher_foreground.xml
│ │ ├── icon_splash.xml
│ │ ├── image.xml
│ │ ├── info_rounded.xml
│ │ ├── library.xml
│ │ ├── lyrics_rounded.xml
│ │ ├── music_note_rounded.xml
│ │ ├── queue_music_rounded.xml
│ │ ├── reset.xml
│ │ ├── resource_import.xml
│ │ ├── saf.xml
│ │ ├── speed_rounded.xml
│ │ ├── system_theme.xml
│ │ ├── trash_rounded.xml
│ │ ├── trash_rounded_filled.xml
│ │ ├── widget_next.xml
│ │ ├── widget_pause.xml
│ │ ├── widget_play.xml
│ │ └── widget_previous.xml
│ │ ├── font
│ │ └── nunito.ttf
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values-es
│ │ └── strings.xml
│ │ ├── values-fr
│ │ └── strings.xml
│ │ ├── values-ro
│ │ └── strings.xml
│ │ ├── values-tr
│ │ └── strings.xml
│ │ ├── values-v31
│ │ └── colors.xml
│ │ ├── values-zh-rCN
│ │ └── strings.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── automotive_app_desc.xml
│ │ └── music_widget_info.xml
│ └── release
│ └── generated
│ └── baselineProfiles
│ ├── baseline-prof.txt
│ └── startup-prof.txt
├── build.gradle.kts
├── fastlane
└── metadata
│ └── android
│ ├── en-US
│ ├── changelogs
│ │ └── 1
│ ├── full_description.txt
│ ├── images
│ │ ├── icon.png
│ │ └── phoneScreenshots
│ │ │ ├── screenshot0.png
│ │ │ ├── screenshot1.png
│ │ │ ├── screenshot2.png
│ │ │ ├── screenshot3.png
│ │ │ └── screenshot4.png
│ ├── short_description.txt
│ └── title.txt
│ ├── es-ES
│ ├── changelogs
│ │ └── 1
│ ├── full_description.txt
│ ├── short_description.txt
│ └── title.txt
│ └── fr-FR
│ ├── changelogs
│ └── 1
│ ├── full_description.txt
│ ├── short_description.txt
│ └── title.txt
├── font_licence.txt
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14 | custom: https://bit.ly/sosaucePayPal
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-ui-bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug UI Bug
3 | about: Use this to report an UI bug
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **Screenshots**
14 | If applicable, add screenshots to help explain your problem.
15 |
16 | **Smartphone (please complete the following information):**
17 | - Device:
18 | - OS:
19 | - Version:
20 |
21 | **Additional context**
22 | Add any other context about the problem here.
23 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest a feature
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/logic-bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Logic Bug
3 | about: Use this to report a logic bug
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Indicate the steps to reproduce.
15 |
16 | **Expected behavior**
17 | What behavior did you expect ?
18 |
19 | **Screenshots**
20 | If applicable, add screenshots to help explain your problem.
21 |
22 | **Smartphone (please complete the following information):**
23 | - Device:
24 | - OS:
25 | - Version:
26 |
27 | **Additional context**
28 | Add any other context about the problem here.
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Gradle files
2 | .gradle/
3 | build/
4 |
5 | # Local configuration file (sdk path, etc)
6 | local.properties
7 |
8 | # Log/OS Files
9 | *.log
10 |
11 | # Android Studio generated files and folders
12 | captures/
13 | .externalNativeBuild/
14 | .cxx/
15 | *.apk
16 | output.json
17 |
18 | # IntelliJ
19 | *.iml
20 | .idea/
21 | misc.xml
22 | deploymentTargetDropDown.xml
23 | render.experimental.xml
24 |
25 | # Keystore files
26 | *.jks
27 | *.keystore
28 |
29 | # Google Services (e.g. APIs or Firebase)
30 | google-services.json
31 |
32 | # Android Profiling
33 | *.hprof
34 | .DS_Store
35 | .kotlin/sessions/kotlin-compiler-9804472835993706459.salive
36 |
--------------------------------------------------------------------------------
/GET_STARTED.md:
--------------------------------------------------------------------------------
1 |
🚀 Getting Started
2 |
3 |
4 | ### Code Guidelines
5 |
6 | CuteApps currently don't have alot of guidelines, but it is best if you follow the few ones they have such as :
7 |
8 | - Clutter-free experience : This is self explanatory, avoid adding elements if un-needed, two example could be :
9 | - flashing music icon : The music icon in the searchbar flashes red, indicating that it is clickable / has an action related to it
10 | - the restart button, the seek to previous automatically becomes one 10 seconds in the song instead being a whole new button which brings us to the next guideline :
11 | - Make things clear : If you are adding a feature, make sure it is clear of what it does, clear text/description, accurate icon
12 | - Landscape : CuteApps MUST be fully compatible with landscape mode, if you are adding any screen, make sure it has a landscape variant, otherwise your PR will be ignored
13 | - Creativity : This isn't mandatory, but if you are designing a screen or something else, be creative ! Try things no other apps has before, be unique ! Remember, failure is just a step closer to perfection !
14 |
15 | ### Prerequisites
16 |
17 | - Android Studio (latest version recommended)
18 | - Java Development Kit (JDK) 11 or higher
19 | - Git
20 |
21 | ### Installation
22 |
23 | 1. **Clone the repository:**
24 | ```sh
25 | git clone https://github.com/sosauce/CuteMusic.git
26 | cd CuteMusic
27 | ```
28 |
29 | 2. **Open the project in Android Studio:**
30 | - Open Android Studio.
31 | - Select `Open an existing project`.
32 | - Navigate to the `CuteMusic` directory and select it.
33 |
34 | 3. **Build the project:**
35 | - Click on `Build` in the top menu.
36 | - Select `Make Project` and ensure there are no errors.
37 |
38 | 4. **Run the app:**
39 | - Connect an Android device or use an emulator.
40 | - Click on `Run` in the top menu.
41 | - Select your device and click `OK`.
42 |
43 | ### Contributing
44 |
45 | 1. **Fork the repository:**
46 | - Click the `Fork` button on the top right of the repository page.
47 |
48 | 2. **Create a new branch:**
49 | ```sh
50 | git checkout -b feature/YourFeatureName
51 | ```
52 |
53 | 3. **Make your changes:**
54 | - Implement your feature or bug fix.
55 | - Ensure your code follows the project's coding standards.
56 | 4. **Commit your changes:**
57 | ```sh
58 | git add .
59 | git commit -m "Add feature: YourFeatureName"
60 | ```
61 | 5. **Push to your fork:**
62 | ```sh
63 | git push origin feature/YourFeatureName
64 | ```
65 | 6. **Create a Pull Request:**
66 | - Go to the original repository.
67 | - Click on `Pull Requests` and then `New Pull Request`.
68 | - Select your branch and submit the pull request.
69 |
70 | Thank you to anyone taking their time to contribute and improve the app :heart:!!!
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | CuteMusic
3 | CuteMusic is a cute and powerful offline music player for Android!
4 |
5 | > [!CAUTION]
6 | > The Google Play Store release is a fake and stolen one. CuteMusic is NOT officialy available on the Google Play Store !!!
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
I recommend installing from GitHub, as it guarantees access to the latest update.
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | 👀 Overview
40 |
41 | - Play any song from anywhere just by sharing the audio file to the app without downloading it!
42 | - Easy search across all your music/albums/artists!
43 | - Very fast and snappy!
44 | - No unnecessary permissions needed!
45 | - Material 3/You & Monet theming (+ Amoled mode)!
46 | - Blacklist Folders!
47 | - Beautiful landscape UI!
48 | - Tag Editor!
49 | - Playlists support!
50 | - Load and persist songs from anywhere using Android's S.A.F!
51 | - Part of the CuteApps ecosystem!
52 | - Makes you a cutie!
53 |
54 | ---
55 | 💬 Contact Me
56 |
57 | - Discord server: https://discord.gg/c6aCu4yjbu
58 | - Email: sosauce_dev@protonmail.com
59 |
60 | ---
61 | ❤️ Support
62 |
63 | You can support me by donating on my PayPal: https://bit.ly/sosaucePayPal. Thank you so much for the support ❤️
64 |
65 | ---
66 | ⚠️ Copyright
67 |
68 | Copyright (c)2025 sosauce
69 |
70 | This program is free software: you can redistribute it and/or modify
71 | it under the terms of the GNU General Public License as published by
72 | the Free Software Foundation, either version 3 of the License, or
73 | (at your option) any later version.
74 |
75 | This program is distributed in the hope that it will be useful,
76 | but WITHOUT ANY WARRANTY; without even the implied warranty of
77 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
78 | GNU General Public License for more details.
79 |
80 | The above copyright notice, this permission notice, and its license shall be included in all copies or substantial portions of the Software.
81 |
82 | You can find a copy of the GNU General Public License v3 [here](https://www.gnu.org/licenses/)
83 |
84 |
85 | ---
86 | To get started with contributing, please check the [get started readme](https://github.com/sosauce/CuteMusic/blob/main/GET_STARTED.md)
87 |
88 | ---
89 |
90 | #### You can find the SHA-256 here : https://sosauce.github.io/projects/
91 |
--------------------------------------------------------------------------------
/README_ES.MD:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CuteMusic
6 | CuteMusic es una aplicación sencilla, ligera y de código abierto para reproducir música offline en Android, desarrollada en Jetpack Compose y Media3.
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Recomendamos la instalación desde GitHub, ya que garantiza el acceso a las últimas actualizaciones.
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | 👀 Resumen
35 |
36 | - Reproduce tus canciones desde cualquier lugar compartiendo el archivo de audio con la aplicación ¡sin necesidad de descargarlo!
37 | - ¡Búsqueda fácil en toda tu música/álbumes/artistas!
38 | - ¡Muy rápida y ágil!
39 | - ¡Sin permisos innecesarios!
40 | - ¡Tematización Material 3/You & Monet (+ modo Amoled)!
41 | - Lista negra de carpetas.
42 | - ¡Bonita interfaz de usuario panorámica!
43 | - ¡Editor de etiquetas!
44 | - ¡¡Soporta listas de reproducción!
45 | - ¡¡Te convierte en una monada!
46 |
47 | 👑 2025 Hoja de ruta !!!
48 |
49 | - Importación de canciones desde cualquier lugar mediante el [S.A.F](https://developer.android.com/guide/topics/providers/document-provider).
50 | - Android Auto (''Querido Google, Media3 necesita urgentemente una integración fácil con Auto'').
51 | - Widget(s?).
52 |
53 | ---
54 |
55 | 💬 Contactar conmigo
56 |
57 | - Servidor de Discord: https://discord.gg/c6aCu4yjbu.
58 | - Email: sosauce_dev@protonmail.com.
59 |
60 | ---
61 |
62 | ❤️ Ayuda
63 |
64 | Puedes apoyarme donando en mi PayPal: https://bit.ly/sosaucePayPal. Muchísimas gracias por el apoyo ❤️
65 |
66 | ---
67 |
68 | ⚠️ Copyright
69 |
70 | Copyright (c)2024 sosauce.
71 |
72 | Este programa es software libre: puede redistribuirlo y/o modificarlo bajo los términos de la Licencia Pública General GNU publicada por la Free Software Foundation, ya sea la versión 3 de la Licencia, o (a su elección) cualquier versión posterior.
73 |
74 | Este programa se distribuye con la esperanza de que sea útil, pero SIN NINGUNA GARANTÍA; ni siquiera la garantía implícita de COMERCIABILIDAD o IDONEIDAD PARA UN PROPÓSITO PARTICULAR. Consulte la Licencia Pública General GNU para más detalles.
75 |
76 | El aviso de copyright anterior, este aviso de permiso y su licencia se incluirán en todas las copias o partes sustanciales del Software.
77 |
78 | Puede encontrar una copia de la Licencia Pública General GNU v3 [aquí](https://www.gnu.org/licenses/).
79 |
80 | ---
81 |
82 | Para empezar a contribuir, consulte el [readme de introducción](https://github.com/sosauce/CuteMusic/blob/main/GET_STARTED.md).
83 |
84 | ---
85 |
86 | #### El SHA-256 se encuentra aquí: https://sosauce.github.io/projects/
87 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidApplication)
3 | alias(libs.plugins.kotlin)
4 | alias(libs.plugins.compose.compiler)
5 | alias(libs.plugins.serialization)
6 | alias(libs.plugins.ksp)
7 | }
8 |
9 | android {
10 | namespace = "com.sosauce.cutemusic"
11 | compileSdk = 36
12 |
13 | defaultConfig {
14 | applicationId = "com.sosauce.cutemusic"
15 | minSdk = 26
16 | targetSdk = 36
17 | versionCode = 31
18 | versionName = "2.8.1"
19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20 | vectorDrawables {
21 | useSupportLibrary = true
22 | }
23 | ndk {
24 | //noinspection ChromeOsAbiSupport
25 | abiFilters += arrayOf("arm64-v8a", "armeabi-v7a")
26 | }
27 | }
28 |
29 | applicationVariants.all {
30 | val variant = this
31 | variant.outputs
32 | .map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
33 | .forEach { output ->
34 | output.outputFileName = "CM_${variant.versionName}.apk"
35 | }
36 | }
37 |
38 |
39 | buildTypes {
40 | release {
41 | isMinifyEnabled = true
42 | isShrinkResources = true
43 | proguardFiles(
44 | getDefaultProguardFile("proguard-android-optimize.txt"),
45 | "proguard-rules.pro"
46 | )
47 | }
48 | compileOptions {
49 | sourceCompatibility = JavaVersion.VERSION_17
50 | targetCompatibility = JavaVersion.VERSION_17
51 | }
52 | kotlinOptions {
53 | jvmTarget = "17"
54 | }
55 | buildFeatures {
56 | compose = true
57 | }
58 | packaging {
59 | resources {
60 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
61 | }
62 | }
63 | dependenciesInfo {
64 | includeInApk = false
65 | includeInBundle = false
66 | }
67 |
68 |
69 | // splits {
70 | // abi {
71 | // isEnable = true
72 | // reset()
73 | // include("armeabi-v7a", "arm64-v8a")
74 | // isUniversalApk = true
75 | // }
76 | // }
77 |
78 |
79 | }
80 |
81 | dependencies {
82 | implementation(platform(libs.androidx.compose.bom))
83 | implementation(libs.androidx.core.ktx)
84 | implementation(libs.androidx.activity.compose)
85 | implementation(libs.androidx.material3)
86 | implementation(libs.androidx.ui)
87 | implementation(libs.androidx.material.icons.extended)
88 | implementation(libs.androidx.lifecycle.runtime.compose)
89 | implementation(libs.androidx.core.splashscreen)
90 | implementation(libs.androidx.datastore.preferences)
91 | implementation(libs.coil.compose)
92 | implementation(libs.androidx.media3.common)
93 | implementation(libs.androidx.media3.exoplayer)
94 | implementation(libs.androidx.media3.session)
95 | implementation(libs.squigglyslider)
96 | implementation(libs.androidx.compose.animation)
97 | implementation(libs.kotlinx.serialization.json)
98 | implementation(libs.koin.android)
99 | implementation(libs.koin.androidx.compose)
100 | implementation(libs.material.kolor)
101 | implementation(libs.koin.androidx.startup)
102 | implementation(libs.taglib)
103 | debugImplementation(libs.androidx.ui.tooling)
104 | implementation(libs.androidx.room.ktx)
105 | implementation(libs.androidx.emoji2.emojipicker)
106 | implementation(libs.kmpalette.core)
107 | implementation(libs.haze)
108 | implementation(libs.androidx.glance)
109 | implementation(libs.androidx.glance.appwidget)
110 | implementation(libs.androidx.navigation3.runtime)
111 | implementation(libs.androidx.navigation3.ui)
112 | implementation(libs.reorderable)
113 | ksp(libs.androidx.room.compiler)
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 |
--------------------------------------------------------------------------------
/app/release/baselineProfiles/0/CM_2.8.0.dm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/app/release/baselineProfiles/0/CM_2.8.0.dm
--------------------------------------------------------------------------------
/app/release/baselineProfiles/1/CM_2.8.0.dm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/app/release/baselineProfiles/1/CM_2.8.0.dm
--------------------------------------------------------------------------------
/app/release/output-metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "artifactType": {
4 | "type": "APK",
5 | "kind": "Directory"
6 | },
7 | "applicationId": "com.sosauce.cutemusic",
8 | "variantName": "release",
9 | "elements": [
10 | {
11 | "type": "SINGLE",
12 | "filters": [],
13 | "attributes": [],
14 | "versionCode": 30,
15 | "versionName": "2.8.0",
16 | "outputFile": "CM_2.8.0.apk"
17 | }
18 | ],
19 | "elementType": "File",
20 | "baselineProfiles": [
21 | {
22 | "minApi": 28,
23 | "maxApi": 30,
24 | "baselineProfiles": [
25 | "baselineProfiles/1/CM_2.8.0.dm"
26 | ]
27 | },
28 | {
29 | "minApi": 31,
30 | "maxApi": 2147483647,
31 | "baselineProfiles": [
32 | "baselineProfiles/0/CM_2.8.0.dm"
33 | ]
34 | }
35 | ],
36 | "minSdkVersionForDexing": 26
37 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
11 |
12 |
13 |
16 |
19 |
20 |
21 |
31 |
32 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
50 |
51 |
52 |
53 |
54 |
55 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
119 |
120 |
121 |
124 |
125 |
126 |
127 |
128 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/data/RoomConverters.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.data
2 |
3 | import androidx.room.TypeConverter
4 | import kotlinx.serialization.json.Json
5 |
6 | class MediaItemConverter {
7 |
8 |
9 | @TypeConverter
10 | fun mediaItemToString(mediaItems: List): String {
11 | return Json.encodeToString(mediaItems)
12 | }
13 |
14 | @TypeConverter
15 | fun stringToMediaItem(string: String): List {
16 | return Json.decodeFromString>(string)
17 | }
18 |
19 |
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/data/actions/MediaItemActions.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.data.actions
2 |
3 | import android.net.Uri
4 | import androidx.activity.result.ActivityResultLauncher
5 | import androidx.activity.result.IntentSenderRequest
6 |
7 | sealed interface MediaItemActions {
8 |
9 | data class DeleteMediaItem(
10 | val uri: List,
11 | val activityResultLauncher: ActivityResultLauncher
12 | ) : MediaItemActions
13 |
14 | data class ShareMediaItem(
15 | val uri: Uri,
16 | ) : MediaItemActions
17 |
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/data/actions/MetadataActions.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.data.actions
2 |
3 | import android.net.Uri
4 |
5 | sealed interface MetadataActions {
6 |
7 | data class LoadSong(
8 | val path: String,
9 | val uri: Uri
10 | ) : MetadataActions
11 |
12 | data class UpdateAudioArt(
13 | val newArtUri: Uri
14 | ) : MetadataActions
15 |
16 | data object SaveChanges : MetadataActions
17 |
18 | data object RemoveArtwork : MetadataActions
19 |
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/data/actions/PlayerActions.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.data.actions
2 |
3 | import androidx.media3.common.MediaItem
4 |
5 | sealed interface PlayerActions {
6 | data object PlayOrPause : PlayerActions
7 | data object SeekToNextMusic : PlayerActions
8 | data object SeekToPreviousMusic : PlayerActions
9 | data object RestartSong : PlayerActions
10 | data object PlayRandom : PlayerActions
11 | data object StopPlayback : PlayerActions
12 | data class SeekTo(val position: Long) : PlayerActions
13 | data class SeekToSlider(val position: Long) : PlayerActions
14 | data class RewindTo(val position: Long) : PlayerActions
15 | data class StartPlayback(val mediaId: String) : PlayerActions
16 | data class SeekToMusicIndex(val index: Int) : PlayerActions
17 |
18 | /**
19 | * @param mediaId If set to null, it means we want to play a random song
20 | */
21 | data class StartAlbumPlayback(
22 | val albumName: String,
23 | val mediaId: String?
24 | ) : PlayerActions
25 |
26 | /**
27 | * @param mediaId If set to null, it means we want to play a random song
28 | */
29 | data class StartArtistPlayback(
30 | val artistName: String,
31 | val mediaId: String?
32 | ) : PlayerActions
33 |
34 | data class StartPlaylistPlayback(
35 | val playlistSongsId: List,
36 | val mediaId: String?
37 | ) : PlayerActions
38 |
39 | data class UpdateCurrentPosition(
40 | val position: Long
41 | ) : PlayerActions
42 |
43 | data class SetSleepTimer(
44 | val hours: Int,
45 | val minutes: Int
46 | ) : PlayerActions
47 |
48 | data class ReArrangeQueue(
49 | val from: Int,
50 | val to: Int
51 | ) : PlayerActions
52 |
53 | data class RemoveFromQueue(
54 | val mediaId: String
55 | ) : PlayerActions
56 |
57 | data class AddToQueue(
58 | val mediaItem: MediaItem
59 | ) : PlayerActions
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/data/actions/PlaylistActions.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.data.actions
2 |
3 | import android.net.Uri
4 | import com.sosauce.cutemusic.domain.model.Playlist
5 |
6 | sealed interface PlaylistActions {
7 |
8 | data object CreatePlaylist : PlaylistActions
9 | data class UpdateStateName(val name: String) : PlaylistActions
10 | data class UpdateStateEmoji(val emoji: String) : PlaylistActions
11 | data class DeletePlaylist(val playlist: Playlist) : PlaylistActions
12 | data class UpsertPlaylist(val playlist: Playlist) : PlaylistActions // Modify a playlist basically
13 | data class ImportM3uPlaylist(val uri: Uri) : PlaylistActions
14 | data class ExportM3uPlaylist(
15 | val uri: Uri,
16 | val tracks: List
17 | ) : PlaylistActions
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/data/datastore/SettingsExt.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.data.datastore
2 |
3 | import android.content.Context
4 | import android.content.res.Configuration
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.MutableState
7 | import androidx.compose.runtime.getValue
8 | import androidx.compose.runtime.remember
9 | import androidx.compose.runtime.rememberCoroutineScope
10 | import androidx.compose.ui.platform.LocalConfiguration
11 | import androidx.compose.ui.platform.LocalContext
12 | import androidx.datastore.preferences.core.Preferences
13 | import androidx.datastore.preferences.core.edit
14 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
15 | import kotlinx.coroutines.flow.Flow
16 | import kotlinx.coroutines.flow.distinctUntilChanged
17 | import kotlinx.coroutines.flow.map
18 | import kotlinx.coroutines.launch
19 | import kotlinx.serialization.json.Json
20 |
21 | @Composable
22 | fun rememberPreference(
23 | key: Preferences.Key,
24 | defaultValue: T,
25 | ): MutableState {
26 | val coroutineScope = rememberCoroutineScope()
27 | val context = LocalContext.current
28 | val state by remember {
29 | context.dataStore.data
30 | .map { it[key] ?: defaultValue }
31 | }.collectAsStateWithLifecycle(defaultValue)
32 |
33 | return remember(state) {
34 | object : MutableState {
35 | override var value: T
36 | get() = state
37 | set(value) {
38 | coroutineScope.launch {
39 | context.dataStore.edit {
40 | it[key] = value
41 | }
42 | }
43 | }
44 |
45 | override fun component1() = value
46 | override fun component2(): (T) -> Unit = { value = it }
47 | }
48 | }
49 | }
50 |
51 | fun getPreference(
52 | key: Preferences.Key,
53 | defaultValue: T,
54 | context: Context
55 | ): Flow =
56 | context.dataStore.data
57 | .map { preference ->
58 | preference[key] ?: defaultValue
59 | }
60 |
61 | suspend inline fun saveCustomPreference(
62 | value: T,
63 | key: Preferences.Key,
64 | context: Context
65 | ) {
66 | val json = Json.encodeToString(value)
67 | context.dataStore.edit { prefs ->
68 | prefs[key] = json
69 | }
70 | }
71 |
72 | inline fun getCustomPreference(
73 | key: Preferences.Key,
74 | defaultValue: T,
75 | context: Context
76 | ): Flow {
77 | return context.dataStore.data.map { preferences ->
78 | val jsonString = preferences[key]
79 |
80 | jsonString?.let { string ->
81 | Json.decodeFromString(string)
82 | } ?: defaultValue
83 |
84 | }.distinctUntilChanged()
85 | }
86 |
87 |
88 | @Composable
89 | fun rememberIsLandscape(): Boolean {
90 | val config = LocalConfiguration.current
91 |
92 | return remember(config.orientation) {
93 | config.orientation == Configuration.ORIENTATION_LANDSCAPE
94 | }
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/data/playlist/PlaylistDao.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.data.playlist
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Delete
5 | import androidx.room.Query
6 | import androidx.room.Upsert
7 | import com.sosauce.cutemusic.domain.model.Playlist
8 | import kotlinx.coroutines.flow.Flow
9 |
10 | @Dao
11 | interface PlaylistDao {
12 |
13 | @Upsert
14 | suspend fun upsertPlaylist(playlist: Playlist)
15 |
16 | @Delete
17 | suspend fun deletePlaylist(playlist: Playlist)
18 |
19 | @Query("SELECT * FROM playlist ORDER BY name ASC")
20 | fun getPlaylists(): Flow>
21 | //
22 | // @Query("UPDATE playlist SET name = :name, emoji = :emoji WHERE id =:id")
23 | // suspend fun updateNameAndEmoji(
24 | // id: Int,
25 | // name: String,
26 | // emoji: String
27 | // )
28 |
29 |
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/data/playlist/PlaylistDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.data.playlist
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import androidx.room.TypeConverters
6 | import com.sosauce.cutemusic.data.MediaItemConverter
7 | import com.sosauce.cutemusic.domain.model.Playlist
8 |
9 | @Database(
10 | entities = [Playlist::class],
11 | version = 1
12 | )
13 | @TypeConverters(MediaItemConverter::class)
14 | abstract class PlaylistDatabase : RoomDatabase() {
15 | abstract val dao: PlaylistDao
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/data/playlist/PlaylistState.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.data.playlist
2 |
3 | data class PlaylistState(
4 | val emoji: String = "",
5 | val name: String = ""
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/data/states/MusicState.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.data.states
2 |
3 | import android.net.Uri
4 | import androidx.compose.runtime.Stable
5 | import com.sosauce.cutemusic.domain.model.Lyrics
6 |
7 | @Stable
8 | data class MusicState(
9 | val title: String = "",
10 | val artist: String = "",
11 | val artistId: Long = 0,
12 | val art: Uri? = null,
13 | val isPlaying: Boolean = false,
14 | val position: Long = 0L,
15 | val duration: Long = 0L,
16 | val uri: String = "",
17 | val path: String = "",
18 | val album: String = "",
19 | val albumId: Long = 0,
20 | val size: Long = 0,
21 | val speed: Float = 1.0f,
22 | val pitch: Float = 1.0f,
23 | val isPlayerReady: Boolean = false,
24 | val sleepTimerActive: Boolean = false,
25 | val mediaId: String = "",
26 | val mediaIndex: Int = 0,
27 | val loadedMedias: Map = emptyMap(), // Map of mediaId to index
28 | val lyrics: List = emptyList()
29 | )
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/di/AppModule.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.di
2 |
3 | import androidx.room.Room
4 | import com.sosauce.cutemusic.data.playlist.PlaylistDatabase
5 | import com.sosauce.cutemusic.domain.repository.MediaStoreHelper
6 | import com.sosauce.cutemusic.domain.repository.MediaStoreHelperImpl
7 | import com.sosauce.cutemusic.domain.repository.SafManager
8 | import com.sosauce.cutemusic.main.quickplay.QuickPlayViewModel
9 | import com.sosauce.cutemusic.ui.screens.metadata.MetadataViewModel
10 | import com.sosauce.cutemusic.ui.shared_components.MusicViewModel
11 | import com.sosauce.cutemusic.ui.shared_components.PlaylistViewModel
12 | import org.koin.android.ext.koin.androidApplication
13 | import org.koin.android.ext.koin.androidContext
14 | import org.koin.core.module.dsl.singleOf
15 | import org.koin.core.module.dsl.viewModelOf
16 | import org.koin.dsl.module
17 |
18 | val appModule = module {
19 |
20 | single {
21 | MediaStoreHelperImpl(androidContext())
22 | }
23 |
24 | single {
25 | Room.databaseBuilder(
26 | context = androidApplication(),
27 | klass = PlaylistDatabase::class.java,
28 | name = "playlist.db"
29 | ).build().dao
30 | }
31 |
32 |
33 | singleOf(::SafManager)
34 | viewModelOf(::MusicViewModel)
35 | viewModelOf(::MetadataViewModel)
36 | viewModelOf(::PlaylistViewModel)
37 | viewModelOf(::QuickPlayViewModel)
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/domain/model/Album.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.domain.model
2 |
3 | data class Album(
4 | val id: Long,
5 | val name: String,
6 | val artist: String
7 | )
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/domain/model/Artist.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.domain.model
2 |
3 | data class Artist(
4 | val id: Long,
5 | val name: String
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/domain/model/Folder.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.domain.model
2 |
3 | data class Folder(
4 | val name: String,
5 | val path: String
6 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/domain/model/Lyrics.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.domain.model
2 |
3 | import java.util.UUID
4 |
5 | data class Lyrics(
6 | val timestamp: Long = 0L,
7 | val lineLyrics: String = "",
8 | val id: String = UUID.randomUUID().toString()
9 | )
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/domain/model/Playlist.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.domain.model
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 |
6 | @Entity
7 | data class Playlist(
8 | @PrimaryKey(autoGenerate = true)
9 | val id: Int = 0,
10 | val emoji: String,
11 | val name: String,
12 | val musics: List // List of songs ID aka mediaId
13 | )
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/domain/repository/MediaStoreHelper.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.domain.repository
2 |
3 | import android.net.Uri
4 | import androidx.activity.result.ActivityResultLauncher
5 | import androidx.activity.result.IntentSenderRequest
6 | import androidx.media3.common.MediaItem
7 | import com.sosauce.cutemusic.domain.model.Album
8 | import com.sosauce.cutemusic.domain.model.Artist
9 | import com.sosauce.cutemusic.domain.model.Folder
10 | import kotlinx.coroutines.flow.Flow
11 |
12 | interface MediaStoreHelper {
13 |
14 | val musics: List
15 | val albums: List
16 | val artists: List
17 | val folders: List
18 |
19 | fun fetchMusics(): List
20 | fun fetchLatestMusics(): Flow>
21 |
22 | fun fetchAlbums(): List
23 | fun fetchLatestAlbums(): Flow>
24 |
25 | fun fetchArtists(): List
26 | fun fetchLatestArtists(): Flow>
27 |
28 | fun fetchFoldersWithMusics(): List
29 | fun fetchLatestFoldersWithMusics(): Flow>
30 |
31 | suspend fun deleteMusics(
32 | uris: List,
33 | intentSenderLauncher: ActivityResultLauncher
34 | )
35 |
36 | suspend fun editMusic(
37 | uris: List,
38 | intentSenderLauncher: ActivityResultLauncher
39 | )
40 |
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/domain/repository/SafManager.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.domain.repository
2 |
3 | import android.content.Context
4 | import android.net.Uri
5 | import android.os.Bundle
6 | import android.os.ParcelFileDescriptor
7 | import androidx.core.net.toUri
8 | import androidx.media3.common.MediaItem
9 | import androidx.media3.common.MediaMetadata
10 | import com.kyant.taglib.Metadata
11 | import com.kyant.taglib.TagLib
12 | import com.sosauce.cutemusic.data.datastore.getSafTracks
13 | import com.sosauce.cutemusic.utils.getUriFromByteArray
14 | import kotlinx.coroutines.Dispatchers
15 | import kotlinx.coroutines.flow.Flow
16 | import kotlinx.coroutines.flow.map
17 | import kotlinx.coroutines.withContext
18 |
19 | class SafManager(
20 | private val context: Context
21 | ) {
22 |
23 |
24 | fun fetchLatestSafTracks(): Flow> = getSafTracks(context)
25 | .map { tracks ->
26 | tracks.map { uri ->
27 | uriToMediaItem(uri.toUri())
28 | }
29 | }
30 |
31 |
32 | private suspend fun uriToMediaItem(uri: Uri): MediaItem {
33 | return withContext(Dispatchers.IO) {
34 | context.contentResolver.openFileDescriptor(uri, "r")?.use { fd ->
35 | val metadata = loadAudioMetadata(fd)
36 |
37 | val title = metadata?.propertyMap?.get("TITLE")?.getOrNull(0) ?: ""
38 | val artist = metadata?.propertyMap?.get("ARTIST")?.joinToString(", ") ?: ""
39 | val album = metadata?.propertyMap?.get("ALBUM")?.getOrNull(0)
40 | val duration = metadata?.propertyMap?.get("DURATION")?.getOrNull(0)
41 | val artUri =
42 | TagLib.getFrontCover(fd.dup().detachFd())?.data?.getUriFromByteArray(context)
43 |
44 | MediaItem
45 | .Builder()
46 | .setUri(uri)
47 | .setMediaId(uri.hashCode().toString())
48 | .setMediaMetadata(
49 | MediaMetadata
50 | .Builder()
51 | .setIsBrowsable(false)
52 | .setIsPlayable(true)
53 | .setTitle(title)
54 | .setArtist(artist)
55 | .setAlbumTitle(album)
56 | .setArtworkUri(artUri)
57 | .setDurationMs(duration?.toLong() ?: 0)
58 | .setExtras(
59 | Bundle()
60 | .apply {
61 | putString("folder", "SAF")
62 | putLong("size", fd.statSize)
63 | putString("path", "${uri.path}")
64 | putString("uri", uri.toString())
65 | putLong("album_id", 0)
66 | putLong("artist_id", 0)
67 | putBoolean("is_saf", true)
68 | }).build()
69 | )
70 | .build()
71 | } ?: throw IllegalArgumentException("Unable to open file descriptor for uri")
72 | }
73 | }
74 |
75 |
76 | private suspend fun loadAudioMetadata(songFd: ParcelFileDescriptor): Metadata? {
77 | val fd = songFd.dup()?.detachFd() ?: throw NullPointerException()
78 |
79 | return withContext(Dispatchers.IO) {
80 | TagLib.getMetadata(fd)
81 | }
82 | }
83 |
84 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/main/App.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.main
2 |
3 | import android.app.Application
4 | import com.sosauce.cutemusic.di.appModule
5 | import org.koin.android.ext.koin.androidContext
6 | import org.koin.androix.startup.KoinStartup.onKoinStartup
7 |
8 | class App : Application() {
9 | init {
10 | @Suppress("OPT_IN_USAGE")
11 | onKoinStartup {
12 | androidContext(this@App)
13 | modules(appModule)
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/main/AutoPlaybackService.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.main
2 |
3 | import android.content.Intent
4 | import android.media.MediaDescription
5 | import android.media.browse.MediaBrowser
6 | import android.net.Uri
7 | import android.os.Bundle
8 | import android.service.media.MediaBrowserService
9 | import com.sosauce.cutemusic.domain.repository.MediaStoreHelperImpl
10 | import com.sosauce.cutemusic.utils.ROOT_ID
11 | import kotlinx.coroutines.CoroutineScope
12 | import kotlinx.coroutines.Dispatchers
13 | import kotlinx.coroutines.SupervisorJob
14 | import kotlinx.coroutines.flow.collectLatest
15 | import kotlinx.coroutines.launch
16 |
17 | class AutoPlaybackService : MediaBrowserService() {
18 |
19 |
20 | private val mediaStoreHelper by lazy { MediaStoreHelperImpl(this) }
21 | private val job = SupervisorJob()
22 | private val scope = CoroutineScope(Dispatchers.IO + job)
23 |
24 |
25 | override fun onGetRoot(
26 | clientPackageName: String,
27 | clientUid: Int,
28 | rootHints: Bundle?
29 | ): BrowserRoot = BrowserRoot(ROOT_ID, null)
30 |
31 | override fun onLoadChildren(
32 | parentId: String,
33 | result: Result?>
34 | ) {
35 |
36 | val mediaItems: MutableList = mutableListOf()
37 |
38 | if (ROOT_ID == parentId) {
39 | scope.launch {
40 | mediaStoreHelper.fetchLatestMusics().collectLatest { list ->
41 | list.forEach { mediaItem ->
42 | mediaItems.add(
43 | MediaBrowser.MediaItem(
44 | MediaDescription.Builder()
45 | .setMediaId(mediaItem.mediaId)
46 | .setTitle(mediaItem.mediaMetadata.title ?: "No title")
47 | .setIconUri(mediaItem.mediaMetadata.artworkUri ?: Uri.EMPTY)
48 | .build(),
49 | MediaBrowser.MediaItem.FLAG_PLAYABLE
50 | )
51 | )
52 | }
53 | }
54 | result.sendResult(mediaItems)
55 | }
56 | } else result.sendResult(listOf())
57 | }
58 |
59 | override fun onDestroy() {
60 | super.onDestroy()
61 | job.cancel()
62 | }
63 |
64 | override fun onTaskRemoved(rootIntent: Intent?) {
65 | super.onTaskRemoved(rootIntent)
66 | job.cancel()
67 | }
68 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/main/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.main
2 |
3 | import android.Manifest
4 | import android.os.Build
5 | import android.os.Bundle
6 | import android.util.Log
7 | import androidx.activity.ComponentActivity
8 | import androidx.activity.compose.setContent
9 | import androidx.activity.enableEdgeToEdge
10 | import androidx.compose.foundation.isSystemInDarkTheme
11 | import androidx.compose.material3.Scaffold
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.runtime.mutableStateOf
14 | import androidx.compose.runtime.remember
15 | import androidx.compose.runtime.setValue
16 | import androidx.compose.ui.graphics.ImageBitmap
17 | import androidx.core.app.ActivityCompat
18 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
19 | import androidx.core.view.WindowCompat
20 | import com.sosauce.cutemusic.data.datastore.rememberAppTheme
21 | import com.sosauce.cutemusic.domain.model.Playlist
22 | import com.sosauce.cutemusic.ui.navigation.Nav
23 | import com.sosauce.cutemusic.ui.theme.CuteMusicTheme
24 | import com.sosauce.cutemusic.utils.CuteTheme
25 |
26 | class MainActivity : ComponentActivity() {
27 | override fun onCreate(savedInstanceState: Bundle?) {
28 | super.onCreate(savedInstanceState)
29 | installSplashScreen()
30 | enableEdgeToEdge()
31 | val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
32 | arrayOf(Manifest.permission.READ_MEDIA_AUDIO)
33 | } else {
34 | arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
35 | }
36 |
37 | ActivityCompat.requestPermissions(
38 | this,
39 | permission,
40 | 0
41 | )
42 | try {
43 | val c = Playlist::class.java
44 | Log.d("FIELDS_DEBUG", "Fields in Playlist: ${c.declaredFields.joinToString { it.name }}")
45 | } catch (e: Exception) {
46 | Log.e("FIELDS_DEBUG", "Error getting fields", e)
47 | }
48 | setContent {
49 | val theme by rememberAppTheme()
50 | var artImageBitmap by remember { mutableStateOf(ImageBitmap(1, 1)) }
51 | val isSystemInDarkTheme = isSystemInDarkTheme()
52 |
53 | CuteMusicTheme(artImageBitmap = artImageBitmap) {
54 |
55 | WindowCompat
56 | .getInsetsController(window, window.decorView)
57 | .apply {
58 |
59 | val isLight = if (theme == CuteTheme.SYSTEM) !isSystemInDarkTheme else theme == CuteTheme.LIGHT
60 |
61 | isAppearanceLightStatusBars = isLight
62 | isAppearanceLightNavigationBars = isLight
63 | }
64 |
65 | Scaffold { _ ->
66 | Nav { imageBitmap ->
67 | artImageBitmap = imageBitmap
68 | }
69 | }
70 | }
71 | }
72 | }
73 |
74 |
75 | // override fun onDestroy() {
76 | // super.onDestroy()
77 | // sendBroadcast(
78 | // Intent(
79 | // "CM_CUR_PLAY_CHANGED"
80 | // ).apply {
81 | // putExtra("currentlyPlaying", "")
82 | // }
83 | // )
84 | // }
85 | }
86 |
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/main/PlaybackService.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.main
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.PendingIntent
5 | import android.appwidget.AppWidgetManager
6 | import android.content.Intent
7 | import android.content.IntentFilter
8 | import android.os.Build
9 | import androidx.media3.common.AudioAttributes
10 | import androidx.media3.common.C
11 | import androidx.media3.common.MediaMetadata
12 | import androidx.media3.common.Player
13 | import androidx.media3.common.util.UnstableApi
14 | import androidx.media3.exoplayer.ExoPlayer
15 | import androidx.media3.session.DefaultMediaNotificationProvider
16 | import androidx.media3.session.MediaLibraryService
17 | import androidx.media3.session.MediaLibraryService.MediaLibrarySession
18 | import androidx.media3.session.MediaSession
19 | import com.sosauce.cutemusic.R
20 | import com.sosauce.cutemusic.ui.widgets.WidgetBroadcastReceiver
21 | import com.sosauce.cutemusic.ui.widgets.WidgetCallback
22 | import com.sosauce.cutemusic.utils.CUTE_MUSIC_ID
23 | import com.sosauce.cutemusic.utils.PACKAGE
24 | import com.sosauce.cutemusic.utils.WIDGET_NEW_DATA
25 | import com.sosauce.cutemusic.utils.WIDGET_NEW_IS_PLAYING
26 |
27 |
28 | class PlaybackService : MediaLibraryService(), MediaLibrarySession.Callback, Player.Listener,
29 | WidgetCallback {
30 |
31 | private var mediaLibrarySession: MediaLibrarySession? = null
32 | private val audioAttributes = AudioAttributes
33 | .Builder()
34 | .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
35 | .setUsage(C.USAGE_MEDIA)
36 | .build()
37 |
38 | private val widgetReceiver = WidgetBroadcastReceiver()
39 |
40 |
41 | override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
42 | super.onMediaMetadataChanged(mediaMetadata)
43 | val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE).apply {
44 | putExtra(WIDGET_NEW_DATA, WIDGET_NEW_DATA)
45 | putExtra("title", mediaMetadata.title.toString())
46 | putExtra("artist", mediaMetadata.artist.toString())
47 | putExtra("artUri", mediaMetadata.artworkUri.toString())
48 | }
49 |
50 | sendBroadcast(intent)
51 | }
52 |
53 | override fun onIsPlayingChanged(isPlaying: Boolean) {
54 | super.onIsPlayingChanged(isPlaying)
55 |
56 | val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE).apply {
57 | putExtra(WIDGET_NEW_DATA, WIDGET_NEW_IS_PLAYING)
58 | putExtra("isPlaying", isPlaying)
59 | }
60 |
61 | sendBroadcast(intent)
62 | }
63 |
64 | override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? =
65 | mediaLibrarySession
66 |
67 |
68 | @SuppressLint("UnspecifiedRegisterReceiverFlag")
69 | @UnstableApi
70 | override fun onCreate() {
71 | super.onCreate()
72 |
73 | val player = ExoPlayer.Builder(applicationContext)
74 | .setAudioAttributes(audioAttributes, true)
75 | .setHandleAudioBecomingNoisy(true)
76 | .build()
77 | mediaLibrarySession = MediaLibrarySession
78 | .Builder(this, player, this)
79 | .setId(CUTE_MUSIC_ID)
80 | .setSessionActivity(
81 | PendingIntent.getActivity(
82 | this,
83 | 0,
84 | Intent(this, MainActivity::class.java),
85 | PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
86 | )
87 | )
88 | .build()
89 |
90 | IntentFilter(PACKAGE).also {
91 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
92 | registerReceiver(
93 | widgetReceiver,
94 | it,
95 | RECEIVER_EXPORTED
96 | )
97 | } else {
98 | registerReceiver(widgetReceiver, it)
99 | }
100 | }
101 | widgetReceiver.startCallback(this)
102 |
103 | setMediaNotificationProvider(
104 | DefaultMediaNotificationProvider.Builder(this).build().apply {
105 | setSmallIcon(R.drawable.music_note_rounded)
106 | }
107 | )
108 |
109 | player.addListener(this)
110 |
111 | }
112 |
113 |
114 | override fun onDestroy() {
115 | mediaLibrarySession?.run {
116 | player.release()
117 | release()
118 | mediaLibrarySession = null
119 | }
120 | stopSelf()
121 | try {
122 | widgetReceiver.also {
123 | it.stopCallback()
124 | unregisterReceiver(it)
125 | }
126 | } catch(e: IllegalArgumentException) {
127 | return
128 | }
129 | super.onDestroy()
130 | }
131 |
132 |
133 | @UnstableApi
134 | override fun onTaskRemoved(rootIntent: Intent?) {
135 | super.onTaskRemoved(rootIntent)
136 | mediaLibrarySession?.run {
137 | player.release()
138 | release()
139 | mediaLibrarySession = null
140 | }
141 | widgetReceiver.also {
142 | it.stopCallback()
143 | unregisterReceiver(it)
144 | }
145 | pauseAllPlayersAndStopSelf()
146 | }
147 |
148 |
149 | companion object {
150 | private const val CURRENTLY_PLAYING_CHANGED = "CM_CUR_PLAY_CHANGED"
151 | }
152 |
153 |
154 | override fun skipToNext() {
155 | mediaLibrarySession?.player?.seekToNextMediaItem()
156 | }
157 |
158 | override fun playOrPause() {
159 |
160 | if (mediaLibrarySession?.player?.isPlaying == true) {
161 | mediaLibrarySession?.player?.pause()
162 | } else {
163 | mediaLibrarySession?.player?.play()
164 | }
165 | }
166 |
167 | override fun skipToPrevious() {
168 | mediaLibrarySession?.player?.seekToPrevious()
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/main/quickplay/QuickPlayUiState.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.main.quickplay
2 |
3 | data class QuickPlayUiState(
4 | val isSongLoaded: Boolean = false,
5 | val title: String = "",
6 | val artist: String = "",
7 | val artUri: String = "",
8 | val duration: Long = 0,
9 | val currentPosition: Long = 0,
10 | val isPlaying: Boolean = false
11 | )
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/main/quickplay/QuickPlayViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.main.quickplay
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.graphics.Bitmap
6 | import android.graphics.BitmapFactory
7 | import android.media.MediaMetadataRetriever
8 | import android.net.Uri
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.mutableStateOf
11 | import androidx.compose.runtime.setValue
12 | import androidx.lifecycle.AndroidViewModel
13 | import androidx.lifecycle.viewModelScope
14 | import androidx.media3.common.AudioAttributes
15 | import androidx.media3.common.C
16 | import androidx.media3.common.MediaItem
17 | import androidx.media3.common.MediaMetadata
18 | import androidx.media3.common.Player
19 | import androidx.media3.exoplayer.ExoPlayer
20 | import com.sosauce.cutemusic.data.actions.PlayerActions
21 | import com.sosauce.cutemusic.data.states.MusicState
22 | import kotlinx.coroutines.delay
23 | import kotlinx.coroutines.flow.MutableStateFlow
24 | import kotlinx.coroutines.flow.asStateFlow
25 | import kotlinx.coroutines.flow.update
26 | import kotlinx.coroutines.launch
27 |
28 | class QuickPlayViewModel(
29 | application: Application
30 | ) : AndroidViewModel(application) {
31 |
32 | private val audioAttributes = AudioAttributes
33 | .Builder()
34 | .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
35 | .setUsage(C.USAGE_MEDIA)
36 | .build()
37 |
38 | private val listener = object : Player.Listener {
39 | override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
40 | super.onMediaMetadataChanged(mediaMetadata)
41 | _musicState.update {
42 | it.copy(
43 | title = mediaMetadata.title.toString(),
44 | artist = mediaMetadata.artist.toString(),
45 | art = mediaMetadata.artworkUri,
46 | )
47 | }
48 | }
49 |
50 | override fun onIsPlayingChanged(isPlaying: Boolean) {
51 | super.onIsPlayingChanged(isPlaying)
52 | _musicState.update {
53 | it.copy(
54 | isPlaying = isPlaying
55 | )
56 | }
57 | }
58 |
59 | override fun onEvents(player: Player, events: Player.Events) {
60 | super.onEvents(player, events)
61 | viewModelScope.launch {
62 | while (player.isPlaying) {
63 | _musicState.update {
64 | it.copy(
65 | duration = player.duration,
66 | position = player.currentPosition
67 | )
68 | }
69 | delay(500)
70 | }
71 | }
72 | }
73 | }
74 |
75 |
76 | private val player = ExoPlayer.Builder(application)
77 | .setAudioAttributes(audioAttributes, true)
78 | .setHandleAudioBecomingNoisy(true)
79 | .build()
80 | .apply {
81 | addListener(listener)
82 | }
83 |
84 | var isSongLoaded by mutableStateOf(false)
85 |
86 | private val _musicState = MutableStateFlow(MusicState())
87 | val musicState = _musicState.asStateFlow()
88 |
89 |
90 | init {
91 | viewModelScope.launch {
92 | while (player.mediaItemCount == 0) delay(300)
93 |
94 | isSongLoaded = true
95 | }
96 | }
97 |
98 | override fun onCleared() {
99 | super.onCleared()
100 | player.removeListener(listener)
101 | player.release()
102 | }
103 |
104 |
105 | fun loadSong(uri: Uri) {
106 | val mediaItem = MediaItem.fromUri(uri)
107 | player.addMediaItem(mediaItem)
108 | player.prepare()
109 | player.play()
110 | }
111 |
112 | fun loadAlbumArt(context: Context, uri: Uri): Bitmap? {
113 | val retriever = MediaMetadataRetriever()
114 | return try {
115 | retriever.setDataSource(context, uri)
116 | val art = retriever.embeddedPicture
117 | art?.let { BitmapFactory.decodeByteArray(it, 0, it.size) }
118 | } catch (e: Exception) {
119 | e.printStackTrace()
120 | null
121 | } finally {
122 | retriever.release()
123 | }
124 | }
125 |
126 |
127 | fun handlePlayerAction(action: PlayerActions) {
128 | when (action) {
129 | is PlayerActions.PlayOrPause -> if (player.isPlaying) player.pause() else player.play()
130 | is PlayerActions.UpdateCurrentPosition -> {
131 | _musicState.update {
132 | it.copy(
133 | position = action.position
134 | )
135 | }
136 | }
137 |
138 | is PlayerActions.SeekToSlider -> player.seekTo(action.position)
139 | is PlayerActions.SeekTo -> player.seekTo(player.currentPosition + action.position)
140 | is PlayerActions.RewindTo -> player.seekTo(player.currentPosition - action.position)
141 | else -> Unit
142 | }
143 | }
144 |
145 |
146 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/navigation/Screen.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.navigation
2 |
3 | import androidx.navigation3.runtime.NavKey
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | sealed class Screen(): NavKey {
8 | @Serializable
9 | data object Main : Screen()
10 |
11 | @Serializable
12 | data object NowPlaying : Screen()
13 |
14 | @Serializable
15 | data object Settings : Screen()
16 |
17 | @Serializable
18 | data object Blacklisted : Screen()
19 |
20 | @Serializable
21 | data object Albums : Screen()
22 |
23 | @Serializable
24 | data object Artists : Screen()
25 |
26 | @Serializable
27 | data object Playlists : Screen()
28 |
29 | @Serializable
30 | data object Saf : Screen()
31 |
32 | @Serializable
33 | data class AlbumsDetails(
34 | val id: Long
35 | ) : Screen()
36 |
37 | @Serializable
38 | data class ArtistsDetails(
39 | val id: Long
40 | ) : Screen()
41 |
42 | @Serializable
43 | data class PlaylistDetails(
44 | val id: Int
45 | ) : Screen()
46 |
47 | @Serializable
48 | data class MetadataEditor(
49 | val id: String
50 | ) : Screen()
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/album/AlbumDetailsLandscape.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalSharedTransitionApi::class)
2 |
3 | package com.sosauce.cutemusic.ui.screens.album
4 |
5 | import android.net.Uri
6 | import androidx.compose.animation.ExperimentalSharedTransitionApi
7 | import androidx.compose.animation.SharedTransitionScope
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.Column
10 | import androidx.compose.foundation.layout.Row
11 | import androidx.compose.foundation.layout.Spacer
12 | import androidx.compose.foundation.layout.displayCutoutPadding
13 | import androidx.compose.foundation.layout.fillMaxSize
14 | import androidx.compose.foundation.layout.height
15 | import androidx.compose.foundation.layout.navigationBarsPadding
16 | import androidx.compose.foundation.layout.padding
17 | import androidx.compose.foundation.layout.size
18 | import androidx.compose.foundation.layout.statusBarsPadding
19 | import androidx.compose.foundation.layout.width
20 | import androidx.compose.foundation.lazy.LazyColumn
21 | import androidx.compose.foundation.lazy.items
22 | import androidx.compose.foundation.shape.RoundedCornerShape
23 | import androidx.compose.material3.MaterialTheme
24 | import androidx.compose.material3.Scaffold
25 | import androidx.compose.runtime.Composable
26 | import androidx.compose.ui.Alignment
27 | import androidx.compose.ui.Modifier
28 | import androidx.compose.ui.draw.clip
29 | import androidx.compose.ui.layout.ContentScale
30 | import androidx.compose.ui.res.pluralStringResource
31 | import androidx.compose.ui.res.stringResource
32 | import androidx.compose.ui.unit.dp
33 | import androidx.media3.common.MediaItem
34 | import androidx.navigation3.ui.LocalNavAnimatedContentScope
35 | import coil3.compose.AsyncImage
36 | import com.sosauce.cutemusic.R
37 | import com.sosauce.cutemusic.data.actions.MediaItemActions
38 | import com.sosauce.cutemusic.data.actions.PlayerActions
39 | import com.sosauce.cutemusic.data.states.MusicState
40 | import com.sosauce.cutemusic.domain.model.Album
41 | import com.sosauce.cutemusic.ui.navigation.Screen
42 | import com.sosauce.cutemusic.ui.shared_components.CuteNavigationButton
43 | import com.sosauce.cutemusic.ui.shared_components.CuteText
44 | import com.sosauce.cutemusic.ui.shared_components.LocalMusicListItem
45 | import com.sosauce.cutemusic.utils.ImageUtils
46 |
47 | @Composable
48 | fun SharedTransitionScope.AlbumDetailsLandscape(
49 | musics: List,
50 | album: Album,
51 | onNavigateUp: () -> Unit,
52 | onNavigate: (Screen) -> Unit,
53 | musicState: MusicState,
54 | onHandlePlayerActions: (PlayerActions) -> Unit,
55 | onLoadMetadata: (String, Uri) -> Unit = { _, _ -> },
56 | onHandleMediaItemAction: (MediaItemActions) -> Unit,
57 | ) {
58 |
59 |
60 | Box(
61 | modifier = Modifier
62 | .fillMaxSize()
63 | .displayCutoutPadding()
64 | ) {
65 | Row(
66 | modifier = Modifier.fillMaxSize()
67 | ) {
68 | Column {
69 | AsyncImage(
70 | model = ImageUtils.getAlbumArt(album.id),
71 | stringResource(R.string.artwork),
72 | modifier = Modifier
73 | .statusBarsPadding()
74 | .size(200.dp)
75 | .sharedElement(
76 | sharedContentState = rememberSharedContentState(key = album.id),
77 | animatedVisibilityScope = LocalNavAnimatedContentScope.current,
78 | )
79 | .clip(RoundedCornerShape(10)),
80 | contentScale = ContentScale.Crop
81 | )
82 | Spacer(Modifier.height(10.dp))
83 | CuteText(
84 | text = album.name,
85 | modifier = Modifier.sharedElement(
86 | sharedContentState = rememberSharedContentState(key = album.name + album.id),
87 | animatedVisibilityScope = LocalNavAnimatedContentScope.current,
88 | )
89 | )
90 | CuteText(
91 | text = album.artist,
92 | color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.85f),
93 | modifier = Modifier.sharedElement(
94 | sharedContentState = rememberSharedContentState(key = album.artist + album.id),
95 | animatedVisibilityScope = LocalNavAnimatedContentScope.current,
96 | )
97 | )
98 | CuteText(pluralStringResource(R.plurals.tracks, musics.size, musics.size))
99 | Spacer(modifier = Modifier.width(5.dp))
100 | }
101 | Scaffold { paddingValues ->
102 | LazyColumn(
103 | contentPadding = paddingValues
104 | ) {
105 | items(
106 | items = musics,
107 | key = { it.mediaId }
108 | ) { music ->
109 | LocalMusicListItem(
110 | modifier = Modifier
111 | .padding(horizontal = 5.dp),
112 | music = music,
113 | currentMusicUri = musicState.uri,
114 | onShortClick = {
115 | onHandlePlayerActions(
116 | PlayerActions.StartPlayback(
117 | it
118 | )
119 | )
120 | },
121 | isPlayerReady = musicState.isPlayerReady,
122 | onLoadMetadata = onLoadMetadata,
123 | onHandleMediaItemAction = onHandleMediaItemAction,
124 | onHandlePlayerActions = onHandlePlayerActions,
125 | onNavigate = onNavigate
126 | )
127 | }
128 | }
129 | }
130 | }
131 |
132 | CuteNavigationButton(
133 | modifier = Modifier
134 | .navigationBarsPadding()
135 | .align(Alignment.BottomStart)
136 | ) { onNavigateUp() }
137 | }
138 |
139 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/main/components/SortingDropdownMenu.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalMaterial3Api::class)
2 |
3 | package com.sosauce.cutemusic.ui.screens.main.components
4 |
5 | import androidx.compose.foundation.shape.RoundedCornerShape
6 | import androidx.compose.material3.DropdownMenu
7 | import androidx.compose.material3.ExperimentalMaterial3Api
8 | import androidx.compose.material3.RadioButton
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.res.stringResource
11 | import androidx.compose.ui.unit.dp
12 | import com.sosauce.cutemusic.R
13 | import com.sosauce.cutemusic.ui.shared_components.CuteDropdownMenuItem
14 | import com.sosauce.cutemusic.ui.shared_components.CuteText
15 |
16 | @Composable
17 | fun SortingDropdownMenu(
18 | expanded: Boolean,
19 | onDismissRequest: () -> Unit,
20 | isSortedByASC: Boolean,
21 | onChangeSorting: (Boolean) -> Unit,
22 | additionalActions: (@Composable () -> Unit)? = null
23 | ) {
24 | DropdownMenu(
25 | expanded = expanded,
26 | onDismissRequest = onDismissRequest,
27 | shape = RoundedCornerShape(24.dp),
28 | ) {
29 | additionalActions?.invoke()
30 | CuteDropdownMenuItem(
31 | onClick = { onChangeSorting(true) },
32 | text = { CuteText(stringResource(R.string.ascending)) },
33 | leadingIcon = {
34 | RadioButton(
35 | selected = isSortedByASC,
36 | onClick = null
37 | )
38 | }
39 | )
40 | CuteDropdownMenuItem(
41 | onClick = { onChangeSorting(false) },
42 | text = { CuteText(stringResource(R.string.descending)) },
43 | leadingIcon = {
44 | RadioButton(
45 | selected = !isSortedByASC,
46 | onClick = null
47 | )
48 | }
49 | )
50 | }
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/metadata/MetadataState.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.screens.metadata
2 |
3 | import android.net.Uri
4 | import androidx.compose.runtime.mutableStateMapOf
5 | import androidx.compose.runtime.snapshots.SnapshotStateMap
6 | import com.kyant.taglib.AudioProperties
7 | import com.kyant.taglib.Metadata
8 | import com.kyant.taglib.Picture
9 |
10 | data class MetadataState(
11 | val mutablePropertiesMap: SnapshotStateMap = mutableStateMapOf(),
12 | val songPath: String = "",
13 | val songUri: Uri = Uri.EMPTY,
14 | val metadata: Metadata? = null,
15 | val audioProperties: AudioProperties? = null,
16 | val art: Picture? = null,
17 | val newArtUri: Uri = Uri.EMPTY
18 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/Artwork.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalSharedTransitionApi::class, ExperimentalMaterial3ExpressiveApi::class)
2 |
3 | package com.sosauce.cutemusic.ui.screens.playing.components
4 |
5 | import androidx.compose.animation.Crossfade
6 | import androidx.compose.animation.ExperimentalSharedTransitionApi
7 | import androidx.compose.animation.SharedTransitionScope
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.PaddingValues
10 | import androidx.compose.foundation.layout.aspectRatio
11 | import androidx.compose.foundation.layout.fillMaxSize
12 | import androidx.compose.foundation.layout.wrapContentSize
13 | import androidx.compose.foundation.pager.HorizontalPager
14 | import androidx.compose.foundation.pager.rememberPagerState
15 | import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.runtime.LaunchedEffect
18 | import androidx.compose.runtime.getValue
19 | import androidx.compose.runtime.mutableIntStateOf
20 | import androidx.compose.runtime.remember
21 | import androidx.compose.runtime.setValue
22 | import androidx.compose.runtime.snapshotFlow
23 | import androidx.compose.ui.Alignment
24 | import androidx.compose.ui.Modifier
25 | import androidx.compose.ui.draw.clip
26 | import androidx.compose.ui.graphics.graphicsLayer
27 | import androidx.compose.ui.layout.ContentScale
28 | import androidx.compose.ui.res.stringResource
29 | import androidx.compose.ui.unit.dp
30 | import androidx.compose.ui.unit.lerp
31 | import androidx.media3.common.MediaItem
32 | import coil3.compose.AsyncImage
33 | import com.sosauce.cutemusic.R
34 | import com.sosauce.cutemusic.data.actions.PlayerActions
35 | import com.sosauce.cutemusic.data.datastore.rememberCarousel
36 | import com.sosauce.cutemusic.data.datastore.rememberNpArtShape
37 | import com.sosauce.cutemusic.data.datastore.rememberShouldApplyShuffle
38 | import com.sosauce.cutemusic.data.states.MusicState
39 | import com.sosauce.cutemusic.utils.ImageUtils
40 | import com.sosauce.cutemusic.utils.toShape
41 | import kotlinx.coroutines.flow.filter
42 | import kotlinx.coroutines.flow.first
43 | import kotlin.math.absoluteValue
44 |
45 | @Composable
46 | fun SharedTransitionScope.Artwork(
47 | pagerModifier: Modifier = Modifier,
48 | loadedMedias: List = emptyList(),
49 | musicState: MusicState,
50 | onHandlePlayerActions: (PlayerActions) -> Unit,
51 | ) {
52 | val useCarousel by rememberCarousel()
53 | val artShape by rememberNpArtShape()
54 | val useShuffle by rememberShouldApplyShuffle()
55 | val pagerState =
56 | rememberPagerState(initialPage = loadedMedias.indexOfFirst { it.mediaId == musicState.mediaId }) { loadedMedias.size }
57 |
58 |
59 | if (useCarousel) {
60 | var lastPage by remember { mutableIntStateOf(loadedMedias.indexOfFirst { it.mediaId == musicState.mediaId }) }
61 |
62 | LaunchedEffect(pagerState.settledPage) {
63 | if (musicState.mediaIndex == pagerState.settledPage) return@LaunchedEffect
64 | if (pagerState.settledPage != lastPage) {
65 | snapshotFlow { pagerState.isScrollInProgress }
66 | .filter { !it }
67 | .first()
68 | if (useShuffle) {
69 | onHandlePlayerActions(PlayerActions.PlayRandom)
70 | } else onHandlePlayerActions(PlayerActions.SeekToMusicIndex(pagerState.settledPage))
71 | lastPage = pagerState.settledPage
72 | }
73 | }
74 |
75 |
76 | LaunchedEffect(musicState.mediaIndex) {
77 | pagerState.animateScrollToPage(musicState.mediaIndex)
78 | }
79 |
80 | HorizontalPager(
81 | state = pagerState,
82 | key = { loadedMedias[it].mediaId },
83 | contentPadding = PaddingValues(horizontal = 30.dp),
84 | modifier = pagerModifier
85 | ) { page ->
86 | Box(
87 | modifier = Modifier
88 | .aspectRatio(1f),
89 | contentAlignment = Alignment.Center
90 | ) {
91 | AsyncImage(
92 | model = loadedMedias[page].mediaMetadata.artworkUri,
93 | contentDescription = stringResource(R.string.artwork),
94 | modifier = Modifier
95 | .graphicsLayer {
96 | val pageOffset =
97 | (pagerState.currentPage - page + pagerState.currentPageOffsetFraction).absoluteValue
98 |
99 | lerp(
100 | start = 75.dp,
101 | stop = 100.dp,
102 | fraction = 1f - pageOffset.coerceIn(0f, 1f)
103 | ).also { scale ->
104 | scaleY = scale / 100.dp
105 | }
106 | }
107 | // .sharedElement(
108 | // state = rememberSharedContentState(key = SharedTransitionKeys.MUSIC_ARTWORK + musicState.currentMediaId),
109 | // animatedVisibilityScope = animatedVisibilityScope
110 | //
111 | // )
112 | .fillMaxSize(0.95f)
113 | .clip(artShape.toShape()),
114 | contentScale = ContentScale.Crop
115 | )
116 | }
117 | }
118 |
119 | } else {
120 | Crossfade(
121 | targetState = musicState.art,
122 | modifier = Modifier
123 | .aspectRatio(1f)
124 | .wrapContentSize()
125 | ) {
126 | AsyncImage(
127 | model = ImageUtils.imageRequester(it),
128 | contentDescription = stringResource(R.string.artwork),
129 | modifier = Modifier
130 | .fillMaxSize(0.9f)
131 | // .sharedElement(
132 | // sharedContentState = rememberSharedContentState(musicState.mediaId),
133 | // animatedVisibilityScope = LocalNavAnimatedContentScope.current
134 | // )
135 | .clip(artShape.toShape()),
136 | contentScale = ContentScale.Crop
137 | )
138 | }
139 | }
140 | }
141 |
142 |
143 |
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/CuteSlider.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
2 |
3 | package com.sosauce.cutemusic.ui.screens.playing.components
4 |
5 | import androidx.compose.animation.core.animateFloatAsState
6 | import androidx.compose.foundation.layout.Arrangement
7 | import androidx.compose.foundation.layout.Column
8 | import androidx.compose.foundation.layout.Row
9 | import androidx.compose.foundation.layout.fillMaxWidth
10 | import androidx.compose.foundation.layout.padding
11 | import androidx.compose.material3.ExperimentalMaterial3Api
12 | import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.getValue
16 | import androidx.compose.runtime.mutableStateOf
17 | import androidx.compose.runtime.remember
18 | import androidx.compose.runtime.setValue
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.unit.dp
21 | import com.sosauce.cutemusic.data.actions.PlayerActions
22 | import com.sosauce.cutemusic.data.datastore.rememberSliderStyle
23 | import com.sosauce.cutemusic.data.states.MusicState
24 | import com.sosauce.cutemusic.ui.shared_components.CuteText
25 | import com.sosauce.cutemusic.utils.formatToReadableTime
26 |
27 | @Composable
28 | fun CuteSlider(
29 | musicState: MusicState,
30 | onHandlePlayerActions: (PlayerActions) -> Unit,
31 | ) {
32 | val sliderStyle by rememberSliderStyle()
33 | var tempSliderValue by remember { mutableStateOf(null) }
34 | val value by animateFloatAsState(
35 | targetValue = tempSliderValue ?: musicState.position.toFloat()
36 | )
37 | val sliderState = rememberCuteSliderState(
38 | value = value,
39 | onValueChange = { tempSliderValue = it },
40 | onValueChangeFinished = {
41 | tempSliderValue?.let {
42 | onHandlePlayerActions(
43 | PlayerActions.UpdateCurrentPosition(it.toLong())
44 | )
45 | onHandlePlayerActions(
46 | PlayerActions.SeekToSlider(it.toLong())
47 | )
48 | }
49 | tempSliderValue = null
50 | },
51 | valueRange = 0f..musicState.duration.toFloat(),
52 | enabled = true
53 | )
54 |
55 |
56 | Column(
57 | modifier = Modifier.padding(horizontal = 15.dp)
58 | ) {
59 | Row(
60 | horizontalArrangement = Arrangement.SpaceBetween,
61 | modifier = Modifier.fillMaxWidth()
62 | ) {
63 | CuteText(
64 | text = musicState.position.formatToReadableTime(),
65 | color = MaterialTheme.colorScheme.primary
66 | )
67 | CuteText(
68 | text = musicState.duration.formatToReadableTime(),
69 | color = MaterialTheme.colorScheme.primary
70 | )
71 | }
72 | sliderStyle.toSlider(
73 | state = sliderState,
74 | isPlaying = musicState.isPlaying
75 | )
76 |
77 | // Slider(
78 | // value = value,
79 | // onValueChange = { tempSliderValue = it },
80 | // onValueChangeFinished = {
81 | // tempSliderValue?.let {
82 | // onHandlePlayerActions(
83 | // PlayerActions.UpdateCurrentPosition(it.toLong())
84 | // )
85 | // onHandlePlayerActions(
86 | // PlayerActions.SeekToSlider(it.toLong())
87 | // )
88 | // }
89 | // tempSliderValue = null
90 | // },
91 | // track = { sliderState ->
92 | // if (useClassicSlider) {
93 | // SliderDefaults.Track(
94 | // sliderState = sliderState,
95 | // drawStopIndicator = null,
96 | // thumbTrackGapSize = 0.dp,
97 | // modifier = Modifier.height(4.dp)
98 | // )
99 | // } else {
100 | // val amplitude by animateDpAsState(
101 | // targetValue = if (musicState.isPlaying && !isDragging) 5.dp else 0.dp,
102 | // animationSpec = MotionScheme.expressive().slowSpatialSpec()
103 | // )
104 | // SquigglySlider.Track(
105 | // interactionSource = rememberInteractionSource(),
106 | // colors = SliderDefaults.colors(),
107 | // enabled = true,
108 | // sliderState = sliderState,
109 | // squigglesSpec = SquigglySlider.SquigglesSpec(
110 | // amplitude = amplitude,
111 | // wavelength = 45.dp
112 | // )
113 | // )
114 | // }
115 | // },
116 | // thumb = {
117 | // val thumbWidth by animateDpAsState(
118 | // targetValue = when (useClassicSlider) {
119 | // true -> if (isDragging) 28.dp else 20.dp
120 | // false -> if (isDragging) 12.dp else 4.dp
121 | // }
122 | // )
123 | // SliderDefaults.Thumb(
124 | // interactionSource = rememberInteractionSource(),
125 | // thumbSize = DpSize(
126 | // width = thumbWidth,
127 | // height = if (useClassicSlider) 20.dp else 22.dp
128 | // ),
129 | // )
130 | // },
131 | // valueRange = 0f..musicState.duration.toFloat(),
132 | // interactionSource = interactionSource
133 | // )
134 | }
135 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/CuteSquigglySlider.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalMaterial3Api::class)
2 |
3 | package com.sosauce.cutemusic.ui.screens.playing.components
4 |
5 | import androidx.compose.foundation.interaction.MutableInteractionSource
6 | import androidx.compose.material3.ExperimentalMaterial3Api
7 | import androidx.compose.material3.Slider
8 | import androidx.compose.material3.SliderColors
9 | import androidx.compose.material3.SliderDefaults
10 | import androidx.compose.material3.SliderState
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.unit.DpSize
15 | import androidx.compose.ui.unit.coerceAtLeast
16 | import androidx.compose.ui.unit.dp
17 | import me.saket.squiggles.SquigglySlider
18 | import me.saket.squiggles.SquigglySlider.SquigglesAnimator
19 | import me.saket.squiggles.SquigglySlider.SquigglesSpec
20 |
21 | /**
22 | * Squiggly slider but you can pass a thumb
23 | */
24 | @Composable
25 | fun CuteSquigglySlider(
26 | value: Float,
27 | onValueChange: (Float) -> Unit,
28 | modifier: Modifier = Modifier,
29 | enabled: Boolean = true,
30 | valueRange: ClosedFloatingPointRange = 0f..1f,
31 | onValueChangeFinished: (() -> Unit)? = null,
32 | colors: SliderColors = SliderDefaults.colors(),
33 | squigglesSpec: SquigglesSpec = SquigglesSpec(),
34 | squigglesAnimator: SquigglesAnimator = SquigglySlider.rememberSquigglesAnimator(),
35 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
36 | thumb: @Composable (SliderState) -> Unit = {
37 | SquigglySlider.Thumb(
38 | interactionSource = interactionSource,
39 | colors = colors,
40 | enabled = enabled,
41 | thumbSize = DpSize(
42 | width = squigglesSpec.strokeWidth.coerceAtLeast(4.dp),
43 | height = (squigglesSpec.strokeWidth * 4).coerceAtLeast(16.dp),
44 | )
45 | )
46 | },
47 | ) {
48 | Slider(
49 | value = value,
50 | onValueChange = onValueChange,
51 | modifier = modifier,
52 | enabled = enabled,
53 | onValueChangeFinished = onValueChangeFinished,
54 | colors = colors,
55 | interactionSource = interactionSource,
56 | thumb = thumb,
57 | track = { sliderState ->
58 | SquigglySlider.Track(
59 | interactionSource = interactionSource,
60 | colors = colors,
61 | enabled = enabled,
62 | sliderState = sliderState,
63 | squigglesSpec = squigglesSpec,
64 | squigglesAnimator = squigglesAnimator,
65 | )
66 | },
67 | valueRange = valueRange
68 | )
69 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/CuteTimePicker.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalMaterial3Api::class)
2 |
3 | package com.sosauce.cutemusic.ui.screens.playing.components
4 |
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.material3.ExperimentalMaterial3Api
8 | import androidx.compose.material3.TextButton
9 | import androidx.compose.material3.TimePicker
10 | import androidx.compose.material3.TimePickerDialog
11 | import androidx.compose.material3.rememberTimePickerState
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.res.stringResource
16 | import androidx.compose.ui.unit.dp
17 | import androidx.compose.ui.unit.sp
18 | import com.sosauce.cutemusic.R
19 | import com.sosauce.cutemusic.ui.shared_components.CuteText
20 |
21 | @Composable
22 | fun CuteTimePicker(
23 | initialMillis: Long = 0,
24 | onDismissRequest: () -> Unit,
25 | onSetTimer: (hours: Int, minutes: Int) -> Unit
26 | ) {
27 |
28 |
29 | val initialSeconds = remember { initialMillis / 1000 }
30 | val initialMinutes = remember { (initialSeconds / 60) % 60 }
31 | val initialHours = remember { (initialMinutes / 60) % 24 }
32 |
33 | // is24Hour is set to true to not show the AM/PM selector, as this time picker is used to start a countdown anyways
34 | val timePickerState = rememberTimePickerState(
35 | is24Hour = true,
36 | initialMinute = initialMinutes.toInt(),
37 | initialHour = initialHours.toInt(),
38 | )
39 |
40 | TimePickerDialog(
41 | title = {
42 | CuteText(
43 | text = stringResource(R.string.set_sleep_timer),
44 | fontSize = 24.sp
45 | )
46 | },
47 | confirmButton = {
48 | TextButton(
49 | onClick = { onSetTimer(timePickerState.hour, timePickerState.minute) }
50 | ) {
51 | CuteText(stringResource(R.string.okay))
52 | }
53 | },
54 | dismissButton = {
55 | TextButton(
56 | onClick = onDismissRequest
57 | ) {
58 | CuteText(stringResource(R.string.cancel))
59 | }
60 | },
61 | onDismissRequest = onDismissRequest
62 | ) {
63 | Spacer(Modifier.height(10.dp))
64 | TimePicker(timePickerState)
65 | }
66 |
67 | // AlertDialog(
68 | // text = { TimePicker(timePickerState) },
69 | // title = {
70 | // CuteText(stringResource(R.string.set_sleep_timer))
71 | // },
72 | // confirmButton = {
73 | // TextButton(
74 | // onClick = { onSetTimer(timePickerState.hour, timePickerState.minute) }
75 | // ) {
76 | // CuteText(stringResource(R.string.okay))
77 | // }
78 | // },
79 | // dismissButton = {
80 | // TextButton(
81 | // onClick = onDismissRequest
82 | // ) {
83 | // CuteText(stringResource(R.string.cancel))
84 | // }
85 | // },
86 | // onDismissRequest = onDismissRequest
87 | // )
88 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/QueueSheet.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalMaterial3Api::class)
2 |
3 | package com.sosauce.cutemusic.ui.screens.playing.components
4 |
5 | import androidx.compose.animation.core.animateFloatAsState
6 | import androidx.compose.foundation.lazy.LazyColumn
7 | import androidx.compose.foundation.lazy.items
8 | import androidx.compose.foundation.lazy.rememberLazyListState
9 | import androidx.compose.material3.ExperimentalMaterial3Api
10 | import androidx.compose.material3.ModalBottomSheet
11 | import androidx.compose.material3.rememberModalBottomSheetState
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.getValue
14 | import androidx.compose.runtime.mutableStateOf
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.runtime.setValue
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.draw.scale
19 | import androidx.media3.common.MediaItem
20 | import com.sosauce.cutemusic.data.actions.PlayerActions
21 | import com.sosauce.cutemusic.data.states.MusicState
22 | import com.sosauce.cutemusic.ui.shared_components.QueueMusicListItem
23 | import com.sosauce.cutemusic.utils.thenIf
24 | import sh.calvin.reorderable.ReorderableItem
25 | import sh.calvin.reorderable.rememberReorderableLazyListState
26 |
27 | @Composable
28 | fun QueueSheet(
29 | onDismissRequest: () -> Unit,
30 | loadedMedias: List,
31 | onHandlePlayerAction: (PlayerActions) -> Unit,
32 | musicState: MusicState
33 | ) {
34 | val lazyListState = rememberLazyListState()
35 | val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to ->
36 | onHandlePlayerAction(
37 | PlayerActions.ReArrangeQueue(from.index, to.index)
38 | )
39 | }
40 | ModalBottomSheet(
41 | onDismissRequest = onDismissRequest,
42 | sheetState = rememberModalBottomSheetState(true),
43 | ) {
44 | LazyColumn(state = lazyListState) {
45 | items(
46 | items = loadedMedias,
47 | key = { it.mediaId }
48 | ) { music ->
49 | ReorderableItem(reorderableLazyListState, key = music.mediaId) { isDragging ->
50 | val scale by animateFloatAsState(
51 | targetValue = if (isDragging) 1.05f else 1f
52 | )
53 | QueueMusicListItem(
54 | modifier = Modifier.scale(scale),
55 | music = music,
56 | currentMusicUri = musicState.uri,
57 | onHandlePlayerActions = onHandlePlayerAction
58 | )
59 | }
60 | }
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/RateAdjustmentDialog.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.screens.playing.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.height
9 | import androidx.compose.foundation.text.KeyboardActions
10 | import androidx.compose.foundation.text.KeyboardOptions
11 | import androidx.compose.material3.OutlinedTextField
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.LaunchedEffect
14 | import androidx.compose.runtime.getValue
15 | import androidx.compose.runtime.mutableStateOf
16 | import androidx.compose.runtime.remember
17 | import androidx.compose.runtime.setValue
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.focus.focusRequester
20 | import androidx.compose.ui.res.stringResource
21 | import androidx.compose.ui.text.TextRange
22 | import androidx.compose.ui.text.TextStyle
23 | import androidx.compose.ui.text.input.KeyboardType
24 | import androidx.compose.ui.text.input.TextFieldValue
25 | import androidx.compose.ui.text.style.TextAlign
26 | import androidx.compose.ui.unit.dp
27 | import com.sosauce.cutemusic.R
28 | import com.sosauce.cutemusic.ui.shared_components.CuteText
29 | import com.sosauce.cutemusic.utils.rememberFocusRequester
30 |
31 | @Composable
32 | fun RateAdjustmentDialog(
33 | rate: Float,
34 | onSetNewRate: (Float) -> Unit,
35 | ) {
36 |
37 | val focusRequest = rememberFocusRequester()
38 | LaunchedEffect(Unit) { focusRequest.requestFocus() }
39 |
40 | var newRate by remember { mutableStateOf("%.2f".format(rate)) }
41 | var textFieldValue by remember {
42 | mutableStateOf(
43 | TextFieldValue(
44 | text = newRate,
45 | selection = TextRange(newRate.length)
46 | )
47 | )
48 | }
49 |
50 |
51 | Column {
52 | CuteText(stringResource(id = R.string.new_rate))
53 | Spacer(Modifier.height(10.dp))
54 | Row(
55 | modifier = Modifier.fillMaxWidth(),
56 | horizontalArrangement = Arrangement.Center
57 | ) {
58 | OutlinedTextField(
59 | value = textFieldValue,
60 | onValueChange = {
61 | textFieldValue = it
62 | newRate = it.text
63 | },
64 | singleLine = true,
65 | keyboardActions = KeyboardActions(
66 | onDone = {
67 | if (newRate.toFloat() > 2.0f) {
68 | onSetNewRate(2.0f)
69 | } else if (newRate.toFloat() < 0.5f) {
70 | onSetNewRate(0.5f)
71 | } else {
72 | onSetNewRate(newRate.toFloat())
73 | }
74 | }
75 | ),
76 | keyboardOptions = KeyboardOptions(
77 | keyboardType = KeyboardType.Number
78 | ),
79 | modifier = Modifier
80 | .fillMaxWidth(0.5f)
81 | .focusRequester(focusRequest),
82 | textStyle = TextStyle(
83 | textAlign = TextAlign.Center
84 | )
85 | )
86 | }
87 | }
88 | }
89 |
90 |
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/playlists/CreatePlaylistDialog.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalMaterial3Api::class)
2 |
3 | package com.sosauce.cutemusic.ui.screens.playlists
4 |
5 | import androidx.compose.animation.AnimatedContent
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.Column
9 | import androidx.compose.foundation.layout.fillMaxWidth
10 | import androidx.compose.foundation.layout.padding
11 | import androidx.compose.foundation.layout.size
12 | import androidx.compose.foundation.rememberScrollState
13 | import androidx.compose.foundation.shape.RoundedCornerShape
14 | import androidx.compose.foundation.verticalScroll
15 | import androidx.compose.material.icons.Icons
16 | import androidx.compose.material.icons.rounded.Close
17 | import androidx.compose.material3.AlertDialog
18 | import androidx.compose.material3.ExperimentalMaterial3Api
19 | import androidx.compose.material3.Icon
20 | import androidx.compose.material3.IconButton
21 | import androidx.compose.material3.ModalBottomSheet
22 | import androidx.compose.material3.OutlinedTextField
23 | import androidx.compose.material3.TextButton
24 | import androidx.compose.runtime.Composable
25 | import androidx.compose.runtime.getValue
26 | import androidx.compose.runtime.mutableStateOf
27 | import androidx.compose.runtime.remember
28 | import androidx.compose.runtime.setValue
29 | import androidx.compose.ui.Alignment
30 | import androidx.compose.ui.Modifier
31 | import androidx.compose.ui.draw.clip
32 | import androidx.compose.ui.res.painterResource
33 | import androidx.compose.ui.res.stringResource
34 | import androidx.compose.ui.unit.dp
35 | import androidx.compose.ui.unit.sp
36 | import androidx.compose.ui.viewinterop.AndroidView
37 | import androidx.emoji2.emojipicker.EmojiPickerView
38 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
39 | import com.sosauce.cutemusic.R
40 | import com.sosauce.cutemusic.data.actions.PlaylistActions
41 | import com.sosauce.cutemusic.ui.shared_components.CuteText
42 | import com.sosauce.cutemusic.ui.shared_components.PlaylistViewModel
43 | import org.koin.androidx.compose.koinViewModel
44 |
45 | @Composable
46 | fun CreatePlaylistDialog(
47 | onDismissRequest: () -> Unit
48 | ) {
49 | val playlistViewModel = koinViewModel()
50 | val playlists by playlistViewModel.allPlaylists.collectAsStateWithLifecycle()
51 | val playlistState by playlistViewModel.state.collectAsStateWithLifecycle()
52 | var showEmojiPicker by remember { mutableStateOf(false) }
53 |
54 | if (showEmojiPicker) {
55 | ModalBottomSheet(
56 | onDismissRequest = { showEmojiPicker = false },
57 | dragHandle = null
58 | ) {
59 | AndroidView(
60 | modifier = Modifier
61 | .fillMaxWidth()
62 | .verticalScroll(rememberScrollState()),
63 | factory = { ctx ->
64 | EmojiPickerView(ctx).apply {
65 | setOnEmojiPickedListener(onEmojiPickedListener = {
66 | playlistViewModel.handlePlaylistActions(PlaylistActions.UpdateStateEmoji(it.emoji))
67 | })
68 | }
69 | }
70 | )
71 | }
72 | }
73 |
74 |
75 | AlertDialog(
76 | onDismissRequest = onDismissRequest,
77 | title = { CuteText(stringResource(R.string.create_playlist)) },
78 | confirmButton = {
79 | TextButton(
80 | onClick = {
81 | playlistViewModel.handlePlaylistActions(PlaylistActions.CreatePlaylist)
82 | onDismissRequest()
83 | }
84 | ) {
85 | CuteText(stringResource(R.string.create))
86 | }
87 | },
88 | dismissButton = {
89 | TextButton(
90 | onClick = onDismissRequest
91 | ) {
92 | CuteText(stringResource(R.string.cancel))
93 | }
94 | },
95 | text = {
96 | Column {
97 | IconButton(
98 | onClick = { playlistViewModel.handlePlaylistActions(PlaylistActions.UpdateStateEmoji("")) },
99 | modifier = Modifier.align(Alignment.End)
100 | ) {
101 | Icon(
102 | imageVector = Icons.Rounded.Close,
103 | contentDescription = stringResource(R.string.remove_emoji),
104 | )
105 | }
106 | Box(
107 | modifier = Modifier
108 | .align(Alignment.CenterHorizontally)
109 | .size(100.dp)
110 | .padding(bottom = 10.dp)
111 | .clip(RoundedCornerShape(10))
112 | .clickable {
113 | showEmojiPicker = true
114 | },
115 | contentAlignment = Alignment.Center
116 | ) {
117 | if (playlistState.emoji.isNotBlank()) {
118 | AnimatedContent(playlistState.emoji) {
119 | CuteText(
120 | text = it,
121 | fontSize = 40.sp
122 | )
123 | }
124 | } else {
125 | Icon(
126 | painter = painterResource(R.drawable.add_emoji_rounded),
127 | contentDescription = stringResource(R.string.emoji),
128 | modifier = Modifier.size(40.dp)
129 | )
130 | }
131 | }
132 | OutlinedTextField(
133 | value = playlistState.name,
134 | onValueChange = { playlistViewModel.handlePlaylistActions(PlaylistActions.UpdateStateName(it)) },
135 | placeholder = {
136 | CuteText("${stringResource(R.string.playlist)} ${playlists.size + 1}")
137 | }
138 | )
139 | }
140 | }
141 | )
142 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/playlists/PlaylistPicker.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalMaterial3Api::class)
2 |
3 | package com.sosauce.cutemusic.ui.screens.playlists
4 |
5 | import android.widget.Toast
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.Spacer
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.width
10 | import androidx.compose.foundation.layout.wrapContentWidth
11 | import androidx.compose.foundation.lazy.LazyColumn
12 | import androidx.compose.foundation.lazy.items
13 | import androidx.compose.material.icons.Icons
14 | import androidx.compose.material.icons.rounded.Add
15 | import androidx.compose.material3.ExperimentalMaterial3Api
16 | import androidx.compose.material3.Icon
17 | import androidx.compose.material3.ModalBottomSheet
18 | import androidx.compose.material3.OutlinedButton
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.getValue
21 | import androidx.compose.runtime.mutableStateOf
22 | import androidx.compose.runtime.remember
23 | import androidx.compose.runtime.setValue
24 | import androidx.compose.ui.Alignment
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.platform.LocalContext
27 | import androidx.compose.ui.res.stringResource
28 | import androidx.compose.ui.unit.dp
29 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
30 | import com.sosauce.cutemusic.R
31 | import com.sosauce.cutemusic.data.actions.PlaylistActions
32 | import com.sosauce.cutemusic.domain.model.Playlist
33 | import com.sosauce.cutemusic.ui.shared_components.CuteText
34 | import com.sosauce.cutemusic.ui.shared_components.PlaylistViewModel
35 | import com.sosauce.cutemusic.utils.ICON_TEXT_SPACING
36 | import com.sosauce.cutemusic.utils.copyMutate
37 | import org.koin.androidx.compose.koinViewModel
38 |
39 | @Composable
40 | fun PlaylistPicker(
41 | mediaId: List,
42 | onDismissRequest: () -> Unit,
43 | onAddingFinished: () -> Unit = {}
44 | ) {
45 | val context = LocalContext.current
46 | val playlistViewModel = koinViewModel()
47 | val playlists by playlistViewModel.allPlaylists.collectAsStateWithLifecycle()
48 | var showPlaylistCreatorDialog by remember { mutableStateOf(false) }
49 |
50 |
51 |
52 | if (showPlaylistCreatorDialog) {
53 | CreatePlaylistDialog { showPlaylistCreatorDialog = false }
54 | }
55 |
56 | ModalBottomSheet(
57 | onDismissRequest = onDismissRequest
58 | ) {
59 | LazyColumn {
60 | item {
61 | OutlinedButton(
62 | onClick = { showPlaylistCreatorDialog = true },
63 | modifier = Modifier
64 | .fillMaxWidth()
65 | .wrapContentWidth()
66 | ) {
67 | Row(
68 | verticalAlignment = Alignment.CenterVertically
69 | ) {
70 | Icon(
71 | imageVector = Icons.Rounded.Add,
72 | contentDescription = null
73 | )
74 | Spacer(Modifier.width(ICON_TEXT_SPACING.dp))
75 | CuteText(stringResource(R.string.create_playlist))
76 | }
77 | }
78 | }
79 |
80 | items(
81 | items = playlists,
82 | key = { it.id }
83 | ) { playlist ->
84 | PlaylistItem(
85 | playlist = playlist,
86 | allowEditAction = false,
87 | onClickPlaylist = {
88 |
89 | val newPlaylist = Playlist(
90 | id = playlist.id,
91 | name = playlist.name,
92 | emoji = playlist.emoji,
93 | musics = playlist.musics.copyMutate {
94 | mediaId.forEach { id ->
95 | if (!contains(id)) {
96 | add(id)
97 | } else {
98 | Toast.makeText(
99 | context,
100 | context.getString(R.string.alrdy_in_playlist),
101 | Toast.LENGTH_SHORT
102 | ).show()
103 | }
104 | }
105 | }
106 | )
107 | playlistViewModel.handlePlaylistActions(
108 | PlaylistActions.UpsertPlaylist(newPlaylist)
109 | )
110 | onAddingFinished()
111 | },
112 | onHandlePlaylistActions = playlistViewModel::handlePlaylistActions
113 | )
114 | }
115 | }
116 | }
117 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/compenents/AboutCard.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.screens.settings.compenents
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.layout.size
10 | import androidx.compose.foundation.shape.RoundedCornerShape
11 | import androidx.compose.material3.Button
12 | import androidx.compose.material3.Card
13 | import androidx.compose.material3.CardDefaults
14 | import androidx.compose.material3.Icon
15 | import androidx.compose.material3.MaterialTheme
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.ui.Alignment
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.draw.clip
20 | import androidx.compose.ui.graphics.Color
21 | import androidx.compose.ui.platform.LocalContext
22 | import androidx.compose.ui.platform.LocalUriHandler
23 | import androidx.compose.ui.res.painterResource
24 | import androidx.compose.ui.res.stringResource
25 | import androidx.compose.ui.unit.dp
26 | import com.sosauce.cutemusic.R
27 | import com.sosauce.cutemusic.ui.shared_components.CuteText
28 |
29 | @Composable
30 | fun AboutCard() {
31 |
32 | val context = LocalContext.current
33 | val version = context.packageManager.getPackageInfo(context.packageName, 0).versionName
34 | val uriHandler = LocalUriHandler.current
35 |
36 | Card(
37 | colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surfaceContainer),
38 | modifier = Modifier
39 | .fillMaxWidth()
40 | .padding(horizontal = 16.dp, vertical = 2.dp),
41 | shape = RoundedCornerShape(24.dp)
42 | ) {
43 | Row(
44 | verticalAlignment = Alignment.CenterVertically
45 | ) {
46 | Box(
47 | modifier = Modifier
48 | .size(100.dp)
49 | .padding(15.dp)
50 | .clip(RoundedCornerShape(15))
51 | .background(Color(0xFFFAB3AA)),
52 | contentAlignment = Alignment.Center
53 | ) {
54 | Icon(
55 | painter = painterResource(R.drawable.music_note_rounded),
56 | contentDescription = stringResource(id = R.string.app_icon),
57 | modifier = Modifier.size(60.dp)
58 | )
59 | }
60 | Column {
61 | CuteText(
62 | text = stringResource(id = R.string.cm_by_sosauce),
63 |
64 | )
65 | CuteText(
66 | text = "${stringResource(id = R.string.version)} $version",
67 | color = MaterialTheme.colorScheme.onSurfaceVariant
68 | )
69 | }
70 | }
71 | Row(
72 | modifier = Modifier
73 | .padding(8.dp)
74 | ) {
75 | Button(
76 | onClick = { uriHandler.openUri("https://github.com/sosauce/CuteMusic/releases") },
77 | shape = RoundedCornerShape(
78 | topStart = 24.dp,
79 | bottomStart = 24.dp,
80 | topEnd = 24.dp,
81 | bottomEnd = 24.dp
82 | ),
83 | modifier = Modifier.fillMaxWidth()
84 | ) {
85 | CuteText(text = stringResource(id = R.string.update))
86 | }
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/compenents/FolderItem.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.screens.settings.compenents
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.basicMarquee
5 | import androidx.compose.foundation.layout.Arrangement
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.Row
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.padding
10 | import androidx.compose.foundation.layout.size
11 | import androidx.compose.foundation.shape.RoundedCornerShape
12 | import androidx.compose.material3.Card
13 | import androidx.compose.material3.CardDefaults
14 | import androidx.compose.material3.MaterialTheme
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.ui.Alignment
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.graphics.ColorFilter
19 | import androidx.compose.ui.res.painterResource
20 | import androidx.compose.ui.unit.Dp
21 | import androidx.compose.ui.unit.dp
22 | import androidx.compose.ui.unit.sp
23 | import com.sosauce.cutemusic.R
24 | import com.sosauce.cutemusic.ui.shared_components.CuteText
25 | import java.io.File
26 |
27 | @Composable
28 | fun FolderItem(
29 | modifier: Modifier = Modifier,
30 | folder: String,
31 | topDp: Dp,
32 | bottomDp: Dp,
33 | actionButton: @Composable () -> Unit
34 | ) {
35 | Card(
36 | modifier = modifier
37 | .padding(horizontal = 16.dp, vertical = 2.dp),
38 | colors = CardDefaults.cardColors(
39 | containerColor = MaterialTheme.colorScheme.surfaceContainer
40 | ),
41 | shape = RoundedCornerShape(
42 | topStart = topDp,
43 | topEnd = topDp,
44 | bottomStart = bottomDp,
45 | bottomEnd = bottomDp
46 | ),
47 | ) {
48 | Row(
49 | modifier = Modifier
50 | .fillMaxWidth()
51 | .padding(15.dp),
52 | verticalAlignment = Alignment.CenterVertically,
53 | horizontalArrangement = Arrangement.SpaceBetween
54 | ) {
55 | Image(
56 | painter = painterResource(R.drawable.folder_rounded),
57 | contentDescription = null,
58 | modifier = Modifier.size(33.dp),
59 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground)
60 | )
61 | Column(
62 | modifier = Modifier
63 | .weight(1f)
64 | .padding(start = 10.dp),
65 | horizontalAlignment = Alignment.Start
66 | ) {
67 | CuteText(
68 | text = File(folder).name,
69 | fontSize = 18.sp
70 | )
71 | CuteText(
72 | text = folder,
73 | fontSize = 13.sp,
74 | color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.5f),
75 | modifier = Modifier.basicMarquee()
76 | )
77 | }
78 | actionButton()
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/compenents/SettingsCategoryCard.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.screens.settings.compenents
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.layout.width
9 | import androidx.compose.foundation.shape.RoundedCornerShape
10 | import androidx.compose.material3.Card
11 | import androidx.compose.material3.CardDefaults
12 | import androidx.compose.material3.Icon
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.graphics.painter.Painter
18 | import androidx.compose.ui.unit.Dp
19 | import androidx.compose.ui.unit.dp
20 | import com.sosauce.cutemusic.ui.shared_components.CuteText
21 |
22 | @Composable
23 | fun SettingsCategoryCard(
24 | icon: Painter,
25 | name: String,
26 | description: String,
27 | topDp: Dp,
28 | bottomDp: Dp,
29 | onNavigate: () -> Unit
30 | ) {
31 | Card(
32 | onClick = onNavigate,
33 | colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surfaceContainer),
34 | modifier = Modifier
35 | .fillMaxWidth()
36 | .padding(horizontal = 16.dp, vertical = 2.dp),
37 | shape = RoundedCornerShape(
38 | topStart = topDp,
39 | topEnd = topDp,
40 | bottomStart = bottomDp,
41 | bottomEnd = bottomDp
42 | )
43 | ) {
44 | Row(
45 | modifier = Modifier
46 | .padding(16.dp),
47 | verticalAlignment = Alignment.CenterVertically
48 | ) {
49 | Icon(
50 | painter = icon,
51 | contentDescription = null
52 | )
53 | Spacer(Modifier.width(15.dp))
54 | Column {
55 | CuteText(name)
56 | CuteText(
57 | text = description,
58 | color = MaterialTheme.colorScheme.onSurfaceVariant,
59 | style = MaterialTheme.typography.bodyMedium
60 | )
61 | }
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/compenents/SettingsScreens.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.screens.settings.compenents
2 |
3 | import androidx.navigation3.runtime.NavKey
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | sealed class SettingsScreens(): NavKey {
8 |
9 | @Serializable
10 | data object Settings: SettingsScreens()
11 |
12 | @Serializable
13 | data object LookAndFeel: SettingsScreens()
14 |
15 | @Serializable
16 | data object NowPlaying: SettingsScreens()
17 |
18 | @Serializable
19 | data object Library: SettingsScreens()
20 |
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/AnimatedIconButton.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.shared_components
2 |
3 | import androidx.compose.animation.core.animateFloatAsState
4 | import androidx.compose.foundation.interaction.collectIsPressedAsState
5 | import androidx.compose.foundation.layout.offset
6 | import androidx.compose.material3.Icon
7 | import androidx.compose.material3.IconButton
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.rememberCoroutineScope
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.graphics.graphicsLayer
13 | import androidx.compose.ui.graphics.vector.ImageVector
14 | import androidx.compose.ui.unit.IntOffset
15 | import com.sosauce.cutemusic.utils.rememberAnimatable
16 | import com.sosauce.cutemusic.utils.rememberInteractionSource
17 | import kotlinx.coroutines.launch
18 |
19 | @Composable
20 | fun AnimatedIconButton(
21 | modifier: Modifier = Modifier,
22 | buttonModifier: Modifier = Modifier,
23 | onClick: () -> Unit,
24 | animationDirection: Float,
25 | icon: ImageVector,
26 | contentDescription: String
27 | ) {
28 | val scope = rememberCoroutineScope()
29 | val animatable = rememberAnimatable()
30 | val interactionSource = rememberInteractionSource()
31 | val isPressed by interactionSource.collectIsPressedAsState()
32 | val scale by animateFloatAsState(
33 | targetValue = if (isPressed) 0.7f else 1f
34 | )
35 |
36 |
37 | IconButton(
38 | onClick = {
39 | onClick()
40 | scope.launch {
41 | animatable.animateTo(animationDirection)
42 | animatable.animateTo(0f)
43 | }
44 | },
45 | modifier = buttonModifier,
46 | interactionSource = interactionSource
47 | ) {
48 | Icon(
49 | imageVector = icon,
50 | contentDescription = contentDescription,
51 | modifier = modifier
52 | .offset {
53 | IntOffset(
54 | x = animatable.value.toInt(),
55 | y = 0
56 | )
57 | }
58 | .graphicsLayer {
59 | scaleX = scale
60 | scaleY = scale
61 | }
62 | )
63 | }
64 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/CuteDropdownMenuItem.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalMaterial3ExpressiveApi::class)
2 |
3 | package com.sosauce.cutemusic.ui.shared_components
4 |
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.foundation.shape.RoundedCornerShape
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.automirrored.rounded.PlaylistAdd
9 | import androidx.compose.material.icons.rounded.PlaylistRemove
10 | import androidx.compose.material3.DropdownMenuItem
11 | import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
12 | import androidx.compose.material3.Icon
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.getValue
15 | import androidx.compose.runtime.mutableStateOf
16 | import androidx.compose.runtime.remember
17 | import androidx.compose.runtime.setValue
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.draw.clip
20 | import androidx.compose.ui.res.stringResource
21 | import androidx.compose.ui.unit.dp
22 | import androidx.media3.common.MediaItem
23 | import com.sosauce.cutemusic.R
24 | import com.sosauce.cutemusic.ui.screens.playlists.PlaylistPicker
25 |
26 | /**
27 | * A dropdown menu item with some padding and clipped corners,
28 | * also adds a visible parameter, if needed.
29 | */
30 | @Composable
31 | fun CuteDropdownMenuItem(
32 | text: @Composable () -> Unit,
33 | onClick: () -> Unit,
34 | modifier: Modifier = Modifier,
35 | leadingIcon: @Composable (() -> Unit)? = null,
36 | trailingIcon: @Composable (() -> Unit)? = null,
37 | visible: Boolean = true
38 | ) {
39 |
40 | if (visible) {
41 | DropdownMenuItem(
42 | text = text,
43 | onClick = onClick,
44 | modifier = modifier
45 | .padding(horizontal = 2.dp)
46 | .clip(RoundedCornerShape(12.dp)),
47 | leadingIcon = leadingIcon,
48 | trailingIcon = trailingIcon,
49 | )
50 | }
51 | }
52 |
53 | @Composable
54 | fun AddToPlaylistDropdownItem(
55 | music: MediaItem
56 | ) {
57 |
58 | var showPlaylistDialog by remember { mutableStateOf(false) }
59 | if (showPlaylistDialog) {
60 | PlaylistPicker(
61 | mediaId = listOf(music.mediaId),
62 | onDismissRequest = { showPlaylistDialog = false }
63 | )
64 | }
65 |
66 |
67 | CuteDropdownMenuItem(
68 | onClick = { showPlaylistDialog = true },
69 | text = {
70 | CuteText(stringResource(R.string.add_to_playlist))
71 | },
72 | leadingIcon = {
73 | Icon(
74 | imageVector = Icons.AutoMirrored.Rounded.PlaylistAdd,
75 | contentDescription = null
76 | )
77 | }
78 | )
79 | }
80 |
81 | @Composable
82 | fun RemoveFromPlaylistDropdownItem(
83 | onRemoveFromPlaylist: () -> Unit
84 | ) {
85 | CuteDropdownMenuItem(
86 | onClick = onRemoveFromPlaylist,
87 | text = {
88 | CuteText(stringResource(R.string.remove_from_playlist))
89 | },
90 | leadingIcon = {
91 | Icon(
92 | imageVector = Icons.Rounded.PlaylistRemove,
93 | contentDescription = null
94 | )
95 | }
96 | )
97 | }
98 |
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/CuteNavigationButton.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.shared_components
2 |
3 | import androidx.compose.foundation.layout.Row
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.foundation.shape.RoundedCornerShape
6 | import androidx.compose.material.icons.Icons
7 | import androidx.compose.material.icons.automirrored.rounded.ArrowBack
8 | import androidx.compose.material.icons.rounded.Shuffle
9 | import androidx.compose.material3.Icon
10 | import androidx.compose.material3.MaterialTheme
11 | import androidx.compose.material3.SmallFloatingActionButton
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.graphics.vector.ImageVector
16 | import androidx.compose.ui.unit.dp
17 |
18 | @Composable
19 | fun CuteNavigationButton(
20 | modifier: Modifier = Modifier,
21 | playlistName: (@Composable () -> Unit)? = null,
22 | onNavigateUp: () -> Unit
23 | ) {
24 | SmallFloatingActionButton(
25 | onClick = onNavigateUp,
26 | modifier = modifier,
27 | shape = RoundedCornerShape(14.dp),
28 | containerColor = MaterialTheme.colorScheme.surfaceContainer
29 | ) {
30 | Row(
31 | verticalAlignment = Alignment.CenterVertically,
32 | modifier = Modifier.padding(if (playlistName != null) 5.dp else 0.dp)
33 | ) {
34 | Icon(
35 | imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
36 | contentDescription = null
37 | )
38 | playlistName?.invoke()
39 | }
40 | }
41 | }
42 |
43 | @Composable
44 | fun CuteActionButton(
45 | modifier: Modifier = Modifier,
46 | imageVector: ImageVector = Icons.Rounded.Shuffle,
47 | action: () -> Unit
48 | ) {
49 | SmallFloatingActionButton(
50 | onClick = action,
51 | modifier = modifier,
52 | shape = RoundedCornerShape(14.dp)
53 | ) {
54 | Icon(
55 | imageVector = imageVector,
56 | contentDescription = null
57 | )
58 | }
59 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/CuteText.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.shared_components
2 |
3 | import androidx.compose.material3.LocalTextStyle
4 | import androidx.compose.material3.Text
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.getValue
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.graphics.Color
9 | import androidx.compose.ui.text.TextLayoutResult
10 | import androidx.compose.ui.text.TextStyle
11 | import androidx.compose.ui.text.style.TextAlign
12 | import androidx.compose.ui.text.style.TextOverflow
13 | import androidx.compose.ui.unit.TextUnit
14 | import com.sosauce.cutemusic.data.datastore.rememberUseSystemFont
15 | import com.sosauce.cutemusic.ui.theme.GlobalFont
16 |
17 | @Composable
18 | fun CuteText(
19 | text: String,
20 | modifier: Modifier = Modifier,
21 | color: Color = Color.Unspecified,
22 | fontSize: TextUnit = TextUnit.Unspecified,
23 | textAlign: TextAlign? = null,
24 | maxLines: Int = Int.MAX_VALUE,
25 | style: TextStyle = LocalTextStyle.current,
26 | onTextLayout: ((TextLayoutResult) -> Unit)? = null,
27 | overflow: TextOverflow = TextOverflow.Clip,
28 | ) {
29 | val useSystemFont by rememberUseSystemFont()
30 | val fontFamily = if (useSystemFont) {
31 | null
32 | } else {
33 | GlobalFont
34 | }
35 |
36 | Text(
37 | text = text,
38 | modifier = modifier,
39 | color = color,
40 | fontSize = fontSize,
41 | textAlign = textAlign,
42 | maxLines = maxLines,
43 | fontFamily = fontFamily,
44 | style = style,
45 | onTextLayout = onTextLayout,
46 | overflow = overflow
47 | )
48 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/LazyRowWithScrollButton.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.shared_components
2 |
3 | import androidx.compose.animation.slideInHorizontally
4 | import androidx.compose.animation.slideOutHorizontally
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.lazy.LazyRow
7 | import androidx.compose.foundation.lazy.items
8 | import androidx.compose.foundation.lazy.rememberLazyListState
9 | import androidx.compose.material.icons.Icons
10 | import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
11 | import androidx.compose.material3.Icon
12 | import androidx.compose.material3.IconButton
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.rememberCoroutineScope
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import kotlinx.coroutines.launch
18 |
19 | @Composable
20 | fun LazyRowWithScrollButton(
21 | items: List,
22 | content: @Composable (T) -> Unit
23 | ) {
24 | val state = rememberLazyListState()
25 | val scope = rememberCoroutineScope()
26 |
27 | Box {
28 | LazyRow(
29 | state = state
30 | ) {
31 | items(
32 | items = items,
33 | key = { it.hashCode() }
34 | ) { type ->
35 | content(type)
36 | }
37 | }
38 | androidx.compose.animation.AnimatedVisibility(
39 | visible = state.canScrollForward,
40 | modifier = Modifier.align(Alignment.CenterEnd),
41 | enter = slideInHorizontally { it },
42 | exit = slideOutHorizontally { it }
43 | ) {
44 | IconButton(
45 | onClick = {
46 | scope.launch {
47 | state.animateScrollToItem(items.lastIndex)
48 | }
49 | }
50 | ) {
51 | Icon(
52 | imageVector = Icons.AutoMirrored.Rounded.KeyboardArrowRight,
53 | contentDescription = null
54 | )
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/ScreenSelection.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.shared_components
2 |
3 | import androidx.compose.animation.animateColorAsState
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.Arrangement
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.material.icons.Icons
10 | import androidx.compose.material.icons.automirrored.rounded.QueueMusic
11 | import androidx.compose.material3.DropdownMenu
12 | import androidx.compose.material3.Icon
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.Immutable
16 | import androidx.compose.runtime.getValue
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.graphics.Color
19 | import androidx.compose.ui.graphics.painter.Painter
20 | import androidx.compose.ui.graphics.vector.rememberVectorPainter
21 | import androidx.compose.ui.res.painterResource
22 | import androidx.compose.ui.res.stringResource
23 | import androidx.compose.ui.unit.dp
24 | import com.sosauce.cutemusic.R
25 | import com.sosauce.cutemusic.ui.navigation.Screen
26 | import com.sosauce.cutemusic.utils.CurrentScreen
27 |
28 | @Composable
29 | fun ScreenSelection(
30 | expanded: Boolean,
31 | onDismissRequest: () -> Unit,
32 | onNavigate: (Screen) -> Unit
33 | ) {
34 | val items = listOf(
35 | NavigationItem(
36 | title = R.string.music,
37 | navigateTo = Screen.Main,
38 | icon = painterResource(R.drawable.music_note_rounded)
39 | ),
40 | NavigationItem(
41 | title = R.string.albums,
42 | navigateTo = Screen.Albums,
43 | icon = painterResource(androidx.media3.session.R.drawable.media3_icon_album)
44 | ),
45 | NavigationItem(
46 | title = R.string.artists,
47 | navigateTo = Screen.Artists,
48 | icon = painterResource(R.drawable.artist_rounded)
49 | ),
50 | NavigationItem(
51 | title = R.string.playlists,
52 | navigateTo = Screen.Playlists,
53 | icon = rememberVectorPainter(Icons.AutoMirrored.Rounded.QueueMusic)
54 | )
55 |
56 | )
57 |
58 | DropdownMenu(
59 | expanded = expanded,
60 | onDismissRequest = onDismissRequest,
61 | shape = RoundedCornerShape(24.dp),
62 | ) {
63 | Column(
64 | verticalArrangement = Arrangement.Center
65 | ) {
66 | items.forEach { navigationItem ->
67 |
68 | val bgColor by animateColorAsState(
69 | targetValue = if (navigationItem.navigateTo == CurrentScreen.screen) MaterialTheme.colorScheme.surfaceContainerHigh else Color.Transparent
70 | )
71 |
72 | CuteDropdownMenuItem(
73 | onClick = { onNavigate(navigationItem.navigateTo) },
74 | text = { CuteText(stringResource(navigationItem.title)) },
75 | leadingIcon = {
76 | Icon(
77 | painter = navigationItem.icon,
78 | contentDescription = stringResource(navigationItem.title)
79 | )
80 | },
81 | modifier = Modifier
82 | .padding(2.dp)
83 | .background(
84 | color = bgColor,
85 | shape = RoundedCornerShape(12.dp)
86 | )
87 | )
88 | }
89 | }
90 | }
91 | }
92 |
93 | @Immutable
94 | data class NavigationItem(
95 | val title: Int,
96 | val navigateTo: Screen,
97 | val icon: Painter,
98 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/SelectedBar.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.shared_components
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.border
5 | import androidx.compose.foundation.layout.Arrangement
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.Row
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.navigationBarsPadding
10 | import androidx.compose.foundation.layout.padding
11 | import androidx.compose.foundation.shape.RoundedCornerShape
12 | import androidx.compose.material.icons.Icons
13 | import androidx.compose.material.icons.automirrored.rounded.PlaylistAdd
14 | import androidx.compose.material.icons.rounded.Close
15 | import androidx.compose.material3.Icon
16 | import androidx.compose.material3.IconButton
17 | import androidx.compose.material3.MaterialTheme
18 | import androidx.compose.runtime.Composable
19 | import androidx.compose.runtime.getValue
20 | import androidx.compose.runtime.mutableStateOf
21 | import androidx.compose.runtime.remember
22 | import androidx.compose.runtime.setValue
23 | import androidx.compose.ui.Alignment
24 | import androidx.compose.ui.Modifier
25 | import androidx.compose.ui.draw.clip
26 | import androidx.compose.ui.res.painterResource
27 | import androidx.compose.ui.unit.dp
28 | import com.sosauce.cutemusic.R
29 | import com.sosauce.cutemusic.ui.screens.playlists.PlaylistPicker
30 | import com.sosauce.cutemusic.utils.rememberSearchbarMaxFloatValue
31 | import com.sosauce.cutemusic.utils.rememberSearchbarRightPadding
32 |
33 | @Composable
34 | fun SelectedBar(
35 | modifier: Modifier = Modifier,
36 | selectedElements: List,
37 | onClearSelected: () -> Unit
38 | ) {
39 |
40 | var showPlaylistDialog by remember { mutableStateOf(false) }
41 |
42 | if (showPlaylistDialog) {
43 | PlaylistPicker(
44 | mediaId = selectedElements,
45 | onDismissRequest = { showPlaylistDialog = false },
46 | onAddingFinished = onClearSelected
47 | )
48 | }
49 |
50 | Column(
51 | modifier = modifier
52 | .navigationBarsPadding()
53 | .fillMaxWidth(rememberSearchbarMaxFloatValue())
54 | .padding(end = rememberSearchbarRightPadding())
55 | .clip(RoundedCornerShape(24.dp))
56 | .background(MaterialTheme.colorScheme.surface)
57 | .border(
58 | width = 1.dp,
59 | color = MaterialTheme.colorScheme.surfaceContainer,
60 | shape = RoundedCornerShape(24.dp)
61 | )
62 | ) {
63 | Row(
64 | verticalAlignment = Alignment.CenterVertically
65 | ) {
66 | IconButton(
67 | onClick = onClearSelected
68 | ) {
69 | Icon(
70 | imageVector = Icons.Rounded.Close,
71 | contentDescription = null
72 | )
73 | }
74 | CuteText(selectedElements.size.toString())
75 | }
76 |
77 | Row(
78 | verticalAlignment = Alignment.CenterVertically,
79 | horizontalArrangement = Arrangement.SpaceEvenly,
80 | modifier = Modifier.fillMaxWidth()
81 | ) {
82 | IconButton(
83 | onClick = { showPlaylistDialog = true }
84 | ) {
85 | Icon(
86 | imageVector = Icons.AutoMirrored.Rounded.PlaylistAdd,
87 | contentDescription = null
88 | )
89 | }
90 |
91 | IconButton(
92 | onClick = {}
93 | ) {
94 | Icon(
95 | painter = painterResource(R.drawable.trash_rounded_filled),
96 | contentDescription = null,
97 | tint = MaterialTheme.colorScheme.error
98 | )
99 | }
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/ThreadDivider.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.shared_components
2 |
3 | import androidx.compose.foundation.Canvas
4 | import androidx.compose.foundation.layout.height
5 | import androidx.compose.foundation.layout.width
6 | import androidx.compose.material3.DividerDefaults
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.graphics.Path
12 | import androidx.compose.ui.graphics.StrokeCap
13 | import androidx.compose.ui.graphics.drawscope.Stroke
14 | import androidx.compose.ui.unit.Dp
15 | import androidx.compose.ui.unit.dp
16 |
17 | // Basically HorizontalDivider + VerticalDivider with a curved edge
18 | @Composable
19 | fun ThreadDivider(
20 | modifier: Modifier = Modifier,
21 | thickness: Dp = DividerDefaults.Thickness,
22 | color: Color = MaterialTheme.colorScheme.onBackground,
23 | curveSize: Dp = 10.dp
24 | ) {
25 | Canvas(
26 | modifier = modifier
27 | .width(20.dp)
28 | .height(50.dp)
29 | ) {
30 | val strokeWidth = thickness.toPx()
31 | val midX = strokeWidth / 2
32 | val midY = size.height / 2
33 | val curvePx = curveSize.toPx()
34 |
35 | val path = Path().apply {
36 | moveTo(midX, 0f)
37 | lineTo(midX, midY - curvePx)
38 | cubicTo(
39 | midX, midY,
40 | midX + curvePx, midY,
41 | midX + curvePx, midY
42 | )
43 | lineTo(size.width, midY)
44 | }
45 |
46 | drawPath(
47 | path = path,
48 | color = color,
49 | style = Stroke(width = strokeWidth, cap = StrokeCap.Round)
50 | )
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val md_theme_light_primary = Color(0xFFB0137F)
6 | val md_theme_light_onPrimary = Color(0xFFFFFFFF)
7 | val md_theme_light_primaryContainer = Color(0xFFFFD8E9)
8 | val md_theme_light_onPrimaryContainer = Color(0xFF3C0029)
9 | val md_theme_light_secondary = Color(0xFF725763)
10 | val md_theme_light_onSecondary = Color(0xFFFFFFFF)
11 | val md_theme_light_secondaryContainer = Color(0xFFFDD9E8)
12 | val md_theme_light_onSecondaryContainer = Color(0xFF291520)
13 | val md_theme_light_tertiary = Color(0xFF7F543B)
14 | val md_theme_light_onTertiary = Color(0xFFFFFFFF)
15 | val md_theme_light_tertiaryContainer = Color(0xFFFFDBC9)
16 | val md_theme_light_onTertiaryContainer = Color(0xFF311302)
17 | val md_theme_light_error = Color(0xFFBA1A1A)
18 | val md_theme_light_errorContainer = Color(0xFFFFDAD6)
19 | val md_theme_light_onError = Color(0xFFFFFFFF)
20 | val md_theme_light_onErrorContainer = Color(0xFF410002)
21 | val md_theme_light_background = Color(0xFFFFFBFF)
22 | val md_theme_light_onBackground = Color(0xFF1F1A1C)
23 | val md_theme_light_surface = Color(0xFFFFFBFF)
24 | val md_theme_light_onSurface = Color(0xFF1F1A1C)
25 | val md_theme_light_surfaceVariant = Color(0xFFF0DEE4)
26 | val md_theme_light_onSurfaceVariant = Color(0xFF504349)
27 | val md_theme_light_outline = Color(0xFF827379)
28 | val md_theme_light_inverseOnSurface = Color(0xFFF9EEF1)
29 | val md_theme_light_inverseSurface = Color(0xFF352F31)
30 | val md_theme_light_inversePrimary = Color(0xFFFFAFD7)
31 | val md_theme_light_surfaceTint = Color(0xFFB0137F)
32 | val md_theme_light_outlineVariant = Color(0xFFD4C2C8)
33 | val md_theme_light_scrim = Color(0xFF000000)
34 |
35 | val md_theme_dark_primary = Color(0xFFFFAFD7)
36 | val md_theme_dark_onPrimary = Color(0xFF610044)
37 | val md_theme_dark_primaryContainer = Color(0xFF890062)
38 | val md_theme_dark_onPrimaryContainer = Color(0xFFFFD8E9)
39 | val md_theme_dark_secondary = Color(0xFFDFBDCC)
40 | val md_theme_dark_onSecondary = Color(0xFF402A35)
41 | val md_theme_dark_secondaryContainer = Color(0xFF58404C)
42 | val md_theme_dark_onSecondaryContainer = Color(0xFFFDD9E8)
43 | val md_theme_dark_tertiary = Color(0xFFF3BA9B)
44 | val md_theme_dark_onTertiary = Color(0xFF4A2812)
45 | val md_theme_dark_tertiaryContainer = Color(0xFF643D26)
46 | val md_theme_dark_onTertiaryContainer = Color(0xFFFFDBC9)
47 | val md_theme_dark_error = Color(0xFFFFB4AB)
48 | val md_theme_dark_errorContainer = Color(0xFF93000A)
49 | val md_theme_dark_onError = Color(0xFF690005)
50 | val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
51 | val md_theme_dark_background = Color(0xFF1F1A1C)
52 | val md_theme_dark_onBackground = Color(0xFFEBE0E2)
53 | val md_theme_dark_surface = Color(0xFF1F1A1C)
54 | val md_theme_dark_onSurface = Color(0xFFEBE0E2)
55 | val md_theme_dark_surfaceVariant = Color(0xFF504349)
56 | val md_theme_dark_onSurfaceVariant = Color(0xFFD4C2C8)
57 | val md_theme_dark_outline = Color(0xFF9C8D93)
58 | val md_theme_dark_inverseOnSurface = Color(0xFF1F1A1C)
59 | val md_theme_dark_inverseSurface = Color(0xFFEBE0E2)
60 | val md_theme_dark_inversePrimary = Color(0xFFB0137F)
61 | val md_theme_dark_surfaceTint = Color(0xFFFFAFD7)
62 | val md_theme_dark_outlineVariant = Color(0xFF504349)
63 | val md_theme_dark_scrim = Color(0xFF000000)
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/widgets/WidgetActions.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.widgets
2 |
3 | import android.app.PendingIntent
4 | import android.content.Context
5 | import android.content.Intent
6 | import com.sosauce.cutemusic.utils.PACKAGE
7 | import com.sosauce.cutemusic.utils.WIDGET_ACTION_BROADCAST
8 | import kotlin.random.Random
9 |
10 | const val WIDGET_ACTION_SKIP_PREVIOUS = "WIDGET_ACTION_SKIP_PREVIOUS"
11 | const val WIDGET_ACTION_PLAYORPAUSE = "WIDGET_ACTION_SKIP_PLAYORPAUSE"
12 | const val WIDGET_ACTION_SKIP_NEXT = "WIDGET_ACTION_SKIP_NEXT"
13 |
14 | fun createWidgetPendingIntent(
15 | context: Context,
16 | widgetActions: String
17 | ): PendingIntent {
18 | val pendingIntent = PendingIntent.getBroadcast(
19 | context,
20 | Random.nextInt(),
21 | Intent(PACKAGE).putExtra(
22 | WIDGET_ACTION_BROADCAST,
23 | widgetActions
24 | ),
25 | PendingIntent.FLAG_IMMUTABLE
26 |
27 | )
28 |
29 | return pendingIntent
30 | }
31 |
32 |
33 | interface WidgetCallback {
34 | fun skipToNext()
35 | fun playOrPause()
36 | fun skipToPrevious()
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/ui/widgets/WidgetBroadcastReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.ui.widgets
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import com.sosauce.cutemusic.utils.WIDGET_ACTION_BROADCAST
7 |
8 | class WidgetBroadcastReceiver : BroadcastReceiver() {
9 |
10 | private var callback: WidgetCallback? = null
11 |
12 | override fun onReceive(context: Context?, intent: Intent?) {
13 | println("broadcast widget action: ${intent?.action}")
14 | val action = intent?.extras?.getString(WIDGET_ACTION_BROADCAST) ?: return
15 |
16 | when (action) {
17 | WIDGET_ACTION_PLAYORPAUSE -> callback?.playOrPause()
18 | WIDGET_ACTION_SKIP_NEXT -> callback?.skipToNext()
19 | WIDGET_ACTION_SKIP_PREVIOUS -> callback?.skipToPrevious()
20 | }
21 | }
22 |
23 | fun startCallback(callback: WidgetCallback) {
24 | this.callback = callback
25 | }
26 |
27 | fun stopCallback() {
28 | this.callback = null
29 | }
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/utils/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.utils
2 |
3 | const val CUTE_MUSIC_ID = "CUTE_MUSIC_ID"
4 | const val PACKAGE = "com.sosauce.cutemusic"
5 | const val ROOT_ID = "cute_music_root"
6 | const val ICON_TEXT_SPACING = 5
7 | const val NAVIGATION_PREFIX = "com.sosauce.cutemusic.ui.navigation.Screen."
8 | const val GOOGLE_SEARCH = "https://www.google.com/search?q="
9 | const val WIDGET_UPDATE = "WIDGET_UPDATE"
10 | const val WIDGET_NEW_DATA = "WIDGET_NEW_DATA"
11 | const val WIDGET_NEW_IS_PLAYING = "WIDGET_NEW_IS_PLAYING"
12 | const val WIDGET_ACTION_BROADCAST = "WIDGET_NEW_DATA"
13 |
14 |
15 |
16 | object SharedTransitionKeys {
17 | const val CURRENTLY_PLAYING = "CURRENTLY_PLAYING"
18 | const val ARTIST = "ARTIST"
19 | const val PLAY_PAUSE_BUTTON = "PLAY_PAUSE_BUTTON"
20 | const val FAB = "FAB"
21 | const val SKIP_NEXT_BUTTON = "SKIP_NEXT_BUTTON"
22 | const val SKIP_PREVIOUS_BUTTON = "SKIP_PREVIOUS_BUTTON"
23 | const val MUSIC_ARTWORK = "MUSIC_ARTWORK"
24 | }
25 |
26 | object CuteTheme {
27 | const val SYSTEM = "SYSTEM"
28 | const val DARK = "DARK"
29 | const val LIGHT = "LIGHT"
30 | const val AMOLED = "AMOLED"
31 | }
32 |
33 | object SliderStyle {
34 | const val WAVY = "WAVY"
35 | const val CLASSIC = "CLASSIC"
36 | const val MATERIAL3 = "MATERIAL3"
37 | }
38 |
39 | object AnimationDirection {
40 | const val LEFT = -25f
41 | const val RIGHT = 25f
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/sosauce/cutemusic/utils/ImageUtils.kt:
--------------------------------------------------------------------------------
1 | package com.sosauce.cutemusic.utils
2 |
3 | import android.content.ContentUris
4 | import android.content.Context
5 | import android.net.Uri
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.graphics.ImageBitmap
8 | import androidx.compose.ui.graphics.asImageBitmap
9 | import androidx.compose.ui.platform.LocalContext
10 | import androidx.core.net.toUri
11 | import coil3.ImageLoader
12 | import coil3.request.ImageRequest
13 | import coil3.request.SuccessResult
14 | import coil3.request.allowHardware
15 | import coil3.request.crossfade
16 | import coil3.request.transformations
17 | import coil3.toBitmap
18 | import kotlinx.coroutines.Dispatchers
19 | import kotlinx.coroutines.withContext
20 | import java.io.FileNotFoundException
21 | import coil3.request.ImageRequest as ImageRequest3
22 |
23 | object ImageUtils {
24 |
25 | @Composable
26 | fun imageRequester(img: Any?): ImageRequest3 {
27 | val context = LocalContext.current
28 | val request = ImageRequest3.Builder(context)
29 | .data(img)
30 | .crossfade(true)
31 | .transformations()
32 | .diskCacheKey(img.toString())
33 | .memoryCacheKey(img.toString())
34 | .build()
35 | .apply {
36 | }
37 |
38 | return request
39 | }
40 |
41 | fun getAlbumArt(albumId: Long): Any? {
42 | val sArtworkUri = "content://media/external/audio/albumart".toUri()
43 | return try {
44 | ContentUris.withAppendedId(sArtworkUri, albumId)
45 | } catch (e: FileNotFoundException) {
46 | e.printStackTrace()
47 | null
48 | }
49 | }
50 |
51 | // Kinda ugly fix to always load the new art for MaterialArt, but I guess it's better than loading the viewmodel in the app's theme
52 | suspend fun loadNewArt(
53 | context: Context,
54 | art: Uri?,
55 | onImageLoadSuccess: (ImageBitmap) -> Unit
56 | ) = withContext(Dispatchers.IO) {
57 | val imageLoader = ImageLoader.Builder(context).build()
58 | val request = ImageRequest.Builder(context)
59 | .data(art)
60 | .allowHardware(false)
61 | .build()
62 | val result = imageLoader.execute(request)
63 |
64 | if (result is SuccessResult) {
65 | onImageLoadSuccess(result.image.toBitmap().asImageBitmap())
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/add_emoji_rounded.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/add_photo_rounded.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/artist_rounded.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bedtime_outlined.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/carousel.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/classic_slider.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/dark_mode.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/edit_rounded.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/export.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/folder_rounded.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/grid_view.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
13 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/image.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/info_rounded.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/library.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/lyrics_rounded.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/music_note_rounded.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/queue_music_rounded.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/reset.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/resource_import.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/saf.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/speed_rounded.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/system_theme.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/trash_rounded.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/trash_rounded_filled.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/widget_next.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/widget_pause.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/widget_play.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/widget_previous.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/font/nunito.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/app/src/main/res/font/nunito.ttf
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-fr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Thème
4 | Paramètres
5 | Mode Sombre
6 | Mode Amoled
7 | Version
8 | Soutenir
9 | Mettre à Jour
10 | A Propos
11 | Taille
12 | Type
13 | Bitrate
14 | Musique précédent
15 | Button jouer/pause
16 | Prochaine musique
17 | Pas d\'artistes trouvés!
18 | Art
19 | Veuillez redémarrer l\'app pour prendre effet !
20 | Utiliser la police système
21 | Artistes
22 | Musiques
23 | CuteMusic par sosauce
24 | Icone de l\'app
25 | Rechercher
26 | Fermer
27 | Plus
28 | Salut!
29 | Il semble que CuteMusic n\'a pas access à vos musiques! Vous pouvez lui autoriser en dessous!
30 | Autoriser les permissions
31 | Pas de musiques trouvées !
32 | Il y a eu un problème en essayant de supprimer la musique.
33 | Musique supprimée avec succès
34 | OK
35 | Divers
36 | Pas d\'albums trouvés !
37 | Suivre le système
38 | Ascendant
39 | Descendant
40 | Régler la vitesse de lecture
41 | Albums
42 | Oui
43 | Dossiers Blacklistés
44 | Supprimer le dossier
45 | Êtes-vous sûr de vouloir supprimer ce dossier ?
46 | Dossier déjà blacklisté!
47 | Titre
48 | Année
49 | Genre
50 | Numéro de piste
51 | Numéro de disque
52 | Éditer
53 | Supprimer
54 | Rien en cours!
55 | UI
56 | Material Art
57 | Succès
58 | Erreur en essayant de sauvegarder les modifications.
59 | Éditeur
60 | Détails
61 | Utiliser le slider classique
62 | Annuler
63 | Fixer la vitesse et le tempo
64 | Fixer la vitesse
65 | Fixer le tempo
66 | Vitesse
67 | Tempo
68 | Taux
69 | Assembler la vitesse et le tempo
70 | Entrer un nouveau taux entre 0.5 et 2
71 | Partager
72 | Montrer le button fermer sur la CuteSearchbar
73 | Duration
74 | Dossiers
75 | Gérer les onglets visibles
76 | Fixer un temps de veille
77 | Aller à:
78 | Pas de paroles trouvées !
79 | Égaliseur
80 | Cette musiques proviens du S.A.F
81 | Ouvrir le S.A.F pour ajouter des musiques
82 | Blacklister
83 | Pas blacklister
84 | S.A.F manageur
85 | Vous n\'avez pas de playlist! Créez en une pour rendre votre expérience encore meilleure!
86 | La musique est déjà dans cette playlist!
87 | Supprimer la playlist
88 | Modifier
89 | Modifier la playlist
90 | Créer une playlist
91 | Créer
92 | Playlist
93 | Playlistes
94 | Ajouter à une playlist
95 | Le thème de l\'app sera basé sur la cover de la musique en cours.
96 | Regrouper les musiques par leurs dossiers
97 | Nom du fichier
98 | Utiliser la vue caroussel
99 | Rechercher ici
100 | Rechercher des paroles
101 | La musique est entrain de charger…
102 | Nombre de grilles
103 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ro/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CuteMusic
3 | Tema
4 | Setări
5 | Mod de întunecare
6 | În mod Amoled
7 | Versiunea
8 | Sprijină
9 | Caută actualizări
10 | Despre
11 | Mărime
12 | Tip
13 | Bitrate
14 | Înapoi Melodia
15 | Butonul Redare/Pauză
16 | Înainte Melodia
17 | Nu s-au găsit artiști !
18 | Artă
19 | Artiști
20 | Albume
21 | CuteMusic de sosauce
22 | Iconiță aplicație
23 | Căutare
24 | Închide
25 | Mai Mult
26 | Bună!
27 | Se pare că CuteMusic nu are permisiunea de a-ți accesa melodiile! O poți acorda mai jos!
28 | Solicită permisiunea
29 | Nu s-a găsit muzică !
30 | A fost o eroare la ștergerea melodia.
31 | Melodia ștearsă cu succes.
32 | OK
33 | Altele
34 | Nu s-au găsit albume !
35 | Urmărește sistemul
36 | Ascendent
37 | Descendent
38 | Setarea vitezei de redare
39 | Da
40 | Foldere Excluse
41 | Șterge Folder
42 | Ești sigur că vrei să ștergi acest folder ?
43 | Folder deja este exclusată !
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v31/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @android:color/system_neutral2_900
4 | @android:color/system_accent1_100
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #201A1A
4 | #f0d2ce
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FAB3AA
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/automotive_app_desc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/music_widget_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidApplication) apply false
3 | alias(libs.plugins.kotlin) apply false
4 | alias(libs.plugins.compose.compiler) apply false
5 | alias(libs.plugins.ksp) apply false
6 | }
7 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/1:
--------------------------------------------------------------------------------
1 | Placeholder
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | - Play any song from anywhere just by sharing the audio file to the app without downloading it!
2 | - Easy search across all your music/albums/artists!
3 | - Very fast and snappy!
4 | - No unnecessary permissions needed!
5 | - Material 3/You & Monet theming (+ Amoled mode)!
6 | - Blacklist Folders!
7 | - Beautiful landscape UI!
8 | - Tag Editor!
9 | - Playlist support !
10 | - Material art: Color scheme dynamically changes depending on currently playing song's artwork !
11 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot0.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot1.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot2.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot3.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot4.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | CuteMusic is a simple, lightweight and offline music player app for Android.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/title.txt:
--------------------------------------------------------------------------------
1 | CuteMusic
--------------------------------------------------------------------------------
/fastlane/metadata/android/es-ES/changelogs/1:
--------------------------------------------------------------------------------
1 | Placeholder
--------------------------------------------------------------------------------
/fastlane/metadata/android/es-ES/full_description.txt:
--------------------------------------------------------------------------------
1 | - ¡Reproduce cualquier canción desde cualquier lugar simplemente compartiendo el archivo de audio con la app sin necesidad de descargarlo!
2 | - ¡Busca fácil y rápidamente entre toda tu música, álbumes y artistas!
3 | - ¡Muy rápido y ágil!
4 | - ¡Sin permisos innecesarios!
5 | - ¡Temas Material 3/You & Monet (y modo AMOLED)!
6 | - ¡Lista negra de carpetas!
7 | ¡Interfaz hermosa en modo horizontal!
8 | - ¡Editor de etiquetas!
9 | - ¡Soporte para listas de reproducción!
10 | - ¡Arte dinámico: el esquema de colores cambia según la carátula de la canción que estés reproduciendo!
11 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/es-ES/short_description.txt:
--------------------------------------------------------------------------------
1 | CuteMusic es una app de reproductor de música para Android que es simple, ligera y funciona sin conexión.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/es-ES/title.txt:
--------------------------------------------------------------------------------
1 | CuteMusic
--------------------------------------------------------------------------------
/fastlane/metadata/android/fr-FR/changelogs/1:
--------------------------------------------------------------------------------
1 | Placeholder
--------------------------------------------------------------------------------
/fastlane/metadata/android/fr-FR/full_description.txt:
--------------------------------------------------------------------------------
1 | - Jouez n'importe quelle musique, just en partageant le fichier à l'application, sans le télécharger !
2 | - Recherche simple à travers touts vos musiques/albums/artistes !
3 | - Très rapide et fluide !
4 | - Pas de permissions inutiles !
5 | - Support pour Material 3/ Monet (+ Mode amoled) !
6 | - Ajouter des dossiers à la liste noir !
7 | - Un beau mode paysage !
8 | - Éditeur de métas-données !
9 | - Support pour les playlistes !
10 | - Material art : La palette de couleurs change dynamiquement en fonction de l'illustration de la chanson en cours de lecture !
11 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/fr-FR/short_description.txt:
--------------------------------------------------------------------------------
1 | CuteMusic est un lecteur de musique hors ligne simple et minialiste pour Android.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/fr-FR/title.txt:
--------------------------------------------------------------------------------
1 | CuteMusic
--------------------------------------------------------------------------------
/font_licence.txt:
--------------------------------------------------------------------------------
1 | Copyright 2014 The Nunito Project Authors (https://github.com/googlefonts/nunito)
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## For more details on how to configure your build environment visit
2 | # http://www.gradle.org/docs/current/userguide/build_environment.html
3 | #
4 | # Specifies the JVM arguments used for the daemon process.
5 | # The setting is particularly useful for tweaking memory settings.
6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m
7 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
8 | #
9 | # When configured, Gradle will run in incubating parallel mode.
10 | # This option should only be used with decoupled projects. For more details, visit
11 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
12 | # org.gradle.parallel=true
13 | #Mon Mar 24 23:03:29 CET 2025
14 | android.nonTransitiveRClass=true
15 | android.useAndroidX=true
16 | kotlin.code.style=official
17 | org.gradle.configuration-cache=true
18 | org.gradle.jvmargs=-Xmx1536M -Dkotlin.daemon.jvm.options\="-Xmx1536M" -Dfile.encoding\=UTF-8
19 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.10.1"
3 | emoji2Emojipicker = "1.5.0"
4 | glance = "1.1.1"
5 | haze = "1.6.0"
6 | koinAndroid = "4.0.2"
7 | koinAndroidxCompose = "4.0.2"
8 | koinAndroidxStartup = "4.0.0"
9 | kotlin = "2.1.20"
10 | activityCompose = "1.10.1"
11 | coilCompose = "3.0.4"
12 | composeBom = "2025.05.01"
13 | composeAnimation = "1.8.2"
14 | coreKtx = "1.16.0"
15 | coreSplashscreen = "1.0.1"
16 | datastorePreferences = "1.1.7"
17 | kotlinxSerializationJson = "1.8.0"
18 | materialKolor = "3.0.0-beta01"
19 | media3Common = "1.7.1"
20 | media3Exoplayer = "1.7.1"
21 | media3Session = "1.7.1"
22 | reorderable = "2.4.3" # https://github.com/Calvin-LL/Reorderable
23 | squigglyslider = "1.0.0"
24 | serialization = "2.0.20"
25 | taglib = "1.0.0-alpha25"
26 | roomCompiler = "2.7.1"
27 | roomKtx = "2.7.1"
28 | ksp = "2.1.20-1.0.31"
29 | kmpalette = "3.1.0"
30 | material3 = "1.4.0-alpha15"
31 | nav3Core = "1.0.0-alpha02"
32 |
33 |
34 |
35 |
36 | [libraries]
37 | androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
38 | androidx-compose-animation = { group = "androidx.compose.animation", name = "animation", version.ref = "composeAnimation" }
39 | androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
40 | androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
41 | androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
42 | androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
43 | androidx-emoji2-emojipicker = { module = "androidx.emoji2:emoji2-emojipicker", version.ref = "emoji2Emojipicker" }
44 | androidx-glance = { module = "androidx.glance:glance", version.ref = "glance" }
45 | androidx-glance-appwidget = { module = "androidx.glance:glance-appwidget", version.ref = "glance" }
46 | androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose" }
47 | androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
48 | androidx-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
49 | androidx-media3-common = { module = "androidx.media3:media3-common", version.ref = "media3Common" }
50 | androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Exoplayer" }
51 | androidx-media3-session = { module = "androidx.media3:media3-session", version.ref = "media3Session" }
52 | androidx-ui = { module = "androidx.compose.ui:ui" }
53 | androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
54 | coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilCompose" }
55 | haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" }
56 | koin-android = { module = "io.insert-koin:koin-android", version.ref = "koinAndroid" }
57 | koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koinAndroidxCompose" }
58 | koin-androidx-startup = { module = "io.insert-koin:koin-androidx-startup", version.ref = "koinAndroidxStartup" }
59 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
60 | material-kolor = { module = "com.materialkolor:material-kolor", version.ref = "materialKolor" }
61 | reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reorderable" }
62 | squigglyslider = { module = "me.saket.squigglyslider:squigglyslider", version.ref = "squigglyslider" }
63 | taglib = { module = "com.github.Kyant0:taglib", version.ref = "taglib" }
64 | androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomCompiler" }
65 | androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" }
66 | kmpalette-core = { module = "com.kmpalette:kmpalette-core", version.ref = "kmpalette" }
67 | androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" }
68 | androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" }
69 |
70 |
71 |
72 | [plugins]
73 | androidApplication = { id = "com.android.application", version.ref = "agp" }
74 | kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
75 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
76 | serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "serialization"}
77 | ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosauce/CuteMusic/649ebcc7adbd51c04018418f162bb5cd82c9276e/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Jan 13 19:30:48 CET 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 |
9 |
10 | dependencyResolutionManagement {
11 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
12 | repositories {
13 | google()
14 | mavenCentral()
15 | maven("https://jitpack.io")
16 | }
17 | }
18 |
19 |
20 | rootProject.name = "CuteMusic"
21 | include(":app")
22 |
--------------------------------------------------------------------------------