├── fastlane
└── metadata
│ └── android
│ └── en-US
│ ├── short_description.txt
│ ├── images
│ ├── icon.png
│ └── phoneScreenshots
│ │ ├── 01.jpg
│ │ ├── 02.jpg
│ │ └── 03.jpg
│ └── full_description.txt
├── android
├── gradle.properties
├── app
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ ├── colors.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── drawable
│ │ │ │ │ ├── background.png
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-hdpi
│ │ │ │ │ ├── splash.png
│ │ │ │ │ ├── android12splash.png
│ │ │ │ │ ├── audio_service_pause.png
│ │ │ │ │ ├── audio_service_stop.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ ├── audio_service_play_arrow.png
│ │ │ │ │ ├── audio_service_skip_next.png
│ │ │ │ │ └── audio_service_skip_previous.png
│ │ │ │ ├── drawable-mdpi
│ │ │ │ │ ├── splash.png
│ │ │ │ │ ├── android12splash.png
│ │ │ │ │ ├── audio_service_pause.png
│ │ │ │ │ ├── audio_service_stop.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ ├── audio_service_play_arrow.png
│ │ │ │ │ ├── audio_service_skip_next.png
│ │ │ │ │ └── audio_service_skip_previous.png
│ │ │ │ ├── drawable-xhdpi
│ │ │ │ │ ├── splash.png
│ │ │ │ │ ├── android12splash.png
│ │ │ │ │ ├── audio_service_pause.png
│ │ │ │ │ ├── audio_service_stop.png
│ │ │ │ │ ├── audio_service_play_arrow.png
│ │ │ │ │ ├── audio_service_skip_next.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── audio_service_skip_previous.png
│ │ │ │ ├── drawable-v21
│ │ │ │ │ ├── background.png
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-xxhdpi
│ │ │ │ │ ├── splash.png
│ │ │ │ │ ├── android12splash.png
│ │ │ │ │ ├── audio_service_stop.png
│ │ │ │ │ ├── audio_service_pause.png
│ │ │ │ │ ├── audio_service_skip_next.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ ├── audio_service_play_arrow.png
│ │ │ │ │ └── audio_service_skip_previous.png
│ │ │ │ ├── drawable-xxxhdpi
│ │ │ │ │ ├── splash.png
│ │ │ │ │ ├── android12splash.png
│ │ │ │ │ ├── audio_service_pause.png
│ │ │ │ │ ├── audio_service_stop.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ ├── audio_service_play_arrow.png
│ │ │ │ │ ├── audio_service_skip_next.png
│ │ │ │ │ └── audio_service_skip_previous.png
│ │ │ │ ├── drawable-night-hdpi
│ │ │ │ │ ├── splash.png
│ │ │ │ │ └── android12splash.png
│ │ │ │ ├── drawable-night-mdpi
│ │ │ │ │ ├── splash.png
│ │ │ │ │ └── android12splash.png
│ │ │ │ ├── drawable-night
│ │ │ │ │ ├── background.png
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── drawable-night-v21
│ │ │ │ │ ├── background.png
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-night-xhdpi
│ │ │ │ │ ├── splash.png
│ │ │ │ │ └── android12splash.png
│ │ │ │ ├── drawable-night-xxhdpi
│ │ │ │ │ ├── splash.png
│ │ │ │ │ └── android12splash.png
│ │ │ │ ├── drawable-night-xxxhdpi
│ │ │ │ │ ├── splash.png
│ │ │ │ │ └── android12splash.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── raw
│ │ │ │ │ └── keep.xml
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ └── launcher_icon.xml
│ │ │ │ ├── values-night
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── values-v31
│ │ │ │ │ └── styles.xml
│ │ │ │ └── values-night-v31
│ │ │ │ │ └── styles.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── gokadzev
│ │ │ │ │ └── musify
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ └── build.gradle
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
└── build.gradle
├── l10n.yaml
├── fonts
└── ubuntu.ttf
├── assets
├── images
│ ├── splash.png
│ ├── ic_launcher.png
│ ├── ic_launcher_round.png
│ ├── ic_launcher_background.jpg
│ └── ic_launcher_foreground.png
└── db
│ └── playlists.db.json
├── lib
├── helper
│ ├── url_launcher.dart
│ ├── flutter_toast.dart
│ ├── formatter.dart
│ ├── mediaitem.dart
│ └── version.dart
├── customWidgets
│ ├── spinner.dart
│ ├── setting_bar.dart
│ ├── marque.dart
│ ├── delayed_display.dart
│ ├── song_bar.dart
│ └── custom_animated_bottom_bar.dart
├── style
│ ├── appColors.dart
│ └── appTheme.dart
├── localization
│ ├── app_zh.arb
│ ├── app_en.arb
│ ├── app_he.arb
│ ├── app_hi.arb
│ ├── app_hu.arb
│ ├── app_de.arb
│ ├── app_id.arb
│ ├── app_nl.arb
│ ├── app_uk.arb
│ ├── app_tr.arb
│ ├── app_it.arb
│ ├── app_pl.arb
│ ├── app_ka.arb
│ ├── app_pt.arb
│ ├── app_fr.arb
│ └── app_es.arb
├── services
│ ├── ext_storage.dart
│ ├── data_manager.dart
│ ├── lyrics_service.dart
│ ├── download_manager.dart
│ └── audio_manager.dart
├── ui
│ ├── aboutPage.dart
│ ├── userPlaylistsPage.dart
│ ├── userLikedSongsPage.dart
│ ├── searchPage.dart
│ ├── playlistPage.dart
│ └── homePage.dart
├── main.dart
└── API
│ └── musify.dart
├── .metadata
├── .gitignore
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── feature_request.yml
│ └── bug_report.yml
└── workflows
│ ├── pre.yml
│ ├── main.yml
│ └── pc.yml
├── test
└── widget_test.dart
├── README.md
├── analysis_options.yaml
└── pubspec.yaml
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Music Streaming and Downloading app
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/l10n.yaml:
--------------------------------------------------------------------------------
1 | arb-dir: lib/localization
2 | template-arb-file: app_en.arb
3 | output-localization-file: app_localizations.dart
--------------------------------------------------------------------------------
/fonts/ubuntu.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/fonts/ubuntu.ttf
--------------------------------------------------------------------------------
/assets/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/assets/images/splash.png
--------------------------------------------------------------------------------
/assets/images/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/assets/images/ic_launcher.png
--------------------------------------------------------------------------------
/assets/images/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/assets/images/ic_launcher_round.png
--------------------------------------------------------------------------------
/assets/images/ic_launcher_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/assets/images/ic_launcher_background.jpg
--------------------------------------------------------------------------------
/assets/images/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/assets/images/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #191919
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable/background.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-hdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-mdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-v21/background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxxhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-hdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-night-hdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-mdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-night-mdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-night/background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-hdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-mdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-v21/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-night-v21/background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-night-xhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-night-xxhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xxxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-night-xxxhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xhdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxhdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/audio_service_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-hdpi/audio_service_pause.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/audio_service_stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-hdpi/audio_service_stop.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/audio_service_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-mdpi/audio_service_pause.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/audio_service_stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-mdpi/audio_service_stop.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/audio_service_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xhdpi/audio_service_pause.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/audio_service_stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xhdpi/audio_service_stop.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/audio_service_stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxhdpi/audio_service_stop.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxxhdpi/android12splash.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/01.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/02.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/03.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/03.jpg
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-hdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-night-hdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-mdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-night-mdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xhdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-night-xhdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/audio_service_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxhdpi/audio_service_pause.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/audio_service_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxxhdpi/audio_service_pause.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/audio_service_stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxxhdpi/audio_service_stop.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/audio_service_play_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-hdpi/audio_service_play_arrow.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/audio_service_skip_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-hdpi/audio_service_skip_next.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/audio_service_play_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-mdpi/audio_service_play_arrow.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/audio_service_skip_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-mdpi/audio_service_skip_next.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/audio_service_play_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xhdpi/audio_service_play_arrow.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/audio_service_skip_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xhdpi/audio_service_skip_next.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/audio_service_skip_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxhdpi/audio_service_skip_next.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/raw/keep.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/audio_service_skip_previous.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-hdpi/audio_service_skip_previous.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/audio_service_skip_previous.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-mdpi/audio_service_skip_previous.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/audio_service_play_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxhdpi/audio_service_play_arrow.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/audio_service_play_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxxhdpi/audio_service_play_arrow.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/audio_service_skip_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxxhdpi/audio_service_skip_next.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/audio_service_skip_previous.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xhdpi/audio_service_skip_previous.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/audio_service_skip_previous.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxhdpi/audio_service_skip_previous.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/audio_service_skip_previous.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdityaJasrai/Project-Flutter---Musify---MusicApplication/HEAD/android/app/src/main/res/drawable-xxxhdpi/audio_service_skip_previous.png
--------------------------------------------------------------------------------
/lib/helper/url_launcher.dart:
--------------------------------------------------------------------------------
1 | import 'package:url_launcher/url_launcher.dart';
2 |
3 | void launchURL(url) async {
4 | if (await canLaunchUrl(url)) {
5 | await launchUrl(url, mode: LaunchMode.externalApplication);
6 | } else {
7 | throw 'Could not launch $url';
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 1ad9baa8b99a2897c20f9e6e54d3b9b359ade314
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 | -
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 | -
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 | -
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 | -
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/lib/customWidgets/spinner.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:musify/style/appTheme.dart';
3 |
4 | class Spinner extends StatelessWidget {
5 | const Spinner({super.key});
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | return Center(
10 | child: CircularProgressIndicator(
11 | valueColor: AlwaysStoppedAnimation(accent.primary),
12 | ),
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | Musify is an app for streaming and downloading music. Its features include:
2 |
3 | * Online Song Search
4 | * Streaming Support
5 | * Download Support
6 | * Play Local / Downloaded Songs Supported
7 | * High Quality m4a / mp3 / flac Format
8 | * Lyrics Support
9 | * No Ads
10 | * No Subscriptions
11 | * SponsorBlock Support
12 | * 12 Supported Languages
13 | * Material UI & Accent Colors
14 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/lib/helper/flutter_toast.dart:
--------------------------------------------------------------------------------
1 | import 'package:fluttertoast/fluttertoast.dart';
2 | import 'package:musify/style/appColors.dart';
3 | import 'package:musify/style/appTheme.dart';
4 |
5 | void showToast(String text) {
6 | Fluttertoast.showToast(
7 | backgroundColor: getMaterialColorFromColor(accent.primary),
8 | textColor: isAccentWhite(),
9 | msg: text,
10 | toastLength: Toast.LENGTH_SHORT,
11 | gravity: ToastGravity.BOTTOM,
12 | fontSize: 14,
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.7.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.2.2'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/lib/customWidgets/setting_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:musify/style/appTheme.dart';
3 |
4 | class SettingBar extends StatelessWidget {
5 | SettingBar(this.tileName, this.tileIcon, this.onTap);
6 |
7 | final Function() onTap;
8 | final String tileName;
9 | final IconData tileIcon;
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Padding(
14 | padding: const EdgeInsets.only(top: 8, left: 8, right: 8, bottom: 6),
15 | child: Card(
16 | child: ListTile(
17 | leading: Icon(tileIcon, color: accent.primary),
18 | title: Text(
19 | tileName,
20 | style: TextStyle(color: accent.primary),
21 | ),
22 | onTap: onTap,
23 | ),
24 | ),
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/gokadzev/musify/MainActivity.kt:
--------------------------------------------------------------------------------
1 | import android.os.Build
2 | import android.os.Bundle
3 | import androidx.core.view.WindowCompat
4 | import io.flutter.embedding.android.FlutterActivity
5 |
6 | class MainActivity : FlutterActivity() {
7 | override fun onCreate(savedInstanceState: Bundle?) {
8 | // Aligns the Flutter view vertically with the window.
9 | WindowCompat.setDecorFitsSystemWindows(getWindow(), false)
10 |
11 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
12 | // Disable the Android splash screen fade out animation to avoid
13 | // a flicker before the similar frame is drawn in Flutter.
14 | splashScreen.setOnExitAnimationListener { splashScreenView -> splashScreenView.remove() }
15 | }
16 |
17 | super.onCreate(savedInstanceState)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | /build/
32 |
33 | # Web related
34 | lib/generated_plugin_registrant.dart
35 |
36 | # Symbolication related
37 | app.*.symbols
38 |
39 | # Obfuscation related
40 | app.*.map.json
41 |
42 | # Exceptions to above rules.
43 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
44 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: ['https://www.buymeacoffee.com/gokadzev18'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:musify/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: ⭐ Feature request
2 | description: Suggest a feature to improve the app
3 | labels: [feature request]
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 should 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 | - type: checkboxes
24 | id: acknowledgements
25 | attributes:
26 | label: Acknowledgements
27 | description: Your issue will be closed if you haven't done these steps.
28 | options:
29 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
30 | required: true
31 | - label: I have written a short but informative title.
32 | required: true
33 | - label: I will fill out all of the requested information in this form.
34 | required: true
35 |
--------------------------------------------------------------------------------
/lib/style/appColors.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:musify/style/appTheme.dart';
3 |
4 | Color getShade(Color color, {bool darker = false, double value = .1}) {
5 | assert(value >= 0 && value <= 1);
6 |
7 | final hsl = HSLColor.fromColor(color);
8 | final hslDark = hsl.withLightness(
9 | (darker ? (hsl.lightness - value) : (hsl.lightness + value))
10 | .clamp(0.0, 1.0),
11 | );
12 |
13 | return hslDark.toColor();
14 | }
15 |
16 | MaterialColor getMaterialColorFromColor(Color color) {
17 | final _colorShades = {
18 | 50: getShade(color, value: 0.5),
19 | 100: getShade(color, value: 0.4),
20 | 200: getShade(color, value: 0.3),
21 | 300: getShade(color, value: 0.2),
22 | 400: getShade(color, value: 0.1),
23 | 500: color,
24 | 600: getShade(color, value: 0.1, darker: true),
25 | 700: getShade(color, value: 0.15, darker: true),
26 | 800: getShade(color, value: 0.2, darker: true),
27 | 900: getShade(color, value: 0.25, darker: true),
28 | };
29 | return MaterialColor(color.value, _colorShades);
30 | }
31 |
32 | Color isAccentWhite() {
33 | return accent.primary != const Color(0xFFFFFFFF)
34 | ? Colors.white
35 | : Colors.black;
36 | }
37 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-v31/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night-v31/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/lib/helper/formatter.dart:
--------------------------------------------------------------------------------
1 | import 'package:youtube_explode_dart/youtube_explode_dart.dart';
2 |
3 | String formatSongTitle(String title) {
4 | return title
5 | .replaceAll('&', '&')
6 | .replaceAll(''', "'")
7 | .replaceAll('"', '"')
8 | .replaceAll('[Official Music Video]', '')
9 | .replaceAll('OFFICIAL MUSIC VIDEO', '')
10 | .replaceAll('Video', '')
11 | .replaceAll('[Official Video]', '')
12 | .replaceAll('[OFFICIAL VIDEO]', '')
13 | .replaceAll('[official music video]', '')
14 | .replaceAll('[Official Perfomance Video]', '')
15 | .replaceAll('[Lyrics]', '')
16 | .replaceAll('[Lyric Video]', '')
17 | .replaceAll('Lyric Video', '')
18 | .replaceAll('[Official Lyric Video]', '')
19 | .split(' (')[0]
20 | .split('|')[0]
21 | .trim();
22 | }
23 |
24 | Map returnSongLayout(dynamic index, Video song) {
25 | return {
26 | 'id': index,
27 | 'ytid': song.id.toString(),
28 | 'title': formatSongTitle(
29 | song.title.split('-')[song.title.split('-').length - 1],
30 | ),
31 | 'image': song.thumbnails.standardResUrl,
32 | 'lowResImage': song.thumbnails.lowResUrl,
33 | 'highResImage': song.thumbnails.maxResUrl,
34 | 'album': '',
35 | 'type': 'song',
36 | 'more_info': {
37 | 'primary_artists': song.title.split('-')[0],
38 | 'singers': song.title.split('-')[0],
39 | }
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/lib/helper/mediaitem.dart:
--------------------------------------------------------------------------------
1 | import 'package:audio_service/audio_service.dart';
2 | import 'package:on_audio_query/on_audio_query.dart';
3 |
4 | Map mediaItemToMap(MediaItem mediaItem) {
5 | return {
6 | 'id': mediaItem.id,
7 | 'ytid': mediaItem.extras!['ytid'],
8 | 'album': mediaItem.album.toString(),
9 | 'artist': mediaItem.artist.toString(),
10 | 'title': mediaItem.title,
11 | 'highResImage': mediaItem.artUri.toString(),
12 | 'lowResImage': mediaItem.extras!['lowResImage'],
13 | 'url': mediaItem.extras!['url'].toString(),
14 | };
15 | }
16 |
17 | MediaItem songModelToMediaItem(SongModel song, String songUrl) {
18 | return MediaItem(
19 | id: song.id.toString(),
20 | album: '',
21 | artist: '',
22 | title: song.displayName,
23 | artUri: Uri.parse(''),
24 | extras: {
25 | 'url': songUrl,
26 | 'lowResImage': '',
27 | 'ytid': '',
28 | 'localSongId': song.id,
29 | 'ogid': song.id
30 | },
31 | );
32 | }
33 |
34 | MediaItem mapToMediaItem(Map song, String songUrl) {
35 | return MediaItem(
36 | id: song['id'].toString(),
37 | album: '',
38 | artist: song['more_info']['singers'].toString(),
39 | title: song['title'].toString(),
40 | artUri: Uri.parse(
41 | song['highResImage'].toString(),
42 | ),
43 | extras: {
44 | 'url': songUrl,
45 | 'lowResImage': song['lowResImage'],
46 | 'ytid': song['ytid'],
47 | 'localSongId': song['localSongId']
48 | },
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/lib/localization/app_zh.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "关于",
3 | "accentChangeMsg": "重音颜色已更改",
4 | "accentColor": "强调色",
5 | "add": "添加",
6 | "addedSuccess": "Added successfully",
7 | "appUpdateAvailableAndDownloading": "应用程序更新可用并正在下载",
8 | "appUpdateIsAvailable": "应用程序更新可用",
9 | "appUpdateIsNotAvailable": "应用程序更新不可用",
10 | "audioFileType": "音频文件扩展名",
11 | "audioFileTypeMsg": "音频文件类型已更改",
12 | "backupUserData": "备份用户数据",
13 | "backupedSuccessfully": "备份成功",
14 | "cacheMsg": "缓存已清除",
15 | "clearCache": "清除缓存",
16 | "clearSearchHistory": "清除搜索历史",
17 | "downloadAppUpdate": "下载应用更新",
18 | "downloadCompleted": "Download Completed",
19 | "downloadStarted": "Download Started",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "语",
23 | "languageMsg": "语言已更改",
24 | "localSongs": "本地歌曲",
25 | "lyrics": "歌词",
26 | "lyricsNotAvailable": "没有可用的歌词 ;(",
27 | "more": "More",
28 | "notYTlist": "This is not a Youtube playlist ID",
29 | "nowPlaying": "现在播放",
30 | "others": "Others",
31 | "pages": "Pages",
32 | "playAll": "全部播放",
33 | "playlist": "播放列表",
34 | "playlists": "播放列表",
35 | "queueInitText": "正在初始化队列...由于性能原因",
36 | "recommendedForYou": "为你推荐",
37 | "restoreUserData": "恢复用户数据",
38 | "restoredSuccessfully": "恢复成功",
39 | "search": "搜索",
40 | "searchHistoryMsg": "搜索记录已清除",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "设置",
43 | "suggestedPlaylists": "推荐的播放列表",
44 | "supportDonate": "Support/Donate",
45 | "themeMode": "Theme mode",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "用户喜欢的歌曲",
50 | "userPlaylists": "用户播放列表",
51 | "yourDownloadedSongsHere": "您下载的歌曲在这里",
52 | "yourFavoriteSongsHere": "你最喜欢的歌曲在这里",
53 | "youtubePlaylistID": "Youtube 播放列表 ID"
54 | }
--------------------------------------------------------------------------------
/lib/helper/version.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 |
4 | import 'package:flutter_downloader/flutter_downloader.dart';
5 | import 'package:musify/main.dart';
6 | import 'package:musify/services/ext_storage.dart';
7 |
8 | String? version;
9 | late String dlUrl;
10 | const apiUrl =
11 | 'https://raw.githubusercontent.com/gokadzev/Musify/update/check.json';
12 |
13 | Future checkAppUpdates() async {
14 | version ??= packageInfo.version;
15 | final client = HttpClient();
16 | final request = await client.getUrl(Uri.parse(apiUrl));
17 | final response = await request.close();
18 | final contentAsString = await utf8.decodeStream(response);
19 | final map = json.decode(contentAsString);
20 | if (map['version'].toString() != version) {
21 | return true;
22 | } else {
23 | return false;
24 | }
25 | }
26 |
27 | Future downloadAppUpdates() async {
28 | final client = HttpClient();
29 | final request = await client.getUrl(Uri.parse(apiUrl));
30 | final response = await request.close();
31 | final contentAsString = await utf8.decodeStream(response);
32 | final map = json.decode(contentAsString);
33 | if (await getCPUArchitecture() == 'aarch64') {
34 | dlUrl = map['arm64url'].toString();
35 | } else {
36 | dlUrl = map['url'].toString();
37 | }
38 | final dlPath = await ExtStorageProvider.getExtStorage(dirName: 'Download');
39 | final file = File('${dlPath!}/Musify.apk');
40 | if (await file.exists()) {
41 | await file.delete();
42 | }
43 | await FlutterDownloader.enqueue(
44 | url: dlUrl,
45 | savedDir: dlPath,
46 | saveInPublicStorage: true,
47 | );
48 | }
49 |
50 | Future getCPUArchitecture() async {
51 | final info = await Process.run('uname', ['-m']);
52 | final cpu = info.stdout.toString().replaceAll('\n', '');
53 | return cpu;
54 | }
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Musify
4 | Music Streaming and Downloading app made in Flutter!
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Features
13 |
14 | Online Song Search :mag:
15 | Streaming Support :musical_note:
16 | Download Support :arrow_down:
17 | Play Local / Downloaded Songs Support :open_file_folder:
18 | High Quality mp3 / m4a / flac Format :fire:
19 | Lyrics Support :pencil:
20 | SponsorBlock Support :scissors:
21 | No Ads :no_entry_sign:
22 | No Subscriptions :dollar:
23 | 12 Supported Languages :us:
24 | Material UI & Accent Colors :art:
25 |
26 |
27 | ---
28 |
29 |
Screenshots
30 |
31 |
36 |
37 | ---
38 |
39 |
40 |
--------------------------------------------------------------------------------
/lib/customWidgets/marque.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class MarqueeWidget extends StatefulWidget {
4 | const MarqueeWidget({
5 | super.key,
6 | required this.child,
7 | this.direction = Axis.horizontal,
8 | this.animationDuration = const Duration(milliseconds: 6000),
9 | this.backDuration = const Duration(milliseconds: 800),
10 | this.pauseDuration = const Duration(milliseconds: 800),
11 | });
12 | final Widget child;
13 | final Axis direction;
14 | final Duration animationDuration, backDuration, pauseDuration;
15 |
16 | @override
17 | _MarqueeWidgetState createState() => _MarqueeWidgetState();
18 | }
19 |
20 | class _MarqueeWidgetState extends State {
21 | late ScrollController scrollController;
22 |
23 | @override
24 | void initState() {
25 | scrollController = ScrollController(initialScrollOffset: 50.0);
26 | WidgetsBinding.instance.addPostFrameCallback(scroll);
27 | super.initState();
28 | }
29 |
30 | @override
31 | void dispose() {
32 | scrollController.dispose();
33 | super.dispose();
34 | }
35 |
36 | @override
37 | Widget build(BuildContext context) {
38 | return SingleChildScrollView(
39 | scrollDirection: widget.direction,
40 | controller: scrollController,
41 | child: widget.child,
42 | );
43 | }
44 |
45 | void scroll(_) async {
46 | while (scrollController.hasClients) {
47 | await Future.delayed(widget.pauseDuration);
48 | if (scrollController.hasClients) {
49 | await scrollController.animateTo(
50 | scrollController.position.maxScrollExtent,
51 | duration: widget.animationDuration,
52 | curve: Curves.ease,
53 | );
54 | }
55 | await Future.delayed(widget.pauseDuration);
56 | if (scrollController.hasClients) {
57 | await scrollController.animateTo(
58 | 0.0,
59 | duration: widget.backDuration,
60 | curve: Curves.easeOut,
61 | );
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/.github/workflows/pre.yml:
--------------------------------------------------------------------------------
1 | on: workflow_dispatch
2 | name: Test, Build and Pre Release apk
3 | env:
4 | PROPERTIES_PATH: "./android/key.properties"
5 | jobs:
6 | build:
7 | name: Build APK
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v3
12 |
13 | # Setup Java environment in order to build the Android app.
14 | - uses: actions/setup-java@v3
15 | with:
16 | distribution: "zulu"
17 | java-version: "12.x"
18 |
19 |
20 | # Gradle cache for faster builds
21 | - uses: actions/cache@v3
22 | with:
23 | path: |
24 | ~/.gradle/caches
25 | ~/.gradle/wrapper
26 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
27 | restore-keys: |
28 | ${{ runner.os }}-gradle-
29 | # Setup the flutter environment.
30 | - uses: subosito/flutter-action@v2
31 | with:
32 | channel: "stable"
33 |
34 | #
35 | - run: |
36 | echo keyPassword=\${{ secrets.KEY_STORE }} > ${{env.PROPERTIES_PATH}}
37 | echo storePassword=\${{ secrets.KEY_PASSWORD }} >> ${{env.PROPERTIES_PATH}}
38 | echo keyAlias=\${{ secrets.KEY_ALIAS }} >> ${{env.PROPERTIES_PATH}}
39 | #
40 | - run: echo "${{ secrets.KEYSTORE2 }}" | base64 --decode > android/app/key.jks
41 |
42 | # Get flutter dependencies.
43 | - run: flutter pub get
44 | # Check for any formatting issues in the code.
45 | - run: flutter format --set-exit-if-changed .
46 | # Statically analyze the Dart code for any errors.
47 | - run: flutter analyze .
48 | # Build universal apk.
49 | - run: flutter build apk --release
50 | - uses: svenstaro/upload-release-action@v2
51 | with:
52 | repo_name: gokadzev/Musify
53 | repo_token: ${{ secrets.GITHUB_TOKEN }}
54 | file: build/app/outputs/apk/release/app-release.apk
55 | asset_name: Musify.apk
56 | tag: ${{ github.ref }}
57 | prerelease: true
58 | overwrite: true
59 | body: "New Musify Pre-Release! [only for testing purposes]"
60 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | on: workflow_dispatch
2 | name: Test, Build and Release apk
3 | env:
4 | PROPERTIES_PATH: "./android/key.properties"
5 | jobs:
6 | build:
7 | name: Build APK
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v3
12 |
13 | # Setup Java environment in order to build the Android app.
14 | - uses: actions/setup-java@v3
15 | with:
16 | distribution: "zulu"
17 | java-version: "12.x"
18 |
19 |
20 | # Gradle cache for faster builds
21 | - uses: actions/cache@v3
22 | with:
23 | path: |
24 | ~/.gradle/caches
25 | ~/.gradle/wrapper
26 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
27 | restore-keys: |
28 | ${{ runner.os }}-gradle-
29 | # Setup the flutter environment.
30 | - uses: subosito/flutter-action@v2
31 | with:
32 | channel: "stable"
33 |
34 | #
35 | - run: |
36 | echo keyPassword=\${{ secrets.KEY_STORE }} > ${{env.PROPERTIES_PATH}}
37 | echo storePassword=\${{ secrets.KEY_PASSWORD }} >> ${{env.PROPERTIES_PATH}}
38 | echo keyAlias=\${{ secrets.KEY_ALIAS }} >> ${{env.PROPERTIES_PATH}}
39 | #
40 | - run: echo "${{ secrets.KEYSTORE2 }}" | base64 --decode > android/app/key.jks
41 |
42 | # Get flutter dependencies.
43 | - run: flutter pub get
44 | # Check for any formatting issues in the code.
45 | - run: flutter format --set-exit-if-changed .
46 | # Statically analyze the Dart code for any errors.
47 | - run: flutter analyze .
48 | # Build arch apks.
49 | - run: flutter build apk --release --split-per-abi
50 | # Upload arm64 generated apk to the artifacts.
51 | - uses: actions/upload-artifact@v3
52 | with:
53 | name: Musify_arm64-v8a.apk
54 | path: build/app/outputs/apk/release/app-arm64-v8a-release.apk
55 | # Build universal apk.
56 | - run: flutter build apk --release
57 | # Upload universal generated apk to the artifacts.
58 | - uses: actions/upload-artifact@v3
59 | with:
60 | name: Musify.apk
61 | path: build/app/outputs/apk/release/app-release.apk
--------------------------------------------------------------------------------
/lib/localization/app_en.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "About",
3 | "accentChangeMsg": "Accent color has been changed",
4 | "accentColor": "Accent color",
5 | "add": "Add",
6 | "addedSuccess": "Added successfully",
7 | "appUpdateAvailableAndDownloading": "App update is available and downloading",
8 | "appUpdateIsAvailable": "App update is available",
9 | "appUpdateIsNotAvailable": "App update is not available",
10 | "audioFileType": "Audio File Extension",
11 | "audioFileTypeMsg": "Audio File Type has been changed",
12 | "backupUserData": "Backup user data",
13 | "backupedSuccessfully": "Backuped Successfully",
14 | "cacheMsg": "Cache cleared",
15 | "clearCache": "Clear cache",
16 | "clearSearchHistory": "Clear Search History",
17 | "downloadAppUpdate": "Download app update",
18 | "downloadCompleted": "Download Completed",
19 | "downloadStarted": "Download Started",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "Language",
23 | "languageMsg": "Language has been changed",
24 | "localSongs": "Local songs",
25 | "lyrics": "Lyrics",
26 | "lyricsNotAvailable": "No lyrics available ;(",
27 | "more": "More",
28 | "notYTlist": "This is not a Youtube playlist ID",
29 | "nowPlaying": "Now playing",
30 | "others": "Others",
31 | "pages": "Pages",
32 | "playAll": "Play all",
33 | "playlist": "Playlist",
34 | "playlists": "Playlists",
35 | "queueInitText": "Initialising queue...",
36 | "recommendedForYou": "Recommended for you",
37 | "restoreUserData": "Restore user data",
38 | "restoredSuccessfully": "Restored Successfully",
39 | "search": "Search",
40 | "searchHistoryMsg": "Search history cleared",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "Settings",
43 | "suggestedPlaylists": "Suggested playlists",
44 | "supportDonate": "Support/Donate",
45 | "themeMode": "Theme mode",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "User liked songs",
50 | "userPlaylists": "User playlists",
51 | "yourDownloadedSongsHere": "Your downloaded songs here",
52 | "yourFavoriteSongsHere": "Your favorite songs here",
53 | "youtubePlaylistID": "Youtube playlist ID"
54 | }
--------------------------------------------------------------------------------
/lib/localization/app_he.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "About",
3 | "accentChangeMsg": "Accent color has been changed",
4 | "accentColor": "Accent color",
5 | "add": "Add",
6 | "addedSuccess": "Added successfully",
7 | "appUpdateAvailableAndDownloading": "App update is available and downloading",
8 | "appUpdateIsAvailable": "App update is available",
9 | "appUpdateIsNotAvailable": "App update is not available",
10 | "audioFileType": "Audio File Extension",
11 | "audioFileTypeMsg": "Audio File Type has been changed",
12 | "backupUserData": "Backup user data",
13 | "backupedSuccessfully": "Backuped Successfully",
14 | "cacheMsg": "Cache cleared",
15 | "clearCache": "Clear cache",
16 | "clearSearchHistory": "Clear Search History",
17 | "downloadAppUpdate": "Download app update",
18 | "downloadCompleted": "Download Completed",
19 | "downloadStarted": "Download Started",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "Language",
23 | "languageMsg": "Language has been changed",
24 | "localSongs": "Local songs",
25 | "lyrics": "Lyrics",
26 | "lyricsNotAvailable": "No lyrics available ;(",
27 | "more": "More",
28 | "notYTlist": "This is not a Youtube playlist ID",
29 | "nowPlaying": "Now playing",
30 | "others": "Others",
31 | "pages": "Pages",
32 | "playAll": "Play all",
33 | "playlist": "Playlist",
34 | "playlists": "Playlists",
35 | "queueInitText": "Initialising queue... ",
36 | "recommendedForYou": "Recommended for you",
37 | "restoreUserData": "Restore user data",
38 | "restoredSuccessfully": "Restored Successfully",
39 | "search": "Search",
40 | "searchHistoryMsg": "Search history cleared",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "Settings",
43 | "suggestedPlaylists": "Suggested playlists",
44 | "supportDonate": "Support/Donate",
45 | "themeMode": "Theme mode",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "User liked songs",
50 | "userPlaylists": "User playlists",
51 | "yourDownloadedSongsHere": "Your downloaded songs here",
52 | "yourFavoriteSongsHere": "Your favorite songs here",
53 | "youtubePlaylistID": "Youtube playlist ID"
54 | }
--------------------------------------------------------------------------------
/lib/localization/app_hi.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "About",
3 | "accentChangeMsg": "Accent color has been changed",
4 | "accentColor": "Accent color",
5 | "add": "Add",
6 | "addedSuccess": "Added successfully",
7 | "appUpdateAvailableAndDownloading": "App update is available and downloading",
8 | "appUpdateIsAvailable": "App update is available",
9 | "appUpdateIsNotAvailable": "App update is not available",
10 | "audioFileType": "Audio File Extension",
11 | "audioFileTypeMsg": "Audio File Type has been changed",
12 | "backupUserData": "Backup user data",
13 | "backupedSuccessfully": "Backuped Successfully",
14 | "cacheMsg": "Cache cleared",
15 | "clearCache": "Clear cache",
16 | "clearSearchHistory": "Clear Search History",
17 | "downloadAppUpdate": "Download app update",
18 | "downloadCompleted": "Download Completed",
19 | "downloadStarted": "Download Started",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "Language",
23 | "languageMsg": "Language has been changed",
24 | "localSongs": "Local songs",
25 | "lyrics": "Lyrics",
26 | "lyricsNotAvailable": "No lyrics available ;(",
27 | "more": "More",
28 | "notYTlist": "This is not a Youtube playlist ID",
29 | "nowPlaying": "Now playing",
30 | "others": "Others",
31 | "pages": "Pages",
32 | "playAll": "Play all",
33 | "playlist": "Playlist",
34 | "playlists": "Playlists",
35 | "queueInitText": "Initialising queue... ",
36 | "recommendedForYou": "Recommended for you",
37 | "restoreUserData": "Restore user data",
38 | "restoredSuccessfully": "Restored Successfully",
39 | "search": "Search",
40 | "searchHistoryMsg": "Search history cleared",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "Settings",
43 | "suggestedPlaylists": "Suggested playlists",
44 | "supportDonate": "Support/Donate",
45 | "themeMode": "Theme mode",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "User liked songs",
50 | "userPlaylists": "User playlists",
51 | "yourDownloadedSongsHere": "Your downloaded songs here",
52 | "yourFavoriteSongsHere": "Your favorite songs here",
53 | "youtubePlaylistID": "Youtube playlist ID"
54 | }
--------------------------------------------------------------------------------
/lib/localization/app_hu.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "About",
3 | "accentChangeMsg": "Accent color has been changed",
4 | "accentColor": "Accent color",
5 | "add": "Add",
6 | "addedSuccess": "Added successfully",
7 | "appUpdateAvailableAndDownloading": "App update is available and downloading",
8 | "appUpdateIsAvailable": "App update is available",
9 | "appUpdateIsNotAvailable": "App update is not available",
10 | "audioFileType": "Audio File Extension",
11 | "audioFileTypeMsg": "Audio File Type has been changed",
12 | "backupUserData": "Backup user data",
13 | "backupedSuccessfully": "Backuped Successfully",
14 | "cacheMsg": "Cache cleared",
15 | "clearCache": "Clear cache",
16 | "clearSearchHistory": "Clear Search History",
17 | "downloadAppUpdate": "Download app update",
18 | "downloadCompleted": "Download Completed",
19 | "downloadStarted": "Download Started",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "Language",
23 | "languageMsg": "Language has been changed",
24 | "localSongs": "Local songs",
25 | "lyrics": "Lyrics",
26 | "lyricsNotAvailable": "No lyrics available ;(",
27 | "more": "More",
28 | "notYTlist": "This is not a Youtube playlist ID",
29 | "nowPlaying": "Now playing",
30 | "others": "Others",
31 | "pages": "Pages",
32 | "playAll": "Play all",
33 | "playlist": "Playlist",
34 | "playlists": "Playlists",
35 | "queueInitText": "Initialising queue...",
36 | "recommendedForYou": "Recommended for you",
37 | "restoreUserData": "Restore user data",
38 | "restoredSuccessfully": "Restored Successfully",
39 | "search": "Search",
40 | "searchHistoryMsg": "Search history cleared",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "Settings",
43 | "suggestedPlaylists": "Suggested playlists",
44 | "supportDonate": "Support/Donate",
45 | "themeMode": "Theme mode",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "User liked songs",
50 | "userPlaylists": "User playlists",
51 | "yourDownloadedSongsHere": "Your downloaded songs here",
52 | "yourFavoriteSongsHere": "Your favorite songs here",
53 | "youtubePlaylistID": "Youtube playlist ID"
54 | }
--------------------------------------------------------------------------------
/.github/workflows/pc.yml:
--------------------------------------------------------------------------------
1 | on: workflow_dispatch
2 | name: Test, Build and Release PC
3 | jobs:
4 | build-and-release-linux:
5 | runs-on: ubuntu-latest
6 |
7 | steps:
8 | - uses: actions/checkout@v2
9 | - uses: subosito/flutter-action@v2
10 | with:
11 | channel: 'stable'
12 | - name: Install dependencies
13 | run: sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev
14 | - name: Install project dependencies
15 | run: flutter pub get
16 | - name: Generate intermediates
17 | run: flutter pub run build_runner build --delete-conflicting-outputs
18 | - name: Enable linux build
19 | run: flutter config --enable-linux-desktop
20 | - name: Build artifacts
21 | run: flutter build linux --release
22 | - name: Archive Release
23 | uses: thedoctor0/zip-release@master
24 | with:
25 | type: 'zip'
26 | filename: Musify-linux.zip
27 | directory: build/linux/x64/release/bundle
28 | - name: Linux Release
29 | uses: actions/upload-artifact@v3
30 | with:
31 | name: Musify-linux.zip
32 | path: build/linux/x64/release/bundle/Musify-linux.zip
33 |
34 |
35 | build-and-release-windows:
36 | runs-on: windows-latest
37 |
38 | steps:
39 | - uses: actions/checkout@v2
40 | - uses: subosito/flutter-action@v2
41 | with:
42 | channel: 'stable'
43 | - name: Install project dependencies
44 | run: flutter pub get
45 | - name: Generate intermediates
46 | run: flutter pub run build_runner build --delete-conflicting-outputs
47 | - name: Enable windows build
48 | run: flutter config --enable-windows-desktop
49 | - name: Build artifacts
50 | run: flutter build windows --release
51 | - name: Archive Release
52 | uses: thedoctor0/zip-release@master
53 | with:
54 | type: 'zip'
55 | filename: Musify-windows.zip
56 | directory: build/windows/runner/Release
57 | - name: Windows Release
58 | uses: actions/upload-artifact@v3
59 | with:
60 | name: Musify-windows.zip
61 | path: build/windows/runner/Release/Musify-windows.zip
62 |
--------------------------------------------------------------------------------
/lib/services/ext_storage.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:path_provider/path_provider.dart';
3 | import 'package:permission_handler/permission_handler.dart';
4 |
5 | // ignore: avoid_classes_with_only_static_members
6 | class ExtStorageProvider {
7 | // asking for permission
8 | static Future requestPermission(Permission permission) async {
9 | if (await permission.isGranted) {
10 | return true;
11 | } else {
12 | final result = await permission.request();
13 | if (result == PermissionStatus.granted) {
14 | return true;
15 | } else {
16 | return false;
17 | }
18 | }
19 | }
20 |
21 | // getting external storage path
22 | static Future getExtStorage({required String dirName}) async {
23 | Directory? directory;
24 |
25 | try {
26 | // checking platform
27 | if (Platform.isAndroid) {
28 | if (await requestPermission(Permission.storage)) {
29 | directory = await getExternalStorageDirectory();
30 |
31 | // getting main path
32 | final newPath = directory!.path
33 | .replaceFirst('Android/data/com.gokadzev.musify/files', dirName);
34 |
35 | directory = Directory(newPath);
36 |
37 | // checking if directory exist or not
38 | if (!await directory.exists()) {
39 | // if directory not exists then asking for permission to create folder
40 | await requestPermission(Permission.manageExternalStorage);
41 | //creating folder
42 |
43 | await directory.create(recursive: true);
44 | }
45 | if (await directory.exists()) {
46 | try {
47 | // if directory exists then returning the complete path
48 | return newPath;
49 | } catch (e) {
50 | rethrow;
51 | }
52 | }
53 | } else {
54 | return throw 'something went wrong';
55 | }
56 | } else if (Platform.isIOS) {
57 | directory = await getApplicationDocumentsDirectory();
58 | return directory.path;
59 | } else {
60 | directory = await getDownloadsDirectory();
61 | return directory!.path;
62 | }
63 | } catch (e) {
64 | rethrow;
65 | }
66 | return directory.path;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/lib/localization/app_de.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "Über",
3 | "accentChangeMsg": "Akzentfarbe wurde geändert",
4 | "accentColor": "Akzentfarbe",
5 | "add": "Hinzufügen",
6 | "addedSuccess": "Erfolgreich hinzugefügt",
7 | "appUpdateAvailableAndDownloading": "Update ist verfügbar und wird heruntergeladen",
8 | "appUpdateIsAvailable": "Update ist verfügbar",
9 | "appUpdateIsNotAvailable": "Kein Update verfügbar",
10 | "audioFileType": "Audiodateierweiterung",
11 | "audioFileTypeMsg": "Der Audiodateityp wurde geändert",
12 | "backupUserData": "Sicherung von Benutzerdaten",
13 | "backupedSuccessfully": "Erfolgreich gesichert",
14 | "cacheMsg": "Cache gelöscht",
15 | "clearCache": "Cache leeren",
16 | "clearSearchHistory": "Suchverlauf löschen",
17 | "downloadAppUpdate": "Update herunterladen",
18 | "downloadCompleted": "Download abgeschlossen",
19 | "downloadStarted": "Download gestartet",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "Sprache",
23 | "languageMsg": "Sprache wurde geändert",
24 | "localSongs": "Lokale Lieder",
25 | "lyrics": "Liedtext",
26 | "lyricsNotAvailable": "Keine Liedtexte verfügbar ;(",
27 | "more": "Mehr",
28 | "notYTlist": "Das ist keine Youtube-Playlist ID",
29 | "nowPlaying": "Läuft gerade",
30 | "others": "Anderes",
31 | "pages": "Seiten",
32 | "playAll": "Alle wiedergeben",
33 | "playlist": "Wiedergabeliste",
34 | "playlists": "Wiedergabelisten",
35 | "queueInitText": "Warteschlange wird initialisiert... ",
36 | "recommendedForYou": "für Dich empfohlen",
37 | "restoreUserData": "Wiederherstellen von Benutzerdaten",
38 | "restoredSuccessfully": "Erfolgreich wiederhergestellt",
39 | "search": "Suche",
40 | "searchHistoryMsg": "Suchverlauf gelöscht",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "Einstellungen",
43 | "suggestedPlaylists": "Vorgeschlagene Wiedergabelisten",
44 | "supportDonate": "Support/Spenden",
45 | "themeMode": "Theme-Modus",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "beliebte Lieder",
50 | "userPlaylists": "Wiedergabelisten",
51 | "yourDownloadedSongsHere": "heruntergeladene Lieder",
52 | "yourFavoriteSongsHere": "Lieblingslieder",
53 | "youtubePlaylistID": "Youtube-Playlist ID"
54 | }
--------------------------------------------------------------------------------
/lib/localization/app_id.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "tentang",
3 | "accentChangeMsg": "Warna aksen telah diubah",
4 | "accentColor": "Warna aksen",
5 | "add": "tambah",
6 | "addedSuccess": "Added successfully",
7 | "appUpdateAvailableAndDownloading": "Pembaruan aplikasi tersedia dan diunduh",
8 | "appUpdateIsAvailable": "Pembaruan aplikasi tersedia",
9 | "appUpdateIsNotAvailable": "Pembaruan aplikasi tidak tersedia",
10 | "audioFileType": "Ekstensi Berkas Audio",
11 | "audioFileTypeMsg": "Jenis File Audio telah diubah",
12 | "backupUserData": "Cadangkan data pengguna",
13 | "backupedSuccessfully": "Backup Berhasil",
14 | "cacheMsg": "Cache dibersihkan",
15 | "clearCache": "Hapus cache",
16 | "clearSearchHistory": "Hapus Riwayat Pencarian",
17 | "downloadAppUpdate": "Unduh pembaruan aplikasi",
18 | "downloadCompleted": "Download Completed",
19 | "downloadStarted": "Download Started",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "Bahasa",
23 | "languageMsg": "Bahasa telah diubah",
24 | "localSongs": "Lagu lokal",
25 | "lyrics": "Lirik",
26 | "lyricsNotAvailable": "Tidak ada lirik yang tersedia ;(",
27 | "more": "More",
28 | "notYTlist": "This is not a Youtube playlist ID",
29 | "nowPlaying": "Sedang dimainkan",
30 | "others": "Others",
31 | "pages": "Pages",
32 | "playAll": "Mainkan semua",
33 | "playlist": "Daftar putar",
34 | "playlists": "Daftar putar",
35 | "queueInitText": "Menginisialisasi antrean... ",
36 | "recommendedForYou": "Direkomendasikan untuk Anda",
37 | "restoreUserData": "Pulihkan data pengguna",
38 | "restoredSuccessfully": "Berhasil Dipulihkan",
39 | "search": "Pencarian",
40 | "searchHistoryMsg": "Riwayat pencarian dihapus",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "Pengaturan",
43 | "suggestedPlaylists": "Daftar putar yang disarankan",
44 | "supportDonate": "Support/Donate",
45 | "themeMode": "Theme mode",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "Lagu yang disukai pengguna",
50 | "userPlaylists": "Daftar putar pengguna",
51 | "yourDownloadedSongsHere": "Lagu yang Anda download di sini",
52 | "yourFavoriteSongsHere": "Lagu favorit Anda di sini",
53 | "youtubePlaylistID": "ID daftar putar YouTube"
54 | }
--------------------------------------------------------------------------------
/lib/localization/app_nl.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "over",
3 | "accentChangeMsg": "Accentkleur is gewijzigd",
4 | "accentColor": "Accentkleur",
5 | "add": "Toevoegen",
6 | "addedSuccess": "Added successfully",
7 | "appUpdateAvailableAndDownloading": "App-update is beschikbaar en wordt gedownload",
8 | "appUpdateIsAvailable": "App-update is beschikbaar",
9 | "appUpdateIsNotAvailable": "App-update is niet beschikbaar",
10 | "audioFileType": "Audiobestandsextensie",
11 | "audioFileTypeMsg": "Audiobestandstype is gewijzigd",
12 | "backupUserData": "Backup gebruikersgegevens",
13 | "backupedSuccessfully": "Succesvol back-up gemaakt",
14 | "cacheMsg": "Cache gewist",
15 | "clearCache": "Cache wissen",
16 | "clearSearchHistory": "Verwijder zoekgeschiedenis",
17 | "downloadAppUpdate": "App-update downloaden",
18 | "downloadCompleted": "Download Completed",
19 | "downloadStarted": "Download Started",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "Taal",
23 | "languageMsg": "Taal is gewijzigd",
24 | "localSongs": "Lokale nummers",
25 | "lyrics": "tekst",
26 | "lyricsNotAvailable": "Geen songtekst beschikbaar ;(",
27 | "more": "More",
28 | "notYTlist": "This is not a Youtube playlist ID",
29 | "nowPlaying": "Nu aan het spelen",
30 | "others": "Others",
31 | "pages": "Pages",
32 | "playAll": "Alles afspelen",
33 | "playlist": "Afspeellijst",
34 | "playlists": "Afspeellijsten",
35 | "queueInitText": "Bezig met initialiseren van wachtrij... ",
36 | "recommendedForYou": "Aanbevolen voor jou",
37 | "restoreUserData": "Gebruikersgegevens herstellen",
38 | "restoredSuccessfully": "Succesvol hersteld",
39 | "search": "zoek",
40 | "searchHistoryMsg": "Zoekgeschiedenis gewist",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "Instellingen",
43 | "suggestedPlaylists": "Voorgestelde afspeellijsten",
44 | "supportDonate": "Support/Donate",
45 | "themeMode": "Theme mode",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "Liedjes die gebruikers leuk vonden",
50 | "userPlaylists": "Gebruikersafspeellijsten",
51 | "yourDownloadedSongsHere": "Uw gedownloade nummers hier",
52 | "yourFavoriteSongsHere": "Uw favoriete nummers hier",
53 | "youtubePlaylistID": "Youtube-afspeellijst-ID"
54 | }
--------------------------------------------------------------------------------
/lib/localization/app_uk.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "Про",
3 | "accentChangeMsg": "Колір акценту змінено",
4 | "accentColor": "Акцентний колір",
5 | "add": "додати",
6 | "addedSuccess": "Added successfully",
7 | "appUpdateAvailableAndDownloading": "Оновлення програми доступне і завантажується",
8 | "appUpdateIsAvailable": "Доступне оновлення програми",
9 | "appUpdateIsNotAvailable": "Оновлення програми недоступне",
10 | "audioFileType": "Розширення аудіофайлу",
11 | "audioFileTypeMsg": "Тип аудіофайлу змінено",
12 | "backupUserData": "Резервне копіювання даних користувача",
13 | "backupedSuccessfully": "Резервне копіювання успішно",
14 | "cacheMsg": "Кеш очищений",
15 | "clearCache": "Очистити кеш",
16 | "clearSearchHistory": "Очистити історію пошуку",
17 | "downloadAppUpdate": "Завантажити оновлення програми",
18 | "downloadCompleted": "Download Completed",
19 | "downloadStarted": "Download Started",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "Мову",
23 | "languageMsg": "Мова була змінена",
24 | "localSongs": "Місцеві пісні",
25 | "lyrics": "лірика",
26 | "lyricsNotAvailable": "Немає текстів ;(",
27 | "more": "More",
28 | "notYTlist": "This is not a Youtube playlist ID",
29 | "nowPlaying": "Зараз грає",
30 | "others": "Others",
31 | "pages": "Pages",
32 | "playAll": "Грати все",
33 | "playlist": "Список відтворення",
34 | "playlists": "Списки відтворення",
35 | "queueInitText": "Ініціалізація черги... ",
36 | "recommendedForYou": "рекомендовано для вас",
37 | "restoreUserData": "Відновити дані користувача",
38 | "restoredSuccessfully": "Відновлено успішно",
39 | "search": "Пошук",
40 | "searchHistoryMsg": "Історію пошуку очищено",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "Налаштування",
43 | "suggestedPlaylists": "Пропоновані списки відтворення",
44 | "supportDonate": "Support/Donate",
45 | "themeMode": "Theme mode",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "Пісні сподобалися користувачеві",
50 | "userPlaylists": "Списки відтворення користувачів",
51 | "yourDownloadedSongsHere": "Ваші завантажені пісні тут",
52 | "yourFavoriteSongsHere": "Ваші улюблені пісні тут",
53 | "youtubePlaylistID": "Ідентифікатор списку відтворення YouTube"
54 | }
--------------------------------------------------------------------------------
/lib/localization/app_tr.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "Hakkında",
3 | "accentChangeMsg": "Vurgu rengi değiştirildi",
4 | "accentColor": "vurgu rengi",
5 | "add": "Ekle",
6 | "addedSuccess": "Added successfully",
7 | "appUpdateAvailableAndDownloading": "Uygulama güncellemesi mevcut ve indiriliyor",
8 | "appUpdateIsAvailable": "Uygulama güncellemesi mevcut",
9 | "appUpdateIsNotAvailable": "Uygulama güncellemesi mevcut değil",
10 | "audioFileType": "Ses Dosyası Uzantısı",
11 | "audioFileTypeMsg": "Ses Dosyası Türü değiştirildi",
12 | "backupUserData": "Yedekleme kullanıcı verileri",
13 | "backupedSuccessfully": "Başarıyla Yedeklendi",
14 | "cacheMsg": "önbellek temizlendi",
15 | "clearCache": "Önbelleği temizle",
16 | "clearSearchHistory": "Arama geçmişini temizle",
17 | "downloadAppUpdate": "Uygulama güncellemesini indirin",
18 | "downloadCompleted": "Download Completed",
19 | "downloadStarted": "Download Started",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "Dil",
23 | "languageMsg": "Dil değiştirildi",
24 | "localSongs": "Yerel şarkılar",
25 | "lyrics": "şarkı sözleri",
26 | "lyricsNotAvailable": "Şarkı sözü yok ;(",
27 | "more": "More",
28 | "notYTlist": "This is not a Youtube playlist ID",
29 | "nowPlaying": "Şimdi oynuyor",
30 | "others": "Others",
31 | "pages": "Pages",
32 | "playAll": "Hepsini Oynat",
33 | "playlist": "çalma listesi",
34 | "playlists": "çalma listeleri",
35 | "queueInitText": "Sıra başlatılıyor... ",
36 | "recommendedForYou": "sizin için tavsiye edilen",
37 | "restoreUserData": "Kullanıcı verilerini kurtar",
38 | "restoredSuccessfully": "Başarıyla Geri Yüklendi",
39 | "search": "arama",
40 | "searchHistoryMsg": "Arama geçmişi temizlendi",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "ayarlar",
43 | "suggestedPlaylists": "Önerilen oynatma listeleri",
44 | "supportDonate": "Support/Donate",
45 | "themeMode": "Theme mode",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "Kullanıcının beğendiği şarkılar",
50 | "userPlaylists": "Kullanıcı oynatma listeleri",
51 | "yourDownloadedSongsHere": "İndirdiğiniz şarkılar burada",
52 | "yourFavoriteSongsHere": "En sevdiğin şarkılar burada",
53 | "youtubePlaylistID": "Youtube oynatma listesi ID"
54 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: 🐞 Issue Report
2 | description: Report a issue in Musify
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: musify-version
41 | attributes:
42 | label: Musify version
43 | description: |
44 | You can find your Musify version in **Settings**.
45 | placeholder: |
46 | Example: "1.0.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 | - type: checkboxes
68 | id: acknowledgements
69 | attributes:
70 | label: Acknowledgements
71 | description: Your issue will be closed if you haven't done these steps.
72 | options:
73 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
74 | required: true
75 | - label: I have written a short but informative title.
76 | required: true
77 | - label: I have updated the app to latest version **[Latest](https://github.com/gokadzev/Musify/releases)**.
78 | required: true
79 | - label: I will fill out all
--------------------------------------------------------------------------------
/lib/localization/app_it.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "Informazioni",
3 | "accentChangeMsg": "Il colore dell'accento è stato modificato",
4 | "accentColor": "Colore in risalto",
5 | "add": "Aggiungi",
6 | "addedSuccess": "Added successfully",
7 | "appUpdateAvailableAndDownloading": "Un aggiornamento dell'app è disponibile ed è in scaricamento",
8 | "appUpdateIsAvailable": "Aggiornamento dell'app disponibile",
9 | "appUpdateIsNotAvailable": "Aggiornamento dell'app non disponibile",
10 | "audioFileType": "Estensione File Audio",
11 | "audioFileTypeMsg": "Il tipo del file audio è stato cambiato",
12 | "backupUserData": "Salva dati utente",
13 | "backupedSuccessfully": "Salvati con successo",
14 | "cacheMsg": "Cache eliminata",
15 | "clearCache": "Pulisci cache",
16 | "clearSearchHistory": "Cancella cronologia delle ricerche",
17 | "downloadAppUpdate": "Scaricata l'aggiornamento dell'app",
18 | "downloadCompleted": "Download Completed",
19 | "downloadStarted": "Download Started",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "Lingua",
23 | "languageMsg": "La lingua è stata cambiata",
24 | "localSongs": "Canzoni locali",
25 | "lyrics": "Testo",
26 | "lyricsNotAvailable": "Nessun testo disponibile ;(",
27 | "more": "More",
28 | "notYTlist": "This is not a Youtube playlist ID",
29 | "nowPlaying": "Riproducendo ora",
30 | "others": "Others",
31 | "pages": "Pages",
32 | "playAll": "Riproduci tutto",
33 | "playlist": "Playlist",
34 | "playlists": "Playlist",
35 | "queueInitText": "Inizializzando la coda... ",
36 | "recommendedForYou": "Consigliati per te",
37 | "restoreUserData": "Ripristina dati utente",
38 | "restoredSuccessfully": "Ripristinati con successo",
39 | "search": "Cerca",
40 | "searchHistoryMsg": "Cronologia delle ricerche cancellata",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "Impostazioni",
43 | "suggestedPlaylists": "Playlist suggerite",
44 | "supportDonate": "Support/Donate",
45 | "themeMode": "Theme mode",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "Le tue canzoni preferite",
50 | "userPlaylists": "Le tue playlist",
51 | "yourDownloadedSongsHere": "Qui le tue canzoni scaricate",
52 | "yourFavoriteSongsHere": "Qui le tue canzoni preferite",
53 | "youtubePlaylistID": "ID playlist di Youtube"
54 | }
--------------------------------------------------------------------------------
/lib/localization/app_pl.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "O",
3 | "accentChangeMsg": "Zmieniono kolor akcentu",
4 | "accentColor": "Kolor akcentu",
5 | "add": "Dodaj",
6 | "addedSuccess": "Added successfully",
7 | "appUpdateAvailableAndDownloading": "Aktualizacja aplikacji jest dostępna i pobierana",
8 | "appUpdateIsAvailable": "Aktualizacja aplikacji jest dostępna",
9 | "appUpdateIsNotAvailable": "Aktualizacja aplikacji nie jest dostępna",
10 | "audioFileType": "Rozszerzenie pliku audio",
11 | "audioFileTypeMsg": "Typ pliku audio został zmieniony",
12 | "backupUserData": "Kopia zapasowa danych użytkownika",
13 | "backupedSuccessfully": "Utworzono kopię zapasową pomyślnie",
14 | "cacheMsg": "Pamięć wyczyszczona",
15 | "clearCache": "Wyczyść pamięć podręczną",
16 | "clearSearchHistory": "Wyczyść historię wyszukiwania",
17 | "downloadAppUpdate": "Pobierz aktualizację aplikacji",
18 | "downloadCompleted": "Download Completed",
19 | "downloadStarted": "Download Started",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "Język",
23 | "languageMsg": "Język został zmieniony",
24 | "localSongs": "Piosenki lokalne",
25 | "lyrics": "teksty",
26 | "lyricsNotAvailable": "Brak dostępnych tekstów ;(",
27 | "more": "More",
28 | "notYTlist": "This is not a Youtube playlist ID",
29 | "nowPlaying": "Teraz gra",
30 | "others": "Others",
31 | "pages": "Pages",
32 | "playAll": "Odtwórz wszystko",
33 | "playlist": "Playlista",
34 | "playlists": "Playlisty",
35 | "queueInitText": "Inicjowanie kolejki...",
36 | "recommendedForYou": "Polecane dla Ciebie",
37 | "restoreUserData": "Przywróć dane użytkownika",
38 | "restoredSuccessfully": "Przywrócono pomyślnie",
39 | "search": "Szukaj",
40 | "searchHistoryMsg": "Historia wyszukiwania wyczyszczona",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "Ustawienia",
43 | "suggestedPlaylists": "Sugerowane listy odtwarzania",
44 | "supportDonate": "Support/Donate",
45 | "themeMode": "Theme mode",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "Utwory lubiane przez użytkowników",
50 | "userPlaylists": "Playlisty użytkownika",
51 | "yourDownloadedSongsHere": "Twoje pobrane utwory tutaj",
52 | "yourFavoriteSongsHere": "Twoje ulubione piosenki tutaj",
53 | "youtubePlaylistID": "Identyfikator playlisty YouTube"
54 | }
--------------------------------------------------------------------------------
/lib/localization/app_ka.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "შესახებ",
3 | "accentChangeMsg": "აქცენტის ფერი შეიცვალა",
4 | "accentColor": "აქცენტის ფერი",
5 | "add": "დამატება",
6 | "addedSuccess": "წარმატებით დაემატა",
7 | "appUpdateAvailableAndDownloading": "აპლიკაციის განახლება ხელმისაწვდომია და იტვირთება",
8 | "appUpdateIsAvailable": "აპლიკაციის განახლება ხელმისაწვდომია",
9 | "appUpdateIsNotAvailable": "აპლიკაციის განახლება არაა ხელმისაწვდომი",
10 | "audioFileType": "სიმღერის ფაილის დაბოლოვება",
11 | "audioFileTypeMsg": "სიმღერის ფაილის დაბოლოვება შეიცვალა",
12 | "backupUserData": "მომხმარებლის ინფორმაციის დარეზერვება",
13 | "backupedSuccessfully": "დარეზერვდა წარმატებით",
14 | "cacheMsg": "ქეში გასუფთავდა",
15 | "clearCache": "ქეშის გასუფთავება",
16 | "clearSearchHistory": "საძიებო ისტორიის გასუფთავება",
17 | "downloadAppUpdate": "აპლიკაციის განახლების ჩამოტვირთვა",
18 | "downloadCompleted": "ჩამოტვირთვა დასრულდა",
19 | "downloadStarted": "ჩამოტვირთვა დაიწყო",
20 | "falseMSG": "მცდარი",
21 | "home": "მთავარი",
22 | "language": "ენა",
23 | "languageMsg": "ენა შეიცვალა",
24 | "localSongs": "ადგილობრივი მუსიკა",
25 | "lyrics": "ტექსტი",
26 | "lyricsNotAvailable": "ტექსტი არაა ხელმისაწვდომი ;(",
27 | "more": "მეტი",
28 | "notYTlist": "ეს არ არის Youtube ფლეილისთ ID",
29 | "nowPlaying": "ახლა ჩართულია",
30 | "others": "სხვა",
31 | "pages": "გვერდები",
32 | "playAll": "ყველას ჩართვა",
33 | "playlist": "ფლეილისთი",
34 | "playlists": "ფლეილისთები",
35 | "queueInitText": "ხდება ინიციალიზება... ",
36 | "recommendedForYou": "შემოთავაზებები შენთვის",
37 | "restoreUserData": "მომხმარებლის ინფორმაციის დაბრუნება",
38 | "restoredSuccessfully": "წარმატებით დაბრუნდა",
39 | "search": "ძიება",
40 | "searchHistoryMsg": "საძიებო ისტორია გასუფთავდა",
41 | "settingChangedMsg": "პარამეტრი შეიცვალა",
42 | "settings": "პარამეტრები",
43 | "suggestedPlaylists": "შემოთავაზებული ფლეილისთები",
44 | "supportDonate": "დონაცია",
45 | "themeMode": "თემის რეჟიმი",
46 | "tools": "ხელსაწყოები",
47 | "trueMSG": "ჭეშმარიტი",
48 | "useSystemColor": "გამოიყენე სისტემის ფერი (Android 12 ახალი ფუნქცია)",
49 | "userLikedSongs": "მომხმარებლის მოწონებული სიმღერები",
50 | "userPlaylists": "მომხმარებლის ფლეილისთები",
51 | "yourDownloadedSongsHere": "შენი გადმოწერილი სიმღერები აქ",
52 | "yourFavoriteSongsHere": "შენი ფავორიტი სიმღერები აქ",
53 | "youtubePlaylistID": "Youtube ფლეილისთის ID"
54 | }
--------------------------------------------------------------------------------
/lib/localization/app_pt.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "Sobre",
3 | "accentChangeMsg": "A cor de destaque foi alterada",
4 | "accentColor": "Cor de destaque",
5 | "add": "Adicionar",
6 | "addedSuccess": "Added successfully",
7 | "appUpdateAvailableAndDownloading": "A atualização do aplicativo está disponível e baixando",
8 | "appUpdateIsAvailable": "A atualização do aplicativo está disponível",
9 | "appUpdateIsNotAvailable": "A atualização do aplicativo não está disponível",
10 | "audioFileType": "Extensão de arquivo de áudio",
11 | "audioFileTypeMsg": "O tipo de arquivo de áudio foi alterado",
12 | "backupUserData": "Fazer backup dos dados do usuário",
13 | "backupedSuccessfully": "Backup feito com sucesso",
14 | "cacheMsg": "Cache limpo",
15 | "clearCache": "Limpar cache",
16 | "clearSearchHistory": "Limpar histórico de pesquisa",
17 | "downloadAppUpdate": "Baixar atualização do aplicativo",
18 | "downloadCompleted": "Download Completed",
19 | "downloadStarted": "Download Started",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "Idioma",
23 | "languageMsg": "O idioma foi alterado",
24 | "localSongs": "Músicas locais",
25 | "lyrics": "Letra da música",
26 | "lyricsNotAvailable": "Nenhuma letra disponível ;(",
27 | "more": "More",
28 | "notYTlist": "This is not a Youtube playlist ID",
29 | "nowPlaying": "Agora tocando",
30 | "others": "Others",
31 | "pages": "Pages",
32 | "playAll": "Reproduzir todos",
33 | "playlist": "Lista de reprodução",
34 | "playlists": "Listas de reprodução",
35 | "queueInitText": "Inicializando a fila... ",
36 | "recommendedForYou": "Recomendado para você",
37 | "restoreUserData": "Restaurar dados do usuário",
38 | "restoredSuccessfully": "Restaurado com sucesso",
39 | "search": "Procurar",
40 | "searchHistoryMsg": "Histórico de pesquisa limpo",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "Configurações",
43 | "suggestedPlaylists": "Listas de reprodução sugeridas",
44 | "supportDonate": "Support/Donate",
45 | "themeMode": "Theme mode",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "Músicas que o usuário gostou",
50 | "userPlaylists": "Listas de reprodução do usuário",
51 | "yourDownloadedSongsHere": "Suas músicas baixadas aqui",
52 | "yourFavoriteSongsHere": "Suas músicas favoritas aqui",
53 | "youtubePlaylistID": "ID da lista de reprodução do Youtube"
54 | }
--------------------------------------------------------------------------------
/lib/services/data_manager.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:hive/hive.dart';
4 | import 'package:musify/services/ext_storage.dart';
5 | import 'package:permission_handler/permission_handler.dart';
6 |
7 | void addOrUpdateData(
8 | String category,
9 | dynamic key,
10 | dynamic value,
11 | ) {
12 | if (!Hive.isBoxOpen(category)) {
13 | Hive.openBox(category);
14 | }
15 | Hive.box(category).put(key, value);
16 | }
17 |
18 | Future getData(String category, dynamic key) async {
19 | if (!Hive.isBoxOpen(category)) {
20 | await Hive.openBox(category);
21 | }
22 | return Hive.box(category).get(key);
23 | }
24 |
25 | void deleteData(String category, dynamic key) {
26 | if (!Hive.isBoxOpen(category)) {
27 | Hive.openBox(category);
28 | }
29 | Hive.box(category).delete(key);
30 | }
31 |
32 | void clearCache() async {
33 | if (!Hive.isBoxOpen('cache')) {
34 | await Hive.openBox('cache');
35 | }
36 | await Hive.box('cache').clear();
37 | }
38 |
39 | Future backupData() async {
40 | final boxNames = ['user', 'settings'];
41 | final dlPath = await ExtStorageProvider.getExtStorage(dirName: 'Musify/Data');
42 |
43 | for (var i = 0; i < boxNames.length; i++) {
44 | await Hive.openBox(boxNames[i]);
45 | try {
46 | await File(Hive.box(boxNames[i]).path!)
47 | .copy('$dlPath/${boxNames[i]}Data.hive');
48 | } catch (e) {
49 | await [
50 | Permission.manageExternalStorage,
51 | ].request();
52 | await File(Hive.box(boxNames[i]).path!)
53 | .copy('$dlPath/${boxNames[i]}Data.hive');
54 | return 'Permissions problem, if you already gave requested permission, Backup data again!';
55 | }
56 | }
57 | return 'Backuped Successfully!';
58 | }
59 |
60 | Future restoreData() async {
61 | final boxNames = ['user', 'settings'];
62 | final uplPath =
63 | await ExtStorageProvider.getExtStorage(dirName: 'Musify/Data');
64 |
65 | for (var i = 0; i < boxNames.length; i++) {
66 | await Hive.openBox(boxNames[i]);
67 | try {
68 | final box = await Hive.openBox(boxNames[i]);
69 | final boxPath = box.path;
70 | await File('${uplPath!}/${boxNames[i]}Data.hive').copy(boxPath!);
71 | } catch (e) {
72 | await [
73 | Permission.manageExternalStorage,
74 | ].request();
75 | return 'Permissions problem, if you already gave requested permission, Restore data again!';
76 | }
77 | }
78 |
79 | return 'Restored Successfully!';
80 | }
81 |
--------------------------------------------------------------------------------
/lib/localization/app_fr.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "À propos de",
3 | "accentChangeMsg": "La couleur d'accentuation a été modifiée",
4 | "accentColor": "Couleur d'accentuation",
5 | "add": "Ajouter",
6 | "addedSuccess": "Added successfully",
7 | "appUpdateAvailableAndDownloading": "La mise à jour de l'application est disponible et en cours de téléchargement",
8 | "appUpdateIsAvailable": "La mise à jour de l'application est disponible",
9 | "appUpdateIsNotAvailable": "La mise à jour de l'application n'est pas disponible",
10 | "audioFileType": "Extension de fichier audio",
11 | "audioFileTypeMsg": "Le type de fichier audio a été modifié",
12 | "backupUserData": "Sauvegardez les données utilisateur",
13 | "backupedSuccessfully": "Sauvegarde réussie",
14 | "cacheMsg": "Cache vidé",
15 | "clearCache": "Vider le cache",
16 | "clearSearchHistory": "Effacer l'historique",
17 | "downloadAppUpdate": "Télécharger la mise à jour de l'application",
18 | "downloadCompleted": "Download Completed",
19 | "downloadStarted": "Download Started",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "Langue",
23 | "languageMsg": "La langue a été modifiée",
24 | "localSongs": "Chansons locales",
25 | "lyrics": "Paroles",
26 | "lyricsNotAvailable": "Pas de paroles disponibles ;(",
27 | "more": "More",
28 | "notYTlist": "This is not a Youtube playlist ID",
29 | "nowPlaying": "Lecture en cours",
30 | "others": "Others",
31 | "pages": "Pages",
32 | "playAll": "Jouer à tous",
33 | "playlist": "Playlist",
34 | "playlists": "Playlists",
35 | "queueInitText": "Initialisation de la file d'attente... ",
36 | "recommendedForYou": "Recommandé pour vous",
37 | "restoreUserData": "Restaurer les données de l'utilisateur",
38 | "restoredSuccessfully": "Restauré avec succès",
39 | "search": "Chercher",
40 | "searchHistoryMsg": "Historique des recherches effacé",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "Réglages",
43 | "suggestedPlaylists": "Listes de lecture suggérées",
44 | "supportDonate": "Soutenir/Faire un don",
45 | "themeMode": "Theme mode",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "Chansons aimées par l'utilisateur",
50 | "userPlaylists": "Listes de lecture utilisateur",
51 | "yourDownloadedSongsHere": "Vos chansons téléchargées ici",
52 | "yourFavoriteSongsHere": "Vos chansons préférées ici",
53 | "youtubePlaylistID": "ID de la liste de playlist Youtube"
54 | }
--------------------------------------------------------------------------------
/lib/localization/app_es.arb:
--------------------------------------------------------------------------------
1 | {
2 | "about": "Sobre",
3 | "accentChangeMsg": "Se ha cambiado el color del acento",
4 | "accentColor": "Acentuar el color",
5 | "add": "Agregar",
6 | "addedSuccess": "Added successfully",
7 | "appUpdateAvailableAndDownloading": "La actualización de la aplicación está disponible y se está descargando",
8 | "appUpdateIsAvailable": "La actualización de la aplicación está disponible",
9 | "appUpdateIsNotAvailable": "La actualización de la aplicación no está disponible",
10 | "audioFileType": "Extensión de archivo de audio",
11 | "audioFileTypeMsg": "Se ha cambiado el tipo de archivo de audio",
12 | "backupUserData": "Copia de seguridad de la información del usuario",
13 | "backupedSuccessfully": "Copia de seguridad exitosa",
14 | "cacheMsg": "Caché despejado",
15 | "clearCache": "Limpiar cache",
16 | "clearSearchHistory": "Limpiar historial de búsqueda",
17 | "downloadAppUpdate": "Descargar actualización de la aplicación",
18 | "downloadCompleted": "Download Completed",
19 | "downloadStarted": "Download Started",
20 | "falseMSG": "False",
21 | "home": "Home",
22 | "language": "Idioma",
23 | "languageMsg": "Se ha cambiado el idioma",
24 | "localSongs": "Canciones locales",
25 | "lyrics": "Letra",
26 | "lyricsNotAvailable": "No hay letras disponibles ;(",
27 | "more": "More",
28 | "notYTlist": "This is not a Youtube playlist ID",
29 | "nowPlaying": "Jugando ahora",
30 | "others": "Others",
31 | "pages": "Pages",
32 | "playAll": "Jugar todo",
33 | "playlist": "lista de reproducción",
34 | "playlists": "listas de reproducción",
35 | "queueInitText": "Inicializando cola...",
36 | "recommendedForYou": "Recomendado para ti",
37 | "restoreUserData": "Restaurar datos de usuario",
38 | "restoredSuccessfully": "Restaurado con éxito",
39 | "search": "Búsqueda",
40 | "searchHistoryMsg": "Historial de búsqueda borrado",
41 | "settingChangedMsg": "Setting changed",
42 | "settings": "Ajustes",
43 | "suggestedPlaylists": "Listas de reproducción sugeridas",
44 | "supportDonate": "Support/Donate",
45 | "themeMode": "Theme mode",
46 | "tools": "Tools",
47 | "trueMSG": "True",
48 | "useSystemColor": "Use System Color (Android 12 New Feature)",
49 | "userLikedSongs": "Al usuario le gustaron las canciones",
50 | "userPlaylists": "Listas de reproducción de usuario",
51 | "yourDownloadedSongsHere": "Tus canciones descargadas aquí",
52 | "yourFavoriteSongsHere": "Tus canciones favoritas aquí",
53 | "youtubePlaylistID": "lista de reproducción de youtube ID"
54 | }
--------------------------------------------------------------------------------
/lib/services/lyrics_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:http/http.dart' as http;
2 |
3 | class Lyrics {
4 | Lyrics({delimiter1, delimiter2}) {
5 | setDelimiters(delimiter1: delimiter1, delimiter2: delimiter2);
6 | }
7 |
8 | final String _url =
9 | 'https://www.google.com/search?client=safari&rls=en&ie=UTF-8&oe=UTF-8&q=';
10 | String _delimiter1 =
11 | '';
12 | String _delimiter2 =
13 | '
';
14 |
15 | void setDelimiters({String? delimiter1, String? delimiter2}) {
16 | _delimiter1 = delimiter1 ?? _delimiter1;
17 | _delimiter2 = delimiter2 ?? _delimiter2;
18 | }
19 |
20 | Future
getLyrics({String? track, String? artist}) async {
21 | if (track == null || artist == null)
22 | throw Exception('track and artist must not be null');
23 |
24 | String lyrics;
25 |
26 | // try multiple queries
27 | try {
28 | lyrics = (await http
29 | .get(Uri.parse(Uri.encodeFull('$_url$track by $artist lyrics'))))
30 | .body;
31 | lyrics = lyrics.split(_delimiter1).last;
32 | lyrics = lyrics.split(_delimiter2).first;
33 | if (lyrics.contains('')) throw Error();
34 | } catch (_) {
35 | try {
36 | lyrics = (await http.get(
37 | Uri.parse(
38 | Uri.encodeFull('$_url$track by $artist song lyrics'),
39 | ),
40 | ))
41 | .body;
42 | lyrics = lyrics.split(_delimiter1).last;
43 | lyrics = lyrics.split(_delimiter2).first;
44 | if (lyrics.contains('')) throw Error();
45 | } catch (_) {
46 | try {
47 | lyrics = (await http.get(
48 | Uri.parse(
49 | Uri.encodeFull(
50 | '$_url${track.split("-").first} by $artist lyrics',
51 | ),
52 | ),
53 | ))
54 | .body;
55 | lyrics = lyrics.split(_delimiter1).last;
56 | lyrics = lyrics.split(_delimiter2).first;
57 | if (lyrics.contains('')) throw Error();
58 | } catch (_) {
59 | // give up
60 | return 'not found';
61 | }
62 | }
63 | }
64 |
65 | final split = lyrics.split('\n');
66 | var result = '';
67 | for (var i = 0; i < split.length; i++) {
68 | result = '$result${split[i]}\n';
69 | }
70 | return result.trim();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/lib/services/download_manager.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
5 | import 'package:musify/API/musify.dart';
6 | import 'package:musify/helper/flutter_toast.dart';
7 | import 'package:musify/services/ext_storage.dart';
8 | import 'package:musify/ui/morePage.dart';
9 | import 'package:permission_handler/permission_handler.dart';
10 | import 'package:youtube_explode_dart/youtube_explode_dart.dart';
11 |
12 | Future downloadSong(BuildContext context, dynamic song) async {
13 | if (await Permission.audio.status.isDenied) {
14 | await Permission.audio.request();
15 | if (await Permission.audio.status.isPermanentlyDenied) {
16 | await openAppSettings();
17 | }
18 | }
19 |
20 | if (await Permission.storage.status.isDenied) {
21 | await [
22 | Permission.storage,
23 | Permission.accessMediaLocation,
24 | Permission.mediaLibrary,
25 | ].request();
26 |
27 | if (await Permission.storage.status.isPermanentlyDenied) {
28 | await openAppSettings();
29 | }
30 | }
31 |
32 | final filename = song['title']
33 | .replaceAll(r'\', '')
34 | .replaceAll('/', '')
35 | .replaceAll('*', '')
36 | .replaceAll('?', '')
37 | .replaceAll('"', '')
38 | .replaceAll('<', '')
39 | .replaceAll('>', '')
40 | .replaceAll('|', '') +
41 | '.' +
42 | prefferedFileExtension.value;
43 |
44 | var filepath = '';
45 | final dlPath = await ExtStorageProvider.getExtStorage(dirName: 'Music');
46 | try {
47 | showToast(
48 | AppLocalizations.of(context)!.downloadStarted,
49 | );
50 | await File('${dlPath!}/$filename')
51 | .create(recursive: true)
52 | .then((value) => filepath = value.path);
53 | await downloadFileFromYT(filename, filepath, dlPath, song).whenComplete(
54 | () => showToast(
55 | AppLocalizations.of(context)!.downloadCompleted,
56 | ),
57 | );
58 | } catch (e) {
59 | await [Permission.manageExternalStorage].request();
60 | await File('${dlPath!}/$filename')
61 | .create(recursive: true)
62 | .then((value) => filepath = value.path);
63 | await downloadFileFromYT(filename, filepath, dlPath, song).whenComplete(
64 | () => showToast(
65 | AppLocalizations.of(context)!.downloadCompleted,
66 | ),
67 | );
68 | }
69 | }
70 |
71 | Future downloadFileFromYT(
72 | String filename,
73 | String filepath,
74 | String dlPath,
75 | dynamic song,
76 | ) async {
77 | final audioStream = await getSong(song['ytid'].toString(), false);
78 | final file = File(filepath);
79 | final fileStream = file.openWrite();
80 | await yt.videos.streamsClient.get(audioStream as StreamInfo).pipe(fileStream);
81 | await fileStream.flush();
82 | await fileStream.close();
83 | }
84 |
--------------------------------------------------------------------------------
/lib/style/appTheme.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:hive/hive.dart';
3 | import 'package:musify/style/appColors.dart';
4 |
5 | ColorScheme accent = ColorScheme.fromSwatch(
6 | primarySwatch: getMaterialColorFromColor(
7 | Color(Hive.box('settings').get('accentColor', defaultValue: 0xFFF08080)),
8 | ),
9 | accentColor:
10 | Color(Hive.box('settings').get('accentColor', defaultValue: 0xFFF08080)),
11 | );
12 |
13 | ThemeData getAppDarkTheme() {
14 | return ThemeData(
15 | scaffoldBackgroundColor: const Color(0xFF121212),
16 | canvasColor: const Color(0xFF121212),
17 | appBarTheme: const AppBarTheme(backgroundColor: Color(0xFF121212)),
18 | bottomAppBarColor: const Color(0xFF151515),
19 | colorScheme: accent,
20 | visualDensity: VisualDensity.adaptivePlatformDensity,
21 | fontFamily: 'Ubuntu',
22 | useMaterial3: true,
23 | pageTransitionsTheme: const PageTransitionsTheme(
24 | builders: {
25 | TargetPlatform.android: ZoomPageTransitionsBuilder(),
26 | },
27 | ),
28 | elevatedButtonTheme: ElevatedButtonThemeData(
29 | style: ButtonStyle(
30 | shape: MaterialStateProperty.all(
31 | RoundedRectangleBorder(
32 | borderRadius: BorderRadius.circular(8),
33 | ),
34 | ),
35 | ),
36 | ),
37 | cardTheme: CardTheme(
38 | color: const Color(0xFF151515),
39 | shape: RoundedRectangleBorder(
40 | borderRadius: BorderRadius.circular(10),
41 | ),
42 | elevation: 2.3,
43 | ),
44 | listTileTheme: const ListTileThemeData(textColor: Colors.white),
45 | iconTheme: const IconThemeData(color: Colors.white),
46 | hintColor: Colors.white,
47 | textTheme: const TextTheme(
48 | bodyText2: TextStyle(color: Colors.white),
49 | ),
50 | );
51 | }
52 |
53 | ThemeData getAppLightTheme() {
54 | return ThemeData(
55 | scaffoldBackgroundColor: Colors.white,
56 | canvasColor: Colors.white,
57 | colorScheme: accent,
58 | visualDensity: VisualDensity.adaptivePlatformDensity,
59 | fontFamily: 'Ubuntu',
60 | useMaterial3: true,
61 | pageTransitionsTheme: const PageTransitionsTheme(
62 | builders: {
63 | TargetPlatform.android: ZoomPageTransitionsBuilder(),
64 | },
65 | ),
66 | elevatedButtonTheme: ElevatedButtonThemeData(
67 | style: ButtonStyle(
68 | shape: MaterialStateProperty.all(
69 | RoundedRectangleBorder(
70 | borderRadius: BorderRadius.circular(8),
71 | ),
72 | ),
73 | ),
74 | ),
75 | cardTheme: CardTheme(
76 | shape: RoundedRectangleBorder(
77 | borderRadius: BorderRadius.circular(10),
78 | ),
79 | elevation: 2.3,
80 | ),
81 | listTileTheme: ListTileThemeData(
82 | selectedColor: accent.primary.withOpacity(0.4),
83 | ),
84 | iconTheme: const IconThemeData(color: Color(0xFF151515)),
85 | hintColor: const Color(0xFF151515),
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | linter:
2 | rules:
3 | - always_declare_return_types
4 | - annotate_overrides
5 | - avoid_bool_literals_in_conditional_expressions
6 | - avoid_classes_with_only_static_members
7 | - avoid_empty_else
8 | - avoid_function_literals_in_foreach_calls
9 | - avoid_init_to_null
10 | - avoid_null_checks_in_equality_operators
11 | - avoid_print
12 | - avoid_relative_lib_imports
13 | - avoid_renaming_method_parameters
14 | - avoid_return_types_on_setters
15 | - avoid_returning_null
16 | - avoid_returning_null_for_future
17 | - avoid_returning_null_for_void
18 | - avoid_returning_this
19 | - avoid_shadowing_type_parameters
20 | - avoid_single_cascade_in_expression_statements
21 | - avoid_types_as_parameter_names
22 | - avoid_unnecessary_containers
23 | - avoid_unused_constructor_parameters
24 | - await_only_futures
25 | - camel_case_types
26 | - cancel_subscriptions
27 | - comment_references
28 | - constant_identifier_names
29 | - control_flow_in_finally
30 | - directives_ordering
31 | - empty_catches
32 | - empty_constructor_bodies
33 | - empty_statements
34 | - hash_and_equals
35 | - implementation_imports
36 | - iterable_contains_unrelated_type
37 | - library_names
38 | - library_prefixes
39 | - list_remove_unrelated_type
40 | - no_adjacent_strings_in_list
41 | - no_duplicate_case_values
42 | - no_logic_in_create_state
43 | - non_constant_identifier_names
44 | - noop_primitive_operations
45 | - null_closures
46 | - omit_local_variable_types
47 | - overridden_fields
48 | - package_api_docs
49 | - package_names
50 | - package_prefixed_library_names
51 | - prefer_adjacent_string_concatenation
52 | - prefer_collection_literals
53 | - prefer_conditional_assignment
54 | - prefer_const_constructors
55 | - prefer_contains
56 | - prefer_equal_for_default_values
57 | - prefer_final_fields
58 | - prefer_final_locals
59 | - prefer_generic_function_type_aliases
60 | - prefer_initializing_formals
61 | - prefer_interpolation_to_compose_strings
62 | - prefer_is_empty
63 | - prefer_is_not_empty
64 | - prefer_null_aware_operators
65 | - prefer_single_quotes
66 | - prefer_typing_uninitialized_variables
67 | - recursive_getters
68 | - require_trailing_commas
69 | - sized_box_for_whitespace
70 | - slash_for_doc_comments
71 | - sort_child_properties_last
72 | - sort_constructors_first
73 | - sort_pub_dependencies
74 | - sort_unnamed_constructors_first
75 | - test_types_in_equals
76 | - throw_in_finally
77 | - type_init_formals
78 | - unawaited_futures
79 | - unnecessary_await_in_return
80 | - unnecessary_brace_in_string_interps
81 | - unnecessary_const
82 | - unnecessary_getters_setters
83 | - unnecessary_lambdas
84 | - unnecessary_new
85 | - unnecessary_null_aware_assignments
86 | - unnecessary_parenthesis
87 | - unnecessary_statements
88 | - unnecessary_this
89 | - unrelated_type_equality_checks
90 | - use_colored_box
91 | - use_decorated_box
92 | - use_function_type_syntax_for_parameters
93 | - use_is_even_rather_than_modulo
94 | - use_named_constants
95 | - use_rethrow_when_possible
96 | - use_super_parameters
97 | - valid_regexps
98 | - void_checks
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | def keystoreProperties = new Properties()
29 | def keystorePropertiesFile = rootProject.file('key.properties')
30 | if (keystorePropertiesFile.exists()) {
31 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
32 | }
33 |
34 | def lifecycle_version = "2.4.0"
35 |
36 | android {
37 | compileSdkVersion 33
38 | ndkVersion flutter.ndkVersion
39 |
40 | compileOptions {
41 | sourceCompatibility JavaVersion.VERSION_1_8
42 | targetCompatibility JavaVersion.VERSION_1_8
43 | }
44 |
45 | kotlinOptions {
46 | jvmTarget = '1.8'
47 | }
48 |
49 | sourceSets {
50 | main.java.srcDirs += 'src/main/kotlin'
51 | }
52 |
53 | lintOptions {
54 | checkReleaseBuilds false
55 | abortOnError false
56 | }
57 |
58 | defaultConfig {
59 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
60 | applicationId "com.gokadzev.musify"
61 | minSdkVersion 21
62 | targetSdkVersion 33
63 | versionCode flutterVersionCode.toInteger()
64 | versionName flutterVersionName
65 | multiDexEnabled true
66 | }
67 |
68 | signingConfigs {
69 | release {
70 | //From decoded key
71 | storeFile = file('key.jks')
72 |
73 | //From key.properties
74 | keyAlias keystoreProperties['keyAlias']
75 | keyPassword keystoreProperties['keyPassword']
76 | storePassword keystoreProperties['storePassword']
77 | }
78 | }
79 |
80 | buildTypes {
81 | release {
82 | // TODO: Add your own signing config for the release build.
83 | // Signing with the debug keys for now, so `flutter run --release` works.
84 | signingConfig signingConfigs.release
85 | shrinkResources false
86 | }
87 | }
88 |
89 | }
90 |
91 | flutter {
92 | source '../..'
93 | }
94 |
95 | dependencies {
96 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
97 | implementation 'com.android.support:multidex:1.0.3'
98 | implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
99 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
100 | }
101 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
22 |
23 |
26 |
27 |
28 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/lib/customWidgets/delayed_display.dart:
--------------------------------------------------------------------------------
1 | // pub: https://pub.dev/packages/delayed_display
2 | // license: https://raw.githubusercontent.com/ThomasEcalle/delayed_display/master/LICENSE
3 | // remade (not original)
4 |
5 | import 'dart:async';
6 |
7 | import 'package:flutter/material.dart';
8 |
9 | class DelayedDisplay extends StatefulWidget {
10 | /// DelayedDisplay constructor
11 | const DelayedDisplay({
12 | required this.child,
13 | this.delay = Duration.zero,
14 | this.fadingDuration = const Duration(milliseconds: 800),
15 | this.slidingCurve = Curves.decelerate,
16 | this.slidingBeginOffset = const Offset(0, 0.35),
17 | this.fadeIn = true,
18 | });
19 |
20 | /// Child that will be displayed with the animation and delay
21 | final Widget child;
22 |
23 | /// Delay before displaying the widget and the animations
24 | final Duration delay;
25 |
26 | /// Duration of the fading animation
27 | final Duration fadingDuration;
28 |
29 | /// Curve of the sliding animation
30 | final Curve slidingCurve;
31 |
32 | /// Offset of the widget at the beginning of the sliding animation
33 | final Offset slidingBeginOffset;
34 |
35 | /// If true, make the child appear, disappear otherwise. Default to true.
36 | final bool fadeIn;
37 |
38 | @override
39 | _DelayedDisplayState createState() => _DelayedDisplayState();
40 | }
41 |
42 | class _DelayedDisplayState extends State
43 | with TickerProviderStateMixin {
44 | /// Controller of the opacity animation
45 | late AnimationController _opacityController;
46 |
47 | /// Sliding Animation offset
48 | late Animation _slideAnimationOffset;
49 |
50 | /// Timer used to delayed animation
51 | Timer? _timer;
52 |
53 | /// Simple getter for widget's delay
54 | Duration get delay => widget.delay;
55 |
56 | /// Simple getter for widget's opacityTransitionDuration
57 | Duration get opacityTransitionDuration => widget.fadingDuration;
58 |
59 | /// Simple getter for widget's slidingCurve
60 | Curve get slidingCurve => widget.slidingCurve;
61 |
62 | /// Simple getter for widget's beginOffset
63 | Offset get beginOffset => widget.slidingBeginOffset;
64 |
65 | /// Simple getter for widget's fadeIn
66 | bool get fadeIn => widget.fadeIn;
67 |
68 | /// Initialize controllers, curve and offset with given parameters or default values
69 | /// Use a Timer in order to delay the animations if needed
70 | @override
71 | void initState() {
72 | super.initState();
73 |
74 | _opacityController = AnimationController(
75 | vsync: this,
76 | duration: opacityTransitionDuration,
77 | );
78 |
79 | final curvedAnimation = CurvedAnimation(
80 | curve: slidingCurve,
81 | parent: _opacityController,
82 | );
83 |
84 | _slideAnimationOffset = Tween(
85 | begin: beginOffset,
86 | end: Offset.zero,
87 | ).animate(curvedAnimation);
88 |
89 | _runFadeAnimation();
90 | }
91 |
92 | /// Dispose the opacity controller
93 | @override
94 | void dispose() {
95 | _opacityController.dispose();
96 | _timer?.cancel();
97 | super.dispose();
98 | }
99 |
100 | /// Whenever the widget is updated and that fadeIn is different from the oldWidget, triggers the fade in
101 | /// or out animation.
102 | @override
103 | void didUpdateWidget(DelayedDisplay oldWidget) {
104 | super.didUpdateWidget(oldWidget);
105 | if (oldWidget.fadeIn == fadeIn) {
106 | return;
107 | }
108 | _runFadeAnimation();
109 | }
110 |
111 | void _runFadeAnimation() {
112 | _timer = Timer(delay, () {
113 | fadeIn ? _opacityController.forward() : _opacityController.reverse();
114 | });
115 | }
116 |
117 | @override
118 | Widget build(BuildContext context) {
119 | return FadeTransition(
120 | opacity: _opacityController,
121 | child: SlideTransition(
122 | position: _slideAnimationOffset,
123 | child: widget.child,
124 | ),
125 | );
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/lib/ui/aboutPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
3 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
4 | import 'package:musify/helper/url_launcher.dart';
5 | import 'package:musify/helper/version.dart';
6 | import 'package:musify/style/appTheme.dart';
7 |
8 | class AboutPage extends StatelessWidget {
9 | const AboutPage({super.key});
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Scaffold(
14 | appBar: AppBar(
15 | centerTitle: true,
16 | title: Text(
17 | AppLocalizations.of(context)!.about,
18 | style: TextStyle(
19 | color: accent.primary,
20 | fontSize: 25,
21 | fontWeight: FontWeight.w700,
22 | ),
23 | ),
24 | leading: IconButton(
25 | icon: Icon(
26 | Icons.arrow_back,
27 | color: accent.primary,
28 | ),
29 | onPressed: () => Navigator.pop(context, false),
30 | ),
31 | backgroundColor: Colors.transparent,
32 | elevation: 0,
33 | ),
34 | body: const SingleChildScrollView(child: AboutCards()),
35 | );
36 | }
37 | }
38 |
39 | class AboutCards extends StatelessWidget {
40 | const AboutCards({super.key});
41 |
42 | @override
43 | Widget build(BuildContext context) {
44 | return Column(
45 | children: [
46 | Padding(
47 | padding: const EdgeInsets.only(top: 17, left: 8, right: 8, bottom: 6),
48 | child: Column(
49 | children: [
50 | ListTile(
51 | title: Padding(
52 | padding: const EdgeInsets.all(13),
53 | child: Center(
54 | child: Text(
55 | 'Musify | $version',
56 | style: TextStyle(
57 | color: accent.primary,
58 | fontSize: 24,
59 | fontWeight: FontWeight.w600,
60 | ),
61 | ),
62 | ),
63 | ),
64 | ),
65 | ],
66 | ),
67 | ),
68 | const Padding(
69 | padding: EdgeInsets.only(bottom: 8, left: 10, right: 10),
70 | child: Divider(
71 | color: Colors.white24,
72 | thickness: 0.8,
73 | ),
74 | ),
75 | Padding(
76 | padding: const EdgeInsets.only(top: 8, left: 8, right: 8, bottom: 6),
77 | child: Card(
78 | child: ListTile(
79 | leading: Container(
80 | height: 50,
81 | width: 50,
82 | decoration: const BoxDecoration(
83 | shape: BoxShape.circle,
84 | image: DecorationImage(
85 | fit: BoxFit.fill,
86 | image: NetworkImage(
87 | 'https://avatars.githubusercontent.com/u/79704324?v=4',
88 | ),
89 | ),
90 | ),
91 | ),
92 | title: const Text(
93 | 'Valeri Gokadze',
94 | ),
95 | subtitle: const Text(
96 | 'Web/APP Developer',
97 | ),
98 | trailing: Wrap(
99 | children: [
100 | IconButton(
101 | icon: Icon(
102 | MdiIcons.github,
103 | color: accent.primary,
104 | ),
105 | tooltip: 'Github',
106 | onPressed: () {
107 | launchURL(
108 | Uri.parse('https://github.com/gokadzev'),
109 | );
110 | },
111 | ),
112 | ],
113 | ),
114 | ),
115 | ),
116 | )
117 | ],
118 | );
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: musify
2 | description: Music Streaming and Downloading app made in Flutter!
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `flutter pub publish`. This is preferred for private packages.
6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
7 |
8 | # The following defines the version and build number for your application.
9 | # A version number is three numbers separated by dots, like 1.2.43
10 | # followed by an optional build number separated by a +.
11 | # Both the version and the builder number may be overridden in flutter
12 | # build by specifying --build-name and --build-number, respectively.
13 | # In Android, build-name is used as versionName while build-number used as versionCode.
14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
16 | # Read more about iOS versioning at
17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
18 | version: 3.6.0+35
19 |
20 | environment:
21 | sdk: '>=2.18.2 <3.0.0'
22 | flutter: ^3.3.7
23 |
24 | # Dependencies specify other packages that your package needs in order to work.
25 | # To automatically upgrade your package dependencies to the latest versions
26 | # consider running `flutter pub upgrade --major-versions`. Alternatively,
27 | # dependencies can be manually updated by changing the version numbers below to
28 | # the latest version available on pub.dev. To see which dependencies have newer
29 | # versions available, run `flutter pub outdated`.
30 | dependencies:
31 | audio_service: ^0.18.7
32 | audio_session: ^0.1.10
33 | cached_network_image: ^3.2.2
34 | dynamic_color: ^1.5.4
35 | flutter:
36 | sdk: flutter
37 | flutter_downloader: ^1.9.0
38 | flutter_localizations:
39 | sdk: flutter
40 | fluttertoast: ^8.0.9
41 | get_it: ^7.2.0
42 | hive: ^2.2.3
43 | hive_flutter: ^1.1.0
44 | http: ^0.13.5
45 | intl: ^0.17.0
46 | just_audio: ^0.9.30
47 | material_design_icons_flutter: ^5.0.6996
48 | on_audio_query: ^2.6.1
49 | package_info_plus: ^3.0.1
50 | path_provider: ^2.0.11
51 | permission_handler: ^10.2.0
52 | url_launcher: ^6.1.6
53 | youtube_explode_dart: ^1.12.1
54 |
55 | dev_dependencies:
56 | flutter_launcher_icons: ^0.10.0
57 | flutter_native_splash: ^2.2.13
58 | flutter_test:
59 | sdk: flutter
60 |
61 | # The "flutter_lints" package below contains a set of recommended lints to
62 | # encourage good coding practices. The lint set provided by the package is
63 | # activated in the `analysis_options.yaml` file located at the root of your
64 | # package. See that file for information about deactivating specific lint
65 | # rules and activating additional ones.
66 | # flutter_lints: ^2.0.0
67 |
68 | # For information on the generic Dart part of this file, see the
69 | # following page: https://dart.dev/tools/pub/pubspec
70 |
71 | # The following section is specific to Flutter packages.
72 | flutter:
73 |
74 | # The following line ensures that the Material Icons font is
75 | # included with your application, so that you can use the icons in
76 | # the material Icons class.
77 | generate: true
78 | uses-material-design: true
79 | fonts:
80 | - family: Ubuntu
81 | fonts:
82 | - asset: fonts/ubuntu.ttf
83 |
84 | # To add assets to your application, add an assets section, like this:
85 | assets:
86 | - assets/db/playlists.db.json
87 |
88 |
89 | flutter_native_splash:
90 | color: "#ffffff"
91 | image: assets/images/splash.png
92 | color_dark: "#151515"
93 | image_dark: assets/images/splash.png
94 |
95 | android_12:
96 | image: assets/images/splash.png
97 | icon_background_color: "#ffffff"
98 | image_dark: assets/images/splash.png
99 | icon_background_color_dark: "#151515"
100 |
101 |
102 | flutter_icons:
103 | android: "launcher_icon"
104 | adaptive_icon_background: "#191919"
105 | adaptive_icon_foreground: "assets/images/ic_launcher_foreground.png"
106 | adaptive_icon_round: "assets/images/ic_launcher_round.png"
107 | image_path: "assets/images/ic_launcher.png"
108 | ios: true
109 |
110 |
--------------------------------------------------------------------------------
/lib/services/audio_manager.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:audio_service/audio_service.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:just_audio/just_audio.dart';
6 | import 'package:musify/API/musify.dart';
7 | import 'package:musify/helper/mediaitem.dart';
8 | import 'package:musify/main.dart';
9 | import 'package:musify/services/audio_handler.dart';
10 | import 'package:musify/services/data_manager.dart';
11 | import 'package:musify/ui/morePage.dart';
12 |
13 | final _equalizer = AndroidEqualizer();
14 | final _loudnessEnhancer = AndroidLoudnessEnhancer();
15 | final _audioHandler = getIt();
16 |
17 | AudioPlayer audioPlayer = AudioPlayer(
18 | audioPipeline: AudioPipeline(
19 | androidAudioEffects: [
20 | _loudnessEnhancer,
21 | _equalizer,
22 | ],
23 | ),
24 | );
25 |
26 | ValueNotifier duration = ValueNotifier(Duration.zero);
27 | ValueNotifier position = ValueNotifier(Duration.zero);
28 |
29 | final shuffleNotifier = ValueNotifier(false);
30 | final repeatNotifier = ValueNotifier(false);
31 | final playerState = ValueNotifier(audioPlayer.playerState);
32 |
33 | bool get hasNext => activePlaylist.isEmpty
34 | ? audioPlayer.hasNext
35 | : id + 1 <= activePlaylist.length;
36 |
37 | bool get hasPrevious =>
38 | activePlaylist.isEmpty ? audioPlayer.hasPrevious : id - 1 >= 0;
39 |
40 | String get durationText =>
41 | duration.value != null ? duration.value.toString().split('.').first : '';
42 |
43 | String get positionText =>
44 | position.value != null ? position.value.toString().split('.').first : '';
45 |
46 | bool isMuted = false;
47 |
48 | Future playSong(Map song) async {
49 | if (song['ytid'].length == 0) {
50 | await MyAudioHandler()
51 | .addQueueItem(mapToMediaItem(song, song['songUrl'].toString()));
52 | } else {
53 | final songUrl = await getSong(song['ytid'], true);
54 |
55 | if (sponsorBlockSupport.value) {
56 | final segments = await getSkipSegments(song['ytid']);
57 | if (segments.isNotEmpty) {
58 | if (segments.length == 1) {
59 | await MyAudioHandler().addQueueItem(
60 | mapToMediaItem(song, songUrl),
61 | Duration(seconds: segments[0]['end']!),
62 | );
63 | } else {
64 | await MyAudioHandler().addQueueItem(
65 | mapToMediaItem(song, songUrl),
66 | Duration(seconds: segments[0]['end']!),
67 | Duration(seconds: segments[1]['start']!),
68 | );
69 | }
70 | } else {
71 | await MyAudioHandler().addQueueItem(mapToMediaItem(song, songUrl));
72 | }
73 | } else {
74 | await MyAudioHandler().addQueueItem(mapToMediaItem(song, songUrl));
75 | }
76 | }
77 | play();
78 | }
79 |
80 | Future changeShuffleStatus() async {
81 | if (shuffleNotifier.value == true) {
82 | await audioPlayer.setShuffleModeEnabled(false);
83 | } else {
84 | await audioPlayer.setShuffleModeEnabled(true);
85 | }
86 | }
87 |
88 | void changeAutoPlayNextStatus() {
89 | if (playNextSongAutomatically.value == false) {
90 | playNextSongAutomatically.value = true;
91 | addOrUpdateData('settings', 'playNextSongAutomatically', true);
92 | } else {
93 | playNextSongAutomatically.value = false;
94 | addOrUpdateData('settings', 'playNextSongAutomatically', false);
95 | }
96 | }
97 |
98 | Future changeLoopStatus() async {
99 | if (repeatNotifier.value == false) {
100 | repeatNotifier.value = true;
101 | await audioPlayer.setLoopMode(LoopMode.one);
102 | } else {
103 | repeatNotifier.value = false;
104 | await audioPlayer.setLoopMode(LoopMode.off);
105 | }
106 | }
107 |
108 | void changeSponsorBlockStatus() {
109 | if (sponsorBlockSupport.value == false) {
110 | sponsorBlockSupport.value = true;
111 | addOrUpdateData('settings', 'sponsorBlockSupport', true);
112 | } else {
113 | sponsorBlockSupport.value = false;
114 | addOrUpdateData('settings', 'sponsorBlockSupport', false);
115 | }
116 | }
117 |
118 | Future enableBooster() async {
119 | await _loudnessEnhancer.setEnabled(true);
120 | await _loudnessEnhancer.setTargetGain(1);
121 | }
122 |
123 | void play() => _audioHandler.play();
124 |
125 | void pause() => _audioHandler.pause();
126 |
127 | void stop() => _audioHandler.stop();
128 |
129 | void playNext() => _audioHandler.skipToNext();
130 |
131 | void playPrevious() => _audioHandler.skipToPrevious();
132 |
133 | Future mute(bool muted) async {
134 | if (muted) {
135 | await audioPlayer.setVolume(0);
136 | } else {
137 | await audioPlayer.setVolume(1);
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/lib/customWidgets/song_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
4 | import 'package:musify/API/musify.dart';
5 | import 'package:musify/services/audio_manager.dart';
6 | import 'package:musify/services/download_manager.dart';
7 | import 'package:musify/style/appTheme.dart';
8 |
9 | class SongBar extends StatelessWidget {
10 | SongBar(this.song, this.moveBackAfterPlay, {super.key});
11 |
12 | late final dynamic song;
13 | late final bool moveBackAfterPlay;
14 | late final songLikeStatus =
15 | ValueNotifier(isSongAlreadyLiked(song['ytid']));
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Container(
20 | padding: const EdgeInsets.only(left: 12, right: 12, bottom: 15),
21 | child: InkWell(
22 | borderRadius: BorderRadius.circular(20),
23 | onTap: () {
24 | playSong(song);
25 | if (activePlaylist.isNotEmpty) {
26 | activePlaylist = [];
27 | id = 0;
28 | }
29 | if (moveBackAfterPlay) {
30 | Navigator.pushReplacementNamed(context, '/');
31 | }
32 | },
33 | splashColor: accent.primary.withOpacity(0.4),
34 | hoverColor: accent.primary.withOpacity(0.4),
35 | focusColor: accent.primary.withOpacity(0.4),
36 | highlightColor: accent.primary.withOpacity(0.4),
37 | child: Row(
38 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
39 | children: [
40 | CachedNetworkImage(
41 | width: 60,
42 | height: 60,
43 | imageUrl: song['lowResImage'].toString(),
44 | imageBuilder: (context, imageProvider) => DecoratedBox(
45 | decoration: BoxDecoration(
46 | borderRadius: BorderRadius.circular(12),
47 | image: DecorationImage(
48 | image: imageProvider,
49 | centerSlice: const Rect.fromLTRB(1, 1, 1, 1),
50 | ),
51 | ),
52 | ),
53 | ),
54 | Flexible(
55 | child: Column(
56 | crossAxisAlignment: CrossAxisAlignment.start,
57 | children: [
58 | Container(
59 | alignment: Alignment.centerLeft,
60 | padding: const EdgeInsets.only(left: 15),
61 | child: Text(
62 | overflow: TextOverflow.ellipsis,
63 | (song['title'])
64 | .toString()
65 | .split('(')[0]
66 | .replaceAll('"', '"')
67 | .replaceAll('&', '&'),
68 | style: TextStyle(
69 | color: accent.primary,
70 | fontSize: 16,
71 | fontWeight: FontWeight.w700,
72 | ),
73 | ),
74 | ),
75 | const SizedBox(
76 | height: 5,
77 | ),
78 | Container(
79 | padding: const EdgeInsets.only(left: 15),
80 | child: Text(
81 | overflow: TextOverflow.ellipsis,
82 | song['more_info']['singers'].toString(),
83 | style: TextStyle(
84 | color: Theme.of(context).hintColor,
85 | fontWeight: FontWeight.w400,
86 | fontSize: 14,
87 | ),
88 | ),
89 | ),
90 | ],
91 | ),
92 | ),
93 | Row(
94 | mainAxisSize: MainAxisSize.min,
95 | children: [
96 | ValueListenableBuilder(
97 | valueListenable: songLikeStatus,
98 | builder: (_, value, __) {
99 | if (value == true) {
100 | return IconButton(
101 | color: accent.primary,
102 | icon: const Icon(MdiIcons.star),
103 | onPressed: () => {
104 | removeUserLikedSong(song['ytid']),
105 | songLikeStatus.value = false
106 | },
107 | );
108 | } else {
109 | return IconButton(
110 | color: accent.primary,
111 | icon: const Icon(MdiIcons.starOutline),
112 | onPressed: () => {
113 | addUserLikedSong(song['ytid']),
114 | songLikeStatus.value = true
115 | },
116 | );
117 | }
118 | },
119 | ),
120 | IconButton(
121 | color: accent.primary,
122 | icon: const Icon(MdiIcons.downloadOutline),
123 | onPressed: () => downloadSong(context, song),
124 | ),
125 | ],
126 | ),
127 | ],
128 | ),
129 | ),
130 | );
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/lib/ui/userPlaylistsPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
3 | import 'package:musify/API/musify.dart';
4 | import 'package:musify/customWidgets/spinner.dart';
5 | import 'package:musify/helper/flutter_toast.dart';
6 | import 'package:musify/style/appColors.dart';
7 | import 'package:musify/style/appTheme.dart';
8 | import 'package:musify/ui/playlistsPage.dart';
9 |
10 | class UserPlaylistsPage extends StatefulWidget {
11 | const UserPlaylistsPage({super.key});
12 |
13 | @override
14 | State createState() => _UserPlaylistsPageState();
15 | }
16 |
17 | class _UserPlaylistsPageState extends State {
18 | @override
19 | Widget build(BuildContext context) {
20 | return Scaffold(
21 | appBar: AppBar(
22 | centerTitle: true,
23 | title: Text(
24 | AppLocalizations.of(context)!.userPlaylists,
25 | style: TextStyle(
26 | color: accent.primary,
27 | fontSize: 25,
28 | fontWeight: FontWeight.w700,
29 | ),
30 | ),
31 | leading: IconButton(
32 | icon: Icon(
33 | Icons.arrow_back,
34 | color: accent.primary,
35 | ),
36 | onPressed: () => Navigator.pop(context, false),
37 | ),
38 | elevation: 0,
39 | ),
40 | floatingActionButton: FloatingActionButton(
41 | onPressed: () {
42 | showDialog(
43 | context: context,
44 | builder: (BuildContext context) {
45 | var id = '';
46 | return AlertDialog(
47 | backgroundColor: Theme.of(context).splashColor,
48 | content: Stack(
49 | children: [
50 | TextField(
51 | decoration: InputDecoration(
52 | hintText:
53 | AppLocalizations.of(context)!.youtubePlaylistID,
54 | hintStyle:
55 | TextStyle(color: Theme.of(context).hintColor),
56 | ),
57 | onChanged: (value) {
58 | setState(() {
59 | id = value;
60 | });
61 | },
62 | )
63 | ],
64 | ),
65 | actions: [
66 | TextButton(
67 | child: Text(
68 | AppLocalizations.of(context)!.add.toUpperCase(),
69 | style: const TextStyle(color: Colors.black),
70 | ),
71 | onPressed: () {
72 | showToast(addUserPlaylist(id, context));
73 | setState(() {
74 | Navigator.pop(context);
75 | });
76 | },
77 | ),
78 | ],
79 | );
80 | },
81 | );
82 | },
83 | backgroundColor: accent.primary,
84 | child: Icon(
85 | Icons.add,
86 | color: isAccentWhite(),
87 | ),
88 | ),
89 | body: SingleChildScrollView(
90 | child: Column(
91 | children: [
92 | const Padding(padding: EdgeInsets.only(top: 20)),
93 | FutureBuilder(
94 | future: getUserPlaylists(),
95 | builder: (context, data) {
96 | return (data as dynamic).data != null
97 | ? GridView.builder(
98 | gridDelegate:
99 | const SliverGridDelegateWithMaxCrossAxisExtent(
100 | maxCrossAxisExtent: 200,
101 | crossAxisSpacing: 20,
102 | mainAxisSpacing: 20,
103 | ),
104 | shrinkWrap: true,
105 | physics: const ScrollPhysics(),
106 | itemCount: (data as dynamic).data.length as int,
107 | padding: const EdgeInsets.only(
108 | left: 16,
109 | right: 16,
110 | top: 16,
111 | bottom: 20,
112 | ),
113 | itemBuilder: (BuildContext context, index) {
114 | return Center(
115 | child: GestureDetector(
116 | onLongPress: () {
117 | removeUserPlaylist(
118 | (data as dynamic)
119 | .data[index]['ytid']
120 | .toString(),
121 | );
122 | setState(() {});
123 | },
124 | child: GetPlaylist(
125 | index: index,
126 | image: (data as dynamic).data[index]['image'],
127 | title: (data as dynamic)
128 | .data[index]['title']
129 | .toString(),
130 | id: (data as dynamic).data[index]['ytid'],
131 | ),
132 | ),
133 | );
134 | },
135 | )
136 | : const Spinner();
137 | },
138 | ),
139 | ],
140 | ),
141 | ),
142 | );
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/lib/ui/userLikedSongsPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
3 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
4 | import 'package:musify/API/musify.dart';
5 | import 'package:musify/customWidgets/song_bar.dart';
6 | import 'package:musify/style/appColors.dart';
7 | import 'package:musify/style/appTheme.dart';
8 |
9 | class UserLikedSongs extends StatefulWidget {
10 | const UserLikedSongs({super.key});
11 |
12 | @override
13 | State createState() => _UserLikedSongsState();
14 | }
15 |
16 | class _UserLikedSongsState extends State {
17 | @override
18 | Widget build(BuildContext context) {
19 | return Scaffold(
20 | appBar: AppBar(
21 | centerTitle: true,
22 | title: Text(
23 | AppLocalizations.of(context)!.userLikedSongs,
24 | style: TextStyle(
25 | color: accent.primary,
26 | fontSize: 25,
27 | fontWeight: FontWeight.w700,
28 | ),
29 | ),
30 | leading: IconButton(
31 | icon: Icon(
32 | Icons.arrow_back,
33 | color: accent.primary,
34 | ),
35 | onPressed: () => Navigator.pop(context, false),
36 | ),
37 | elevation: 0,
38 | ),
39 | body: SingleChildScrollView(
40 | child: Column(
41 | children: [
42 | Row(
43 | children: [
44 | Container(
45 | margin: const EdgeInsets.only(left: 10, right: 10),
46 | height: 200,
47 | width: 200,
48 | child: Card(
49 | child: Container(
50 | width: 200,
51 | height: 200,
52 | decoration: BoxDecoration(
53 | borderRadius: BorderRadius.circular(10),
54 | color: Theme.of(context).backgroundColor.withAlpha(30),
55 | ),
56 | child: Column(
57 | mainAxisAlignment: MainAxisAlignment.center,
58 | children: [
59 | Icon(
60 | MdiIcons.musicNoteOutline,
61 | size: 30,
62 | color: accent.primary,
63 | ),
64 | Text(
65 | AppLocalizations.of(context)!.userLikedSongs,
66 | style: TextStyle(color: accent.primary),
67 | textAlign: TextAlign.center,
68 | ),
69 | ],
70 | ),
71 | ),
72 | ),
73 | ),
74 | const SizedBox(width: 16),
75 | Expanded(
76 | child: Column(
77 | crossAxisAlignment: CrossAxisAlignment.start,
78 | children: [
79 | const SizedBox(height: 12),
80 | Text(
81 | AppLocalizations.of(context)!.userLikedSongs,
82 | style: TextStyle(
83 | color: accent.primary,
84 | fontSize: 18,
85 | fontWeight: FontWeight.w600,
86 | ),
87 | ),
88 | const SizedBox(height: 16),
89 | Text(
90 | '${AppLocalizations.of(context)!.yourFavoriteSongsHere}!',
91 | style: TextStyle(
92 | color: accent.primary,
93 | fontSize: 10,
94 | fontWeight: FontWeight.w600,
95 | ),
96 | ),
97 | const Padding(
98 | padding: EdgeInsets.only(top: 5, bottom: 5),
99 | ),
100 | ElevatedButton(
101 | onPressed: () => {
102 | setActivePlaylist(userLikedSongsList),
103 | Navigator.pop(context, false)
104 | },
105 | style: ButtonStyle(
106 | backgroundColor:
107 | MaterialStateProperty.all(accent.primary),
108 | ),
109 | child: Text(
110 | AppLocalizations.of(context)!.playAll.toUpperCase(),
111 | style: TextStyle(
112 | color: isAccentWhite(),
113 | ),
114 | ),
115 | ),
116 | ],
117 | ),
118 | )
119 | ],
120 | ),
121 | const Padding(padding: EdgeInsets.only(top: 20)),
122 | ListView.builder(
123 | shrinkWrap: true,
124 | physics: const BouncingScrollPhysics(),
125 | addAutomaticKeepAlives:
126 | false, // may be problem with lazyload if it implemented
127 | addRepaintBoundaries: false,
128 | // Need to display a loading tile if more items are coming
129 | itemCount: userLikedSongsList.length,
130 | itemBuilder: (BuildContext context, int index) {
131 | return Padding(
132 | padding: const EdgeInsets.only(top: 5, bottom: 5),
133 | child: SongBar(
134 | userLikedSongsList[index],
135 | true,
136 | ),
137 | );
138 | },
139 | )
140 | ],
141 | ),
142 | ),
143 | );
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:audio_service/audio_service.dart';
4 | import 'package:audio_session/audio_session.dart';
5 | import 'package:dynamic_color/dynamic_color.dart';
6 | import 'package:flutter/foundation.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:flutter_downloader/flutter_downloader.dart';
9 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
10 | import 'package:flutter_localizations/flutter_localizations.dart';
11 | import 'package:get_it/get_it.dart';
12 | import 'package:hive_flutter/hive_flutter.dart';
13 | import 'package:musify/services/audio_handler.dart';
14 | import 'package:musify/services/audio_manager.dart';
15 | import 'package:musify/style/appColors.dart';
16 | import 'package:musify/style/appTheme.dart';
17 | import 'package:musify/ui/morePage.dart';
18 | import 'package:musify/ui/rootPage.dart';
19 | import 'package:package_info_plus/package_info_plus.dart';
20 |
21 | GetIt getIt = GetIt.instance;
22 | late PackageInfo packageInfo;
23 | bool _interrupted = false;
24 | ThemeMode themeMode = ThemeMode.system;
25 |
26 | final codes = {
27 | 'English': 'en',
28 | 'Georgian': 'ka',
29 | 'Chinese': 'zh',
30 | 'Dutch': 'nl',
31 | 'German': 'de',
32 | 'Indonesian': 'id',
33 | 'Italian': 'it',
34 | 'Polish': 'pl',
35 | 'Portuguese': 'pt',
36 | 'Spanish': 'es',
37 | 'Turkish': 'tr',
38 | 'Ukrainian': 'uk',
39 | };
40 |
41 | class MyApp extends StatefulWidget {
42 | const MyApp({super.key});
43 |
44 | static Future setThemeMode(
45 | BuildContext context,
46 | ThemeMode newThemeMode,
47 | ) async {
48 | final state = context.findAncestorStateOfType<_MyAppState>()!;
49 | state.changeTheme(newThemeMode);
50 | }
51 |
52 | static Future setLocale(
53 | BuildContext context,
54 | Locale newLocale,
55 | ) async {
56 | final state = context.findAncestorStateOfType<_MyAppState>()!;
57 | state.changeLanguage(newLocale);
58 | }
59 |
60 | static Future setAccentColor(
61 | BuildContext context,
62 | Color newAccentColor,
63 | bool systemColorStatus,
64 | ) async {
65 | final state = context.findAncestorStateOfType<_MyAppState>()!;
66 | state.changeAccentColor(newAccentColor, systemColorStatus);
67 | }
68 |
69 | @override
70 | _MyAppState createState() => _MyAppState();
71 | }
72 |
73 | class _MyAppState extends State {
74 | Locale _locale = const Locale('en', '');
75 |
76 | void changeTheme(ThemeMode newThemeMode) {
77 | setState(() {
78 | themeMode = newThemeMode;
79 | });
80 | }
81 |
82 | void changeLanguage(Locale locale) {
83 | setState(() {
84 | _locale = locale;
85 | });
86 | }
87 |
88 | void changeAccentColor(Color newAccentColor, bool systemColorStatus) {
89 | setState(() {
90 | useSystemColor.value = systemColorStatus;
91 | accent = ColorScheme.fromSwatch(
92 | primarySwatch: getMaterialColorFromColor(
93 | newAccentColor,
94 | ),
95 | accentColor: newAccentColor,
96 | );
97 | });
98 | }
99 |
100 | @override
101 | void initState() {
102 | super.initState();
103 | _locale = Locale(
104 | codes[Hive.box('settings').get('language', defaultValue: 'English')
105 | as String]!,
106 | );
107 | themeMode = Hive.box('settings').get('themeMode', defaultValue: 'system') ==
108 | 'system'
109 | ? ThemeMode.system
110 | : Hive.box('settings').get('themeMode') == 'light'
111 | ? ThemeMode.light
112 | : ThemeMode.dark;
113 | }
114 |
115 | @override
116 | void dispose() {
117 | Hive.close();
118 | super.dispose();
119 | }
120 |
121 | @override
122 | Widget build(BuildContext context) {
123 | return DynamicColorBuilder(
124 | builder: (lightColorScheme, darkColorScheme) {
125 | if (lightColorScheme != null && useSystemColor.value == true)
126 | accent = (themeMode == ThemeMode.light
127 | ? lightColorScheme
128 | : darkColorScheme)!;
129 | return MaterialApp(
130 | themeMode: themeMode,
131 | debugShowCheckedModeBanner: false,
132 | darkTheme: darkColorScheme != null && useSystemColor.value == true
133 | ? getAppDarkTheme().copyWith(
134 | colorScheme: darkColorScheme,
135 | )
136 | : getAppDarkTheme(),
137 | theme: lightColorScheme != null && useSystemColor.value == true
138 | ? getAppLightTheme().copyWith(
139 | colorScheme: lightColorScheme,
140 | )
141 | : getAppLightTheme(),
142 | localizationsDelegates: const [
143 | AppLocalizations.delegate,
144 | GlobalMaterialLocalizations.delegate,
145 | GlobalWidgetsLocalizations.delegate,
146 | GlobalCupertinoLocalizations.delegate,
147 | ],
148 | supportedLocales: const [
149 | Locale('en', ''),
150 | Locale('ka', ''),
151 | Locale('zh', ''),
152 | Locale('nl', ''),
153 | Locale('fr', ''),
154 | Locale('de', ''),
155 | Locale('he', ''),
156 | Locale('hi', ''),
157 | Locale('hu', ''),
158 | Locale('id', ''),
159 | Locale('it', ''),
160 | Locale('pl', ''),
161 | Locale('pt', ''),
162 | Locale('es', ''),
163 | Locale('ta', ''),
164 | Locale('tr', ''),
165 | Locale('uk', ''),
166 | Locale('ur', '')
167 | ],
168 | locale: _locale,
169 | initialRoute: '/',
170 | routes: {
171 | '/': (context) => Musify(),
172 | },
173 | );
174 | },
175 | );
176 | }
177 | }
178 |
179 | void main() async {
180 | WidgetsFlutterBinding.ensureInitialized();
181 | await Hive.initFlutter();
182 | await Hive.openBox('settings');
183 | await Hive.openBox('user');
184 | await Hive.openBox('cache');
185 | await initialisation();
186 | runApp(const MyApp());
187 | }
188 |
189 | Future initialisation() async {
190 | final session = await AudioSession.instance;
191 | await session.configure(const AudioSessionConfiguration.music());
192 | session.interruptionEventStream.listen((event) {
193 | if (event.begin) {
194 | if (audioPlayer.playing) {
195 | pause();
196 | _interrupted = true;
197 | }
198 | } else {
199 | switch (event.type) {
200 | case AudioInterruptionType.pause:
201 | case AudioInterruptionType.duck:
202 | if (!audioPlayer.playing && _interrupted) {
203 | play();
204 | }
205 | break;
206 | case AudioInterruptionType.unknown:
207 | break;
208 | }
209 | _interrupted = false;
210 | }
211 | });
212 | final audioHandler = await AudioService.init(
213 | builder: MyAudioHandler.new,
214 | config: const AudioServiceConfig(
215 | androidNotificationChannelId: 'com.gokadzev.musify',
216 | androidNotificationChannelName: 'Musify',
217 | androidNotificationOngoing: true,
218 | androidNotificationIcon: 'mipmap/launcher_icon',
219 | androidShowNotificationBadge: true,
220 | ),
221 | );
222 | getIt.registerSingleton(audioHandler);
223 | await enableBooster();
224 |
225 | packageInfo = await PackageInfo.fromPlatform();
226 |
227 | try {
228 | await FlutterDownloader.initialize(
229 | debug: kDebugMode,
230 | ignoreSsl: true,
231 | );
232 |
233 | await FlutterDownloader.registerCallback(downloadCallback);
234 | } catch (e) {
235 | if (kDebugMode) {
236 | print(e);
237 | }
238 | }
239 | }
240 |
241 | @pragma('vm:entry-point')
242 | void downloadCallback(String id, DownloadTaskStatus status, int progress) {}
243 |
--------------------------------------------------------------------------------
/lib/ui/searchPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
3 | import 'package:hive_flutter/hive_flutter.dart';
4 | import 'package:musify/API/musify.dart';
5 | import 'package:musify/customWidgets/song_bar.dart';
6 | import 'package:musify/customWidgets/spinner.dart';
7 | import 'package:musify/services/data_manager.dart';
8 | import 'package:musify/style/appTheme.dart';
9 |
10 | class SearchPage extends StatefulWidget {
11 | @override
12 | _SearchPageState createState() => _SearchPageState();
13 | }
14 |
15 | List searchHistory = Hive.box('user').get('searchHistory', defaultValue: []);
16 |
17 | class _SearchPageState extends State {
18 | final TextEditingController _searchBar = TextEditingController();
19 | final ValueNotifier _fetchingSongs = ValueNotifier(false);
20 | final FocusNode _inputNode = FocusNode();
21 | String _searchQuery = '';
22 |
23 | Future search() async {
24 | _searchQuery = _searchBar.text;
25 | if (_searchQuery.isNotEmpty) {
26 | _fetchingSongs.value = true;
27 | if (!searchHistory.contains(_searchQuery)) {
28 | searchHistory.insert(0, _searchQuery);
29 | addOrUpdateData('user', 'searchHistory', searchHistory);
30 | }
31 | _fetchingSongs.value = false;
32 | }
33 | setState(() {});
34 | }
35 |
36 | @override
37 | Widget build(BuildContext context) {
38 | return Scaffold(
39 | appBar: AppBar(
40 | centerTitle: true,
41 | title: Text(
42 | AppLocalizations.of(context)!.search,
43 | style: TextStyle(
44 | color: accent.primary,
45 | fontSize: 30,
46 | fontWeight: FontWeight.w700,
47 | ),
48 | ),
49 | elevation: 0,
50 | ),
51 | body: SingleChildScrollView(
52 | child: Column(
53 | children: [
54 | Padding(
55 | padding: const EdgeInsets.only(
56 | top: 12,
57 | bottom: 20,
58 | left: 12,
59 | right: 12,
60 | ),
61 | child: TextField(
62 | onSubmitted: (String value) {
63 | search();
64 | FocusManager.instance.primaryFocus?.unfocus();
65 | },
66 | controller: _searchBar,
67 | focusNode: _inputNode,
68 | style: TextStyle(
69 | fontSize: 16,
70 | color: accent.primary,
71 | ),
72 | cursorColor: Colors.green[50],
73 | decoration: InputDecoration(
74 | filled: true,
75 | enabledBorder: OutlineInputBorder(
76 | borderRadius: const BorderRadius.all(
77 | Radius.circular(100),
78 | ),
79 | borderSide: BorderSide(
80 | color: Theme.of(context).backgroundColor,
81 | ),
82 | ),
83 | focusedBorder: OutlineInputBorder(
84 | borderRadius: const BorderRadius.all(
85 | Radius.circular(100),
86 | ),
87 | borderSide: BorderSide(color: accent.primary),
88 | ),
89 | suffixIcon: ValueListenableBuilder(
90 | valueListenable: _fetchingSongs,
91 | builder: (_, value, __) {
92 | if (value == true) {
93 | return IconButton(
94 | icon: const SizedBox(
95 | height: 18,
96 | width: 18,
97 | child: Spinner(),
98 | ),
99 | color: accent.primary,
100 | onPressed: () {
101 | search();
102 | FocusManager.instance.primaryFocus?.unfocus();
103 | },
104 | );
105 | } else {
106 | return IconButton(
107 | icon: Icon(
108 | Icons.search,
109 | color: accent.primary,
110 | ),
111 | color: accent.primary,
112 | onPressed: () {
113 | search();
114 | FocusManager.instance.primaryFocus?.unfocus();
115 | },
116 | );
117 | }
118 | },
119 | ),
120 | border: InputBorder.none,
121 | hintText: '${AppLocalizations.of(context)!.search}...',
122 | hintStyle: TextStyle(
123 | color: accent.primary,
124 | ),
125 | contentPadding: const EdgeInsets.only(
126 | left: 18,
127 | right: 20,
128 | top: 14,
129 | bottom: 14,
130 | ),
131 | ),
132 | ),
133 | ),
134 | if (_searchQuery.isEmpty)
135 | ListView.builder(
136 | shrinkWrap: true,
137 | addAutomaticKeepAlives: false,
138 | addRepaintBoundaries: false,
139 | physics: const NeverScrollableScrollPhysics(),
140 | itemCount: searchHistory.length,
141 | itemBuilder: (BuildContext ctxt, int index) {
142 | return Padding(
143 | padding: const EdgeInsets.only(top: 8, bottom: 6),
144 | child: Card(
145 | child: ListTile(
146 | leading: Icon(Icons.search, color: accent.primary),
147 | title: Text(
148 | searchHistory[index],
149 | style: TextStyle(color: accent.primary),
150 | ),
151 | onTap: () async {
152 | _fetchingSongs.value = true;
153 | _searchQuery = searchHistory[index];
154 | await fetchSongsList(searchHistory[index]);
155 | _fetchingSongs.value = false;
156 | setState(() {});
157 | },
158 | ),
159 | ),
160 | );
161 | },
162 | )
163 | else
164 | FutureBuilder(
165 | future: fetchSongsList(_searchQuery),
166 | builder: (context, data) {
167 | return (data as dynamic).data != null
168 | ? ListView.builder(
169 | shrinkWrap: true,
170 | addAutomaticKeepAlives: false,
171 | addRepaintBoundaries: false,
172 | physics: const NeverScrollableScrollPhysics(),
173 | itemCount: (data as dynamic).data.length,
174 | itemBuilder: (BuildContext ctxt, int index) {
175 | return Padding(
176 | padding: const EdgeInsets.only(top: 5, bottom: 5),
177 | child: SongBar(
178 | (data as dynamic).data[index],
179 | false,
180 | ),
181 | );
182 | },
183 | )
184 | : const Spinner();
185 | },
186 | )
187 | ],
188 | ),
189 | ),
190 | );
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/lib/customWidgets/custom_animated_bottom_bar.dart:
--------------------------------------------------------------------------------
1 | // pub: https://pub.dev/packages/flashy_tab_bar2
2 | // license: https://raw.githubusercontent.com/Cuberto/flashy-tabbar-android/master/LICENSE
3 | // remade (not original)
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:musify/customWidgets/marque.dart';
7 |
8 | class CustomAnimatedBottomBar extends StatelessWidget {
9 | CustomAnimatedBottomBar({
10 | super.key,
11 | this.selectedIndex = 0,
12 | this.height = 60,
13 | this.showElevation = true,
14 | this.iconSize = 20,
15 | this.backgroundColor,
16 | this.animationDuration = const Duration(milliseconds: 170),
17 | this.animationCurve = Curves.linear,
18 | this.shadows = const [
19 | BoxShadow(
20 | color: Colors.black12,
21 | blurRadius: 3,
22 | ),
23 | ],
24 | required this.items,
25 | required this.onItemSelected,
26 | }) {
27 | assert(height >= 55 && height <= 100);
28 | assert(items.length >= 2 && items.length <= 5);
29 | }
30 |
31 | final Curve animationCurve;
32 | final Duration animationDuration;
33 | final Color? backgroundColor;
34 | final double height;
35 | final double iconSize;
36 | final List items;
37 | final ValueChanged onItemSelected;
38 | final int selectedIndex;
39 | final List shadows;
40 | final bool showElevation;
41 |
42 | @override
43 | Widget build(BuildContext context) {
44 | final bg = (backgroundColor == null)
45 | ? Theme.of(context).bottomAppBarColor
46 | : backgroundColor;
47 |
48 | return DecoratedBox(
49 | decoration: BoxDecoration(
50 | color: bg,
51 | boxShadow: showElevation ? shadows : [],
52 | ),
53 | child: SafeArea(
54 | child: Container(
55 | width: double.infinity,
56 | height: height,
57 | padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 20),
58 | child: Row(
59 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
60 | children: items.map((item) {
61 | final index = items.indexOf(item);
62 | return Expanded(
63 | child: GestureDetector(
64 | onTap: () => onItemSelected(index),
65 | child: _FlashTabBarItem(
66 | item: item,
67 | tabBarHeight: height,
68 | iconSize: iconSize,
69 | isSelected: index == selectedIndex,
70 | backgroundColor: bg!,
71 | animationDuration: animationDuration,
72 | animationCurve: animationCurve,
73 | ),
74 | ),
75 | );
76 | }).toList(),
77 | ),
78 | ),
79 | ),
80 | );
81 | }
82 | }
83 |
84 | class BottomNavBarItem {
85 | BottomNavBarItem({
86 | required this.icon,
87 | required this.title,
88 | this.activeColor = const Color(0xff272e81),
89 | this.inactiveColor = const Color(0xff9496c1),
90 | });
91 |
92 | Color activeColor;
93 | final Icon icon;
94 | Color inactiveColor;
95 | final Text title;
96 | }
97 |
98 | class _FlashTabBarItem extends StatelessWidget {
99 | const _FlashTabBarItem({
100 | required this.item,
101 | required this.isSelected,
102 | required this.tabBarHeight,
103 | required this.backgroundColor,
104 | required this.animationDuration,
105 | required this.animationCurve,
106 | required this.iconSize,
107 | });
108 |
109 | final Curve animationCurve;
110 | final Duration animationDuration;
111 | final Color backgroundColor;
112 | final double iconSize;
113 | final bool isSelected;
114 | final BottomNavBarItem item;
115 | final double tabBarHeight;
116 |
117 | @override
118 | Widget build(BuildContext context) {
119 | return Container(
120 | color: backgroundColor,
121 | height: double.maxFinite,
122 | child: Stack(
123 | clipBehavior: Clip.hardEdge,
124 | alignment: Alignment.center,
125 | children: [
126 | AnimatedAlign(
127 | duration: animationDuration,
128 | alignment: isSelected ? Alignment.topCenter : Alignment.center,
129 | child: AnimatedOpacity(
130 | opacity: isSelected ? 1.0 : 1.0,
131 | duration: animationDuration,
132 | child: IconTheme(
133 | data: IconThemeData(
134 | size: iconSize,
135 | color: isSelected
136 | ? item.activeColor.withOpacity(1)
137 | : item.inactiveColor,
138 | ),
139 | child: item.icon,
140 | ),
141 | ),
142 | ),
143 | AnimatedPositioned(
144 | curve: animationCurve,
145 | duration: animationDuration,
146 | top: isSelected ? -2.0 * iconSize : tabBarHeight / 4,
147 | child: Column(
148 | mainAxisAlignment: MainAxisAlignment.center,
149 | children: [
150 | SizedBox(
151 | width: iconSize,
152 | height: iconSize,
153 | ),
154 | CustomPaint(
155 | painter: _CustomPath(backgroundColor),
156 | child: SizedBox(
157 | width: 80,
158 | height: iconSize,
159 | ),
160 | )
161 | ],
162 | ),
163 | ),
164 | AnimatedAlign(
165 | alignment: isSelected ? Alignment.center : Alignment.bottomCenter,
166 | duration: animationDuration,
167 | curve: animationCurve,
168 | child: AnimatedOpacity(
169 | opacity: isSelected ? 1.0 : 0.0,
170 | duration: animationDuration,
171 | child: MarqueeWidget(
172 | direction: Axis.horizontal,
173 | child: DefaultTextStyle.merge(
174 | style: TextStyle(
175 | color: item.activeColor,
176 | fontWeight: FontWeight.bold,
177 | ),
178 | child: item.title,
179 | ),
180 | ),
181 | ),
182 | ),
183 | Positioned(
184 | bottom: 0,
185 | child: CustomPaint(
186 | painter: _CustomPath(backgroundColor),
187 | child: SizedBox(
188 | width: 80,
189 | height: iconSize - 8,
190 | ),
191 | ),
192 | ),
193 | Align(
194 | alignment: Alignment.bottomCenter,
195 | child: AnimatedOpacity(
196 | duration: animationDuration,
197 | opacity: isSelected ? 1.0 : 0.0,
198 | child: Container(
199 | width: 5,
200 | height: 5,
201 | alignment: Alignment.bottomCenter,
202 | margin: const EdgeInsets.all(5),
203 | decoration: BoxDecoration(
204 | color: item.activeColor,
205 | borderRadius: BorderRadius.circular(2.5),
206 | ),
207 | ),
208 | ),
209 | )
210 | ],
211 | ),
212 | );
213 | }
214 | }
215 |
216 | class _CustomPath extends CustomPainter {
217 | _CustomPath(this.backgroundColor);
218 |
219 | final Color backgroundColor;
220 |
221 | @override
222 | void paint(Canvas canvas, Size size) {
223 | final path = Path();
224 | final paint = Paint();
225 |
226 | path.lineTo(0, 0);
227 | path.lineTo(0, 2.0 * size.height);
228 | path.lineTo(1.0 * size.width, 2.0 * size.height);
229 | path.lineTo(1.0 * size.width, 1.0 * size.height);
230 | path.lineTo(0, 0);
231 | path.close();
232 |
233 | paint.color = backgroundColor;
234 | canvas.drawPath(path, paint);
235 | }
236 |
237 | @override
238 | bool shouldRepaint(CustomPainter oldDelegate) {
239 | return oldDelegate != this;
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/lib/ui/playlistPage.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:cached_network_image/cached_network_image.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
6 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
7 | import 'package:musify/API/musify.dart';
8 | import 'package:musify/customWidgets/song_bar.dart';
9 | import 'package:musify/customWidgets/spinner.dart';
10 | import 'package:musify/helper/flutter_toast.dart';
11 | import 'package:musify/style/appColors.dart';
12 | import 'package:musify/style/appTheme.dart';
13 |
14 | class PlaylistPage extends StatefulWidget {
15 | const PlaylistPage({super.key, required this.playlist});
16 | final dynamic playlist;
17 |
18 | @override
19 | _PlaylistPageState createState() => _PlaylistPageState();
20 | }
21 |
22 | class _PlaylistPageState extends State {
23 | final _songsList = [];
24 |
25 | bool _isLoading = true;
26 | bool _hasMore = true;
27 | final _itemsPerPage = 35;
28 | var _currentPage = 0;
29 | var _currentLastLoadedId = 0;
30 |
31 | @override
32 | void initState() {
33 | super.initState();
34 | _isLoading = true;
35 | _hasMore = true;
36 | _loadMore();
37 | }
38 |
39 | @override
40 | void dispose() {
41 | super.dispose();
42 | }
43 |
44 | void _loadMore() {
45 | _isLoading = true;
46 | fetch().then((List fetchedList) {
47 | if (!mounted) return;
48 | if (fetchedList.isEmpty) {
49 | setState(() {
50 | _isLoading = false;
51 | _hasMore = false;
52 | });
53 | } else {
54 | setState(() {
55 | _isLoading = false;
56 | _songsList.addAll(fetchedList);
57 | });
58 | }
59 | });
60 | }
61 |
62 | Future fetch() async {
63 | final list = [];
64 | final _count = widget.playlist['list'].length as int;
65 | final n = min(_itemsPerPage, _count - _currentPage * _itemsPerPage);
66 | await Future.delayed(const Duration(seconds: 1), () {
67 | for (var i = 0; i < n; i++) {
68 | list.add(widget.playlist['list'][_currentLastLoadedId]);
69 | _currentLastLoadedId++;
70 | }
71 | });
72 | _currentPage++;
73 | return list;
74 | }
75 |
76 | @override
77 | Widget build(BuildContext context) {
78 | return Scaffold(
79 | appBar: AppBar(
80 | centerTitle: true,
81 | title: Text(
82 | AppLocalizations.of(context)!.playlist,
83 | style: TextStyle(
84 | color: accent.primary,
85 | fontSize: 25,
86 | fontWeight: FontWeight.w700,
87 | ),
88 | ),
89 | leading: IconButton(
90 | icon: Icon(
91 | Icons.arrow_back,
92 | color: accent.primary,
93 | ),
94 | onPressed: () => Navigator.pop(context, false),
95 | ),
96 | elevation: 0,
97 | ),
98 | body: SingleChildScrollView(
99 | child: widget.playlist != null
100 | ? Column(
101 | children: [
102 | Container(
103 | margin: const EdgeInsets.only(left: 10, right: 26),
104 | height: 250,
105 | width: 250,
106 | child: Card(
107 | color: Colors.transparent,
108 | child: widget.playlist['image'] != ''
109 | ? DecoratedBox(
110 | decoration: BoxDecoration(
111 | borderRadius: BorderRadius.circular(10),
112 | image: DecorationImage(
113 | fit: BoxFit.cover,
114 | image: CachedNetworkImageProvider(
115 | widget.playlist['image'].toString(),
116 | ),
117 | ),
118 | ),
119 | )
120 | : Container(
121 | width: 200,
122 | height: 200,
123 | decoration: BoxDecoration(
124 | borderRadius: BorderRadius.circular(10),
125 | color: const Color.fromARGB(30, 255, 255, 255),
126 | ),
127 | child: Column(
128 | mainAxisAlignment: MainAxisAlignment.center,
129 | children: [
130 | Icon(
131 | MdiIcons.musicNoteOutline,
132 | size: 30,
133 | color: accent.primary,
134 | ),
135 | Text(
136 | widget.playlist['title'].toString(),
137 | style: TextStyle(color: accent.primary),
138 | textAlign: TextAlign.center,
139 | ),
140 | ],
141 | ),
142 | ),
143 | ),
144 | ),
145 | Column(
146 | crossAxisAlignment: CrossAxisAlignment.center,
147 | children: [
148 | const SizedBox(height: 12),
149 | Text(
150 | widget.playlist['title'].toString(),
151 | textAlign: TextAlign.center,
152 | style: TextStyle(
153 | color: accent.primary,
154 | fontSize: 18,
155 | fontWeight: FontWeight.w600,
156 | ),
157 | ),
158 | const SizedBox(height: 16),
159 | Text(
160 | widget.playlist['header_desc'].toString(),
161 | textAlign: TextAlign.center,
162 | style: TextStyle(
163 | color: accent.primary,
164 | fontSize: 10,
165 | fontWeight: FontWeight.w600,
166 | ),
167 | ),
168 | const SizedBox(height: 10),
169 | ElevatedButton(
170 | onPressed: () => {
171 | setActivePlaylist(
172 | widget.playlist['list'] as List,
173 | ),
174 | showToast(
175 | AppLocalizations.of(context)!.queueInitText,
176 | ),
177 | Navigator.pushReplacementNamed(context, '/'),
178 | },
179 | style: ButtonStyle(
180 | backgroundColor:
181 | MaterialStateProperty.all(accent.primary),
182 | ),
183 | child: Text(
184 | AppLocalizations.of(context)!.playAll.toUpperCase(),
185 | style: TextStyle(
186 | color: isAccentWhite(),
187 | ),
188 | ),
189 | ),
190 | ],
191 | ),
192 | const SizedBox(height: 30),
193 | if (_songsList.isNotEmpty)
194 | ListView.builder(
195 | shrinkWrap: true,
196 | physics: const BouncingScrollPhysics(),
197 | addAutomaticKeepAlives:
198 | false, // may be problem with lazyload if it implemented
199 | addRepaintBoundaries: false,
200 | // Need to display a loading tile if more items are coming
201 | itemCount:
202 | _hasMore ? _songsList.length + 1 : _songsList.length,
203 | itemBuilder: (BuildContext context, int index) {
204 | if (index >= _songsList.length) {
205 | if (!_isLoading) {
206 | _loadMore();
207 | }
208 | return const Spinner();
209 | }
210 | return Padding(
211 | padding: const EdgeInsets.only(top: 5, bottom: 5),
212 | child: SongBar(
213 | _songsList[index],
214 | true,
215 | ),
216 | );
217 | },
218 | )
219 | else
220 | const Spinner()
221 | ],
222 | )
223 | : SizedBox(
224 | height: MediaQuery.of(context).size.height - 100,
225 | child: const Spinner(),
226 | ),
227 | ),
228 | );
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/lib/ui/homePage.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
4 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
5 | import 'package:musify/API/musify.dart';
6 | import 'package:musify/customWidgets/delayed_display.dart';
7 | import 'package:musify/customWidgets/song_bar.dart';
8 | import 'package:musify/customWidgets/spinner.dart';
9 | import 'package:musify/style/appTheme.dart';
10 | import 'package:musify/ui/playlistPage.dart';
11 |
12 | class HomePage extends StatefulWidget {
13 | @override
14 | _HomePageState createState() => _HomePageState();
15 | }
16 |
17 | class _HomePageState extends State {
18 | @override
19 | Widget build(BuildContext context) {
20 | return Scaffold(
21 | appBar: AppBar(
22 | centerTitle: true,
23 | title: Text(
24 | 'Musify.',
25 | style: TextStyle(
26 | color: accent.primary,
27 | fontSize: 35,
28 | fontWeight: FontWeight.w800,
29 | ),
30 | ),
31 | elevation: 0,
32 | ),
33 | body: SingleChildScrollView(
34 | child: Column(
35 | crossAxisAlignment: CrossAxisAlignment.start,
36 | children: [
37 | FutureBuilder(
38 | future: getPlaylists(5),
39 | builder: (context, data) {
40 | return data.hasData
41 | ? Wrap(
42 | children: [
43 | Padding(
44 | padding: EdgeInsets.only(
45 | top: MediaQuery.of(context).size.height / 55,
46 | bottom: 10,
47 | left: 20,
48 | right: 20,
49 | ),
50 | child: Text(
51 | AppLocalizations.of(context)!.suggestedPlaylists,
52 | style: TextStyle(
53 | color: accent.primary,
54 | fontSize: 20,
55 | fontWeight: FontWeight.w700,
56 | ),
57 | ),
58 | ),
59 | SizedBox(
60 | height: 230,
61 | child: ListView.builder(
62 | scrollDirection: Axis.horizontal,
63 | itemCount: (data as dynamic).data.length as int,
64 | itemBuilder: (context, index) {
65 | return Padding(
66 | padding: const EdgeInsets.symmetric(
67 | horizontal: 15,
68 | ),
69 | child: CubeContainer(
70 | id: (data as dynamic)
71 | .data[index]['ytid']
72 | .toString(),
73 | image: (data as dynamic)
74 | .data[index]['image']
75 | .toString(),
76 | ),
77 | );
78 | },
79 | ),
80 | )
81 | ],
82 | )
83 | : const Center(
84 | child: Padding(
85 | padding: EdgeInsets.all(35),
86 | child: Spinner(),
87 | ),
88 | );
89 | },
90 | ),
91 | FutureBuilder(
92 | future: get10Music('PLgzTt0k8mXzEk586ze4BjvDXR7c-TUSnx'),
93 | builder: (context, data) {
94 | if (data.connectionState != ConnectionState.done) {
95 | return const Center(
96 | child: Padding(
97 | padding: EdgeInsets.all(35),
98 | child: Spinner(),
99 | ),
100 | );
101 | }
102 | if (data.hasError) {
103 | return Center(
104 | child: Text(
105 | 'Error!',
106 | style: TextStyle(color: accent.primary, fontSize: 18),
107 | ),
108 | );
109 | }
110 | if (!data.hasData) {
111 | return Center(
112 | child: Text(
113 | 'Nothing Found!',
114 | style: TextStyle(color: accent.primary, fontSize: 18),
115 | ),
116 | );
117 | }
118 | return Wrap(
119 | children: [
120 | Padding(
121 | padding: EdgeInsets.only(
122 | top: MediaQuery.of(context).size.height / 55,
123 | bottom: 10,
124 | left: 20,
125 | right: 20,
126 | ),
127 | child: Text(
128 | AppLocalizations.of(context)!.recommendedForYou,
129 | style: TextStyle(
130 | color: accent.primary,
131 | fontSize: 20,
132 | fontWeight: FontWeight.w700,
133 | ),
134 | ),
135 | ),
136 | Padding(
137 | padding: const EdgeInsets.symmetric(
138 | horizontal: 7,
139 | ),
140 | child: ListView.builder(
141 | shrinkWrap: true,
142 | addAutomaticKeepAlives: false,
143 | addRepaintBoundaries: false,
144 | physics: const BouncingScrollPhysics(),
145 | itemCount: (data as dynamic).data.length as int,
146 | itemBuilder: (context, index) {
147 | return SongBar(
148 | (data as dynamic).data[index],
149 | false,
150 | );
151 | },
152 | ),
153 | )
154 | ],
155 | );
156 | },
157 | ),
158 | ],
159 | ),
160 | ),
161 | );
162 | }
163 | }
164 |
165 | class CubeContainer extends StatelessWidget {
166 | const CubeContainer({
167 | required this.id,
168 | required this.image,
169 | });
170 | final String id;
171 | final String image;
172 |
173 | @override
174 | Widget build(BuildContext context) {
175 | final size = MediaQuery.of(context).size;
176 | return DelayedDisplay(
177 | delay: const Duration(milliseconds: 200),
178 | fadingDuration: const Duration(milliseconds: 400),
179 | child: GestureDetector(
180 | onTap: () {
181 | getPlaylistInfoForWidget(id).then(
182 | (value) => {
183 | Navigator.push(
184 | context,
185 | MaterialPageRoute(
186 | builder: (context) => PlaylistPage(playlist: value),
187 | ),
188 | )
189 | },
190 | );
191 | },
192 | child: Column(
193 | children: [
194 | SizedBox(
195 | height: size.height / 4.15,
196 | width: size.width / 1.9,
197 | child: Card(
198 | color: Colors.transparent,
199 | child: CachedNetworkImage(
200 | imageUrl: image,
201 | imageBuilder: (context, imageProvider) => DecoratedBox(
202 | decoration: BoxDecoration(
203 | borderRadius: BorderRadius.circular(10),
204 | image: DecorationImage(
205 | image: imageProvider,
206 | fit: BoxFit.cover,
207 | ),
208 | ),
209 | ),
210 | errorWidget: (context, url, error) => DecoratedBox(
211 | decoration: BoxDecoration(
212 | borderRadius: BorderRadius.circular(10),
213 | color: const Color.fromARGB(30, 255, 255, 255),
214 | ),
215 | child: Column(
216 | mainAxisAlignment: MainAxisAlignment.center,
217 | children: [
218 | Icon(
219 | MdiIcons.musicNoteOutline,
220 | size: 30,
221 | color: accent.primary,
222 | ),
223 | ],
224 | ),
225 | ),
226 | ),
227 | ),
228 | ),
229 | ],
230 | ),
231 | ),
232 | );
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/lib/API/musify.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:math';
3 |
4 | import 'package:audio_service/audio_service.dart';
5 | import 'package:flutter/services.dart';
6 | import 'package:flutter/widgets.dart';
7 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
8 | import 'package:hive/hive.dart';
9 | import 'package:http/http.dart' as http;
10 | import 'package:musify/helper/formatter.dart';
11 | import 'package:musify/helper/mediaitem.dart';
12 | import 'package:musify/services/audio_handler.dart';
13 | import 'package:musify/services/audio_manager.dart';
14 | import 'package:musify/services/data_manager.dart';
15 | import 'package:musify/services/ext_storage.dart';
16 | import 'package:musify/services/lyrics_service.dart';
17 | import 'package:on_audio_query/on_audio_query.dart';
18 | import 'package:permission_handler/permission_handler.dart';
19 | import 'package:youtube_explode_dart/youtube_explode_dart.dart';
20 |
21 | final yt = YoutubeExplode();
22 | final OnAudioQuery _audioQuery = OnAudioQuery();
23 |
24 | final random = Random();
25 |
26 | List playlists = [];
27 | List userPlaylists = Hive.box('user').get('playlists', defaultValue: []);
28 | List userLikedSongsList = Hive.box('user').get('likedSongs', defaultValue: []);
29 | List suggestedPlaylists = [];
30 | List activePlaylist = [];
31 |
32 | final lyrics = ValueNotifier('null');
33 | String lastFetchedLyrics = 'null';
34 |
35 | int id = 0;
36 |
37 | Future fetchSongsList(String searchQuery) async {
38 | final List list = await yt.search.search(searchQuery);
39 | final searchedList = [
40 | for (final s in list)
41 | returnSongLayout(
42 | 0,
43 | s,
44 | )
45 | ];
46 |
47 | return searchedList;
48 | }
49 |
50 | Future get10Music(dynamic playlistid) async {
51 | final List playlistSongs =
52 | await getData('cache', 'playlist10Songs$playlistid') ?? [];
53 | if (playlistSongs.isEmpty) {
54 | var index = 0;
55 | await for (final song in yt.playlists.getVideos(playlistid).take(10)) {
56 | playlistSongs.add(
57 | returnSongLayout(
58 | index,
59 | song,
60 | ),
61 | );
62 | index += 1;
63 | }
64 |
65 | addOrUpdateData('cache', 'playlist10Songs$playlistid', playlistSongs);
66 | }
67 |
68 | return playlistSongs;
69 | }
70 |
71 | Future> getUserPlaylists() async {
72 | final playlistsByUser = [];
73 | for (final playlistID in userPlaylists) {
74 | final plist = await yt.playlists.get(playlistID);
75 | playlistsByUser.add({
76 | 'ytid': plist.id,
77 | 'title': plist.title,
78 | 'subtitle': 'Just Updated',
79 | 'header_desc': plist.description.length < 120
80 | ? plist.description
81 | : plist.description.substring(0, 120),
82 | 'type': 'playlist',
83 | 'image': '',
84 | 'list': []
85 | });
86 | }
87 | return playlistsByUser;
88 | }
89 |
90 | String addUserPlaylist(String playlistId, BuildContext context) {
91 | if (playlistId.length != 34) {
92 | return '${AppLocalizations.of(context)!.notYTlist}!';
93 | } else {
94 | userPlaylists.add(playlistId);
95 | addOrUpdateData('user', 'playlists', userPlaylists);
96 | return '${AppLocalizations.of(context)!.addedSuccess}!';
97 | }
98 | }
99 |
100 | void removeUserPlaylist(String playlistId) {
101 | userPlaylists.remove(playlistId);
102 | addOrUpdateData('user', 'playlists', userPlaylists);
103 | }
104 |
105 | Future addUserLikedSong(dynamic songId) async {
106 | userLikedSongsList
107 | .add(await getSongDetails(userLikedSongsList.length, songId));
108 | addOrUpdateData('user', 'likedSongs', userLikedSongsList);
109 | }
110 |
111 | void removeUserLikedSong(dynamic songId) {
112 | userLikedSongsList.removeWhere((song) => song['ytid'] == songId);
113 | addOrUpdateData('user', 'likedSongs', userLikedSongsList);
114 | }
115 |
116 | bool isSongAlreadyLiked(dynamic songId) {
117 | return userLikedSongsList.where((song) => song['ytid'] == songId).isNotEmpty;
118 | }
119 |
120 | Future getPlaylists([int? playlistsNum]) async {
121 | if (playlists.isEmpty) {
122 | playlists =
123 | json.decode(await rootBundle.loadString('assets/db/playlists.db.json'))
124 | as List;
125 | }
126 |
127 | if (playlistsNum != null) {
128 | if (suggestedPlaylists.isEmpty) {
129 | suggestedPlaylists =
130 | (playlists.toList()..shuffle()).take(playlistsNum).toList();
131 | }
132 | return suggestedPlaylists;
133 | } else {
134 | return playlists;
135 | }
136 | }
137 |
138 | Future searchPlaylist(String query) async {
139 | if (playlists.isEmpty) {
140 | playlists =
141 | json.decode(await rootBundle.loadString('assets/db/playlists.db.json'))
142 | as List;
143 | }
144 |
145 | return playlists
146 | .where(
147 | (playlist) =>
148 | playlist['title'].toLowerCase().contains(query.toLowerCase()),
149 | )
150 | .toList();
151 | }
152 |
153 | Future