├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug-report.yml
│ ├── config.yml
│ └── feature-request.yml
└── workflows
│ ├── ci.yml
│ └── unsigned_release.yml
├── .gitignore
├── LICENSE
├── README.de-DE.md
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
├── schemas
│ └── app.suhasdissa.vibeyou.data.database.SongDatabase
│ │ ├── 1.json
│ │ └── 2.json
└── src
│ ├── debug
│ └── res
│ │ └── values
│ │ └── strings.xml
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ └── app
│ │ └── suhasdissa
│ │ └── vibeyou
│ │ ├── AppContainer.kt
│ │ ├── MainActivity.kt
│ │ ├── MellowMusicApplication.kt
│ │ ├── data
│ │ ├── api
│ │ │ ├── HyperpipeApi.kt
│ │ │ └── PipedApi.kt
│ │ └── database
│ │ │ ├── SongDatabase.kt
│ │ │ ├── dao
│ │ │ ├── PlaylistDao.kt
│ │ │ ├── RawDao.kt
│ │ │ ├── SearchDao.kt
│ │ │ └── SongsDao.kt
│ │ │ └── entities
│ │ │ ├── PlaylistEntity.kt
│ │ │ ├── PlaylistWithSongs.kt
│ │ │ ├── SearchQuery.kt
│ │ │ ├── SongEntity.kt
│ │ │ └── SongPlaylistMap.kt
│ │ ├── domain
│ │ ├── models
│ │ │ ├── Login.kt
│ │ │ ├── PipedInstance.kt
│ │ │ ├── PipedVIdeoResponse.kt
│ │ │ ├── PlayerRepeatMode.kt
│ │ │ ├── PlayerState.kt
│ │ │ ├── SearchFilter.kt
│ │ │ ├── Token.kt
│ │ │ ├── artists
│ │ │ │ ├── Artist.kt
│ │ │ │ ├── Artists.kt
│ │ │ │ ├── Channel.kt
│ │ │ │ └── ChannelTabResponse.kt
│ │ │ ├── hyper
│ │ │ │ ├── MediaSession.kt
│ │ │ │ ├── NextSongsResponse.kt
│ │ │ │ ├── Song.kt
│ │ │ │ └── Thumbnail.kt
│ │ │ ├── playlists
│ │ │ │ ├── Playlist.kt
│ │ │ │ ├── PlaylistInfo.kt
│ │ │ │ └── Playlists.kt
│ │ │ ├── primary
│ │ │ │ ├── Album.kt
│ │ │ │ ├── Artist.kt
│ │ │ │ ├── EqualizerData.kt
│ │ │ │ └── Song.kt
│ │ │ └── songs
│ │ │ │ ├── SongItem.kt
│ │ │ │ └── Songs.kt
│ │ └── repository
│ │ │ ├── AuthRepository.kt
│ │ │ ├── LocalMusicRepository.kt
│ │ │ ├── PipedMusicRepository.kt
│ │ │ ├── PlaylistRepository.kt
│ │ │ ├── SongDatabaseRepository.kt
│ │ │ └── SongDatabaseRepositoryImpl.kt
│ │ ├── navigation
│ │ ├── CustomNavTypes.kt
│ │ ├── Destination.kt
│ │ └── NavHost.kt
│ │ ├── presentation
│ │ ├── components
│ │ │ ├── ChipSelect.kt
│ │ │ ├── IconCard.kt
│ │ │ ├── IllustratedMessageScreen.kt
│ │ │ ├── LoadingScreen.kt
│ │ │ ├── MiniPlayerScaffold.kt
│ │ │ ├── ModalNavDrawerContent.kt
│ │ │ ├── SongCard.kt
│ │ │ ├── SongList.kt
│ │ │ ├── SongListView.kt
│ │ │ ├── SongSettingsSheet.kt
│ │ │ └── VerticalSlider.kt
│ │ ├── layout
│ │ │ ├── MainAppContent.kt
│ │ │ ├── ModelNavDrawerLayout.kt
│ │ │ ├── PermanentNavDrawerLayout.kt
│ │ │ └── PermanentNavDrawerWithPlayerLayout.kt
│ │ └── screens
│ │ │ ├── album
│ │ │ ├── AlbumScreen.kt
│ │ │ ├── components
│ │ │ │ ├── AlbumCard.kt
│ │ │ │ └── AlbumList.kt
│ │ │ └── model
│ │ │ │ ├── LocalPlaylistViewModel.kt
│ │ │ │ ├── NewPlaylistViewModel.kt
│ │ │ │ └── OnlinePlaylistViewModel.kt
│ │ │ ├── artist
│ │ │ ├── ArtistScreen.kt
│ │ │ ├── components
│ │ │ │ ├── ArtistCard.kt
│ │ │ │ └── ArtistList.kt
│ │ │ └── model
│ │ │ │ ├── LocalArtistViewModel.kt
│ │ │ │ └── OnlineArtistViewModel.kt
│ │ │ ├── localmusic
│ │ │ ├── LocalMusicScreen.kt
│ │ │ ├── components
│ │ │ │ └── SortOrderDialog.kt
│ │ │ └── model
│ │ │ │ └── LocalSongViewModel.kt
│ │ │ ├── localsearch
│ │ │ ├── LocalSearchScreen.kt
│ │ │ └── model
│ │ │ │ └── LocalSearchViewModel.kt
│ │ │ ├── onlinemusic
│ │ │ ├── MusicScreen.kt
│ │ │ ├── components
│ │ │ │ └── SongsScreen.kt
│ │ │ └── model
│ │ │ │ ├── SongOptionsViewModel.kt
│ │ │ │ └── SongViewModel.kt
│ │ │ ├── onlinesearch
│ │ │ ├── SearchScreen.kt
│ │ │ └── model
│ │ │ │ ├── PipedSearchViewModel.kt
│ │ │ │ └── state
│ │ │ │ ├── AlbumInfoState.kt
│ │ │ │ ├── ArtistInfoState.kt
│ │ │ │ └── SearchState.kt
│ │ │ ├── player
│ │ │ ├── FullScreenPlayer.kt
│ │ │ ├── MiniPlayer.kt
│ │ │ ├── components
│ │ │ │ ├── EqualizerSheet.kt
│ │ │ │ ├── Queue.kt
│ │ │ │ ├── QueueSheet.kt
│ │ │ │ └── SongOptionsSheet.kt
│ │ │ └── model
│ │ │ │ └── PlayerViewModel.kt
│ │ │ ├── playlists
│ │ │ ├── PlaylistsScreen.kt
│ │ │ ├── components
│ │ │ │ └── PlaylistOptionsSheet.kt
│ │ │ └── model
│ │ │ │ ├── PlaylistInfoViewModel.kt
│ │ │ │ └── PlaylistViewModel.kt
│ │ │ └── settings
│ │ │ ├── AboutScreen.kt
│ │ │ ├── AppearanceSettingsScreen.kt
│ │ │ ├── DatabaseSettingsScreen.kt
│ │ │ ├── NetworkSettingsScreen.kt
│ │ │ ├── SettingsScreen.kt
│ │ │ ├── components
│ │ │ ├── ButtonGroupPref.kt
│ │ │ ├── CacheSizeDialog.kt
│ │ │ ├── ColorPref.kt
│ │ │ ├── CustomInstanceOption.kt
│ │ │ ├── InstanceSelectDialog.kt
│ │ │ ├── LocalMusicPathsDialog.kt
│ │ │ ├── SettingItem.kt
│ │ │ ├── SwitchPref.kt
│ │ │ └── TextFieldPref.kt
│ │ │ └── model
│ │ │ ├── AuthViewModel.kt
│ │ │ ├── CheckUpdateViewModel.kt
│ │ │ ├── DatabaseViewModel.kt
│ │ │ ├── SettingsModel.kt
│ │ │ └── SettingsViewModel.kt
│ │ ├── ui
│ │ └── theme
│ │ │ ├── Color.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ │ └── utils
│ │ ├── Convertions.kt
│ │ ├── DynamicDataSource.kt
│ │ ├── Mappers.kt
│ │ ├── OpenBrowser.kt
│ │ ├── PermissionHelper.kt
│ │ ├── Player.kt
│ │ ├── PlayerState.kt
│ │ ├── Pref.kt
│ │ ├── Preferences.kt
│ │ ├── RetrofitHelper.kt
│ │ ├── ThemeUtil.kt
│ │ ├── TimeUtil.kt
│ │ ├── UpdateUtil.kt
│ │ ├── UriSerializer.kt
│ │ └── services
│ │ └── PlayerService.kt
│ └── res
│ ├── drawable
│ ├── blob.xml
│ ├── ic_launcher_foreground.xml
│ ├── ic_launcher_monochrome.xml
│ ├── ic_piped.xml
│ └── music_placeholder.xml
│ ├── mipmap-anydpi-v26
│ └── ic_launcher.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-ar
│ └── strings.xml
│ ├── values-az
│ └── strings.xml
│ ├── values-bn
│ └── strings.xml
│ ├── values-ca
│ └── strings.xml
│ ├── values-cs
│ └── strings.xml
│ ├── values-da
│ └── strings.xml
│ ├── values-de-rDE
│ └── strings.xml
│ ├── values-es
│ └── strings.xml
│ ├── values-et
│ └── strings.xml
│ ├── values-fa
│ └── strings.xml
│ ├── values-fi
│ └── strings.xml
│ ├── values-fr
│ └── strings.xml
│ ├── values-hi
│ └── strings.xml
│ ├── values-ia
│ └── strings.xml
│ ├── values-it
│ └── strings.xml
│ ├── values-iw
│ └── strings.xml
│ ├── values-ja
│ └── strings.xml
│ ├── values-nb-rNO
│ └── strings.xml
│ ├── values-night
│ └── colors.xml
│ ├── values-or
│ └── strings.xml
│ ├── values-pa
│ └── strings.xml
│ ├── values-pl
│ └── strings.xml
│ ├── values-pt-rBR
│ └── strings.xml
│ ├── values-pt
│ └── strings.xml
│ ├── values-ru
│ └── strings.xml
│ ├── values-sk
│ └── strings.xml
│ ├── values-sr
│ └── strings.xml
│ ├── values-sv
│ └── strings.xml
│ ├── values-ta
│ └── strings.xml
│ ├── values-tr
│ └── strings.xml
│ ├── values-uk
│ └── strings.xml
│ ├── values-zh-rCN
│ └── strings.xml
│ ├── values-zh-rTW
│ └── strings.xml
│ ├── values
│ ├── colors.xml
│ ├── ic_launcher_background.xml
│ ├── strings.xml
│ └── themes.xml
│ └── xml
│ ├── backup_rules.xml
│ └── data_extraction_rules.xml
├── build.gradle.kts
├── fastlane
└── metadata
│ └── android
│ └── en-US
│ ├── full_description.txt
│ ├── images
│ ├── icon.png
│ └── phoneScreenshots
│ │ ├── 1.jpg
│ │ ├── 2.jpg
│ │ ├── 3.jpg
│ │ ├── 4.jpg
│ │ ├── 5.jpg
│ │ ├── 6.jpg
│ │ └── 7.jpg
│ └── short_description.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [Bnyro, SuhasDissa]
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.yml:
--------------------------------------------------------------------------------
1 | name: 🐞 Bug Report
2 | description: Report an issue in the app
3 | labels: [bug]
4 | body:
5 |
6 | - type: textarea
7 | id: reproduce-steps
8 | attributes:
9 | label: Steps to reproduce
10 | description: Provide an example of the issue.
11 | placeholder: |
12 | Example:
13 | 1. First step
14 | 2. Second step
15 | 3. Issue here
16 | validations:
17 | required: true
18 |
19 | - type: textarea
20 | id: expected-behavior
21 | attributes:
22 | label: Expected behavior
23 | placeholder: |
24 | Example:
25 | "This should happen..."
26 | validations:
27 | required: true
28 |
29 | - type: textarea
30 | id: actual-behavior
31 | attributes:
32 | label: Actual behavior
33 | placeholder: |
34 | Example:
35 | "This happened instead..."
36 | validations:
37 | required: true
38 |
39 | - type: input
40 | id: Vibe_You-version
41 | attributes:
42 | label: Vibe You version
43 | description: |
44 | You can find your Vibe You version in **About**.
45 | placeholder: |
46 | Example: "0.5.0"
47 | validations:
48 | required: true
49 |
50 | - type: input
51 | id: android-version
52 | attributes:
53 | label: Android version
54 | description: |
55 | You can find this somewhere in your Android settings.
56 | placeholder: |
57 | Example: "Android 12"
58 | validations:
59 | required: true
60 |
61 | - type: textarea
62 | id: other-details
63 | attributes:
64 | label: Other details
65 | placeholder: |
66 | Additional details and attachments.
67 |
68 | - type: checkboxes
69 | id: acknowledgements
70 | attributes:
71 | label: Acknowledgements
72 | description: Your issue will be closed if you haven't done these steps.
73 | options:
74 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
75 | required: true
76 | - label: I have written a short but informative title.
77 | required: true
78 | - label: I will fill out all of the requested information in this form.
79 | required: true
80 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.yml:
--------------------------------------------------------------------------------
1 | name: ⭐ Feature request
2 | description: Suggest a feature to improve Vibe You
3 | labels: [enhancement]
4 | body:
5 |
6 | - type: textarea
7 | id: feature-description
8 | attributes:
9 | label: Describe your suggested feature
10 | description: How can an existing source be improved?
11 | placeholder: |
12 | Example:
13 | "It could work like this..."
14 | validations:
15 | required: true
16 |
17 | - type: textarea
18 | id: other-details
19 | attributes:
20 | label: Other details
21 | placeholder: |
22 | Additional details and attachments.
23 |
24 | - type: checkboxes
25 | id: acknowledgements
26 | attributes:
27 | label: Acknowledgements
28 | description: Your issue will be closed if you haven't done these steps.
29 | options:
30 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
31 | required: true
32 | - label: I have written a short but informative title.
33 | required: true
34 | - label: I will fill out all of the requested information in this form.
35 | required: true
36 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Create Debug Apk
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - "README*.md"
7 | - "app/src/main/res/**"
8 | - ".github/**"
9 | pull_request:
10 | paths-ignore:
11 | - "README*.md"
12 | - "app/src/main/res/**"
13 | - ".github/**"
14 |
15 | jobs:
16 | debug-builds:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@v4
21 |
22 | - name: Validate Gradle Wrapper
23 | uses: gradle/wrapper-validation-action@v2
24 |
25 | - name: Setup JDK
26 | uses: actions/setup-java@v4
27 | with:
28 | java-version: 17
29 | distribution: "temurin"
30 | cache: "gradle"
31 |
32 | - name: Build APK
33 | run: bash ./gradlew assembleDebug --stacktrace
34 |
35 | - name: Upload APK
36 | uses: actions/upload-artifact@v4
37 | with:
38 | name: app
39 | path: app/build/outputs/apk/debug/*.apk
40 |
--------------------------------------------------------------------------------
/.github/workflows/unsigned_release.yml:
--------------------------------------------------------------------------------
1 | name: Create Unsigned Release APK
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | debug-builds:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v4
12 |
13 | - name: Validate Gradle Wrapper
14 | uses: gradle/wrapper-validation-action@v2
15 |
16 | - name: Setup JDK
17 | uses: actions/setup-java@v4
18 | with:
19 | java-version: 17
20 | distribution: "temurin"
21 |
22 | - name: Build APK
23 | run: bash ./gradlew assembleRelease --stacktrace
24 |
25 | - name: Upload APK
26 | uses: actions/upload-artifact@v4
27 | with:
28 | name: app
29 | path: app/build/outputs/apk/release/*.apk
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | .idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | local.properties
11 |
--------------------------------------------------------------------------------
/README.de-DE.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
Vibe You
4 |
Vibe You (formerly MellowMusic) is a music app that lets you play music from your device storage and Piped Music.
5 |
It has a Material You dynamic theme with dark mode support, offline cached playback, shuffle, queue, and more.
6 |
7 |
8 |
9 |
10 |

11 |

12 |

13 |

14 |

15 |
16 |
17 |
18 | ---
19 |
20 |
21 | Screenshots
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | ## Features
38 |
39 | - **Material You dynamic theme with dark mode support:** Enjoy a sleek and modern interface that
40 | matches your device's theme.
41 | - **Play songs from your device storage:** Play your favorite songs, even when you're offline.
42 | - **Listen to online songs from Piped Music:** Access Piped Music's vast library of songs,
43 | without region blocks.
44 | - **Offline cached playback:** Listen to your music offline, even without an internet connection.
45 | - **Add songs to a favorite playlist:** Create playlists of your favorite songs and listen to them
46 | on demand.
47 | - **Shuffle songs:** Listen to your music in random order.
48 | - **Advanced queue management:** Add/Remove and reorder queue to personallize listening experience.
49 |
50 | ## Installation
51 |
52 | [
](https://github.com/you-apps/VibeYou/releases/latest)
55 |
56 |
57 | ## Feedback and contributions
58 | ***All contributions are very welcome!***
59 |
60 | * Feel free to join the [Matrix room](https://matrix.to/#/#you-apps:matrix.org) for discussions about the app.
61 | * Bug reports and feature requests can be submitted [here](https://github.com/you-apps/VibeYou/issues) (please make sure to fill out all the requested information properly!).
62 | * If you are a developer and wish to contribute to the app, please **fork** the project and submit a [**pull request**](https://help.github.com/articles/about-pull-requests/).
63 |
64 | ## Translation
65 |
66 |
67 |
68 |
69 | ## Credits
70 | * Icon design by [M00NJ](https://github.com/M00NJ)
71 |
72 | ## License
73 |
74 | Vibe You is licensed under the [**GNU General Public License**](https://www.gnu.org/licenses/gpl.html): You can use, study and share it as you want.
75 |
76 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
2 |
3 | -keepattributes RuntimeVisibleAnnotations,AnnotationDefault
4 |
5 | -dontwarn okhttp3.internal.platform.**
6 | -dontwarn org.conscrypt.**
7 | -dontwarn org.bouncycastle.**
8 | -dontwarn org.openjsse.**
--------------------------------------------------------------------------------
/app/schemas/app.suhasdissa.vibeyou.data.database.SongDatabase/1.json:
--------------------------------------------------------------------------------
1 | {
2 | "formatVersion": 1,
3 | "database": {
4 | "version": 1,
5 | "identityHash": "ada7c6c2bcd1884e04cc9a6260733689",
6 | "entities": [
7 | {
8 | "tableName": "song",
9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `artistsText` TEXT, `durationText` TEXT, `thumbnailUrl` TEXT, `likedAt` INTEGER, `totalPlayTimeMs` INTEGER NOT NULL, PRIMARY KEY(`id`))",
10 | "fields": [
11 | {
12 | "fieldPath": "id",
13 | "columnName": "id",
14 | "affinity": "TEXT",
15 | "notNull": true
16 | },
17 | {
18 | "fieldPath": "title",
19 | "columnName": "title",
20 | "affinity": "TEXT",
21 | "notNull": true
22 | },
23 | {
24 | "fieldPath": "artistsText",
25 | "columnName": "artistsText",
26 | "affinity": "TEXT",
27 | "notNull": false
28 | },
29 | {
30 | "fieldPath": "durationText",
31 | "columnName": "durationText",
32 | "affinity": "TEXT",
33 | "notNull": false
34 | },
35 | {
36 | "fieldPath": "thumbnailUrl",
37 | "columnName": "thumbnailUrl",
38 | "affinity": "TEXT",
39 | "notNull": false
40 | },
41 | {
42 | "fieldPath": "likedAt",
43 | "columnName": "likedAt",
44 | "affinity": "INTEGER",
45 | "notNull": false
46 | },
47 | {
48 | "fieldPath": "totalPlayTimeMs",
49 | "columnName": "totalPlayTimeMs",
50 | "affinity": "INTEGER",
51 | "notNull": true
52 | }
53 | ],
54 | "primaryKey": {
55 | "autoGenerate": false,
56 | "columnNames": [
57 | "id"
58 | ]
59 | },
60 | "indices": [],
61 | "foreignKeys": []
62 | },
63 | {
64 | "tableName": "SearchQuery",
65 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query` TEXT NOT NULL)",
66 | "fields": [
67 | {
68 | "fieldPath": "id",
69 | "columnName": "id",
70 | "affinity": "INTEGER",
71 | "notNull": true
72 | },
73 | {
74 | "fieldPath": "query",
75 | "columnName": "query",
76 | "affinity": "TEXT",
77 | "notNull": true
78 | }
79 | ],
80 | "primaryKey": {
81 | "autoGenerate": true,
82 | "columnNames": [
83 | "id"
84 | ]
85 | },
86 | "indices": [
87 | {
88 | "name": "index_SearchQuery_query",
89 | "unique": true,
90 | "columnNames": [
91 | "query"
92 | ],
93 | "orders": [],
94 | "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_SearchQuery_query` ON `${TABLE_NAME}` (`query`)"
95 | }
96 | ],
97 | "foreignKeys": []
98 | }
99 | ],
100 | "views": [],
101 | "setupQueries": [
102 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
103 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ada7c6c2bcd1884e04cc9a6260733689')"
104 | ]
105 | }
106 | }
--------------------------------------------------------------------------------
/app/src/debug/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Vibe You Debug
4 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/AppContainer.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou
2 |
3 | import android.content.ContentResolver
4 | import androidx.media3.session.MediaController
5 | import app.suhasdissa.vibeyou.backend.repository.AuthRepository
6 | import app.suhasdissa.vibeyou.backend.repository.AuthRepositoryImpl
7 | import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository
8 | import app.suhasdissa.vibeyou.backend.repository.PipedMusicRepository
9 | import app.suhasdissa.vibeyou.backend.repository.PlaylistRepository
10 | import app.suhasdissa.vibeyou.backend.repository.SongDatabaseRepository
11 | import app.suhasdissa.vibeyou.backend.repository.SongDatabaseRepositoryImpl
12 | import app.suhasdissa.vibeyou.data.database.SongDatabase
13 | import com.google.common.util.concurrent.ListenableFuture
14 |
15 | interface AppContainer {
16 | val database: SongDatabase
17 | val songDatabaseRepository: SongDatabaseRepository
18 | val pipedMusicRepository: PipedMusicRepository
19 | val localMusicRepository: LocalMusicRepository
20 | val playlistRepository: PlaylistRepository
21 | val authRepository: AuthRepository
22 | val controllerFuture: ListenableFuture
23 | val contentResolver: ContentResolver
24 | }
25 |
26 | class DefaultAppContainer(
27 | override val database: SongDatabase,
28 | override val controllerFuture: ListenableFuture,
29 | override val contentResolver: ContentResolver
30 | ) : AppContainer {
31 | override val songDatabaseRepository: SongDatabaseRepository by lazy {
32 | SongDatabaseRepositoryImpl(database.songsDao())
33 | }
34 | override val pipedMusicRepository: PipedMusicRepository by lazy {
35 | PipedMusicRepository(database.songsDao(), database.searchDao())
36 | }
37 | override val localMusicRepository: LocalMusicRepository by lazy {
38 | LocalMusicRepository(contentResolver, database.searchDao())
39 | }
40 | override val playlistRepository: PlaylistRepository by lazy {
41 | PlaylistRepository(database.playlistDao(), database.songsDao())
42 | }
43 | override val authRepository: AuthRepository by lazy {
44 | AuthRepositoryImpl()
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/MellowMusicApplication.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou
2 |
3 | import android.app.Application
4 | import android.content.ComponentName
5 | import android.graphics.Color
6 | import androidx.media3.session.MediaController
7 | import androidx.media3.session.SessionToken
8 | import app.suhasdissa.vibeyou.data.database.SongDatabase
9 | import app.suhasdissa.vibeyou.domain.models.primary.EqualizerData
10 | import app.suhasdissa.vibeyou.utils.Pref
11 | import app.suhasdissa.vibeyou.utils.UpdateUtil
12 | import app.suhasdissa.vibeyou.utils.preferences
13 | import app.suhasdissa.vibeyou.utils.services.PlayerService
14 | import coil.ImageLoader
15 | import coil.ImageLoaderFactory
16 | import coil.disk.DiskCache
17 |
18 | class MellowMusicApplication : Application(), ImageLoaderFactory {
19 |
20 | private val database by lazy { SongDatabase.getDatabase(this) }
21 | lateinit var container: AppContainer
22 | var accentColor: Int = Color.TRANSPARENT
23 |
24 | /**
25 | * Data stored here with the details of the equalizer
26 | */
27 | var supportedEqualizerData: EqualizerData? = null
28 |
29 | override fun onCreate() {
30 | super.onCreate()
31 | val sessionToken =
32 | SessionToken(
33 | this,
34 | ComponentName(this, PlayerService::class.java)
35 | )
36 | val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
37 | container = DefaultAppContainer(database, controllerFuture, contentResolver)
38 | Pref.sharedPreferences = preferences
39 | UpdateUtil.getCurrentVersion(this.applicationContext)
40 | }
41 |
42 | override fun newImageLoader(): ImageLoader {
43 | return ImageLoader.Builder(this)
44 | .crossfade(true)
45 | .respectCacheHeaders(false)
46 | .diskCache(
47 | DiskCache.Builder()
48 | .directory(cacheDir.resolve("image_cache"))
49 | .build()
50 | ).build()
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/data/api/HyperpipeApi.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.data.api
2 |
3 | import app.suhasdissa.vibeyou.backend.models.hyper.NextSongsResponse
4 | import retrofit2.http.GET
5 | import retrofit2.http.Path
6 |
7 | interface HyperpipeApi {
8 | @GET("https://{instance}/next/{videoId}")
9 | suspend fun getNext(
10 | @Path("instance") instance: String,
11 | @Path("videoId") videoId: String
12 | ): NextSongsResponse
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/data/database/SongDatabase.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.data.database
2 |
3 | import android.content.Context
4 | import androidx.room.AutoMigration
5 | import androidx.room.Database
6 | import androidx.room.Room
7 | import androidx.room.RoomDatabase
8 | import app.suhasdissa.vibeyou.data.database.dao.PlaylistDao
9 | import app.suhasdissa.vibeyou.data.database.dao.RawDao
10 | import app.suhasdissa.vibeyou.data.database.dao.SearchDao
11 | import app.suhasdissa.vibeyou.data.database.dao.SongsDao
12 | import app.suhasdissa.vibeyou.data.database.entities.PlaylistEntity
13 | import app.suhasdissa.vibeyou.data.database.entities.SearchQuery
14 | import app.suhasdissa.vibeyou.data.database.entities.SongEntity
15 | import app.suhasdissa.vibeyou.data.database.entities.SongPlaylistMap
16 |
17 | @Database(
18 | entities = [
19 | SongEntity::class,
20 | SearchQuery::class,
21 | PlaylistEntity::class,
22 | SongPlaylistMap::class
23 | ],
24 | version = 2,
25 | exportSchema = true,
26 | autoMigrations = [
27 | AutoMigration(from = 1, to = 2)
28 | ]
29 | )
30 | abstract class SongDatabase : RoomDatabase() {
31 |
32 | abstract fun songsDao(): SongsDao
33 | abstract fun playlistDao(): PlaylistDao
34 | abstract fun searchDao(): SearchDao
35 | abstract fun rawDao(): RawDao
36 |
37 | companion object {
38 | @Volatile
39 | private var INSTANCE: SongDatabase? = null
40 |
41 | fun getDatabase(context: Context): SongDatabase {
42 | return INSTANCE ?: synchronized(this) {
43 | val instance = Room.databaseBuilder(
44 | context.applicationContext,
45 | SongDatabase::class.java,
46 | "song_database"
47 | )
48 | .fallbackToDestructiveMigration()
49 | .allowMainThreadQueries().build()
50 | INSTANCE = instance
51 | instance
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/PlaylistDao.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.data.database.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Delete
5 | import androidx.room.Insert
6 | import androidx.room.OnConflictStrategy
7 | import androidx.room.Query
8 | import androidx.room.Transaction
9 | import app.suhasdissa.vibeyou.data.database.entities.PlaylistEntity
10 | import app.suhasdissa.vibeyou.data.database.entities.PlaylistWithSongs
11 | import app.suhasdissa.vibeyou.data.database.entities.SongPlaylistMap
12 | import kotlinx.coroutines.flow.Flow
13 |
14 | @Dao
15 | interface PlaylistDao {
16 | @Insert(entity = PlaylistEntity::class, onConflict = OnConflictStrategy.REPLACE)
17 | fun addPlaylist(playlist: PlaylistEntity)
18 |
19 | @Insert(entity = SongPlaylistMap::class, onConflict = OnConflictStrategy.REPLACE)
20 | fun addPlaylistMaps(maps: List)
21 |
22 | @Query("SELECT * from playlists")
23 | fun getAllPlaylists(): Flow>
24 |
25 | @Transaction
26 | @Query("SELECT * from playlists WHERE id=:id")
27 | fun getPlaylist(id: String): PlaylistWithSongs
28 |
29 | @Delete(entity = PlaylistEntity::class)
30 | fun removePlaylist(playlist: PlaylistEntity)
31 |
32 | @Query("DELETE FROM playlist_songs WHERE playlistId = :playlistId")
33 | fun clearPlaylist(playlistId: String)
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/RawDao.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.data.database.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.RawQuery
5 | import androidx.sqlite.db.SupportSQLiteQuery
6 |
7 | @Dao
8 | interface RawDao {
9 | @RawQuery
10 | fun raw(supportSQLiteQuery: SupportSQLiteQuery): Int
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/SearchDao.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.data.database.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.Query
7 | import app.suhasdissa.vibeyou.data.database.entities.SearchQuery
8 |
9 | @Dao
10 | interface SearchDao {
11 | @Insert(entity = SearchQuery::class, onConflict = OnConflictStrategy.REPLACE)
12 | fun addSearchQuery(query: SearchQuery)
13 |
14 | @Query("SELECT * from SearchQuery")
15 | fun getSearchHistory(): List
16 |
17 | @Query("DELETE from SearchQuery")
18 | fun deleteAll()
19 |
20 | @Query("DELETE from SearchQuery WHERE `query` = :query")
21 | fun deleteQuery(query: String)
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/SongsDao.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.data.database.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Delete
5 | import androidx.room.Insert
6 | import androidx.room.OnConflictStrategy
7 | import androidx.room.Query
8 | import app.suhasdissa.vibeyou.data.database.entities.SongEntity
9 | import kotlinx.coroutines.flow.Flow
10 |
11 | @Dao
12 | interface SongsDao {
13 | @Insert(entity = SongEntity::class, onConflict = OnConflictStrategy.REPLACE)
14 | fun addSong(song: SongEntity)
15 |
16 | @Insert(entity = SongEntity::class, onConflict = OnConflictStrategy.REPLACE)
17 | fun addSongs(songs: List)
18 |
19 | @Delete(entity = SongEntity::class)
20 | fun removeSong(song: SongEntity)
21 |
22 | @Delete(entity = SongEntity::class)
23 | fun removeSongs(song: List)
24 |
25 | @Query("SELECT * from song WHERE id=:id")
26 | fun getSongById(id: String): SongEntity?
27 |
28 | @Query("SELECT * from song")
29 | fun getAllSongs(): List
30 |
31 | @Query("SELECT * from song WHERE likedAt IS NOT NULL")
32 | fun getFavSongs(): List
33 |
34 | @Query("SELECT * from song ORDER BY title ASC")
35 | fun getAllSongsStream(): Flow>
36 |
37 | @Query("SELECT * from song WHERE likedAt IS NOT NULL ORDER BY title ASC")
38 | fun getFavSongsStream(): Flow>
39 |
40 | @Query("SELECT * from song ORDER BY id DESC LIMIT :limit")
41 | fun getRecentSongs(limit: Int): List
42 |
43 | @Query("SELECT * from song WHERE title LIKE :search OR artistsText LIKE :search")
44 | fun search(search: String): List
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/PlaylistEntity.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.data.database.entities
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 | import app.suhasdissa.vibeyou.domain.models.primary.Album
6 |
7 | @Entity(tableName = "playlists")
8 | data class PlaylistEntity(
9 | @PrimaryKey val id: String,
10 | val title: String,
11 | val type: Album.Type,
12 | val subTitle: String? = null,
13 | val thumbnailUrl: String? = null
14 | )
15 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/PlaylistWithSongs.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.data.database.entities
2 |
3 | import androidx.room.Embedded
4 | import androidx.room.Junction
5 | import androidx.room.Relation
6 |
7 | data class PlaylistWithSongs(
8 | @Embedded val playlist: PlaylistEntity,
9 | @Relation(
10 | entity = SongEntity::class,
11 | parentColumn = "id",
12 | entityColumn = "id",
13 | associateBy = Junction(
14 | value = SongPlaylistMap::class,
15 | parentColumn = "playlistId",
16 | entityColumn = "songId"
17 | )
18 | )
19 | val songs: List = listOf()
20 | )
21 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/SearchQuery.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.data.database.entities
2 |
3 | import androidx.room.Entity
4 | import androidx.room.Index
5 | import androidx.room.PrimaryKey
6 |
7 | @Entity(
8 | indices = [
9 | Index(
10 | value = ["query"],
11 | unique = true
12 | )
13 | ]
14 | )
15 | data class SearchQuery(
16 | @PrimaryKey(autoGenerate = true) val id: Long = 0,
17 | val query: String
18 | )
19 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/SongEntity.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.data.database.entities
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 |
7 | @Entity(tableName = "song")
8 | data class SongEntity(
9 | @PrimaryKey val id: String,
10 | val title: String,
11 | val artistsText: String? = null,
12 | val durationText: String?,
13 | val thumbnailUrl: String?,
14 | val likedAt: Long? = null,
15 | val totalPlayTimeMs: Long = 0,
16 | @ColumnInfo(defaultValue = "0") val isLocal: Boolean = false
17 | )
18 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/SongPlaylistMap.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.data.database.entities
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.ForeignKey
6 |
7 | @Entity(
8 | tableName = "playlist_songs",
9 | primaryKeys = ["playlistId", "songId"],
10 | foreignKeys = [
11 | ForeignKey(
12 | entity = SongEntity::class,
13 | parentColumns = ["id"],
14 | childColumns = ["songId"],
15 | onDelete = ForeignKey.CASCADE
16 | ),
17 | ForeignKey(
18 | entity = PlaylistEntity::class,
19 | parentColumns = ["id"],
20 | childColumns = ["playlistId"],
21 | onDelete = ForeignKey.CASCADE
22 | )
23 | ]
24 | )
25 | data class SongPlaylistMap(
26 | @ColumnInfo(index = true) val playlistId: String,
27 | @ColumnInfo(index = true) val songId: String
28 | )
29 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/Login.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Login(
8 | @SerialName("username") val username: String,
9 | @SerialName("password") val password: String
10 | )
11 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/PipedInstance.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class PipedInstance(
8 | val name: String,
9 | @SerialName("api_url") val apiUrl: String,
10 | val cache: Boolean = false,
11 | val cdn: Boolean = false,
12 | @SerialName("image_proxy_url") val imageProxyUrl: String = "",
13 | @SerialName("last_checked") val lastChecked: Int = 0,
14 | val locations: String = "",
15 | val registered: Int = 0,
16 | @SerialName("registration_disabled") val registrationDisabled: Boolean = false,
17 | @SerialName("s3_enabled") val s3Enabled: Boolean = false,
18 | @SerialName("up_to_date") val upToDate: Boolean = false,
19 | val version: String = ""
20 | ) {
21 | val netLoc = apiUrl.replace("https://", "")
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/PipedVIdeoResponse.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models
2 |
3 | import app.suhasdissa.vibeyou.backend.models.songs.SongItem
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class PipedSongResponse(
9 | @SerialName("title") var title: String? = null,
10 | @SerialName("description") var description: String? = null,
11 | @SerialName("uploader") var uploader: String? = null,
12 | @SerialName("thumbnailUrl") var thumbnailUrl: String? = null,
13 | @SerialName("hls") var hls: String? = null,
14 | @SerialName("duration") var duration: Int? = null,
15 | @SerialName("audioStreams") var audioStreams: ArrayList = arrayListOf(),
16 | @SerialName("relatedStreams") var relatedStreams: ArrayList = arrayListOf()
17 |
18 | )
19 |
20 | @Serializable
21 | data class AudioStreams(
22 |
23 | @SerialName("url") var url: String? = null
24 | /* @SerialName("format") var format: String? = null,
25 | @SerialName("quality") var quality: String? = null,
26 | @SerialName("mimeType") var mimeType: String? = null,
27 | @SerialName("codec") var codec: String? = null,
28 | @SerialName("audioTrackId") var audioTrackId: String? = null,
29 | @SerialName("audioTrackName") var audioTrackName: String? = null,
30 | @SerialName("videoOnly") var videoOnly: Boolean? = null,
31 | @SerialName("bitrate") var bitrate: Int? = null,
32 | @SerialName("initStart") var initStart: Int? = null,
33 | @SerialName("initEnd") var initEnd: Int? = null,
34 | @SerialName("indexStart") var indexStart: Int? = null,
35 | @SerialName("indexEnd") var indexEnd: Int? = null,
36 | @SerialName("width") var width: Int? = null,
37 | @SerialName("height") var height: Int? = null,
38 | @SerialName("fps") var fps: Int? = null*/
39 |
40 | )
41 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/PlayerRepeatMode.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models
2 |
3 | import androidx.media3.common.Player
4 |
5 | /*
6 | DO not change order
7 | int REPEAT_MODE_OFF = 0;
8 | int REPEAT_MODE_ONE = 1;
9 | int REPEAT_MODE_ALL = 2;
10 | */
11 | enum class PlayerRepeatMode(val mode: Int) {
12 | OFF(Player.REPEAT_MODE_OFF),
13 | ONE(Player.REPEAT_MODE_ONE),
14 | ALL(Player.REPEAT_MODE_ALL)
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/PlayerState.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models
2 |
3 | enum class PlayerState {
4 | Buffer, Play, Pause
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/SearchFilter.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models
2 |
3 | enum class SearchFilter(val value: String) {
4 | Songs("music_songs"),
5 | Videos("music_videos"),
6 | Albums("music_albums"),
7 | Playlists("music_playlists"),
8 | Artists("music_artists")
9 | }
10 |
11 | enum class LocalSearchFilter {
12 | Songs,
13 | Albums,
14 | Artists
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/Token.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Token(
8 | @SerialName("token") val token: String? = null,
9 | @SerialName("error") val error: String? = null
10 | )
11 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/artists/Artist.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models.artists
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Artist(
8 | @SerialName("url") var url: String,
9 | @SerialName("name") var name: String,
10 | @SerialName("thumbnail") var thumbnail: String? = null,
11 | @SerialName("description") var description: String? = null
12 | ) {
13 | val artistId
14 | get() = url.replace("/channel/", "")
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/artists/Artists.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models.artists
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Artists(
8 | @SerialName("items") var items: ArrayList = arrayListOf()
9 | )
10 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/artists/Channel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models.artists
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Channel(
7 | val id: String? = null,
8 | val name: String? = null,
9 | val avatarUrl: String? = null,
10 | val bannerUrl: String? = null,
11 | val description: String? = null,
12 | val tabs: List = emptyList()
13 | )
14 |
15 | @Serializable
16 | data class ChannelTab(
17 | val name: String,
18 | val data: String
19 | )
20 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/artists/ChannelTabResponse.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models.artists
2 |
3 | import app.suhasdissa.vibeyou.backend.models.playlists.Playlist
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class ChannelTabResponse(
9 | @SerialName("content") var content: List = emptyList()
10 | )
11 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/MediaSession.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models.hyper
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class MediaSession(
7 | val album: String,
8 | val thumbnails: List
9 | )
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/NextSongsResponse.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models.hyper
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class NextSongsResponse(
7 | val lyricsId: String,
8 | val mediaSession: MediaSession,
9 | val songs: List
10 | )
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/Song.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models.hyper
2 |
3 | import androidx.core.net.toUri
4 | import app.suhasdissa.vibeyou.data.database.entities.SongEntity
5 | import app.suhasdissa.vibeyou.domain.models.primary.Song
6 | import kotlinx.serialization.Serializable
7 |
8 | @Serializable
9 | data class Song(
10 | val id: String,
11 | val subtitle: String,
12 | val thumbnails: List,
13 | val title: String
14 | ) {
15 | val asSong: Song
16 | get() {
17 | return Song(
18 | id = id,
19 | title = title,
20 | artistsText = subtitle,
21 | durationText = null,
22 | thumbnailUri = thumbnails.maxByOrNull { it.height }?.url?.toUri()
23 | )
24 | }
25 |
26 | val asSongEntity: SongEntity
27 | get() {
28 | return SongEntity(
29 | id = id,
30 | title = title,
31 | artistsText = subtitle,
32 | durationText = null,
33 | thumbnailUrl = thumbnails.maxByOrNull { it.height }?.url
34 | )
35 | }
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/Thumbnail.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models.hyper
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Thumbnail(
7 | val height: Int,
8 | val url: String,
9 | val width: Int
10 | )
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/playlists/Playlist.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models.playlists
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Playlist(
8 | @SerialName("url") var url: String = "",
9 | @SerialName("type") var type: String = "",
10 | @SerialName("name") var name: String = "",
11 | @SerialName("thumbnail") var thumbnail: String = "",
12 | @SerialName("uploaderName") var uploaderName: String = ""
13 | ) {
14 | val playlistId
15 | get() = url.replace("/playlist?list=", "")
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/playlists/PlaylistInfo.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models.playlists
2 |
3 | import app.suhasdissa.vibeyou.backend.models.songs.SongItem
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class PlaylistInfo(
9 | @SerialName("name") var name: String = "",
10 | @SerialName("thumbnailUrl") var thumbnailUrl: String? = null,
11 | @SerialName("relatedStreams") var relatedStreams: ArrayList = arrayListOf()
12 | )
13 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/playlists/Playlists.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models.playlists
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Playlists(
8 | @SerialName("items") var items: ArrayList = arrayListOf()
9 | )
10 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/Album.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.domain.models.primary
2 |
3 | import android.net.Uri
4 | import android.os.Parcelable
5 | import app.suhasdissa.vibeyou.utils.UriSerializer
6 | import kotlinx.parcelize.Parcelize
7 | import kotlinx.serialization.Serializable
8 |
9 | @Serializable
10 | @Parcelize
11 | data class Album(
12 | val id: String,
13 | val title: String,
14 | @Serializable(with = UriSerializer::class)
15 | val thumbnailUri: Uri? = null,
16 | val artistsText: String,
17 | val numberOfSongs: Int? = null,
18 | val isLocal: Boolean = false,
19 | val type: Type = Type.ALBUM
20 | ) : Parcelable {
21 | enum class Type {
22 | PLAYLIST, ALBUM
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/Artist.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.domain.models.primary
2 |
3 | import android.net.Uri
4 | import android.os.Parcelable
5 | import app.suhasdissa.vibeyou.utils.UriSerializer
6 | import kotlinx.parcelize.Parcelize
7 | import kotlinx.serialization.Serializable
8 |
9 | @Parcelize
10 | @Serializable
11 | data class Artist(
12 | val id: String,
13 | val artistsText: String,
14 | @Serializable(with = UriSerializer::class)
15 | val thumbnailUri: Uri? = null,
16 | val description: String? = null,
17 | val numberOfTracks: Int? = null,
18 | val numberOfAlbums: Int? = null
19 | ) : Parcelable
20 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/EqualizerData.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.domain.models.primary
2 |
3 | data class EqualizerBand(
4 | val frequency: Int,
5 | val minLevel: Short,
6 | val maxLevel: Short,
7 | )
8 |
9 | data class EqualizerData(
10 | val presets: List,
11 | val bands: List
12 | )
13 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/Song.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.domain.models.primary
2 |
3 | import android.net.Uri
4 |
5 | data class Song(
6 | val id: String,
7 | val title: String,
8 | val artistsText: String? = null,
9 | val durationText: String?,
10 | val likedAt: Long? = null,
11 | val thumbnailUri: Uri? = null,
12 | val albumId: Long? = null,
13 | val artistId: Long? = null,
14 | val isLocal: Boolean = false,
15 | val creationDate: Long? = null,
16 | val dateAdded: Long? = null,
17 | val trackNumber: Long? = null
18 | ) {
19 | fun toggleLike(): Song {
20 | return copy(
21 | likedAt = if (likedAt == null) System.currentTimeMillis() else null
22 | )
23 | }
24 |
25 | val isFavourite
26 | get() = likedAt != null
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/songs/SongItem.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models.songs
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class SongItem(
8 | @SerialName("url") var url: String = "",
9 | @SerialName("title") var title: String = "",
10 | @SerialName("thumbnail") var thumbnail: String = "",
11 | @SerialName("uploaderName") var uploaderName: String = "",
12 | @SerialName("duration") var duration: Int = 0
13 | ) {
14 | val videoId
15 | get() = url.replace("/watch?v=", "")
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/models/songs/Songs.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.models.songs
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Songs(
8 | @SerialName("items") var items: ArrayList = arrayListOf()
9 | )
10 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/AuthRepository.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.repository
2 |
3 | import app.suhasdissa.vibeyou.backend.models.Login
4 | import app.suhasdissa.vibeyou.backend.models.Token
5 | import app.suhasdissa.vibeyou.utils.RetrofitHelper
6 |
7 | interface AuthRepository {
8 | suspend fun getAuthToken(login: Login): Token
9 | }
10 |
11 | class AuthRepositoryImpl : AuthRepository {
12 | private val pipedApi = RetrofitHelper.createPipedApi()
13 | override suspend fun getAuthToken(login: Login): Token = pipedApi.login(login)
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/PlaylistRepository.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.repository
2 |
3 | import app.suhasdissa.vibeyou.data.database.dao.PlaylistDao
4 | import app.suhasdissa.vibeyou.data.database.dao.SongsDao
5 | import app.suhasdissa.vibeyou.data.database.entities.PlaylistEntity
6 | import app.suhasdissa.vibeyou.data.database.entities.PlaylistWithSongs
7 | import app.suhasdissa.vibeyou.data.database.entities.SongPlaylistMap
8 | import app.suhasdissa.vibeyou.domain.models.primary.Album
9 | import app.suhasdissa.vibeyou.domain.models.primary.Song
10 | import app.suhasdissa.vibeyou.utils.asPlaylistEntity
11 | import app.suhasdissa.vibeyou.utils.asSongEntity
12 | import kotlinx.coroutines.Dispatchers
13 | import kotlinx.coroutines.flow.Flow
14 | import kotlinx.coroutines.withContext
15 |
16 | class PlaylistRepository(private val playlistDao: PlaylistDao, private val songsDao: SongsDao) {
17 | private fun createNew(album: Album) {
18 | playlistDao.addPlaylist(
19 | album.asPlaylistEntity
20 | )
21 | }
22 |
23 | private fun addSongsToPlaylist(playlistId: String, songs: List) {
24 | songsDao.addSongs(songs.map { it.asSongEntity })
25 | val songMap = songs.map { SongPlaylistMap(playlistId, it.id) }
26 | playlistDao.addPlaylistMaps(songMap)
27 | }
28 |
29 | suspend fun newPlaylistWithSongs(album: Album, songs: List) =
30 | withContext(Dispatchers.IO) {
31 | createNew(album)
32 | addSongsToPlaylist(album.id, songs)
33 | }
34 |
35 | fun getPlaylists(): Flow> = playlistDao.getAllPlaylists()
36 |
37 | suspend fun getPlaylist(id: String): PlaylistWithSongs =
38 | withContext(Dispatchers.IO) { return@withContext playlistDao.getPlaylist(id) }
39 |
40 | suspend fun deletePlaylist(album: Album) =
41 | withContext(Dispatchers.IO) { playlistDao.removePlaylist(album.asPlaylistEntity) }
42 |
43 | suspend fun clearPlaylist(album: Album) = withContext(Dispatchers.IO) {
44 | playlistDao.clearPlaylist(album.id)
45 | }
46 |
47 | suspend fun deletePlaylistAndSongs(album: Album) = withContext(Dispatchers.IO) {
48 | val songs = playlistDao.getPlaylist(album.id).songs
49 | playlistDao.removePlaylist(album.asPlaylistEntity)
50 | songsDao.removeSongs(songs)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/SongDatabaseRepository.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.repository
2 |
3 | import app.suhasdissa.vibeyou.data.database.entities.SongEntity
4 | import app.suhasdissa.vibeyou.domain.models.primary.Song
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface SongDatabaseRepository {
8 | suspend fun addSong(song: Song)
9 | suspend fun addSongs(songs: List)
10 | suspend fun removeSong(song: Song)
11 | suspend fun getSongById(id: String): Song?
12 | fun getAllSongsStream(): Flow>
13 | fun getFavSongsStream(): Flow>
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/SongDatabaseRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.repository
2 |
3 | import app.suhasdissa.vibeyou.data.database.dao.SongsDao
4 | import app.suhasdissa.vibeyou.data.database.entities.SongEntity
5 | import app.suhasdissa.vibeyou.domain.models.primary.Song
6 | import app.suhasdissa.vibeyou.utils.asSong
7 | import app.suhasdissa.vibeyou.utils.asSongEntity
8 | import kotlinx.coroutines.flow.Flow
9 |
10 | class SongDatabaseRepositoryImpl(private val songsDao: SongsDao) :
11 | SongDatabaseRepository {
12 | override suspend fun addSong(song: Song) = songsDao.addSong(song.asSongEntity)
13 | override suspend fun addSongs(songs: List) =
14 | songsDao.addSongs(songs.map { it.asSongEntity })
15 |
16 | override suspend fun getSongById(id: String): Song? = songsDao.getSongById(id)?.asSong
17 | override fun getAllSongsStream(): Flow> = songsDao.getAllSongsStream()
18 | override fun getFavSongsStream(): Flow> = songsDao.getFavSongsStream()
19 | override suspend fun removeSong(song: Song) = songsDao.removeSong(song.asSongEntity)
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/navigation/CustomNavTypes.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.navigation
2 |
3 | import android.os.Bundle
4 | import androidx.navigation.NavType
5 | import app.suhasdissa.vibeyou.domain.models.primary.Album
6 | import app.suhasdissa.vibeyou.domain.models.primary.Artist
7 | import kotlinx.serialization.json.Json
8 | import java.net.URLDecoder
9 | import java.net.URLEncoder
10 |
11 | val AlbumType = object : NavType(
12 | isNullableAllowed = false
13 | ) {
14 | override fun put(bundle: Bundle, key: String, value: Album) {
15 | bundle.putParcelable(key, value)
16 | }
17 |
18 | override fun get(bundle: Bundle, key: String): Album? {
19 | return bundle.getParcelable(key)
20 | }
21 |
22 | override fun parseValue(value: String): Album {
23 | return Json.decodeFromString(URLDecoder.decode(value, "UTF-8"))
24 | }
25 |
26 | override fun serializeAsValue(value: Album): String {
27 | return URLEncoder.encode(Json.encodeToString(Album.serializer(), value), "UTF-8")
28 | }
29 | }
30 |
31 | val ArtistType = object : NavType(
32 | isNullableAllowed = false
33 | ) {
34 | override fun put(bundle: Bundle, key: String, value: Artist) {
35 | bundle.putParcelable(key, value)
36 | }
37 |
38 | override fun get(bundle: Bundle, key: String): Artist? {
39 | return bundle.getParcelable(key)
40 | }
41 |
42 | override fun parseValue(value: String): Artist {
43 | return Json.decodeFromString(URLDecoder.decode(value, "UTF-8"))
44 | }
45 |
46 | override fun serializeAsValue(value: Artist): String {
47 | return URLEncoder.encode(Json.encodeToString(Artist.serializer(), value), "UTF-8")
48 | }
49 |
50 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/navigation/Destination.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.navigation
2 |
3 | import app.suhasdissa.vibeyou.domain.models.primary.Album
4 | import app.suhasdissa.vibeyou.domain.models.primary.Artist
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | sealed class Destination {
9 | @Serializable
10 | object OnlineMusic : Destination()
11 |
12 | @Serializable
13 | object LocalMusic : Destination()
14 |
15 | @Serializable
16 | object OnlineSearch : Destination()
17 |
18 | @Serializable
19 | object LocalSearch : Destination()
20 |
21 | @Serializable
22 | object Settings : Destination()
23 |
24 | @Serializable
25 | object About : Destination()
26 |
27 | @Serializable
28 | object NetworkSettings : Destination()
29 |
30 | @Serializable
31 | object DatabaseSettings : Destination()
32 |
33 | @Serializable
34 | object AppearanceSettings : Destination()
35 |
36 | @Serializable
37 | data class Playlists(val album: Album) : Destination()
38 |
39 | @Serializable
40 | data class LocalPlaylists(val album: Album) : Destination()
41 |
42 | @Serializable
43 | data class SavedPlaylists(val album: Album) : Destination()
44 |
45 | @Serializable
46 | data class OnlineArtist(val artist: Artist) : Destination()
47 |
48 | @Serializable
49 | data class LocalArtist(val artist: Artist) : Destination()
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/ChipSelect.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.components
2 |
3 | import android.view.SoundEffectConstants
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.PaddingValues
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.lazy.LazyRow
8 | import androidx.compose.foundation.lazy.items
9 | import androidx.compose.material3.ExperimentalMaterial3Api
10 | import androidx.compose.material3.FilterChip
11 | import androidx.compose.material3.Text
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.platform.LocalView
19 | import androidx.compose.ui.unit.dp
20 |
21 | @OptIn(ExperimentalMaterial3Api::class)
22 | @Composable
23 | inline fun > ChipSelector(
24 | crossinline onItemSelected: (T) -> Unit,
25 | defaultValue: T
26 | ) {
27 | val options = enumValues()
28 | var selectedOption by remember { mutableStateOf(defaultValue) }
29 | val view = LocalView.current
30 | LazyRow(
31 | modifier = Modifier.fillMaxWidth(),
32 | horizontalArrangement = Arrangement.spacedBy(8.dp),
33 | contentPadding = PaddingValues(horizontal = 8.dp)
34 | ) {
35 | items(items = options) {
36 | FilterChip(selected = (selectedOption == it), onClick = {
37 | view.playSoundEffect(SoundEffectConstants.CLICK)
38 | selectedOption = it
39 | onItemSelected(it)
40 | }, label = { Text(it.name.replace("_", " ")) })
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/IconCard.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.components
2 |
3 | import androidx.annotation.StringRes
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.aspectRatio
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.material3.ElevatedCard
9 | import androidx.compose.material3.ExperimentalMaterial3Api
10 | import androidx.compose.material3.Icon
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.vector.ImageVector
15 | import androidx.compose.ui.res.stringResource
16 | import androidx.compose.ui.unit.dp
17 |
18 | @OptIn(ExperimentalMaterial3Api::class)
19 | @Composable
20 | fun IconCard(onClick: () -> Unit, icon: ImageVector, @StringRes name: Int) {
21 | ElevatedCard(
22 | onClick,
23 | Modifier
24 | .size(150.dp)
25 | .aspectRatio(1f)
26 | ) {
27 | Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
28 | Icon(
29 | modifier = Modifier.size(64.dp),
30 | imageVector = icon,
31 | contentDescription = stringResource(name)
32 | )
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/IllustratedMessageScreen.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.components
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.annotation.StringRes
5 | import androidx.compose.foundation.Image
6 | import androidx.compose.foundation.layout.Arrangement
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.Column
9 | import androidx.compose.foundation.layout.fillMaxSize
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.height
12 | import androidx.compose.foundation.layout.size
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.material3.Text
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.ui.Alignment
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.draw.alpha
19 | import androidx.compose.ui.graphics.Color
20 | import androidx.compose.ui.graphics.ColorFilter
21 | import androidx.compose.ui.res.painterResource
22 | import androidx.compose.ui.res.stringResource
23 | import androidx.compose.ui.tooling.preview.Preview
24 | import androidx.compose.ui.unit.dp
25 | import app.suhasdissa.vibeyou.R
26 |
27 | @Composable
28 | fun IllustratedMessageScreen(
29 | @DrawableRes image: Int,
30 | @StringRes message: Int? = null,
31 | messageColor: Color = MaterialTheme.colorScheme.error,
32 | action: @Composable () -> Unit = {}
33 | ) {
34 | Column(
35 | Modifier.fillMaxSize(),
36 | horizontalAlignment = Alignment.CenterHorizontally,
37 | verticalArrangement = Arrangement.Center
38 | ) {
39 | message?.let {
40 | Text(
41 | stringResource(message),
42 | style = MaterialTheme.typography.displaySmall,
43 | color = messageColor,
44 | modifier = Modifier.alpha(0.5f)
45 | )
46 | }
47 | Box(
48 | contentAlignment = Alignment.Center,
49 | modifier = Modifier
50 | .fillMaxWidth()
51 | .height(350.dp)
52 | .alpha(0.3f)
53 | ) {
54 | Image(
55 | modifier = Modifier.size(350.dp),
56 | painter = painterResource(id = R.drawable.blob),
57 | contentDescription = null,
58 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.secondaryContainer)
59 | )
60 | Image(
61 | modifier = Modifier.size(250.dp),
62 | painter = painterResource(id = image),
63 | contentDescription = null,
64 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSecondaryContainer)
65 | )
66 | }
67 | action()
68 | }
69 | }
70 |
71 | @Composable
72 | @Preview(showBackground = true)
73 | private fun IllustratedMsgScreenPreview() {
74 | IllustratedMessageScreen(
75 | image = R.drawable.ic_launcher_monochrome,
76 | message = R.string.something_went_wrong
77 | )
78 | }
79 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/LoadingScreen.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.components
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.material3.CircularProgressIndicator
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 |
10 | @Composable
11 | fun LoadingScreen() {
12 | Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
13 | CircularProgressIndicator()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/SongList.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.PaddingValues
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.lazy.LazyColumn
7 | import androidx.compose.foundation.lazy.items
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.unit.dp
12 | import app.suhasdissa.vibeyou.R
13 | import app.suhasdissa.vibeyou.domain.models.primary.Song
14 |
15 | @Composable
16 | fun SongList(
17 | items: List,
18 | header: @Composable (() -> Unit)? = null,
19 | onClickCard: (song: Song) -> Unit,
20 | onLongPress: (song: Song) -> Unit
21 | ) {
22 | if (items.isEmpty()) {
23 | IllustratedMessageScreen(image = R.drawable.ic_launcher_monochrome)
24 | }
25 | LazyColumn(
26 | Modifier.fillMaxSize(),
27 | horizontalAlignment = Alignment.CenterHorizontally,
28 | verticalArrangement = Arrangement.spacedBy(8.dp),
29 | contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp)
30 | ) {
31 | if (header != null) {
32 | item { header() }
33 | }
34 | items(items = items) { item ->
35 | SongCard(
36 | song = item,
37 | onClickCard = {
38 | onClickCard(item)
39 | },
40 | onLongPress = {
41 | onLongPress(item)
42 | }
43 | )
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/VerticalSlider.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.components
2 |
3 | import androidx.compose.foundation.interaction.MutableInteractionSource
4 | import androidx.compose.material3.Slider
5 | import androidx.compose.material3.SliderColors
6 | import androidx.compose.material3.SliderDefaults
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.remember
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.TransformOrigin
11 | import androidx.compose.ui.graphics.graphicsLayer
12 | import androidx.compose.ui.layout.layout
13 | import androidx.compose.ui.unit.Constraints
14 |
15 | @Composable
16 | fun VerticalSlider(
17 | value: Float,
18 | onValueChange: (Float) -> Unit,
19 | modifier: Modifier = Modifier,
20 | enabled: Boolean = true,
21 | valueRange: ClosedFloatingPointRange = 0f..1f,
22 | steps: Int = 0,
23 | onValueChangeFinished: (() -> Unit)? = null,
24 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
25 | colors: SliderColors = SliderDefaults.colors()
26 | ) {
27 | Slider(
28 | colors = colors,
29 | interactionSource = interactionSource,
30 | onValueChangeFinished = onValueChangeFinished,
31 | steps = steps,
32 | valueRange = valueRange,
33 | enabled = enabled,
34 | value = value,
35 | onValueChange = onValueChange,
36 | modifier = Modifier
37 | .graphicsLayer {
38 | rotationZ = 270f
39 | transformOrigin = TransformOrigin(0f, 0f)
40 | }
41 | .layout { measurable, constraints ->
42 | val placeable = measurable.measure(
43 | Constraints(
44 | minWidth = constraints.minHeight,
45 | maxWidth = constraints.maxHeight,
46 | minHeight = constraints.minWidth,
47 | maxHeight = constraints.maxHeight,
48 | )
49 | )
50 | layout(placeable.height, placeable.width) {
51 | placeable.place(-placeable.width, 0)
52 | }
53 | }
54 | .then(modifier)
55 | )
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/MainAppContent.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.layout
2 |
3 | import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
4 | import androidx.compose.runtime.Composable
5 | import androidx.navigation.compose.rememberNavController
6 | import androidx.window.core.layout.WindowHeightSizeClass
7 | import androidx.window.core.layout.WindowSizeClass
8 | import androidx.window.core.layout.WindowWidthSizeClass
9 | import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel
10 | import app.suhasdissa.vibeyou.presentation.screens.settings.model.SettingsModel
11 |
12 | @Composable
13 | fun MainAppContent(
14 | playerViewModel: PlayerViewModel,
15 | settingsModel: SettingsModel,
16 | windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
17 | ) {
18 | val navHostController = rememberNavController()
19 | if (windowSizeClass.windowHeightSizeClass != WindowHeightSizeClass.COMPACT) {
20 | when (windowSizeClass.windowWidthSizeClass) {
21 | WindowWidthSizeClass.COMPACT -> {
22 | ModelNavDrawerLayout(
23 | navHostController,
24 | playerViewModel,
25 | settingsModel
26 | )
27 | }
28 |
29 | WindowWidthSizeClass.MEDIUM -> {
30 | PermanentNavDrawerLayout(
31 | navHostController,
32 | playerViewModel,
33 | settingsModel
34 | )
35 | }
36 |
37 | WindowWidthSizeClass.EXPANDED -> {
38 | PermanentNavDrawerWithPlayerLayout(
39 | navHostController,
40 | playerViewModel,
41 | settingsModel
42 | )
43 | }
44 | }
45 | } else {
46 | PermanentNavDrawerLayout(
47 | navHostController,
48 | playerViewModel,
49 | settingsModel,
50 | horizontalPlayer = true
51 | )
52 | }
53 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/ModelNavDrawerLayout.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.layout
2 |
3 | import androidx.compose.foundation.layout.consumeWindowInsets
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material3.DrawerValue
7 | import androidx.compose.material3.ModalNavigationDrawer
8 | import androidx.compose.material3.rememberDrawerState
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.DisposableEffect
11 | import androidx.compose.runtime.getValue
12 | import androidx.compose.runtime.mutableStateOf
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.runtime.rememberCoroutineScope
15 | import androidx.compose.runtime.setValue
16 | import androidx.compose.ui.Modifier
17 | import androidx.navigation.NavController
18 | import androidx.navigation.NavDestination.Companion.hasRoute
19 | import androidx.navigation.NavHostController
20 | import app.suhasdissa.vibeyou.navigation.AppNavHost
21 | import app.suhasdissa.vibeyou.navigation.Destination
22 | import app.suhasdissa.vibeyou.presentation.components.MiniPlayerScaffold
23 | import app.suhasdissa.vibeyou.presentation.components.ModalNavDrawerContent
24 | import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel
25 | import app.suhasdissa.vibeyou.presentation.screens.settings.model.SettingsModel
26 | import kotlinx.coroutines.launch
27 |
28 | @Composable
29 | internal fun ModelNavDrawerLayout(
30 | navHostController: NavHostController,
31 | playerViewModel: PlayerViewModel,
32 | settingsModel: SettingsModel
33 | ) {
34 | val drawerState = rememberDrawerState(DrawerValue.Closed)
35 | val scope = rememberCoroutineScope()
36 | var currentDestination by remember {
37 | mutableStateOf(Destination.LocalMusic)
38 | }
39 |
40 | DisposableEffect(Unit) {
41 | val listener = NavController.OnDestinationChangedListener { _, destination, _ ->
42 | listOf(
43 | Destination.LocalMusic,
44 | Destination.OnlineMusic,
45 | Destination.Settings
46 | ).firstOrNull { destination.hasRoute(it::class) }
47 | ?.let { currentDestination = it }
48 | }
49 | navHostController.addOnDestinationChangedListener(listener)
50 |
51 | onDispose {
52 | navHostController.removeOnDestinationChangedListener(listener)
53 | }
54 | }
55 |
56 | ModalNavigationDrawer(
57 | drawerState = drawerState,
58 | gesturesEnabled = drawerState.isOpen,
59 | drawerContent = {
60 | ModalNavDrawerContent(
61 | currentDestination = currentDestination,
62 | onDestinationSelected = {
63 | scope.launch {
64 | drawerState.close()
65 | }
66 | navHostController.popBackStack()
67 | navHostController.navigate(it)
68 | }
69 | )
70 | }
71 | ) {
72 | MiniPlayerScaffold(playerViewModel) { pV ->
73 | AppNavHost(
74 | modifier = Modifier
75 | .fillMaxSize()
76 | .consumeWindowInsets(pV)
77 | .padding(pV),
78 | navHostController = navHostController,
79 | onDrawerOpen = {
80 | scope.launch {
81 | drawerState.open()
82 | }
83 | },
84 | playerViewModel,
85 | settingsModel
86 | )
87 | }
88 |
89 | }
90 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/PermanentNavDrawerLayout.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.layout
2 |
3 | import androidx.compose.foundation.layout.consumeWindowInsets
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material3.PermanentNavigationDrawer
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.DisposableEffect
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.mutableStateOf
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.runtime.setValue
13 | import androidx.compose.ui.Modifier
14 | import androidx.navigation.NavController
15 | import androidx.navigation.NavDestination.Companion.hasRoute
16 | import androidx.navigation.NavHostController
17 | import app.suhasdissa.vibeyou.navigation.AppNavHost
18 | import app.suhasdissa.vibeyou.navigation.Destination
19 | import app.suhasdissa.vibeyou.presentation.components.MiniPlayerScaffold
20 | import app.suhasdissa.vibeyou.presentation.components.PermanentNavDrawerContent
21 | import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel
22 | import app.suhasdissa.vibeyou.presentation.screens.settings.model.SettingsModel
23 |
24 | @Composable
25 | internal fun PermanentNavDrawerLayout(
26 | navHostController: NavHostController,
27 | playerViewModel: PlayerViewModel,
28 | settingsModel: SettingsModel,
29 | horizontalPlayer: Boolean = false
30 | ) {
31 | var currentDestination by remember {
32 | mutableStateOf(Destination.LocalMusic)
33 | }
34 |
35 | DisposableEffect(Unit) {
36 | val listener = NavController.OnDestinationChangedListener { _, destination, _ ->
37 | listOf(
38 | Destination.LocalMusic,
39 | Destination.OnlineMusic,
40 | Destination.Settings
41 | ).firstOrNull { destination.hasRoute(it::class) }
42 | ?.let { currentDestination = it }
43 | }
44 | navHostController.addOnDestinationChangedListener(listener)
45 |
46 | onDispose {
47 | navHostController.removeOnDestinationChangedListener(listener)
48 | }
49 | }
50 |
51 | PermanentNavigationDrawer(drawerContent = {
52 | PermanentNavDrawerContent(
53 | currentDestination = currentDestination,
54 | onDestinationSelected = {
55 | currentDestination = it
56 | navHostController.popBackStack()
57 | navHostController.navigate(it)
58 | }
59 | )
60 | }) {
61 | MiniPlayerScaffold(playerViewModel, horizontalPlayer = horizontalPlayer) { pV ->
62 | AppNavHost(
63 | modifier = Modifier
64 | .fillMaxSize()
65 | .consumeWindowInsets(pV)
66 | .padding(pV),
67 | navHostController = navHostController,
68 | onDrawerOpen = null,
69 | playerViewModel,
70 | settingsModel
71 | )
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/components/AlbumCard.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.album.components
2 |
3 | import android.view.SoundEffectConstants
4 | import androidx.compose.foundation.ExperimentalFoundationApi
5 | import androidx.compose.foundation.combinedClickable
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.Spacer
8 | import androidx.compose.foundation.layout.aspectRatio
9 | import androidx.compose.foundation.layout.fillMaxWidth
10 | import androidx.compose.foundation.layout.height
11 | import androidx.compose.foundation.layout.size
12 | import androidx.compose.foundation.shape.RoundedCornerShape
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.material3.Text
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.ui.Alignment
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.draw.clip
19 | import androidx.compose.ui.hapticfeedback.HapticFeedbackType
20 | import androidx.compose.ui.layout.ContentScale
21 | import androidx.compose.ui.platform.LocalHapticFeedback
22 | import androidx.compose.ui.platform.LocalView
23 | import androidx.compose.ui.res.painterResource
24 | import androidx.compose.ui.res.stringResource
25 | import androidx.compose.ui.text.style.TextAlign
26 | import androidx.compose.ui.text.style.TextOverflow
27 | import androidx.compose.ui.unit.dp
28 | import app.suhasdissa.vibeyou.R
29 | import app.suhasdissa.vibeyou.domain.models.primary.Album
30 | import coil.compose.AsyncImage
31 |
32 | @OptIn(ExperimentalFoundationApi::class)
33 | @Composable
34 | fun AlbumCard(
35 | album: Album,
36 | onClickCard: () -> Unit,
37 | onLongPress: () -> Unit
38 | ) {
39 | val view = LocalView.current
40 | val haptic = LocalHapticFeedback.current
41 | Column(
42 | Modifier
43 | .fillMaxWidth()
44 | .combinedClickable(
45 | onClick = {
46 | view.playSoundEffect(SoundEffectConstants.CLICK)
47 | onClickCard()
48 | },
49 | onLongClick = {
50 | haptic.performHapticFeedback(HapticFeedbackType.LongPress)
51 | onLongPress()
52 | }
53 | ),
54 | horizontalAlignment = Alignment.CenterHorizontally
55 | ) {
56 | AsyncImage(
57 | modifier = Modifier
58 | .size(148.dp)
59 | .aspectRatio(1f)
60 | .clip(RoundedCornerShape(16.dp)),
61 | model = album.thumbnailUri,
62 | contentDescription = stringResource(R.string.album_art),
63 | contentScale = ContentScale.Crop,
64 | error = painterResource(id = R.drawable.music_placeholder)
65 | )
66 | Spacer(Modifier.height(8.dp))
67 | Text(
68 | album.title,
69 | style = MaterialTheme.typography.titleSmall,
70 | maxLines = 1,
71 | overflow = TextOverflow.Ellipsis,
72 | textAlign = TextAlign.Center
73 | )
74 | Text(
75 | album.artistsText,
76 | style = MaterialTheme.typography.bodyMedium,
77 | maxLines = 1,
78 | overflow = TextOverflow.Ellipsis,
79 | textAlign = TextAlign.Center
80 | )
81 | Spacer(Modifier.height(8.dp))
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/components/AlbumList.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.album.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.PaddingValues
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.lazy.grid.GridCells
7 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
8 | import androidx.compose.foundation.lazy.grid.items
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.unit.dp
12 | import app.suhasdissa.vibeyou.R
13 | import app.suhasdissa.vibeyou.domain.models.primary.Album
14 | import app.suhasdissa.vibeyou.presentation.components.IllustratedMessageScreen
15 |
16 | @Composable
17 | fun AlbumList(
18 | items: List,
19 | onClickCard: (playlist: Album) -> Unit,
20 | onLongPress: (playlist: Album) -> Unit
21 | ) {
22 | if (items.isEmpty()) {
23 | IllustratedMessageScreen(image = R.drawable.ic_launcher_monochrome)
24 | }
25 | LazyVerticalGrid(
26 | columns = GridCells.Adaptive(180.dp),
27 | modifier = Modifier.fillMaxSize(),
28 | horizontalArrangement = Arrangement.spacedBy(8.dp),
29 | verticalArrangement = Arrangement.spacedBy(8.dp),
30 | contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp)
31 | ) {
32 | items(items = items) { item ->
33 | AlbumCard(
34 | album = item,
35 | onClickCard = { onClickCard(item) },
36 | onLongPress = { onLongPress(item) }
37 | )
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/model/LocalPlaylistViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.album.model
2 |
3 | import android.util.Log
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.lifecycle.SavedStateHandle
8 | import androidx.lifecycle.ViewModel
9 | import androidx.lifecycle.ViewModelProvider
10 | import androidx.lifecycle.createSavedStateHandle
11 | import androidx.lifecycle.viewModelScope
12 | import androidx.lifecycle.viewmodel.initializer
13 | import androidx.lifecycle.viewmodel.viewModelFactory
14 | import androidx.navigation.toRoute
15 | import app.suhasdissa.vibeyou.MellowMusicApplication
16 | import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository
17 | import app.suhasdissa.vibeyou.domain.models.primary.Album
18 | import app.suhasdissa.vibeyou.navigation.Destination
19 | import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.AlbumInfoState
20 | import kotlinx.coroutines.launch
21 |
22 | class LocalPlaylistViewModel(
23 | private val musicRepository: LocalMusicRepository,
24 | savedStateHandle: SavedStateHandle
25 | ) : ViewModel() {
26 |
27 | val playlist = savedStateHandle.toRoute()
28 |
29 | var albumInfoState: AlbumInfoState by mutableStateOf(AlbumInfoState.Loading)
30 | private set
31 |
32 | init {
33 | getAlbumInfo(playlist.album)
34 | }
35 |
36 | private fun getAlbumInfo(album: Album) {
37 | viewModelScope.launch {
38 | albumInfoState = AlbumInfoState.Loading
39 | albumInfoState = try {
40 | AlbumInfoState.Success(
41 | album,
42 | musicRepository.getAlbumInfo(album.id.toLong())
43 | )
44 | } catch (e: Exception) {
45 | Log.e("Playlist Info", e.toString())
46 | AlbumInfoState.Error
47 | }
48 | }
49 | }
50 |
51 | companion object {
52 | val Factory: ViewModelProvider.Factory = viewModelFactory {
53 | initializer {
54 | val application =
55 | (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication)
56 | LocalPlaylistViewModel(
57 | application.container.localMusicRepository,
58 | this.createSavedStateHandle()
59 | )
60 | }
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/model/NewPlaylistViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.album.model
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import androidx.lifecycle.viewModelScope
6 | import androidx.lifecycle.viewmodel.initializer
7 | import androidx.lifecycle.viewmodel.viewModelFactory
8 | import app.suhasdissa.vibeyou.MellowMusicApplication
9 | import app.suhasdissa.vibeyou.backend.repository.PlaylistRepository
10 | import app.suhasdissa.vibeyou.domain.models.primary.Album
11 | import app.suhasdissa.vibeyou.domain.models.primary.Song
12 | import kotlinx.coroutines.launch
13 |
14 | class NewPlaylistViewModel(private val playlistRepository: PlaylistRepository) : ViewModel() {
15 | fun newPlaylistWithSongs(album: Album, songs: List) {
16 | viewModelScope.launch {
17 | playlistRepository.newPlaylistWithSongs(album, songs)
18 | }
19 | }
20 |
21 | companion object {
22 | val Factory: ViewModelProvider.Factory = viewModelFactory {
23 | initializer {
24 | val application =
25 | (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication)
26 | NewPlaylistViewModel(
27 | application.container.playlistRepository
28 | )
29 | }
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/model/OnlinePlaylistViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.album.model
2 |
3 | import android.util.Log
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.lifecycle.SavedStateHandle
8 | import androidx.lifecycle.ViewModel
9 | import androidx.lifecycle.ViewModelProvider
10 | import androidx.lifecycle.createSavedStateHandle
11 | import androidx.lifecycle.viewModelScope
12 | import androidx.lifecycle.viewmodel.initializer
13 | import androidx.lifecycle.viewmodel.viewModelFactory
14 | import androidx.navigation.toRoute
15 | import app.suhasdissa.vibeyou.MellowMusicApplication
16 | import app.suhasdissa.vibeyou.backend.repository.PipedMusicRepository
17 | import app.suhasdissa.vibeyou.domain.models.primary.Album
18 | import app.suhasdissa.vibeyou.navigation.Destination
19 | import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.AlbumInfoState
20 | import app.suhasdissa.vibeyou.utils.asSong
21 | import kotlinx.coroutines.launch
22 |
23 | class OnlinePlaylistViewModel(
24 | private val musicRepository: PipedMusicRepository,
25 | savedStateHandle: SavedStateHandle
26 | ) : ViewModel() {
27 |
28 | val playlist = savedStateHandle.toRoute()
29 |
30 | var albumInfoState: AlbumInfoState by mutableStateOf(AlbumInfoState.Loading)
31 | private set
32 |
33 | init {
34 | getPlaylistInfo(playlist.album)
35 | }
36 |
37 | private fun getPlaylistInfo(playlist: Album) {
38 | viewModelScope.launch {
39 | albumInfoState = AlbumInfoState.Loading
40 | albumInfoState = try {
41 | val info = musicRepository.getPlaylistInfo(playlist.id)
42 | AlbumInfoState.Success(
43 | playlist,
44 | info.relatedStreams.map { it.asSong }
45 | )
46 | } catch (e: Exception) {
47 | Log.e("Playlist Info", e.toString())
48 | AlbumInfoState.Error
49 | }
50 | }
51 | }
52 |
53 | companion object {
54 | val Factory: ViewModelProvider.Factory = viewModelFactory {
55 | initializer {
56 | val application =
57 | (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication)
58 | OnlinePlaylistViewModel(
59 | application.container.pipedMusicRepository,
60 | this.createSavedStateHandle()
61 | )
62 | }
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/ArtistScreen.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.artist
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material3.ExperimentalMaterial3Api
6 | import androidx.compose.material3.Scaffold
7 | import androidx.compose.material3.Text
8 | import androidx.compose.material3.TopAppBar
9 | import androidx.compose.material3.TopAppBarDefaults
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.res.stringResource
13 | import androidx.compose.ui.text.style.TextOverflow
14 | import app.suhasdissa.vibeyou.R
15 | import app.suhasdissa.vibeyou.domain.models.primary.Album
16 | import app.suhasdissa.vibeyou.presentation.components.IllustratedMessageScreen
17 | import app.suhasdissa.vibeyou.presentation.components.LoadingScreen
18 | import app.suhasdissa.vibeyou.presentation.screens.album.components.AlbumList
19 | import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.ArtistInfoState
20 |
21 | @OptIn(ExperimentalMaterial3Api::class)
22 | @Composable
23 | fun ArtistScreen(
24 | onClickAlbum: (Album) -> Unit,
25 | state: ArtistInfoState
26 | ) {
27 | Scaffold(topBar = {
28 | TopAppBar(title = {
29 | when (state) {
30 | is ArtistInfoState.Success -> Text(
31 | text = state.artist.artistsText,
32 | maxLines = 1,
33 | overflow = TextOverflow.Ellipsis
34 | )
35 |
36 | else -> Text(stringResource(R.string.artist))
37 | }
38 | }, scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior())
39 | }) { pV ->
40 | Column(Modifier.padding(pV)) {
41 | when (state) {
42 | ArtistInfoState.Error -> IllustratedMessageScreen(
43 | image = R.drawable.ic_launcher_monochrome,
44 | message = R.string.something_went_wrong
45 | )
46 |
47 | ArtistInfoState.Loading -> LoadingScreen()
48 | is ArtistInfoState.Success -> {
49 | AlbumList(items = state.playlists, onClickCard = {
50 | onClickAlbum.invoke(it)
51 | }, onLongPress = {
52 | })
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/components/ArtistCard.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.artist.components
2 |
3 | import android.view.SoundEffectConstants
4 | import androidx.compose.foundation.ExperimentalFoundationApi
5 | import androidx.compose.foundation.combinedClickable
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.aspectRatio
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.layout.size
13 | import androidx.compose.foundation.shape.CircleShape
14 | import androidx.compose.material3.MaterialTheme
15 | import androidx.compose.material3.Text
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.hapticfeedback.HapticFeedbackType
21 | import androidx.compose.ui.layout.ContentScale
22 | import androidx.compose.ui.platform.LocalHapticFeedback
23 | import androidx.compose.ui.platform.LocalView
24 | import androidx.compose.ui.res.painterResource
25 | import androidx.compose.ui.res.stringResource
26 | import androidx.compose.ui.text.style.TextOverflow
27 | import androidx.compose.ui.unit.dp
28 | import app.suhasdissa.vibeyou.R
29 | import app.suhasdissa.vibeyou.domain.models.primary.Artist
30 | import coil.compose.AsyncImage
31 |
32 | @OptIn(ExperimentalFoundationApi::class)
33 | @Composable
34 | fun ArtistCard(
35 | artist: Artist,
36 | onClickCard: () -> Unit,
37 | onLongPress: () -> Unit
38 | ) {
39 | val view = LocalView.current
40 | val haptic = LocalHapticFeedback.current
41 | Row(
42 | Modifier
43 | .fillMaxWidth()
44 | .combinedClickable(
45 | onClick = {
46 | view.playSoundEffect(SoundEffectConstants.CLICK)
47 | onClickCard()
48 | },
49 | onLongClick = {
50 | haptic.performHapticFeedback(HapticFeedbackType.LongPress)
51 | onLongPress()
52 | }
53 | ),
54 | verticalAlignment = Alignment.CenterVertically
55 | ) {
56 | AsyncImage(
57 | modifier = Modifier
58 | .size(80.dp)
59 | .padding(8.dp)
60 | .aspectRatio(1f)
61 | .clip(CircleShape),
62 | model = artist.thumbnailUri,
63 | contentDescription = stringResource(R.string.artist_avatar),
64 | contentScale = ContentScale.Crop,
65 | error = painterResource(id = R.drawable.music_placeholder)
66 | )
67 | Column(
68 | Modifier
69 | .weight(1f)
70 | .padding(8.dp)
71 | ) {
72 | Text(
73 | artist.artistsText,
74 | style = MaterialTheme.typography.titleMedium,
75 | maxLines = 1,
76 | overflow = TextOverflow.Ellipsis
77 | )
78 | Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
79 | artist.numberOfAlbums?.let {
80 | Text(
81 | text = "${stringResource(id = R.string.albums)} $it",
82 | style = MaterialTheme.typography.bodyMedium
83 | )
84 | }
85 | artist.numberOfTracks?.let {
86 | Text(
87 | text = "${stringResource(id = R.string.songs)} $it",
88 | style = MaterialTheme.typography.bodyMedium
89 | )
90 | }
91 | }
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/components/ArtistList.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.artist.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.PaddingValues
5 | import androidx.compose.foundation.layout.fillMaxSize
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.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.unit.dp
12 | import app.suhasdissa.vibeyou.R
13 | import app.suhasdissa.vibeyou.domain.models.primary.Artist
14 | import app.suhasdissa.vibeyou.presentation.components.IllustratedMessageScreen
15 |
16 | @Composable
17 | fun ArtistList(
18 | items: List,
19 | onClickCard: (artist: Artist) -> Unit,
20 | onLongPress: (artist: Artist) -> Unit
21 | ) {
22 | if (items.isEmpty()) {
23 | IllustratedMessageScreen(image = R.drawable.ic_launcher_monochrome)
24 | }
25 | val state = rememberLazyListState()
26 | // LazyColumnScrollbar(
27 | // listState = state,
28 | // thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f),
29 | // thumbSelectedColor = MaterialTheme.colorScheme.primary,
30 | // thickness = 8.dp
31 | // ) {
32 | LazyColumn(
33 | modifier = Modifier.fillMaxSize(),
34 | state = state,
35 | verticalArrangement = Arrangement.spacedBy(8.dp),
36 | contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp)
37 | ) {
38 | items(items = items) { item ->
39 | ArtistCard(
40 | artist = item,
41 | onClickCard = { onClickCard(item) },
42 | onLongPress = { onLongPress(item) }
43 | )
44 | }
45 | }
46 | // }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/model/LocalArtistViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.artist.model
2 |
3 | import android.util.Log
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.lifecycle.SavedStateHandle
8 | import androidx.lifecycle.ViewModel
9 | import androidx.lifecycle.ViewModelProvider
10 | import androidx.lifecycle.createSavedStateHandle
11 | import androidx.lifecycle.viewModelScope
12 | import androidx.lifecycle.viewmodel.initializer
13 | import androidx.lifecycle.viewmodel.viewModelFactory
14 | import androidx.navigation.toRoute
15 | import app.suhasdissa.vibeyou.MellowMusicApplication
16 | import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository
17 | import app.suhasdissa.vibeyou.domain.models.primary.Artist
18 | import app.suhasdissa.vibeyou.navigation.Destination
19 | import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.ArtistInfoState
20 | import kotlinx.coroutines.launch
21 |
22 | class LocalArtistViewModel(
23 | private val musicRepository: LocalMusicRepository,
24 | savedStateHandle: SavedStateHandle
25 | ) : ViewModel() {
26 |
27 | val artist = savedStateHandle.toRoute()
28 | var artistInfoState: ArtistInfoState by mutableStateOf(ArtistInfoState.Loading)
29 | private set
30 |
31 | init {
32 | getArtistInfo(artist.artist)
33 | }
34 |
35 | private fun getArtistInfo(artist: Artist) {
36 | viewModelScope.launch {
37 | artistInfoState = ArtistInfoState.Loading
38 | artistInfoState = try {
39 | ArtistInfoState.Success(
40 | artist,
41 | musicRepository.getArtistInfo(artist.artistsText)
42 | )
43 | } catch (e: Exception) {
44 | Log.e("Artist Info", e.toString())
45 | ArtistInfoState.Error
46 | }
47 | }
48 | }
49 |
50 | companion object {
51 | val Factory: ViewModelProvider.Factory = viewModelFactory {
52 | initializer {
53 | val application =
54 | (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication)
55 | LocalArtistViewModel(
56 | application.container.localMusicRepository,
57 | this.createSavedStateHandle()
58 | )
59 | }
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/model/OnlineArtistViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.artist.model
2 |
3 | import android.util.Log
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.core.net.toUri
8 | import androidx.lifecycle.SavedStateHandle
9 | import androidx.lifecycle.ViewModel
10 | import androidx.lifecycle.ViewModelProvider
11 | import androidx.lifecycle.createSavedStateHandle
12 | import androidx.lifecycle.viewModelScope
13 | import androidx.lifecycle.viewmodel.initializer
14 | import androidx.lifecycle.viewmodel.viewModelFactory
15 | import androidx.navigation.toRoute
16 | import app.suhasdissa.vibeyou.MellowMusicApplication
17 | import app.suhasdissa.vibeyou.backend.repository.PipedMusicRepository
18 | import app.suhasdissa.vibeyou.domain.models.primary.Artist
19 | import app.suhasdissa.vibeyou.navigation.Destination
20 | import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.ArtistInfoState
21 | import kotlinx.coroutines.launch
22 |
23 | class OnlineArtistViewModel(
24 | private val musicRepository: PipedMusicRepository,
25 | savedStateHandle: SavedStateHandle
26 | ) : ViewModel() {
27 | val artist = savedStateHandle.toRoute()
28 | var artistInfoState: ArtistInfoState by mutableStateOf(ArtistInfoState.Loading)
29 | private set
30 |
31 | init {
32 | getArtistInfo(artist.artist)
33 | }
34 |
35 | private fun getArtistInfo(artist: Artist) {
36 | viewModelScope.launch {
37 | artistInfoState = ArtistInfoState.Loading
38 | artistInfoState = try {
39 | val info = musicRepository.getChannelInfo(artist.id)
40 | val playlists = musicRepository.getChannelPlaylists(artist.id, info.tabs)
41 | ArtistInfoState.Success(
42 | artist.copy(
43 | thumbnailUri = info.avatarUrl?.toUri(),
44 | description = info.description
45 | ),
46 | playlists
47 | )
48 | } catch (e: Exception) {
49 | Log.e("Playlist Info", e.toString())
50 | ArtistInfoState.Error
51 | }
52 | }
53 | }
54 |
55 | companion object {
56 | val Factory: ViewModelProvider.Factory = viewModelFactory {
57 | initializer {
58 | val application =
59 | (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication)
60 | OnlineArtistViewModel(
61 | application.container.pipedMusicRepository,
62 | this.createSavedStateHandle()
63 | )
64 | }
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localmusic/components/SortOrderDialog.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.localmusic.components
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.interaction.MutableInteractionSource
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.material3.AlertDialog
8 | import androidx.compose.material3.Checkbox
9 | import androidx.compose.material3.Text
10 | import androidx.compose.material3.TextButton
11 | import androidx.compose.runtime.Composable
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.Alignment
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.res.stringResource
19 | import app.suhasdissa.vibeyou.R
20 | import app.suhasdissa.vibeyou.presentation.components.ChipSelector
21 |
22 | enum class SortOrder {
23 | Alphabetic,
24 | Creation_Date,
25 | Date_Added,
26 | Artist_Name
27 | }
28 |
29 | @Composable
30 | fun SortOrderDialog(
31 | onDismissRequest: () -> Unit,
32 | onSortOrderChange: (order: SortOrder, reverse: Boolean) -> Unit,
33 | defaultSortOrder: SortOrder,
34 | defaultReverse: Boolean
35 | ) {
36 | var sortOrder by remember {
37 | mutableStateOf(defaultSortOrder)
38 | }
39 | var reverse by remember {
40 | mutableStateOf(defaultReverse)
41 | }
42 |
43 | AlertDialog(
44 | onDismissRequest = onDismissRequest,
45 | title = {
46 | Text(stringResource(R.string.sort_order))
47 | },
48 | text = {
49 | Column {
50 | ChipSelector(
51 | onItemSelected = {
52 | sortOrder = it
53 | },
54 | defaultValue = sortOrder
55 | )
56 | val interactionSource = remember { MutableInteractionSource() }
57 | Row(
58 | verticalAlignment = Alignment.CenterVertically,
59 | modifier = Modifier
60 | .clickable(interactionSource = interactionSource, indication = null) {
61 | reverse = !reverse
62 | }
63 | ) {
64 | Checkbox(checked = reverse, onCheckedChange = { reverse = it })
65 | Text(text = stringResource(R.string.reversed))
66 | }
67 | }
68 | },
69 | confirmButton = {
70 | TextButton(
71 | onClick = {
72 | onSortOrderChange(sortOrder, reverse)
73 | onDismissRequest()
74 | }
75 | ) {
76 | Text(stringResource(R.string.ok))
77 | }
78 | },
79 | dismissButton = {
80 | TextButton(onClick = onDismissRequest) {
81 | Text(stringResource(R.string.cancel))
82 | }
83 | }
84 | )
85 | }
86 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localmusic/model/LocalSongViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.localmusic.model
2 |
3 | import android.util.Log
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.lifecycle.ViewModel
8 | import androidx.lifecycle.ViewModelProvider
9 | import androidx.lifecycle.viewModelScope
10 | import androidx.lifecycle.viewmodel.initializer
11 | import androidx.lifecycle.viewmodel.viewModelFactory
12 | import app.suhasdissa.vibeyou.MellowMusicApplication
13 | import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository
14 | import app.suhasdissa.vibeyou.domain.models.primary.Album
15 | import app.suhasdissa.vibeyou.domain.models.primary.Artist
16 | import app.suhasdissa.vibeyou.domain.models.primary.Song
17 | import app.suhasdissa.vibeyou.presentation.screens.localmusic.components.SortOrder
18 | import app.suhasdissa.vibeyou.utils.Pref
19 | import kotlinx.coroutines.launch
20 |
21 | class LocalSongViewModel(private val musicRepository: LocalMusicRepository) : ViewModel() {
22 | var songs by mutableStateOf(listOf())
23 | var albums by mutableStateOf(listOf())
24 | var artists by mutableStateOf(listOf())
25 |
26 | var songsSortOrder = Pref.sharedPreferences.getString(Pref.latestSongsSortOrderKey, null)?.let {
27 | runCatching { SortOrder.valueOf(it) }.getOrNull()
28 | } ?: SortOrder.Alphabetic
29 | var reverseSongs = Pref.sharedPreferences.getBoolean(Pref.latestReverseSongsPrefKey, false)
30 |
31 | init {
32 | viewModelScope.launch {
33 | try {
34 | songs = musicRepository.getAllSongs()
35 | updateSongsSortOrder()
36 | } catch (e: Exception) {
37 | Log.e("Get All Songs", e.message, e)
38 | }
39 | }
40 | viewModelScope.launch {
41 | try {
42 | albums = musicRepository.getAllAlbums()
43 | } catch (e: Exception) {
44 | Log.e("Get All Albums", e.message, e)
45 | }
46 | }
47 | viewModelScope.launch {
48 | try {
49 | artists = musicRepository.getAllArtists()
50 | } catch (e: Exception) {
51 | Log.e("Get All Artists", e.message, e)
52 | }
53 | }
54 | }
55 |
56 | fun updateSongsSortOrder() {
57 | val sortedSongs = when (songsSortOrder) {
58 | SortOrder.Alphabetic -> songs.sortedBy { it.title.lowercase() }
59 | SortOrder.Creation_Date -> songs.sortedBy { it.creationDate }
60 | SortOrder.Date_Added -> songs.sortedBy { it.dateAdded }
61 | SortOrder.Artist_Name -> songs.sortedBy { it.artistsText.orEmpty().lowercase() }
62 | }
63 | songs = if (reverseSongs) sortedSongs.reversed() else sortedSongs
64 | }
65 |
66 | companion object {
67 | val Factory: ViewModelProvider.Factory = viewModelFactory {
68 | initializer {
69 | val application =
70 | (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication)
71 | LocalSongViewModel(
72 | application.container.localMusicRepository
73 | )
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/components/SongsScreen.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.onlinemusic.components
2 |
3 | import android.view.SoundEffectConstants
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.fillMaxSize
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.layout.width
10 | import androidx.compose.material.icons.Icons
11 | import androidx.compose.material.icons.rounded.PlayArrow
12 | import androidx.compose.material.icons.rounded.Shuffle
13 | import androidx.compose.material3.FloatingActionButton
14 | import androidx.compose.material3.Icon
15 | import androidx.compose.material3.Scaffold
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.runtime.collectAsState
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.platform.LocalView
20 | import androidx.compose.ui.res.stringResource
21 | import androidx.compose.ui.unit.dp
22 | import androidx.lifecycle.viewmodel.compose.viewModel
23 | import app.suhasdissa.vibeyou.R
24 | import app.suhasdissa.vibeyou.presentation.components.SongListView
25 | import app.suhasdissa.vibeyou.presentation.screens.onlinemusic.model.SongViewModel
26 | import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel
27 | import app.suhasdissa.vibeyou.utils.asSong
28 |
29 | @Composable
30 | fun SongsScreen(
31 | showFavourites: Boolean,
32 | playerViewModel: PlayerViewModel,
33 | songViewModel: SongViewModel = viewModel(factory = SongViewModel.Factory)
34 | ) {
35 | val view = LocalView.current
36 | val songs =
37 | (if (showFavourites) songViewModel.favSongs.collectAsState() else songViewModel.songs.collectAsState()).value.map {
38 | it.asSong
39 | }
40 | Scaffold(floatingActionButton = {
41 | Row {
42 | FloatingActionButton(onClick = {
43 | view.playSoundEffect(SoundEffectConstants.CLICK)
44 | playerViewModel.playSongs(songs)
45 | }) {
46 | Icon(
47 | imageVector = Icons.Rounded.PlayArrow,
48 | contentDescription = stringResource(R.string.play_all)
49 | )
50 | }
51 |
52 | Spacer(modifier = Modifier.width(12.dp))
53 |
54 | FloatingActionButton(onClick = {
55 | view.playSoundEffect(SoundEffectConstants.CLICK)
56 | playerViewModel.playSongs(songs, shuffle = true)
57 | }) {
58 | Icon(
59 | imageVector = Icons.Rounded.Shuffle,
60 | contentDescription = stringResource(R.string.shuffle)
61 | )
62 | }
63 | }
64 | }) { innerPadding ->
65 | Column(
66 | Modifier
67 | .fillMaxSize()
68 | .padding(innerPadding)
69 | ) {
70 | SongListView(songs, playerViewModel)
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/model/SongOptionsViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.onlinemusic.model
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import androidx.lifecycle.viewModelScope
6 | import androidx.lifecycle.viewmodel.initializer
7 | import androidx.lifecycle.viewmodel.viewModelFactory
8 | import app.suhasdissa.vibeyou.MellowMusicApplication
9 | import app.suhasdissa.vibeyou.backend.repository.SongDatabaseRepository
10 | import app.suhasdissa.vibeyou.domain.models.primary.Song
11 | import kotlinx.coroutines.launch
12 |
13 | class SongOptionsViewModel(private val songDatabaseRepository: SongDatabaseRepository) :
14 | ViewModel() {
15 | fun removeSong(song: Song) {
16 | viewModelScope.launch {
17 | songDatabaseRepository.removeSong(song)
18 | }
19 | }
20 |
21 | fun toggleFavourite(id: String) {
22 | viewModelScope.launch {
23 | val song = songDatabaseRepository.getSongById(id)
24 | song ?: return@launch
25 | song.let {
26 | songDatabaseRepository.addSong(it.toggleLike())
27 | }
28 | }
29 | }
30 |
31 | companion object {
32 | val Factory: ViewModelProvider.Factory = viewModelFactory {
33 | initializer {
34 | val application =
35 | (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication)
36 | SongOptionsViewModel(
37 | application.container.songDatabaseRepository
38 | )
39 | }
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/model/SongViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.onlinemusic.model
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import androidx.lifecycle.viewModelScope
6 | import androidx.lifecycle.viewmodel.initializer
7 | import androidx.lifecycle.viewmodel.viewModelFactory
8 | import app.suhasdissa.vibeyou.MellowMusicApplication
9 | import app.suhasdissa.vibeyou.backend.repository.SongDatabaseRepository
10 | import kotlinx.coroutines.flow.SharingStarted
11 | import kotlinx.coroutines.flow.stateIn
12 |
13 | class SongViewModel(private val songDatabaseRepository: SongDatabaseRepository) : ViewModel() {
14 | val songs = songDatabaseRepository.getAllSongsStream().stateIn(
15 | scope = viewModelScope,
16 | started = SharingStarted.WhileSubscribed(5000L),
17 | initialValue = listOf()
18 | )
19 |
20 | val favSongs = songDatabaseRepository.getFavSongsStream().stateIn(
21 | scope = viewModelScope,
22 | started = SharingStarted.WhileSubscribed(5000L),
23 | initialValue = listOf()
24 | )
25 |
26 | companion object {
27 | val Factory: ViewModelProvider.Factory = viewModelFactory {
28 | initializer {
29 | val application =
30 | (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication)
31 | SongViewModel(
32 | application.container.songDatabaseRepository
33 | )
34 | }
35 | }
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/state/AlbumInfoState.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state
2 |
3 | import app.suhasdissa.vibeyou.domain.models.primary.Album
4 | import app.suhasdissa.vibeyou.domain.models.primary.Song
5 |
6 | sealed interface AlbumInfoState {
7 | object Loading : AlbumInfoState
8 | object Error : AlbumInfoState
9 | data class Success(
10 | val album: Album,
11 | val songs: List
12 |
13 | ) : AlbumInfoState
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/state/ArtistInfoState.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state
2 |
3 | import app.suhasdissa.vibeyou.domain.models.primary.Album
4 | import app.suhasdissa.vibeyou.domain.models.primary.Artist
5 |
6 | sealed interface ArtistInfoState {
7 | object Loading : ArtistInfoState
8 | object Error : ArtistInfoState
9 | data class Success(
10 | val artist: Artist,
11 | val playlists: List
12 |
13 | ) : ArtistInfoState
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/state/SearchState.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state
2 |
3 | import app.suhasdissa.vibeyou.domain.models.primary.Album
4 | import app.suhasdissa.vibeyou.domain.models.primary.Artist
5 | import app.suhasdissa.vibeyou.domain.models.primary.Song
6 |
7 | sealed interface SearchState {
8 | sealed interface Success : SearchState {
9 | data class Songs(val items: List) : Success
10 | data class Playlists(val items: List) : Success
11 | data class Artists(val items: List) : Success
12 | }
13 |
14 | data class Error(val error: String) : SearchState
15 | object Loading : SearchState
16 | object Empty : SearchState
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/components/QueueSheet.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.player.components
2 |
3 | import android.view.SoundEffectConstants
4 | import androidx.compose.foundation.layout.fillMaxWidth
5 | import androidx.compose.foundation.shape.RoundedCornerShape
6 | import androidx.compose.material.icons.Icons
7 | import androidx.compose.material.icons.rounded.ExpandMore
8 | import androidx.compose.material.icons.rounded.MoreVert
9 | import androidx.compose.material3.CenterAlignedTopAppBar
10 | import androidx.compose.material3.DropdownMenu
11 | import androidx.compose.material3.DropdownMenuItem
12 | import androidx.compose.material3.ExperimentalMaterial3Api
13 | import androidx.compose.material3.HorizontalDivider
14 | import androidx.compose.material3.Icon
15 | import androidx.compose.material3.IconButton
16 | import androidx.compose.material3.ModalBottomSheet
17 | import androidx.compose.material3.Text
18 | import androidx.compose.material3.rememberModalBottomSheetState
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.rememberCoroutineScope
24 | import androidx.compose.runtime.setValue
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.platform.LocalView
27 | import androidx.compose.ui.res.stringResource
28 | import androidx.compose.ui.unit.dp
29 | import app.suhasdissa.vibeyou.R
30 | import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel
31 | import kotlinx.coroutines.launch
32 |
33 | @OptIn(ExperimentalMaterial3Api::class)
34 | @Composable
35 | fun QueueSheet(
36 | onDismissRequest: () -> Unit,
37 | playerViewModel: PlayerViewModel
38 | ) {
39 | val playerSheetState = rememberModalBottomSheetState(
40 | skipPartiallyExpanded = true
41 | )
42 | val scope = rememberCoroutineScope()
43 | val view = LocalView.current
44 | ModalBottomSheet(
45 | onDismissRequest = onDismissRequest,
46 | sheetState = playerSheetState,
47 | shape = RoundedCornerShape(8.dp),
48 | tonalElevation = 0.dp,
49 | dragHandle = null
50 | ) {
51 | CenterAlignedTopAppBar(navigationIcon = {
52 | IconButton({
53 | view.playSoundEffect(SoundEffectConstants.CLICK)
54 | scope.launch {
55 | playerSheetState.hide()
56 | }.invokeOnCompletion {
57 | onDismissRequest()
58 | }
59 | }) {
60 | Icon(
61 | Icons.Rounded.ExpandMore,
62 | contentDescription = stringResource(R.string.close_queue)
63 | )
64 | }
65 | }, title = { Text(stringResource(R.string.player_queue)) }, actions = {
66 | var showDropdown by remember { mutableStateOf(false) }
67 | IconButton({
68 | view.playSoundEffect(SoundEffectConstants.CLICK)
69 | showDropdown = true
70 | }) {
71 | Icon(
72 | Icons.Rounded.MoreVert,
73 | contentDescription = stringResource(R.string.close_queue)
74 | )
75 | }
76 | DropdownMenu(expanded = showDropdown, onDismissRequest = { showDropdown = false }) {
77 | DropdownMenuItem(text = {
78 | Text(text = stringResource(R.string.clear_queue))
79 | }, onClick = {
80 | playerViewModel.controller?.clearMediaItems()
81 | scope.launch {
82 | playerSheetState.hide()
83 | }.invokeOnCompletion {
84 | onDismissRequest()
85 | }
86 | })
87 | }
88 | })
89 | HorizontalDivider(Modifier.fillMaxWidth())
90 | playerViewModel.controller?.let { controller ->
91 | Queue(controller)
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/PlaylistsScreen.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.playlists
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.collectAsState
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import androidx.lifecycle.viewmodel.compose.viewModel
10 | import app.suhasdissa.vibeyou.domain.models.primary.Album
11 | import app.suhasdissa.vibeyou.navigation.Destination
12 | import app.suhasdissa.vibeyou.presentation.screens.album.components.AlbumList
13 | import app.suhasdissa.vibeyou.presentation.screens.playlists.components.PlaylistOptionsSheet
14 | import app.suhasdissa.vibeyou.presentation.screens.playlists.model.PlaylistViewModel
15 | import app.suhasdissa.vibeyou.utils.asAlbum
16 |
17 | @Composable
18 | fun PlaylistsScreen(
19 | onNavigate: (Destination) -> Unit,
20 | playlistViewModel: PlaylistViewModel = viewModel(
21 | factory = PlaylistViewModel.Factory
22 | )
23 | ) {
24 | val albums by playlistViewModel.albums.collectAsState()
25 | var selectedAlbum: Album? by remember { mutableStateOf(null) }
26 | AlbumList(items = albums.map { it.asAlbum }, onClickCard = {
27 | onNavigate(Destination.SavedPlaylists(it))
28 | }, onLongPress = {
29 | selectedAlbum = it
30 | })
31 | selectedAlbum?.let {
32 | PlaylistOptionsSheet(
33 | onDismissRequest = { selectedAlbum = null },
34 | album = it,
35 | playlistViewModel = playlistViewModel
36 | )
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/model/PlaylistInfoViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.playlists.model
2 |
3 | import android.util.Log
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.lifecycle.SavedStateHandle
8 | import androidx.lifecycle.ViewModel
9 | import androidx.lifecycle.ViewModelProvider
10 | import androidx.lifecycle.createSavedStateHandle
11 | import androidx.lifecycle.viewModelScope
12 | import androidx.lifecycle.viewmodel.initializer
13 | import androidx.lifecycle.viewmodel.viewModelFactory
14 | import androidx.navigation.toRoute
15 | import app.suhasdissa.vibeyou.MellowMusicApplication
16 | import app.suhasdissa.vibeyou.backend.repository.PlaylistRepository
17 | import app.suhasdissa.vibeyou.domain.models.primary.Album
18 | import app.suhasdissa.vibeyou.navigation.Destination
19 | import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.AlbumInfoState
20 | import app.suhasdissa.vibeyou.utils.asSong
21 | import kotlinx.coroutines.launch
22 |
23 | class PlaylistInfoViewModel(
24 | private val playlistRepository: PlaylistRepository,
25 | savedStateHandle: SavedStateHandle
26 | ) : ViewModel() {
27 | val playlist = savedStateHandle.toRoute()
28 |
29 | var albumInfoState: AlbumInfoState by mutableStateOf(AlbumInfoState.Loading)
30 | private set
31 |
32 | init {
33 | getPlaylistInfo(playlist.album)
34 | }
35 |
36 | private fun getPlaylistInfo(playlist: Album) {
37 | viewModelScope.launch {
38 | albumInfoState = AlbumInfoState.Loading
39 | albumInfoState = try {
40 | Log.e("PlaylistViewModel", "Getting info")
41 | val info = playlistRepository.getPlaylist(playlist.id)
42 | AlbumInfoState.Success(
43 | playlist,
44 | info.songs.map { it.asSong }
45 | )
46 | } catch (e: Exception) {
47 | Log.e("Playlist Info", e.toString())
48 | AlbumInfoState.Error
49 | }
50 | }
51 | }
52 |
53 | companion object {
54 | val Factory: ViewModelProvider.Factory = viewModelFactory {
55 | initializer {
56 | val application =
57 | (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication)
58 | PlaylistInfoViewModel(
59 | application.container.playlistRepository,
60 | this.createSavedStateHandle()
61 | )
62 | }
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/model/PlaylistViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.playlists.model
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import androidx.lifecycle.viewModelScope
6 | import androidx.lifecycle.viewmodel.initializer
7 | import androidx.lifecycle.viewmodel.viewModelFactory
8 | import app.suhasdissa.vibeyou.MellowMusicApplication
9 | import app.suhasdissa.vibeyou.backend.repository.PlaylistRepository
10 | import app.suhasdissa.vibeyou.domain.models.primary.Album
11 | import kotlinx.coroutines.flow.SharingStarted
12 | import kotlinx.coroutines.flow.stateIn
13 | import kotlinx.coroutines.launch
14 |
15 | class PlaylistViewModel(
16 | private val playlistRepository: PlaylistRepository
17 | ) : ViewModel() {
18 |
19 |
20 | var albums = playlistRepository.getPlaylists().stateIn(
21 | scope = viewModelScope,
22 | started = SharingStarted.WhileSubscribed(5000L),
23 | initialValue = listOf()
24 | )
25 |
26 | fun deletePlaylist(album: Album) {
27 | viewModelScope.launch {
28 | playlistRepository.deletePlaylist(album)
29 | }
30 | }
31 |
32 | fun clearPlaylist(album: Album) {
33 | viewModelScope.launch {
34 | playlistRepository.clearPlaylist(album)
35 | }
36 | }
37 |
38 | fun deletePlaylistAndSongs(album: Album) {
39 | viewModelScope.launch {
40 | playlistRepository.deletePlaylistAndSongs(album)
41 | }
42 | }
43 |
44 | companion object {
45 | val Factory: ViewModelProvider.Factory = viewModelFactory {
46 | initializer {
47 | val application =
48 | (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication)
49 | PlaylistViewModel(
50 | application.container.playlistRepository
51 | )
52 | }
53 | }
54 | }
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/NetworkSettingsScreen.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.settings
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.material.icons.Icons
7 | import androidx.compose.material.icons.rounded.Web
8 | import androidx.compose.material3.ExperimentalMaterial3Api
9 | import androidx.compose.material3.LargeTopAppBar
10 | import androidx.compose.material3.Scaffold
11 | import androidx.compose.material3.Text
12 | import androidx.compose.material3.TopAppBarDefaults
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.LaunchedEffect
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.input.nestedscroll.nestedScroll
21 | import androidx.compose.ui.res.stringResource
22 | import androidx.lifecycle.viewmodel.compose.viewModel
23 | import app.suhasdissa.vibeyou.R
24 | import app.suhasdissa.vibeyou.presentation.screens.settings.components.CustomInstanceOption
25 | import app.suhasdissa.vibeyou.presentation.screens.settings.components.InstanceSelectDialog
26 | import app.suhasdissa.vibeyou.presentation.screens.settings.components.SettingItem
27 | import app.suhasdissa.vibeyou.presentation.screens.settings.components.TextFieldPref
28 | import app.suhasdissa.vibeyou.presentation.screens.settings.model.SettingsViewModel
29 | import app.suhasdissa.vibeyou.utils.Pref
30 |
31 | @OptIn(ExperimentalMaterial3Api::class)
32 | @Composable
33 | fun NetworkSettingsScreen() {
34 | val viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory)
35 |
36 | var showDialog by remember { mutableStateOf(false) }
37 | var currentServer by remember {
38 | mutableStateOf(Pref.currentInstance)
39 | }
40 | val topBarBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
41 | LaunchedEffect(Unit) {
42 | viewModel.loadInstances()
43 | }
44 |
45 | Scaffold(modifier = Modifier.fillMaxSize(), topBar = {
46 | LargeTopAppBar(
47 | title = { Text(stringResource(R.string.network_settings)) },
48 | scrollBehavior = topBarBehavior
49 | )
50 | }) { innerPadding ->
51 | LazyColumn(
52 | Modifier
53 | .fillMaxSize()
54 | .padding(innerPadding)
55 | .nestedScroll(topBarBehavior.nestedScrollConnection)
56 | ) {
57 | item {
58 | SettingItem(
59 | title = stringResource(R.string.change_server),
60 | description = currentServer.name,
61 | icon = Icons.Rounded.Web
62 | ) {
63 | showDialog = true
64 | }
65 | }
66 |
67 | item {
68 | CustomInstanceOption {
69 | currentServer = it
70 | }
71 | }
72 |
73 | item {
74 | TextFieldPref(
75 | key = Pref.hyperpipeApiUrlKey,
76 | defaultValue = Pref.defaultHyperInstance,
77 | title = stringResource(id = R.string.hyperpipe_api_url)
78 | )
79 | }
80 | }
81 | }
82 | if (showDialog) {
83 | InstanceSelectDialog(onDismissRequest = {
84 | showDialog = false
85 | }, onSelectionChange = { instance ->
86 | currentServer = instance
87 | Pref.setInstance(instance)
88 | })
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/ButtonGroupPref.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.settings.components
2 |
3 | import androidx.compose.foundation.BorderStroke
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.layout.offset
10 | import androidx.compose.foundation.layout.padding
11 | import androidx.compose.foundation.shape.RoundedCornerShape
12 | import androidx.compose.material3.ButtonDefaults
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.material3.OutlinedButton
15 | import androidx.compose.material3.Text
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.runtime.getValue
18 | import androidx.compose.runtime.mutableStateOf
19 | import androidx.compose.runtime.remember
20 | import androidx.compose.runtime.setValue
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.unit.dp
23 | import androidx.compose.ui.zIndex
24 |
25 | @Composable
26 | fun ButtonGroupPref(
27 | title: String,
28 | options: List,
29 | values: List,
30 | currentValue: T,
31 | onChange: (T) -> Unit
32 | ) {
33 | Column(
34 | modifier = Modifier
35 | .padding(top = 8.dp)
36 | .padding(horizontal = 12.dp)
37 | ) {
38 | Text(title)
39 | Spacer(modifier = Modifier.height(8.dp))
40 | Row(
41 | modifier = Modifier
42 | .fillMaxWidth()
43 | ) {
44 | val cornerRadius = 20.dp
45 | var selectedItem by remember {
46 | mutableStateOf(
47 | currentValue
48 | )
49 | }
50 |
51 | values.forEachIndexed { index, value ->
52 | val startRadius = if (index != 0) 0.dp else cornerRadius
53 | val endRadius = if (index == values.size - 1) cornerRadius else 0.dp
54 |
55 | OutlinedButton(
56 | onClick = {
57 | selectedItem = value
58 | onChange.invoke(values[index])
59 | },
60 | modifier = Modifier
61 | .offset(if (index == 0) 0.dp else (-1 * index).dp, 0.dp)
62 | .zIndex(if (selectedItem == value) 1f else 0f),
63 | shape = RoundedCornerShape(
64 | topStart = startRadius,
65 | topEnd = endRadius,
66 | bottomStart = startRadius,
67 | bottomEnd = endRadius
68 | ),
69 | border = BorderStroke(
70 | 1.dp,
71 | if (selectedItem == value) {
72 | MaterialTheme.colorScheme.primary
73 | } else {
74 | MaterialTheme.colorScheme.primary.copy(alpha = 0.75f)
75 | }
76 | ),
77 | colors = if (selectedItem == value) {
78 | ButtonDefaults.outlinedButtonColors(
79 | containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f),
80 | contentColor = MaterialTheme.colorScheme.primary
81 | )
82 | } else {
83 | ButtonDefaults.outlinedButtonColors(
84 | containerColor = MaterialTheme.colorScheme.surface,
85 | contentColor = MaterialTheme.colorScheme.onSurface
86 | )
87 | }
88 | ) {
89 | Text(options[index])
90 | }
91 | }
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/CacheSizeDialog.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.settings.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.lazy.grid.GridCells
5 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
6 | import androidx.compose.foundation.lazy.grid.items
7 | import androidx.compose.material3.AlertDialog
8 | import androidx.compose.material3.Button
9 | import androidx.compose.material3.ExperimentalMaterial3Api
10 | import androidx.compose.material3.FilterChip
11 | import androidx.compose.material3.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.getValue
14 | import androidx.compose.runtime.setValue
15 | import androidx.compose.ui.res.stringResource
16 | import androidx.compose.ui.unit.dp
17 | import app.suhasdissa.vibeyou.R
18 | import app.suhasdissa.vibeyou.utils.Pref
19 | import app.suhasdissa.vibeyou.utils.formatMB
20 | import app.suhasdissa.vibeyou.utils.rememberPreference
21 |
22 | @OptIn(ExperimentalMaterial3Api::class)
23 | @Composable
24 | fun CacheSizeDialog(onDismissRequest: () -> Unit) {
25 | val cacheSizes = listOf(0, 512, 1024, 1024 * 2, 1024 * 4, 1024 * 6)
26 | var prefSize by rememberPreference(key = Pref.exoCacheKey, defaultValue = 0)
27 | AlertDialog(
28 | onDismissRequest,
29 | title = { Text(stringResource(R.string.change_music_cache_size)) },
30 | confirmButton = {
31 | Button(onClick = {
32 | onDismissRequest.invoke()
33 | }) {
34 | Text(text = stringResource(R.string.ok))
35 | }
36 | },
37 | text = {
38 | LazyVerticalGrid(
39 | columns = GridCells.Fixed(3),
40 | verticalArrangement = Arrangement.spacedBy(8.dp),
41 | horizontalArrangement = Arrangement.spacedBy(8.dp)
42 | ) {
43 | items(items = cacheSizes) {
44 | FilterChip(
45 | selected = prefSize == it,
46 | onClick = { prefSize = it },
47 | label = {
48 | Text(
49 | if (it == 0) {
50 | stringResource(
51 | R.string.unlimited
52 | )
53 | } else {
54 | formatMB(it)
55 | }
56 | )
57 | }
58 | )
59 | }
60 | }
61 | }
62 | )
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/ColorPref.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.settings.components
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.Arrangement
6 | import androidx.compose.foundation.layout.Box
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.lazy.LazyRow
11 | import androidx.compose.foundation.lazy.items
12 | import androidx.compose.foundation.shape.CircleShape
13 | import androidx.compose.material.icons.Icons
14 | import androidx.compose.material.icons.rounded.Check
15 | import androidx.compose.material3.Icon
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.unit.dp
22 | import app.suhasdissa.vibeyou.utils.catpucchinLatte
23 |
24 | @Composable
25 | fun ColorPref(selectedColor: Int, onSelect: (Int) -> Unit) {
26 | LazyRow(
27 | modifier = Modifier
28 | .fillMaxWidth()
29 | .padding(top = 8.dp)
30 | .padding(horizontal = 12.dp),
31 | horizontalArrangement = Arrangement.spacedBy(8.dp)
32 | ) {
33 | items(catpucchinLatte) { color ->
34 | Box(
35 | modifier = Modifier
36 | .size(48.dp)
37 | .clip(CircleShape)
38 | .background(Color(color))
39 | .clickable { onSelect(color) },
40 | contentAlignment = Alignment.Center
41 | ) {
42 | if (color == selectedColor) {
43 | Icon(imageVector = Icons.Rounded.Check, contentDescription = null)
44 | }
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/InstanceSelectDialog.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.settings.components
2 |
3 | import android.view.SoundEffectConstants
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.height
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.layout.width
10 | import androidx.compose.foundation.lazy.LazyColumn
11 | import androidx.compose.foundation.lazy.items
12 | import androidx.compose.foundation.shape.RoundedCornerShape
13 | import androidx.compose.material3.AlertDialog
14 | import androidx.compose.material3.RadioButton
15 | import androidx.compose.material3.Surface
16 | import androidx.compose.material3.Text
17 | import androidx.compose.material3.TextButton
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.platform.LocalView
27 | import androidx.compose.ui.res.stringResource
28 | import androidx.compose.ui.unit.dp
29 | import androidx.lifecycle.viewmodel.compose.viewModel
30 | import app.suhasdissa.vibeyou.R
31 | import app.suhasdissa.vibeyou.backend.models.PipedInstance
32 | import app.suhasdissa.vibeyou.presentation.screens.settings.model.SettingsViewModel
33 | import app.suhasdissa.vibeyou.utils.Pref
34 |
35 | @Composable
36 | fun InstanceSelectDialog(
37 | onDismissRequest: () -> Unit,
38 | onSelectionChange: (instance: PipedInstance) -> Unit
39 | ) {
40 | val viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory)
41 |
42 | var selectedInstance by remember {
43 | mutableStateOf(Pref.currentInstance)
44 | }
45 | val view = LocalView.current
46 |
47 | AlertDialog(
48 | onDismissRequest = { onDismissRequest.invoke() },
49 | confirmButton = {
50 | TextButton(onClick = { onDismissRequest.invoke() }) {
51 | Text(text = stringResource(android.R.string.cancel))
52 | }
53 | },
54 | title = {
55 | Text(stringResource(R.string.select_server))
56 | },
57 | text = {
58 | Surface(
59 | modifier = Modifier.width(300.dp),
60 | shape = RoundedCornerShape(10.dp)
61 | ) {
62 | LazyColumn(modifier = Modifier.height(500.dp)) {
63 | items(items = viewModel.instances) { item ->
64 | Row(
65 | Modifier
66 | .fillMaxWidth()
67 | .clip(RoundedCornerShape(12.dp))
68 | .clickable(onClick = {
69 | view.playSoundEffect(SoundEffectConstants.CLICK)
70 | selectedInstance = item
71 | onSelectionChange(item)
72 | onDismissRequest.invoke()
73 | })
74 | .padding(horizontal = 6.dp, vertical = 12.dp),
75 | verticalAlignment = Alignment.CenterVertically
76 | ) {
77 | RadioButton(
78 | selected = (selectedInstance.name == item.name),
79 | onClick = null
80 | )
81 | Text(
82 | text = item.name,
83 | modifier = Modifier.padding(start = 16.dp)
84 | )
85 | }
86 | }
87 | }
88 | }
89 | }
90 | )
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/SettingItem.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.settings.components
2 |
3 | import android.view.SoundEffectConstants
4 | import androidx.compose.foundation.clickable
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.material3.Icon
11 | import androidx.compose.material3.MaterialTheme
12 | import androidx.compose.material3.Surface
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.graphics.vector.ImageVector
18 | import androidx.compose.ui.platform.LocalView
19 | import androidx.compose.ui.unit.dp
20 |
21 | @Composable
22 | fun SettingItem(title: String, description: String, icon: ImageVector?, onClick: () -> Unit) {
23 | val view = LocalView.current
24 | Surface(
25 | modifier = Modifier.clickable {
26 | view.playSoundEffect(SoundEffectConstants.CLICK)
27 | onClick()
28 | }
29 | ) {
30 | Row(
31 | modifier = Modifier
32 | .fillMaxWidth()
33 | .padding(4.dp, 12.dp),
34 | verticalAlignment = Alignment.CenterVertically
35 | ) {
36 | icon?.let {
37 | Icon(
38 | imageVector = icon,
39 | contentDescription = null,
40 | modifier = Modifier
41 | .padding(start = 8.dp, end = 16.dp)
42 | .size(24.dp),
43 | tint = MaterialTheme.colorScheme.secondary
44 | )
45 | }
46 | Column(
47 | modifier = Modifier
48 | .weight(1f)
49 | .padding(start = if (icon == null) 14.dp else 0.dp)
50 | ) {
51 | Text(
52 | text = title,
53 | maxLines = 1,
54 | style = MaterialTheme.typography.titleLarge,
55 | color = MaterialTheme.colorScheme.onSurface
56 | )
57 | Text(
58 | text = description,
59 | color = MaterialTheme.colorScheme.onSurfaceVariant,
60 | maxLines = 1,
61 | style = MaterialTheme.typography.bodyMedium
62 | )
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/SwitchPref.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.settings.components
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.interaction.MutableInteractionSource
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.Spacer
9 | import androidx.compose.foundation.layout.fillMaxWidth
10 | import androidx.compose.foundation.layout.padding
11 | import androidx.compose.foundation.layout.width
12 | import androidx.compose.material3.Switch
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.getValue
16 | import androidx.compose.runtime.remember
17 | import androidx.compose.runtime.setValue
18 | import androidx.compose.ui.Alignment
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.unit.dp
21 | import androidx.compose.ui.unit.sp
22 | import app.suhasdissa.vibeyou.utils.rememberPreference
23 |
24 | @Composable
25 | fun SwitchPref(
26 | prefKey: String,
27 | title: String,
28 | summary: String? = null,
29 | defaultValue: Boolean = false,
30 | onCheckedChange: (Boolean) -> Unit = {}
31 | ) {
32 | var checked by rememberPreference(key = prefKey, defaultValue = defaultValue)
33 | val interactionSource = remember { MutableInteractionSource() }
34 |
35 | Row(
36 | modifier = Modifier
37 | .fillMaxWidth()
38 | .padding(horizontal = 12.dp)
39 | .clickable(
40 | interactionSource = interactionSource,
41 | indication = null
42 | ) {
43 | checked = !checked
44 | onCheckedChange.invoke(checked)
45 | },
46 | horizontalArrangement = Arrangement.SpaceBetween,
47 | verticalAlignment = Alignment.CenterVertically
48 | ) {
49 | Column(
50 | modifier = Modifier.weight(1f),
51 | verticalArrangement = Arrangement.Center
52 | ) {
53 | Text(fontSize = 18.sp, text = title)
54 | if (summary != null) {
55 | Text(modifier = Modifier.padding(top = 6.dp), text = summary)
56 | }
57 | }
58 | Spacer(modifier = Modifier.width(6.dp))
59 | Switch(
60 | checked = checked,
61 | onCheckedChange = {
62 | checked = it
63 | onCheckedChange.invoke(it)
64 | }
65 | )
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/TextFieldPref.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.settings.components
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material3.OutlinedTextField
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.getValue
9 | import androidx.compose.runtime.mutableStateOf
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.runtime.setValue
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.unit.dp
14 | import androidx.core.content.edit
15 | import app.suhasdissa.vibeyou.utils.Pref
16 |
17 | @Composable
18 | fun TextFieldPref(
19 | key: String,
20 | defaultValue: String,
21 | title: String,
22 | onValueChange: (String) -> Unit = {}
23 | ) {
24 | var value by remember {
25 | mutableStateOf(Pref.sharedPreferences.getString(key, defaultValue).orEmpty())
26 | }
27 |
28 | OutlinedTextField(
29 | modifier = Modifier
30 | .fillMaxWidth()
31 | .padding(horizontal = 8.dp),
32 | value = value,
33 | onValueChange = {
34 | value = it
35 | Pref.sharedPreferences.edit(true) { putString(key, it) }
36 | onValueChange(it)
37 | },
38 | label = {
39 | Text(text = title)
40 | }
41 | )
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/AuthViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.backend.viewmodel
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import android.widget.Toast
6 | import androidx.core.content.edit
7 | import androidx.lifecycle.ViewModel
8 | import androidx.lifecycle.ViewModelProvider
9 | import androidx.lifecycle.viewModelScope
10 | import androidx.lifecycle.viewmodel.initializer
11 | import androidx.lifecycle.viewmodel.viewModelFactory
12 | import app.suhasdissa.vibeyou.MellowMusicApplication
13 | import app.suhasdissa.vibeyou.backend.models.Login
14 | import app.suhasdissa.vibeyou.backend.repository.AuthRepository
15 | import app.suhasdissa.vibeyou.utils.Pref
16 | import app.suhasdissa.vibeyou.utils.preferences
17 | import kotlinx.coroutines.launch
18 |
19 | class AuthViewModel(private val authRepository: AuthRepository) : ViewModel() {
20 | fun login(context: Context, login: Login) {
21 | viewModelScope.launch {
22 | Toast.makeText(context, "Authenticating..", Toast.LENGTH_SHORT).show()
23 | val token = try {
24 | authRepository.getAuthToken(login)
25 | } catch (e: Exception) {
26 | Log.e("Playlist Info", e.toString())
27 | Toast.makeText(context, "Login Failed", Toast.LENGTH_SHORT).show()
28 | null
29 | }
30 | token?.token?.let {
31 | context.preferences.edit {
32 | putString(Pref.authTokenKey, it)
33 | }
34 | } ?: return@launch
35 | Toast.makeText(context, "Login Success", Toast.LENGTH_SHORT).show()
36 | }
37 | }
38 |
39 | companion object {
40 | val Factory: ViewModelProvider.Factory = viewModelFactory {
41 | initializer {
42 | val application =
43 | (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication)
44 | AuthViewModel(
45 | application.container.authRepository
46 | )
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/CheckUpdateViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.settings.model
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import app.suhasdissa.vibeyou.utils.UpdateUtil
9 | import kotlinx.coroutines.launch
10 |
11 | class CheckUpdateViewModel : ViewModel() {
12 | var latestVersion: Float? by mutableStateOf(null)
13 | val currentVersion = UpdateUtil.currentVersion
14 |
15 | init {
16 | getLatestRelease()
17 | }
18 |
19 | private fun getLatestRelease() {
20 | viewModelScope.launch {
21 | latestVersion = UpdateUtil.getLatestVersion()
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/DatabaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.settings.model
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.net.Uri
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.ViewModelProvider
8 | import androidx.lifecycle.viewModelScope
9 | import androidx.lifecycle.viewmodel.initializer
10 | import androidx.lifecycle.viewmodel.viewModelFactory
11 | import androidx.sqlite.db.SimpleSQLiteQuery
12 | import app.suhasdissa.vibeyou.MellowMusicApplication
13 | import app.suhasdissa.vibeyou.data.database.SongDatabase
14 | import app.suhasdissa.vibeyou.utils.services.PlayerService
15 | import kotlinx.coroutines.launch
16 | import java.io.FileInputStream
17 | import java.io.FileOutputStream
18 | import kotlin.system.exitProcess
19 |
20 | class DatabaseViewModel(private val database: SongDatabase) : ViewModel() {
21 |
22 | init {
23 | }
24 |
25 | fun backupDatabase(uri: Uri, context: Context) {
26 | viewModelScope.launch {
27 | database.rawDao().raw(SimpleSQLiteQuery("PRAGMA wal_checkpoint(FULL)"))
28 |
29 | context.applicationContext.contentResolver.openOutputStream(uri)
30 | ?.use { outputStream ->
31 | FileInputStream(database.openHelper.writableDatabase.path).use { inputStream ->
32 | inputStream.copyTo(outputStream)
33 | }
34 | }
35 | }
36 | }
37 |
38 | fun restoreDatabase(uri: Uri, context: Context) {
39 | viewModelScope.launch {
40 | database.rawDao().raw(SimpleSQLiteQuery("PRAGMA wal_checkpoint(FULL)"))
41 | database.close()
42 |
43 | context.applicationContext.contentResolver.openInputStream(uri)
44 | ?.use { inputStream ->
45 | FileOutputStream(
46 | database.openHelper.writableDatabase.path
47 | ).use { outputStream ->
48 | inputStream.copyTo(outputStream)
49 | }
50 | }
51 | context.stopService(Intent(context, PlayerService::class.java))
52 | exitProcess(0)
53 | }
54 | }
55 |
56 | companion object {
57 | val Factory: ViewModelProvider.Factory = viewModelFactory {
58 | initializer {
59 | val application =
60 | (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication)
61 | DatabaseViewModel(
62 | application.container.database
63 | )
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/SettingsModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.settings.model
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import androidx.annotation.StringRes
6 | import androidx.compose.runtime.getValue
7 | import androidx.compose.runtime.setValue
8 | import androidx.core.content.edit
9 | import androidx.lifecycle.ViewModel
10 | import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
11 | import androidx.lifecycle.viewmodel.initializer
12 | import androidx.lifecycle.viewmodel.viewModelFactory
13 | import app.suhasdissa.vibeyou.R
14 | import app.suhasdissa.vibeyou.utils.Pref
15 | import app.suhasdissa.vibeyou.utils.catpucchinLatte
16 | import app.suhasdissa.vibeyou.utils.mutableStatePreferenceOf
17 | import app.suhasdissa.vibeyou.utils.preferences
18 |
19 | class SettingsModel(preferences: SharedPreferences) : ViewModel() {
20 | enum class Theme(@StringRes val resId: Int) {
21 | SYSTEM(R.string.system), LIGHT(R.string.light), DARK(R.string.dark),
22 | AMOLED(R.string.amoled)
23 | }
24 |
25 | enum class ColorTheme(@StringRes val resId: Int) {
26 | SYSTEM(R.string.system),
27 | CATPPUCCIN(R.string.catppuccin)
28 | }
29 |
30 | private val themeModePref =
31 | preferences.getString(Pref.themeKey, Theme.SYSTEM.name) ?: Theme.SYSTEM.name
32 |
33 | var themeMode: Theme by mutableStatePreferenceOf(
34 | Theme.valueOf(themeModePref.uppercase())
35 | ) {
36 | preferences.edit { putString(Pref.themeKey, it.name) }
37 | }
38 |
39 | private val colorThemePref =
40 | preferences.getString(Pref.colorThemeKey, ColorTheme.SYSTEM.name)
41 | ?: ColorTheme.SYSTEM.name
42 |
43 | var colorTheme: ColorTheme by mutableStatePreferenceOf(
44 | ColorTheme.valueOf(colorThemePref.uppercase())
45 | ) {
46 | preferences.edit { putString(Pref.colorThemeKey, it.name) }
47 | }
48 |
49 | var customColor by mutableStatePreferenceOf(
50 | preferences.getInt(
51 | Pref.customColorKey,
52 | catpucchinLatte.first()
53 | )
54 | ) {
55 | preferences.edit { putInt(Pref.customColorKey, it) }
56 | }
57 |
58 | companion object {
59 | val Factory = viewModelFactory {
60 | initializer {
61 | val context = this[APPLICATION_KEY] as Context
62 | SettingsModel(preferences = context.preferences)
63 | }
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/SettingsViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.presentation.screens.settings.model
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.ViewModelProvider
8 | import androidx.lifecycle.viewmodel.initializer
9 | import androidx.lifecycle.viewmodel.viewModelFactory
10 | import app.suhasdissa.vibeyou.MellowMusicApplication
11 | import app.suhasdissa.vibeyou.backend.repository.PipedMusicRepository
12 | import app.suhasdissa.vibeyou.utils.Pref
13 | import kotlinx.coroutines.Dispatchers
14 | import kotlinx.coroutines.withContext
15 |
16 | class SettingsViewModel(
17 | private val pipedMusicRepository: PipedMusicRepository
18 | ) : ViewModel() {
19 | var instances by mutableStateOf(Pref.pipedInstances)
20 |
21 | suspend fun loadInstances() = runCatching {
22 | instances = withContext(Dispatchers.IO) {
23 | pipedMusicRepository.pipedApi.getInstanceList()
24 | }
25 | }
26 |
27 | companion object {
28 | val Factory: ViewModelProvider.Factory = viewModelFactory {
29 | initializer {
30 | val application =
31 | (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication)
32 | SettingsViewModel(application.container.pipedMusicRepository)
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple80 = Color(0xFFD0BCFF)
6 | val PurpleGrey80 = Color(0xFFCCC2DC)
7 | val Pink80 = Color(0xFFEFB8C8)
8 |
9 | val Purple40 = Color(0xFF6650a4)
10 | val PurpleGrey40 = Color(0xFF625b71)
11 | val Pink40 = Color(0xFF7D5260)
12 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.ui.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.foundation.isSystemInDarkTheme
6 | import androidx.compose.material3.ColorScheme
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.material3.dynamicDarkColorScheme
9 | import androidx.compose.material3.dynamicLightColorScheme
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.SideEffect
12 | import androidx.compose.ui.graphics.Color
13 | import androidx.compose.ui.platform.LocalContext
14 | import androidx.compose.ui.platform.LocalView
15 | import androidx.core.view.WindowCompat
16 |
17 | @Composable
18 | fun VibeYouTheme(
19 | darkTheme: Boolean = isSystemInDarkTheme(),
20 | customColorScheme: ColorScheme,
21 | dynamicColor: Boolean = true,
22 | amoledDark: Boolean = false,
23 | content: @Composable () -> Unit
24 | ) {
25 | val colorScheme = when {
26 | amoledDark -> {
27 | if (dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
28 | val context = LocalContext.current
29 | dynamicDarkColorScheme(context).copy(
30 | background = Color.Black,
31 | surface = Color.Black
32 | )
33 | } else {
34 | customColorScheme.copy(background = Color.Black, surface = Color.Black)
35 | }
36 | }
37 |
38 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
39 | val context = LocalContext.current
40 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
41 | }
42 |
43 | else -> customColorScheme
44 | }
45 | val view = LocalView.current
46 | if (!view.isInEditMode) {
47 | SideEffect {
48 | val activity = view.context as Activity
49 | val insetsController = WindowCompat.getInsetsController(
50 | activity.window,
51 | view
52 | )
53 | insetsController.isAppearanceLightStatusBars = !darkTheme
54 | insetsController.isAppearanceLightNavigationBars = !darkTheme
55 | }
56 | }
57 |
58 | MaterialTheme(
59 | colorScheme = colorScheme,
60 | typography = Typography,
61 | content = content
62 | )
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyLarge = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | lineHeight = 24.sp,
16 | letterSpacing = 0.5.sp
17 | )
18 | /* Other default text styles to override
19 | titleLarge = TextStyle(
20 | fontFamily = FontFamily.Default,
21 | fontWeight = FontWeight.Normal,
22 | fontSize = 22.sp,
23 | lineHeight = 28.sp,
24 | letterSpacing = 0.sp
25 | ),
26 | labelSmall = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Medium,
29 | fontSize = 11.sp,
30 | lineHeight = 16.sp,
31 | letterSpacing = 0.5.sp
32 | )
33 | */
34 | )
35 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/utils/Convertions.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.utils
2 |
3 | import kotlin.math.ln
4 | import kotlin.math.pow
5 |
6 | fun formatMB(mb: Int): String {
7 | val index = (ln(mb.toDouble()) / ln(1024.0)).toInt()
8 | return "${mb.div(1024f.pow(index)).toInt()} ${arrayOf("M", "G", "T")[index]}B"
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/utils/DynamicDataSource.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.utils
2 |
3 | import android.net.Uri
4 | import androidx.media3.common.util.UnstableApi
5 | import androidx.media3.datasource.DataSource
6 | import androidx.media3.datasource.DataSpec
7 | import androidx.media3.datasource.DefaultDataSource
8 | import androidx.media3.datasource.ResolvingDataSource
9 | import androidx.media3.datasource.TransferListener
10 |
11 | /**
12 | * A dynamic data source, which attempts to play from the cache if the source is online
13 | * Else, if the source uri is not online, it doesn't cache and plays directly from the device storage
14 | */
15 | @UnstableApi
16 | class DynamicDataSource(
17 | private val resolvingDataSource: ResolvingDataSource,
18 | private val defaultDataSource: DefaultDataSource
19 | ) : DataSource {
20 | private var isOnline = false
21 | private val dataSource get() = if (isOnline) resolvingDataSource else defaultDataSource
22 |
23 | override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
24 | return dataSource.read(buffer, offset, length)
25 | }
26 |
27 | override fun addTransferListener(transferListener: TransferListener) {
28 | dataSource.addTransferListener(transferListener)
29 | }
30 |
31 | override fun open(dataSpec: DataSpec): Long {
32 | isOnline = dataSpec.uri.scheme != "content"
33 | return dataSource.open(dataSpec)
34 | }
35 |
36 | override fun getUri(): Uri? = dataSource.uri
37 |
38 | override fun close() {
39 | dataSource.close()
40 | }
41 |
42 | companion object {
43 | class Factory(
44 | private val resolvingDataSource: ResolvingDataSource.Factory,
45 | private val defaultDataSource: DefaultDataSource.Factory
46 | ) : DataSource.Factory {
47 | override fun createDataSource(): DataSource {
48 | return DynamicDataSource(
49 | resolvingDataSource.createDataSource(),
50 | defaultDataSource.createDataSource()
51 | )
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/utils/Mappers.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.utils
2 |
3 | import android.annotation.SuppressLint
4 | import android.text.format.DateUtils
5 | import androidx.core.net.toUri
6 | import androidx.core.os.bundleOf
7 | import androidx.media3.common.MediaItem
8 | import androidx.media3.common.MediaMetadata
9 | import app.suhasdissa.vibeyou.backend.models.PipedSongResponse
10 | import app.suhasdissa.vibeyou.backend.models.playlists.Playlist
11 | import app.suhasdissa.vibeyou.backend.models.songs.SongItem
12 | import app.suhasdissa.vibeyou.data.database.entities.PlaylistEntity
13 | import app.suhasdissa.vibeyou.data.database.entities.SongEntity
14 | import app.suhasdissa.vibeyou.domain.models.primary.Album
15 | import app.suhasdissa.vibeyou.domain.models.primary.Artist
16 | import app.suhasdissa.vibeyou.domain.models.primary.Song
17 |
18 | val SongItem.asSong: Song
19 | get() = Song(
20 | id = videoId,
21 | title = title,
22 | artistsText = uploaderName,
23 | durationText = DateUtils.formatElapsedTime(duration.toLong()),
24 | thumbnailUri = thumbnail.toUri()
25 | )
26 |
27 | val Song.asSongEntity: SongEntity
28 | get() = SongEntity(
29 | id = id,
30 | title = title,
31 | artistsText = artistsText,
32 | durationText = durationText,
33 | thumbnailUrl = thumbnailUri.toString(),
34 | likedAt = likedAt
35 | )
36 |
37 | val SongEntity.asSong: Song
38 | get() = Song(
39 | id = id,
40 | title = title,
41 | artistsText = artistsText,
42 | durationText = durationText,
43 | thumbnailUri = thumbnailUrl?.toUri(),
44 | likedAt = likedAt
45 | )
46 |
47 | fun PipedSongResponse.asSong(id: String): Song {
48 | return Song(
49 | id = id,
50 | title = title!!,
51 | artistsText = uploader ?: "",
52 | durationText = DateUtils.formatElapsedTime(duration?.toLong() ?: 0L),
53 | thumbnailUri = thumbnailUrl?.toUri()
54 | )
55 | }
56 |
57 | val Playlist.asAlbum: Album
58 | get() = Album(
59 | id = playlistId,
60 | title = name,
61 | artistsText = uploaderName,
62 | thumbnailUri = thumbnail.toUri()
63 | )
64 |
65 | val Album.asPlaylistEntity: PlaylistEntity
66 | get() = PlaylistEntity(
67 | id = id,
68 | title = title,
69 | type = type,
70 | subTitle = artistsText,
71 | thumbnailUrl = thumbnailUri.toString()
72 | )
73 | val PlaylistEntity.asAlbum: Album
74 | get() = Album(
75 | id = id,
76 | title = title,
77 | thumbnailUri = thumbnailUrl?.toUri(),
78 | artistsText = subTitle ?: "",
79 | isLocal = true,
80 | type = type
81 | )
82 | val app.suhasdissa.vibeyou.backend.models.artists.Artist.asArtist: Artist
83 | get() = Artist(
84 | id = artistId,
85 | thumbnailUri = thumbnail?.toUri(),
86 | description = description,
87 | artistsText = name
88 | )
89 |
90 | val Song.asMediaItem: MediaItem
91 | @SuppressLint("UnsafeOptInUsageError")
92 | get() = MediaItem.Builder()
93 | .setMediaMetadata(
94 | MediaMetadata.Builder()
95 | .setTitle(title)
96 | .setArtist(artistsText)
97 | .setArtworkUri(thumbnailUri)
98 | .setExtras(
99 | bundleOf(IS_LOCAL_KEY to isLocal)
100 | )
101 | .build()
102 | )
103 | .setUri(id)
104 | .setMediaId(id)
105 | .setCustomCacheKey(id)
106 | .build()
107 |
108 | val MediaItem.maxResThumbnail: String
109 | get() {
110 | if (mediaId.startsWith("content")) return ""
111 | val pipedProxyUrl =
112 | Pref.currentInstance.imageProxyUrl.ifEmpty { "https://pipedproxy.kavin.rocks" }
113 | return "$pipedProxyUrl/vi_webp/$mediaId/maxresdefault.webp?host=i.ytimg.com"
114 | }
115 |
116 | const val IS_LOCAL_KEY = "isLocal"
117 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/utils/OpenBrowser.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.utils
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.net.Uri
6 |
7 | fun openBrowser(context: Context, url: String) {
8 | val viewIntent: Intent = Intent().apply {
9 | action = Intent.ACTION_VIEW
10 | data = Uri.parse(url)
11 | }
12 | context.startActivity(viewIntent)
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/utils/PermissionHelper.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.utils
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.pm.PackageManager
6 | import androidx.core.app.ActivityCompat
7 |
8 | object PermissionHelper {
9 | fun checkPermissions(context: Context, permissions: Array): Boolean {
10 | if (permissions.isEmpty()) return true
11 | if (!hasPermission(context, permissions.first())) {
12 | ActivityCompat.requestPermissions(
13 | context as Activity,
14 | permissions,
15 | 1
16 | )
17 | return false
18 | }
19 | return true
20 | }
21 |
22 | private fun hasPermission(context: Context, permission: String): Boolean {
23 | return ActivityCompat.checkSelfPermission(
24 | context,
25 | permission
26 | ) == PackageManager.PERMISSION_GRANTED
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/utils/Player.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.utils
2 |
3 | import androidx.media3.common.C
4 | import androidx.media3.common.MediaItem
5 | import androidx.media3.common.Player
6 | import androidx.media3.common.Timeline
7 |
8 | inline val Timeline.windows: List
9 | get() = List(windowCount) {
10 | getWindow(it, Timeline.Window())
11 | }
12 |
13 | inline val Timeline.queue: List>
14 | get() = List(windowCount) {
15 | it to getWindow(it, Timeline.Window()).mediaItem
16 | }
17 |
18 | inline val Timeline.mediaIdList: List
19 | get() = List(windowCount) {
20 | getWindow(it, Timeline.Window()).mediaItem.mediaId
21 | }
22 |
23 | fun Player.playPause() {
24 | if (isPlaying) {
25 | pause()
26 | } else {
27 | if (playbackState == Player.STATE_IDLE) {
28 | prepare()
29 | }
30 | play()
31 | }
32 | }
33 |
34 | fun Player.forcePlay(mediaItem: MediaItem) {
35 | setMediaItem(mediaItem, true)
36 | playWhenReady = true
37 | prepare()
38 | }
39 |
40 | fun Player.playGracefully(mediaItem: MediaItem) {
41 | if (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED) {
42 | forcePlay(mediaItem)
43 | } else {
44 | val newIndex = currentPeriodIndex + 1
45 | addMediaItem(newIndex, mediaItem)
46 | seekTo(newIndex, C.TIME_UNSET)
47 | }
48 | }
49 |
50 | fun Player.seekNext() {
51 | if (playbackState == Player.STATE_IDLE) {
52 | prepare()
53 | }
54 | seekToNext()
55 |
56 | }
57 |
58 | fun Player.seek(position: Long) {
59 | if (playbackState == Player.STATE_IDLE) {
60 | prepare()
61 | }
62 | seekTo(position)
63 | }
64 |
65 | fun Player.enqueue(mediaItem: MediaItem) {
66 | if (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED) {
67 | forcePlay(mediaItem)
68 | } else {
69 | addMediaItem(mediaItemCount, mediaItem)
70 | }
71 | }
72 |
73 | fun Player.forcePlayAtIndex(mediaItems: List, mediaItemIndex: Int) {
74 | if (mediaItems.isEmpty()) return
75 |
76 | setMediaItems(mediaItems, mediaItemIndex, C.TIME_UNSET)
77 | playWhenReady = true
78 | prepare()
79 | }
80 |
81 | fun Player.forcePlayFromBeginning(mediaItems: List) =
82 | forcePlayAtIndex(mediaItems, 0)
83 |
84 | fun Player.enqueue(mediaItems: List) {
85 | if (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED) {
86 | forcePlayFromBeginning(mediaItems)
87 | } else {
88 | addMediaItems(mediaItemCount, mediaItems)
89 | }
90 | }
91 |
92 | fun Player.addNext(mediaItem: MediaItem) {
93 | if (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED) {
94 | forcePlay(mediaItem)
95 | } else {
96 | addMediaItem(currentMediaItemIndex + 1, mediaItem)
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/utils/Pref.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.utils
2 |
3 | import android.content.SharedPreferences
4 | import androidx.core.content.edit
5 | import app.suhasdissa.vibeyou.backend.models.PipedInstance
6 | import kotlinx.serialization.encodeToString
7 | import kotlinx.serialization.json.Json
8 | import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
9 |
10 | object Pref {
11 | private const val pipedInstanceKey = "SelectedPipedInstanceKey"
12 | const val authTokenKey = "AuthTokenKey"
13 | const val exoCacheKey = "ExoCacheKey"
14 | const val thumbnailColorFallbackKey = "ThumbnailColorFallbackef"
15 | const val latestSongsSortOrderKey = "LatestSongsSortOrderKey"
16 | const val latestReverseSongsPrefKey = "LatestReverseSongsPrefKey"
17 | const val customPipedInstanceKey = "CustomPipedInstanceKey"
18 | const val disableSearchHistoryKey = "DisableSearchHistory"
19 | const val hyperpipeApiUrlKey = "HyperpipeApiUrl"
20 | const val customColorKey = "customColor"
21 | const val themeKey = "theme"
22 | const val colorThemeKey = "colorTheme"
23 | const val equalizerKey = "equalizer"
24 | const val equalizerPresetKey = "equalizerPreset"
25 | const val equalizerBandsKey = "equalizerBands"
26 | const val showAllMusicKey = "showAllMusicKey"
27 | const val musicDirectoriesKey = "musicDirectoriesKey"
28 |
29 | lateinit var sharedPreferences: SharedPreferences
30 |
31 | val defaultHyperInstance = "https://hyperpipeapi.onrender.com"
32 |
33 | val pipedInstances = listOf(
34 | PipedInstance(
35 | "kavin.rocks Libre",
36 | "https://pipedapi-libre.kavin.rocks"
37 | ),
38 | PipedInstance(
39 | "kavin.rocks",
40 | "https://pipedapi.kavin.rocks"
41 | ),
42 | PipedInstance(
43 | "lunar.icu",
44 | "https://piped-api.lunar.icu"
45 | ),
46 | PipedInstance(
47 | "whatever.social",
48 | "https://watchapi.whatever.social"
49 | ),
50 | PipedInstance(
51 | "tokhmi.xyz",
52 | "https://pipedapi.tokhmi.xyz"
53 | ),
54 | PipedInstance(
55 | "mha.fi",
56 | "https://api-piped.mha.fi"
57 | ),
58 | PipedInstance(
59 | "garudalinux.org",
60 | "https://piped-api.garudalinux.org"
61 | ),
62 | PipedInstance(
63 | "piped.yt",
64 | "https://api.piped.yt"
65 | )
66 | )
67 |
68 | val currentInstance
69 | get() = run {
70 | runCatching {
71 | val instanceJsonStr = sharedPreferences.getString(pipedInstanceKey, "").orEmpty()
72 | return@run json.decodeFromString(instanceJsonStr)
73 | }
74 | pipedInstances.first()
75 | }
76 |
77 | val hyperInstance: String?
78 | get() {
79 | val url = sharedPreferences.getString(hyperpipeApiUrlKey, defaultHyperInstance)
80 | ?: defaultHyperInstance
81 | return url.toHttpUrlOrNull()?.host
82 | }
83 |
84 | private val json = Json { ignoreUnknownKeys = true }
85 |
86 | fun setInstance(instance: PipedInstance) {
87 | val instanceJson = json.encodeToString(instance)
88 | sharedPreferences.edit(commit = true) { putString(pipedInstanceKey, instanceJson) }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/utils/Preferences.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.utils
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.MutableState
7 | import androidx.compose.runtime.SnapshotMutationPolicy
8 | import androidx.compose.runtime.mutableStateOf
9 | import androidx.compose.runtime.remember
10 | import androidx.compose.ui.platform.LocalContext
11 | import androidx.core.content.edit
12 |
13 | inline fun > SharedPreferences.getEnum(
14 | key: String,
15 | defaultValue: T
16 | ): T =
17 | getString(key, null)?.let {
18 | try {
19 | enumValueOf(it)
20 | } catch (e: IllegalArgumentException) {
21 | null
22 | }
23 | } ?: defaultValue
24 |
25 | inline fun > SharedPreferences.Editor.putEnum(
26 | key: String,
27 | value: T
28 | ): SharedPreferences.Editor =
29 | putString(key, value.name)
30 |
31 | val Context.preferences: SharedPreferences
32 | get() = getSharedPreferences("preferences", Context.MODE_PRIVATE)
33 |
34 | @Composable
35 | fun rememberPreference(key: String, defaultValue: Boolean): MutableState {
36 | val context = LocalContext.current
37 | return remember {
38 | mutableStatePreferenceOf(context.preferences.getBoolean(key, defaultValue)) {
39 | context.preferences.edit { putBoolean(key, it) }
40 | }
41 | }
42 | }
43 |
44 | @Composable
45 | fun rememberPreference(key: String, defaultValue: Int): MutableState {
46 | val context = LocalContext.current
47 | return remember {
48 | mutableStatePreferenceOf(context.preferences.getInt(key, defaultValue)) {
49 | context.preferences.edit { putInt(key, it) }
50 | }
51 | }
52 | }
53 |
54 | @Composable
55 | fun rememberPreference(key: String, defaultValue: String): MutableState {
56 | val context = LocalContext.current
57 | return remember {
58 | mutableStatePreferenceOf(context.preferences.getString(key, null) ?: defaultValue) {
59 | context.preferences.edit { putString(key, it) }
60 | }
61 | }
62 | }
63 |
64 | @Composable
65 | inline fun > rememberPreference(key: String, defaultValue: T): MutableState {
66 | val context = LocalContext.current
67 | return remember {
68 | mutableStatePreferenceOf(context.preferences.getEnum(key, defaultValue)) {
69 | context.preferences.edit { putEnum(key, it) }
70 | }
71 | }
72 | }
73 |
74 | inline fun mutableStatePreferenceOf(
75 | value: T,
76 | crossinline onStructuralInequality: (newValue: T) -> Unit
77 | ) =
78 | mutableStateOf(
79 | value = value,
80 | policy = object : SnapshotMutationPolicy {
81 | override fun equivalent(a: T, b: T): Boolean {
82 | val areEquals = a == b
83 | if (!areEquals) onStructuralInequality(b)
84 | return areEquals
85 | }
86 | }
87 | )
88 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/utils/RetrofitHelper.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.utils
2 |
3 | import app.suhasdissa.vibeyou.data.api.HyperpipeApi
4 | import app.suhasdissa.vibeyou.data.api.PipedApi
5 | import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
6 | import kotlinx.serialization.json.Json
7 | import okhttp3.MediaType.Companion.toMediaType
8 | import retrofit2.Retrofit
9 | import retrofit2.create
10 |
11 | object RetrofitHelper {
12 | val json by lazy {
13 | Json { ignoreUnknownKeys = true }
14 | }
15 |
16 | fun createPipedApi(): PipedApi {
17 | val retrofit: Retrofit = Retrofit.Builder()
18 | .baseUrl("https://pipedapi.kavin.rocks/")
19 | .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
20 | .build()
21 |
22 | return retrofit.create()
23 | }
24 |
25 | fun createHyperpipeApi(): HyperpipeApi {
26 | val retrofit = Retrofit.Builder()
27 | .baseUrl("https://hyperpipeapi.onrender.com")
28 | .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
29 | .build()
30 |
31 | return retrofit.create()
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/utils/ThemeUtil.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.utils
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.compose.material3.ColorScheme
5 | import androidx.compose.ui.graphics.Color
6 | import com.google.android.material.color.utilities.Scheme
7 |
8 | object ThemeUtil {
9 | @SuppressLint("RestrictedApi")
10 | fun getSchemeFromSeed(color: Int, dark: Boolean): ColorScheme {
11 | return if (dark) {
12 | Scheme.dark(color).toColorScheme()
13 | } else {
14 | Scheme.light(color).toColorScheme()
15 | }
16 | }
17 | }
18 |
19 | val catpucchinLatte = arrayOf(
20 | android.graphics.Color.rgb(220, 138, 120),
21 | android.graphics.Color.rgb(221, 120, 120),
22 | android.graphics.Color.rgb(234, 118, 203),
23 | android.graphics.Color.rgb(136, 57, 239),
24 | android.graphics.Color.rgb(210, 15, 57),
25 | android.graphics.Color.rgb(230, 69, 83),
26 | android.graphics.Color.rgb(254, 100, 11),
27 | android.graphics.Color.rgb(223, 142, 29),
28 | android.graphics.Color.rgb(64, 160, 43),
29 | android.graphics.Color.rgb(23, 146, 153),
30 | android.graphics.Color.rgb(4, 165, 229),
31 | android.graphics.Color.rgb(32, 159, 181),
32 | android.graphics.Color.rgb(30, 102, 245),
33 | android.graphics.Color.rgb(114, 135, 253)
34 | )
35 |
36 | @SuppressLint("RestrictedApi")
37 | fun Scheme.toColorScheme() = ColorScheme(
38 | primary = Color(primary),
39 | onPrimary = Color(onPrimary),
40 | primaryContainer = Color(primaryContainer),
41 | onPrimaryContainer = Color(onPrimaryContainer),
42 | inversePrimary = Color(inversePrimary),
43 | secondary = Color(secondary),
44 | onSecondary = Color(onSecondary),
45 | secondaryContainer = Color(secondaryContainer),
46 | onSecondaryContainer = Color(onSecondaryContainer),
47 | tertiary = Color(tertiary),
48 | onTertiary = Color(onTertiary),
49 | tertiaryContainer = Color(tertiaryContainer),
50 | onTertiaryContainer = Color(onTertiaryContainer),
51 | background = Color(background),
52 | onBackground = Color(onBackground),
53 | surface = Color(surface),
54 | onSurface = Color(onSurface),
55 | surfaceVariant = Color(surfaceVariant),
56 | onSurfaceVariant = Color(onSurfaceVariant),
57 | surfaceTint = Color(primary),
58 | inverseSurface = Color(inverseSurface),
59 | inverseOnSurface = Color(inverseOnSurface),
60 | error = Color(error),
61 | onError = Color(onError),
62 | errorContainer = Color(errorContainer),
63 | onErrorContainer = Color(onErrorContainer),
64 | outline = Color(outline),
65 | outlineVariant = Color(outlineVariant),
66 | scrim = Color(scrim)
67 | )
68 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/utils/TimeUtil.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.utils
2 |
3 | import java.time.Instant
4 | import java.time.ZoneId
5 |
6 | object TimeUtil {
7 | fun getYear(timestamp: Long): Int {
8 | val instant = Instant.ofEpochSecond(timestamp)
9 | return instant.atZone(ZoneId.systemDefault()).year
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/utils/UpdateUtil.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.utils
2 |
3 | import android.content.Context
4 | import android.content.pm.PackageManager
5 | import android.os.Build
6 | import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
7 | import kotlinx.serialization.SerialName
8 | import kotlinx.serialization.Serializable
9 | import kotlinx.serialization.json.Json
10 | import okhttp3.MediaType.Companion.toMediaType
11 | import retrofit2.Retrofit
12 | import retrofit2.http.GET
13 | import retrofit2.http.Headers
14 | import java.util.regex.Pattern
15 |
16 | object UpdateUtil {
17 | var currentVersion = 0f
18 |
19 | private suspend fun getLatestRelease(): LatestRelease? {
20 | return try {
21 | UpdateApi.retrofitService.getLatestRelease()
22 | } catch (e: Exception) {
23 | null
24 | }
25 | }
26 |
27 | suspend fun getLatestVersion(): Float? {
28 | return getLatestRelease()?.let {
29 | it.tagName.toVersion()
30 | }
31 | }
32 |
33 | fun getCurrentVersion(context: Context) {
34 | currentVersion = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
35 | context.packageManager.getPackageInfo(
36 | context.packageName,
37 | PackageManager.PackageInfoFlags.of(0)
38 | ).versionName.toFloat()
39 | } else {
40 | context.packageManager.getPackageInfo(
41 | context.packageName,
42 | 0
43 | ).versionName.toFloat()
44 | }
45 | }
46 |
47 | private val pattern = Pattern.compile("""v(.+)""")
48 |
49 | private fun String?.toVersion(): Float = this?.run {
50 | val matcher = pattern.matcher(this)
51 | if (matcher.find()) {
52 | matcher.group(1)?.toFloat() ?: 0f
53 | } else {
54 | 0f
55 | }
56 | } ?: 0f
57 | }
58 |
59 | @Serializable
60 | data class LatestRelease(
61 | @SerialName("tag_name") val tagName: String? = null
62 | )
63 |
64 | private val jsonFormat = Json { ignoreUnknownKeys = true }
65 |
66 | private val retrofitVideo = Retrofit.Builder()
67 | .baseUrl("https://api.github.com/")
68 | .addConverterFactory(jsonFormat.asConverterFactory("application/json".toMediaType()))
69 | .build()
70 |
71 | interface UpdateApiService {
72 | @Headers(
73 | "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
74 | )
75 | @GET("repos/you-apps/VibeYou/releases/latest")
76 | suspend fun getLatestRelease(): LatestRelease
77 | }
78 |
79 | object UpdateApi {
80 | val retrofitService: UpdateApiService by lazy {
81 | retrofitVideo.create(UpdateApiService::class.java)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/vibeyou/utils/UriSerializer.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.vibeyou.utils
2 |
3 | import android.net.Uri
4 | import androidx.core.net.toUri
5 | import kotlinx.serialization.KSerializer
6 | import kotlinx.serialization.descriptors.PrimitiveKind
7 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
8 | import kotlinx.serialization.descriptors.SerialDescriptor
9 | import kotlinx.serialization.encoding.Decoder
10 | import kotlinx.serialization.encoding.Encoder
11 |
12 | class UriSerializer : KSerializer {
13 |
14 | override val descriptor: SerialDescriptor =
15 | PrimitiveSerialDescriptor("Uri", PrimitiveKind.STRING)
16 |
17 | override fun deserialize(decoder: Decoder): Uri {
18 | return decoder.decodeString().toUri()
19 | }
20 |
21 | override fun serialize(encoder: Encoder, value: Uri) {
22 | encoder.encodeString(value.toString())
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/blob.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_monochrome.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_piped.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/music_placeholder.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-bn/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | সেটিংস
4 | আমাকে পড়ুন
5 | সম্পর্কে
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values-fa/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | آواها
4 | مرابخوان
5 | جدیدترین رهانش
6 | مورد علاقه ها
7 | پخش آوا
8 | تنظیمات
9 | پخش
10 | حذف مورد از صف
11 | صف پخش
12 | تنظیمات شبکه
13 | تغییر سرور
14 | انتخاب سرور
15 | دیدن صف
16 | هنرمند
17 | رمز عبور
18 | آواهای اخیر
19 | بستن جستجو
20 | آلبوم ها
21 | هنرمندان
22 | باشه
23 | درباره
24 | بی نهایت
25 | سرعت پخش
26 | پخش همه
27 | لغو
28 | تنظیمات ظاهر
29 | سیستم
30 | روشن
31 | تاریک
32 | فعال
33 | تنظیمات
34 | نسخه فعلی
35 | اضافه کردن به صف
36 | حذف آوا
37 | در حال پخش
38 | پاک کردن جستجو
39 | ورود
40 | نام کاربری
41 | جستجوی آواها
42 | جستجو برای یک آوا
43 | تنظیمات آوا
44 | بستن صف
45 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ja/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 設定
4 | 最新リリース
5 | 現在のバージョン
6 | GitHub Issue
7 | お気に入り
8 | キューに追加
9 | 曲を再生
10 | 曲を削除
11 | 全てリピート
12 | 再生中
13 | 詳細
14 | リポジトリとREADMEを確認
15 | バグ報告や機能リクエストを送信
16 | バージョン、連絡先
17 | README
18 | 次に再生
19 | 曲を検索
20 | 曲を検索
21 | 設定
22 | 繰り返しオフ
23 |
--------------------------------------------------------------------------------
/app/src/main/res/values-nb-rNO/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Favoritter
4 | Lukk avspiller
5 | Lukk kø
6 | Opprett en sikkerhetskopifil
7 | Nyeste utgave
8 | Passord
9 | Albumsomslag
10 | Nettverksinnstillinger
11 | Fjern element fra kø
12 | Nylige spor
13 | Gjenta alt
14 | Send inn feilrapporter og funksjonsforespørsler
15 | Hopp over neste
16 | Musikkhurtiglagringsgrense
17 | Endre tjener
18 | Pause
19 | Gjenopprett database
20 | Ubegrenset
21 | Spor-albumskunst
22 | Fjern spor
23 | Spill spor
24 | Gjenta én
25 | Innstillinger
26 | Brukernavn
27 | Gjenopprett en tidligere sikkerhetskopifil
28 | Sikkerhetskopi og gjenoppretting
29 | LESMEG
30 | Nåværende versjon
31 | Versjon, kontakt
32 | OK
33 | Artister
34 | Noe gikk galt
35 | Søk etter et spor
36 | Album
37 | Sikkerhetskopier database
38 | Artist
39 | Spillerkø
40 | Sporalternativer
41 | Vis kø
42 | Artistavatar
43 | Omstokking
44 | Sjekk kodelageret og LESMEG
45 | Velg tjener
46 | Spilles nå
47 | Spor
48 | Endre musikkhurtiglagringsstørrelse
49 | Logg inn
50 | Piped-musikk
51 | Endre tjener for å fikse avspillingsproblemer
52 | Tøm søk
53 | Innstillinger
54 | Spill
55 | Gjentagelse av
56 | Lokal musikk
57 | Legg til i kø
58 | GitHub-problem
59 | Sikkerhetskopier din sporliste, eller gjenopprett en gammel sikkerhetskopi.
60 | Lukk søk
61 | Søk etter spor
62 | Om
63 | Spill neste
64 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF000000
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values-pa/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ਸੈਟਿੰਗਾਂ
4 | ਮੈਨੂੰ ਪੜ੍ਹੋ
5 | ਗੀਤ
6 | ਦੇ ਬਾਰੇ
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values-sv/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Pausa
4 | Återställ databas
5 | Inställningar
6 | Avbryt
7 | Låtar
8 | Inställningar
9 | Om
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 歌曲
4 | 關於
5 | 設定
6 | 目前版本
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFFFF
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #EFF1F5
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id("com.android.application") version "8.4.0" apply false
4 | id("com.android.library") version "8.4.0" apply false
5 | id("org.jetbrains.kotlin.android") version "1.9.23" apply false
6 | id("com.google.devtools.ksp") version "1.9.23-1.0.20" apply false
7 | }
8 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | Vibe You is a music app that lets you play music from your device storage or from Piped Music.
2 | It has a Material You dynamic theme with dark mode support, offline cached playback, shuffle, queue, and more.
3 |
4 | With the added benefit of playing region blocked music, This app also has advanced queue management and offline caching for songs to play them later without data consumption.
5 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/fastlane/metadata/android/en-US/images/phoneScreenshots/6.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/fastlane/metadata/android/en-US/images/phoneScreenshots/7.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Music app supporting playback via local storage and Piped
2 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/you-apps/VibeYou/c5c7eb3e9a2228b715689d9524cdc98db139287d/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu May 02 18:14:03 IST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-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 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | maven("https://jitpack.io")
14 | }
15 | }
16 | rootProject.name = "VibeYou"
17 | include(":app")
18 |
--------------------------------------------------------------------------------