├── .flutter-plugins-dependencies ├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── android ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── music_app │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── settings_aar.gradle ├── first_screen.png ├── fourth_screen.png ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── flutter_export_environment.sh ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ └── contents.xcworkspacedata └── Runner │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── main.m ├── lib ├── main.dart └── src │ ├── blocs │ ├── global.dart │ ├── music_player.dart │ └── permissions.dart │ ├── common │ ├── empty_screen.dart │ └── music_icons.dart │ ├── models │ ├── album.dart │ ├── playback.dart │ └── playerstate.dart │ ├── root.dart │ └── ui │ ├── albums │ ├── album_tile.dart │ ├── albums_screen.dart │ └── specific_album_screen.dart │ ├── all_songs │ ├── all_songs_screen.dart │ └── song_tile.dart │ ├── favorites │ └── favorites_screen.dart │ ├── music_homepage │ ├── bottom_panel.dart │ └── music_homepage.dart │ ├── now_playing │ ├── album_art_container.dart │ ├── empty_album_art.dart │ ├── music_board_controls.dart │ ├── now_playing_screen.dart │ ├── now_playing_slider.dart │ └── preferences_board.dart │ └── search │ ├── search_screen.dart │ └── search_screen_bloc.dart ├── pubspec.lock ├── pubspec.yaml ├── second_screen.png └── third_screen.png /.flutter-plugins-dependencies: -------------------------------------------------------------------------------- 1 | {"_info":"// This is a generated file; do not edit or check into version control.","dependencyGraph":[{"name":"flute_music_player","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"shared_preferences","dependencies":[]}]} -------------------------------------------------------------------------------- /.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 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .packages 26 | .pub-cache/ 27 | .pub/ 28 | /build/ 29 | 30 | # Android related 31 | **/android/**/gradle-wrapper.jar 32 | **/android/.gradle 33 | **/android/captures/ 34 | **/android/gradlew 35 | **/android/gradlew.bat 36 | **/android/local.properties 37 | **/android/**/GeneratedPluginRegistrant.java 38 | 39 | # iOS/XCode related 40 | **/ios/**/*.mode1v3 41 | **/ios/**/*.mode2v3 42 | **/ios/**/*.moved-aside 43 | **/ios/**/*.pbxuser 44 | **/ios/**/*.perspectivev3 45 | **/ios/**/*sync/ 46 | **/ios/**/.sconsign.dblite 47 | **/ios/**/.tags* 48 | **/ios/**/.vagrant/ 49 | **/ios/**/DerivedData/ 50 | **/ios/**/Icon? 51 | **/ios/**/Pods/ 52 | **/ios/**/.symlinks/ 53 | **/ios/**/profile 54 | **/ios/**/xcuserdata 55 | **/ios/.generated/ 56 | **/ios/Flutter/App.framework 57 | **/ios/Flutter/Flutter.framework 58 | **/ios/Flutter/Generated.xcconfig 59 | **/ios/Flutter/app.flx 60 | **/ios/Flutter/app.zip 61 | **/ios/Flutter/flutter_assets/ 62 | **/ios/ServiceDefinitions.json 63 | **/ios/Runner/GeneratedPluginRegistrant.* 64 | 65 | # Exceptions to above rules. 66 | !**/ios/**/default.mode1v3 67 | !**/ios/**/default.mode2v3 68 | !**/ios/**/default.pbxuser 69 | !**/ios/**/default.perspectivev3 70 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 71 | -------------------------------------------------------------------------------- /.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: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Karim Elghamry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Awesome Flutter 3 | 4 | 5 | # Chillify 6 | 7 | A Flutter music app made with Provider and BLoC pattern. (Works on Android for now) 8 | 9 | Recommended Flutter version: 1.7.8+hotfix.4 10 | 11 | UI heavily inspired by: https://dribbble.com/shots/6523216-Music-Player-Application/attachments 12 | 13 | current app version (apk): https://github.com/KarimElghamry/chillify/releases/tag/v1.5 14 | 15 | ## Current Features 16 | [✓] Retrieve all songs 17 | 18 | [✓] Pause 19 | 20 | [✓] Play 21 | 22 | [✓] Seek 23 | 24 | [✓] Repeat 25 | 26 | [✓] Shuffle 27 | 28 | [✓] Now Playing 29 | 30 | [✓] add to favorites 31 | 32 | [✓] Search songs 33 | 34 | [✓] Albums 35 | 36 | [✓] Well-designed UI 37 | 38 | 39 | ## TODO List 40 | 41 | ᛫ Add foreground notification of "now playing" song with playback controls 42 | 43 | ᛫ Add SQLite database or alternatives 44 | 45 | ᛫ Add tabs => Artists and Playlists 46 | 47 | ᛫ Implement beautiful animations 48 | 49 | 50 | ## Screenshots 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.example.music_app" 37 | minSdkVersion 16 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | multiDexEnabled true 43 | } 44 | 45 | buildTypes { 46 | release { 47 | // TODO: Add your own signing config for the release build. 48 | // Signing with the debug keys for now, so `flutter run --release` works. 49 | signingConfig signingConfigs.debug 50 | } 51 | } 52 | } 53 | 54 | flutter { 55 | source '../..' 56 | } 57 | 58 | dependencies { 59 | testImplementation 'junit:junit:4.12' 60 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 61 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 62 | } 63 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 11 | 15 | 22 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/example/music_app/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.music_app; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | -------------------------------------------------------------------------------- /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-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /first_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/first_screen.png -------------------------------------------------------------------------------- /fourth_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/fourth_screen.png -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=D:\flutter" 4 | export "FLUTTER_APPLICATION_PATH=C:\Users\kimoe\Desktop\chillify\chillify" 5 | export "FLUTTER_TARGET=lib\main.dart" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "SYMROOT=${SOURCE_ROOT}/../build\ios" 8 | export "FLUTTER_FRAMEWORK_DIR=D:\flutter\bin\cache\artifacts\engine\ios" 9 | export "FLUTTER_BUILD_NAME=1.0.0" 10 | export "FLUTTER_BUILD_NUMBER=1" 11 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 13 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 15 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 16 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 17 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 18 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 19 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 20 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 21 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXCopyFilesBuildPhase section */ 25 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 26 | isa = PBXCopyFilesBuildPhase; 27 | buildActionMask = 2147483647; 28 | dstPath = ""; 29 | dstSubfolderSpec = 10; 30 | files = ( 31 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 32 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 33 | ); 34 | name = "Embed Frameworks"; 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXCopyFilesBuildPhase section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 41 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 42 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 43 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 44 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 45 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 46 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 47 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 48 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 49 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 50 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 52 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 53 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 55 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | /* End PBXFileReference section */ 57 | 58 | /* Begin PBXFrameworksBuildPhase section */ 59 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 64 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 9740EEB11CF90186004384FC /* Flutter */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 3B80C3931E831B6300D905FE /* App.framework */, 75 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 76 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 77 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 78 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 79 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 80 | ); 81 | name = Flutter; 82 | sourceTree = ""; 83 | }; 84 | 97C146E51CF9000F007C117D = { 85 | isa = PBXGroup; 86 | children = ( 87 | 9740EEB11CF90186004384FC /* Flutter */, 88 | 97C146F01CF9000F007C117D /* Runner */, 89 | 97C146EF1CF9000F007C117D /* Products */, 90 | CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, 91 | ); 92 | sourceTree = ""; 93 | }; 94 | 97C146EF1CF9000F007C117D /* Products */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 97C146EE1CF9000F007C117D /* Runner.app */, 98 | ); 99 | name = Products; 100 | sourceTree = ""; 101 | }; 102 | 97C146F01CF9000F007C117D /* Runner */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 106 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 107 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 108 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 109 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 110 | 97C147021CF9000F007C117D /* Info.plist */, 111 | 97C146F11CF9000F007C117D /* Supporting Files */, 112 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 113 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 114 | ); 115 | path = Runner; 116 | sourceTree = ""; 117 | }; 118 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 97C146F21CF9000F007C117D /* main.m */, 122 | ); 123 | name = "Supporting Files"; 124 | sourceTree = ""; 125 | }; 126 | /* End PBXGroup section */ 127 | 128 | /* Begin PBXNativeTarget section */ 129 | 97C146ED1CF9000F007C117D /* Runner */ = { 130 | isa = PBXNativeTarget; 131 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 132 | buildPhases = ( 133 | 9740EEB61CF901F6004384FC /* Run Script */, 134 | 97C146EA1CF9000F007C117D /* Sources */, 135 | 97C146EB1CF9000F007C117D /* Frameworks */, 136 | 97C146EC1CF9000F007C117D /* Resources */, 137 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 138 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 139 | ); 140 | buildRules = ( 141 | ); 142 | dependencies = ( 143 | ); 144 | name = Runner; 145 | productName = Runner; 146 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 147 | productType = "com.apple.product-type.application"; 148 | }; 149 | /* End PBXNativeTarget section */ 150 | 151 | /* Begin PBXProject section */ 152 | 97C146E61CF9000F007C117D /* Project object */ = { 153 | isa = PBXProject; 154 | attributes = { 155 | LastUpgradeCheck = 0910; 156 | ORGANIZATIONNAME = "The Chromium Authors"; 157 | TargetAttributes = { 158 | 97C146ED1CF9000F007C117D = { 159 | CreatedOnToolsVersion = 7.3.1; 160 | }; 161 | }; 162 | }; 163 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 164 | compatibilityVersion = "Xcode 3.2"; 165 | developmentRegion = English; 166 | hasScannedForEncodings = 0; 167 | knownRegions = ( 168 | en, 169 | Base, 170 | ); 171 | mainGroup = 97C146E51CF9000F007C117D; 172 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 173 | projectDirPath = ""; 174 | projectRoot = ""; 175 | targets = ( 176 | 97C146ED1CF9000F007C117D /* Runner */, 177 | ); 178 | }; 179 | /* End PBXProject section */ 180 | 181 | /* Begin PBXResourcesBuildPhase section */ 182 | 97C146EC1CF9000F007C117D /* Resources */ = { 183 | isa = PBXResourcesBuildPhase; 184 | buildActionMask = 2147483647; 185 | files = ( 186 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 187 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 188 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 189 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 190 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 191 | ); 192 | runOnlyForDeploymentPostprocessing = 0; 193 | }; 194 | /* End PBXResourcesBuildPhase section */ 195 | 196 | /* Begin PBXShellScriptBuildPhase section */ 197 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 198 | isa = PBXShellScriptBuildPhase; 199 | buildActionMask = 2147483647; 200 | files = ( 201 | ); 202 | inputPaths = ( 203 | ); 204 | name = "Thin Binary"; 205 | outputPaths = ( 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | shellPath = /bin/sh; 209 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 210 | }; 211 | 9740EEB61CF901F6004384FC /* Run Script */ = { 212 | isa = PBXShellScriptBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | ); 216 | inputPaths = ( 217 | ); 218 | name = "Run Script"; 219 | outputPaths = ( 220 | ); 221 | runOnlyForDeploymentPostprocessing = 0; 222 | shellPath = /bin/sh; 223 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 224 | }; 225 | /* End PBXShellScriptBuildPhase section */ 226 | 227 | /* Begin PBXSourcesBuildPhase section */ 228 | 97C146EA1CF9000F007C117D /* Sources */ = { 229 | isa = PBXSourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 233 | 97C146F31CF9000F007C117D /* main.m in Sources */, 234 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | /* End PBXSourcesBuildPhase section */ 239 | 240 | /* Begin PBXVariantGroup section */ 241 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 242 | isa = PBXVariantGroup; 243 | children = ( 244 | 97C146FB1CF9000F007C117D /* Base */, 245 | ); 246 | name = Main.storyboard; 247 | sourceTree = ""; 248 | }; 249 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 250 | isa = PBXVariantGroup; 251 | children = ( 252 | 97C147001CF9000F007C117D /* Base */, 253 | ); 254 | name = LaunchScreen.storyboard; 255 | sourceTree = ""; 256 | }; 257 | /* End PBXVariantGroup section */ 258 | 259 | /* Begin XCBuildConfiguration section */ 260 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 261 | isa = XCBuildConfiguration; 262 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 263 | buildSettings = { 264 | ALWAYS_SEARCH_USER_PATHS = NO; 265 | CLANG_ANALYZER_NONNULL = YES; 266 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 267 | CLANG_CXX_LIBRARY = "libc++"; 268 | CLANG_ENABLE_MODULES = YES; 269 | CLANG_ENABLE_OBJC_ARC = YES; 270 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 271 | CLANG_WARN_BOOL_CONVERSION = YES; 272 | CLANG_WARN_COMMA = YES; 273 | CLANG_WARN_CONSTANT_CONVERSION = YES; 274 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 275 | CLANG_WARN_EMPTY_BODY = YES; 276 | CLANG_WARN_ENUM_CONVERSION = YES; 277 | CLANG_WARN_INFINITE_RECURSION = YES; 278 | CLANG_WARN_INT_CONVERSION = YES; 279 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 280 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 281 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 282 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 283 | CLANG_WARN_STRICT_PROTOTYPES = YES; 284 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 285 | CLANG_WARN_UNREACHABLE_CODE = YES; 286 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 287 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 288 | COPY_PHASE_STRIP = NO; 289 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 290 | ENABLE_NS_ASSERTIONS = NO; 291 | ENABLE_STRICT_OBJC_MSGSEND = YES; 292 | GCC_C_LANGUAGE_STANDARD = gnu99; 293 | GCC_NO_COMMON_BLOCKS = YES; 294 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 295 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 296 | GCC_WARN_UNDECLARED_SELECTOR = YES; 297 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 298 | GCC_WARN_UNUSED_FUNCTION = YES; 299 | GCC_WARN_UNUSED_VARIABLE = YES; 300 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 301 | MTL_ENABLE_DEBUG_INFO = NO; 302 | SDKROOT = iphoneos; 303 | TARGETED_DEVICE_FAMILY = "1,2"; 304 | VALIDATE_PRODUCT = YES; 305 | }; 306 | name = Profile; 307 | }; 308 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 309 | isa = XCBuildConfiguration; 310 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 311 | buildSettings = { 312 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 313 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 314 | DEVELOPMENT_TEAM = S8QB4VV633; 315 | ENABLE_BITCODE = NO; 316 | FRAMEWORK_SEARCH_PATHS = ( 317 | "$(inherited)", 318 | "$(PROJECT_DIR)/Flutter", 319 | ); 320 | INFOPLIST_FILE = Runner/Info.plist; 321 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 322 | LIBRARY_SEARCH_PATHS = ( 323 | "$(inherited)", 324 | "$(PROJECT_DIR)/Flutter", 325 | ); 326 | PRODUCT_BUNDLE_IDENTIFIER = com.example.musicApp; 327 | PRODUCT_NAME = "$(TARGET_NAME)"; 328 | VERSIONING_SYSTEM = "apple-generic"; 329 | }; 330 | name = Profile; 331 | }; 332 | 97C147031CF9000F007C117D /* Debug */ = { 333 | isa = XCBuildConfiguration; 334 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 335 | buildSettings = { 336 | ALWAYS_SEARCH_USER_PATHS = NO; 337 | CLANG_ANALYZER_NONNULL = YES; 338 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 339 | CLANG_CXX_LIBRARY = "libc++"; 340 | CLANG_ENABLE_MODULES = YES; 341 | CLANG_ENABLE_OBJC_ARC = YES; 342 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 343 | CLANG_WARN_BOOL_CONVERSION = YES; 344 | CLANG_WARN_COMMA = YES; 345 | CLANG_WARN_CONSTANT_CONVERSION = YES; 346 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 347 | CLANG_WARN_EMPTY_BODY = YES; 348 | CLANG_WARN_ENUM_CONVERSION = YES; 349 | CLANG_WARN_INFINITE_RECURSION = YES; 350 | CLANG_WARN_INT_CONVERSION = YES; 351 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 353 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 354 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 355 | CLANG_WARN_STRICT_PROTOTYPES = YES; 356 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 357 | CLANG_WARN_UNREACHABLE_CODE = YES; 358 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 359 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 360 | COPY_PHASE_STRIP = NO; 361 | DEBUG_INFORMATION_FORMAT = dwarf; 362 | ENABLE_STRICT_OBJC_MSGSEND = YES; 363 | ENABLE_TESTABILITY = YES; 364 | GCC_C_LANGUAGE_STANDARD = gnu99; 365 | GCC_DYNAMIC_NO_PIC = NO; 366 | GCC_NO_COMMON_BLOCKS = YES; 367 | GCC_OPTIMIZATION_LEVEL = 0; 368 | GCC_PREPROCESSOR_DEFINITIONS = ( 369 | "DEBUG=1", 370 | "$(inherited)", 371 | ); 372 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 373 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 374 | GCC_WARN_UNDECLARED_SELECTOR = YES; 375 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 376 | GCC_WARN_UNUSED_FUNCTION = YES; 377 | GCC_WARN_UNUSED_VARIABLE = YES; 378 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 379 | MTL_ENABLE_DEBUG_INFO = YES; 380 | ONLY_ACTIVE_ARCH = YES; 381 | SDKROOT = iphoneos; 382 | TARGETED_DEVICE_FAMILY = "1,2"; 383 | }; 384 | name = Debug; 385 | }; 386 | 97C147041CF9000F007C117D /* Release */ = { 387 | isa = XCBuildConfiguration; 388 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 389 | buildSettings = { 390 | ALWAYS_SEARCH_USER_PATHS = NO; 391 | CLANG_ANALYZER_NONNULL = YES; 392 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 393 | CLANG_CXX_LIBRARY = "libc++"; 394 | CLANG_ENABLE_MODULES = YES; 395 | CLANG_ENABLE_OBJC_ARC = YES; 396 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 397 | CLANG_WARN_BOOL_CONVERSION = YES; 398 | CLANG_WARN_COMMA = YES; 399 | CLANG_WARN_CONSTANT_CONVERSION = YES; 400 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 401 | CLANG_WARN_EMPTY_BODY = YES; 402 | CLANG_WARN_ENUM_CONVERSION = YES; 403 | CLANG_WARN_INFINITE_RECURSION = YES; 404 | CLANG_WARN_INT_CONVERSION = YES; 405 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 406 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 407 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 408 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 409 | CLANG_WARN_STRICT_PROTOTYPES = YES; 410 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 411 | CLANG_WARN_UNREACHABLE_CODE = YES; 412 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 413 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 414 | COPY_PHASE_STRIP = NO; 415 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 416 | ENABLE_NS_ASSERTIONS = NO; 417 | ENABLE_STRICT_OBJC_MSGSEND = YES; 418 | GCC_C_LANGUAGE_STANDARD = gnu99; 419 | GCC_NO_COMMON_BLOCKS = YES; 420 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 421 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 422 | GCC_WARN_UNDECLARED_SELECTOR = YES; 423 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 424 | GCC_WARN_UNUSED_FUNCTION = YES; 425 | GCC_WARN_UNUSED_VARIABLE = YES; 426 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 427 | MTL_ENABLE_DEBUG_INFO = NO; 428 | SDKROOT = iphoneos; 429 | TARGETED_DEVICE_FAMILY = "1,2"; 430 | VALIDATE_PRODUCT = YES; 431 | }; 432 | name = Release; 433 | }; 434 | 97C147061CF9000F007C117D /* Debug */ = { 435 | isa = XCBuildConfiguration; 436 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 437 | buildSettings = { 438 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 439 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 440 | ENABLE_BITCODE = NO; 441 | FRAMEWORK_SEARCH_PATHS = ( 442 | "$(inherited)", 443 | "$(PROJECT_DIR)/Flutter", 444 | ); 445 | INFOPLIST_FILE = Runner/Info.plist; 446 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 447 | LIBRARY_SEARCH_PATHS = ( 448 | "$(inherited)", 449 | "$(PROJECT_DIR)/Flutter", 450 | ); 451 | PRODUCT_BUNDLE_IDENTIFIER = com.example.musicApp; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | VERSIONING_SYSTEM = "apple-generic"; 454 | }; 455 | name = Debug; 456 | }; 457 | 97C147071CF9000F007C117D /* Release */ = { 458 | isa = XCBuildConfiguration; 459 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 460 | buildSettings = { 461 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 462 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 463 | ENABLE_BITCODE = NO; 464 | FRAMEWORK_SEARCH_PATHS = ( 465 | "$(inherited)", 466 | "$(PROJECT_DIR)/Flutter", 467 | ); 468 | INFOPLIST_FILE = Runner/Info.plist; 469 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 470 | LIBRARY_SEARCH_PATHS = ( 471 | "$(inherited)", 472 | "$(PROJECT_DIR)/Flutter", 473 | ); 474 | PRODUCT_BUNDLE_IDENTIFIER = com.example.musicApp; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | VERSIONING_SYSTEM = "apple-generic"; 477 | }; 478 | name = Release; 479 | }; 480 | /* End XCBuildConfiguration section */ 481 | 482 | /* Begin XCConfigurationList section */ 483 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 484 | isa = XCConfigurationList; 485 | buildConfigurations = ( 486 | 97C147031CF9000F007C117D /* Debug */, 487 | 97C147041CF9000F007C117D /* Release */, 488 | 249021D3217E4FDB00AE95B9 /* Profile */, 489 | ); 490 | defaultConfigurationIsVisible = 0; 491 | defaultConfigurationName = Release; 492 | }; 493 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 494 | isa = XCConfigurationList; 495 | buildConfigurations = ( 496 | 97C147061CF9000F007C117D /* Debug */, 497 | 97C147071CF9000F007C117D /* Release */, 498 | 249021D4217E4FDB00AE95B9 /* Profile */, 499 | ); 500 | defaultConfigurationIsVisible = 0; 501 | defaultConfigurationName = Release; 502 | }; 503 | /* End XCConfigurationList section */ 504 | }; 505 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 506 | } 507 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | music_app 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | //Flutter version 1.7.8+hotfix.4 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:music_app/src/root.dart'; 5 | 6 | void main() { 7 | WidgetsFlutterBinding.ensureInitialized(); 8 | runApp(ChillifyApp()); 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/blocs/global.dart: -------------------------------------------------------------------------------- 1 | import 'package:music_app/src/blocs/music_player.dart'; 2 | import 'package:music_app/src/blocs/permissions.dart'; 3 | 4 | class GlobalBloc { 5 | PermissionsBloc _permissionsBloc; 6 | MusicPlayerBloc _musicPlayerBloc; 7 | MusicPlayerBloc get musicPlayerBloc => _musicPlayerBloc; 8 | PermissionsBloc get permissionsBloc => _permissionsBloc; 9 | 10 | GlobalBloc() { 11 | _musicPlayerBloc = MusicPlayerBloc(); 12 | _permissionsBloc = PermissionsBloc(); 13 | } 14 | 15 | void dispose() { 16 | _musicPlayerBloc.dispose(); 17 | _permissionsBloc.dispose(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/blocs/music_player.dart: -------------------------------------------------------------------------------- 1 | import 'package:flute_music_player/flute_music_player.dart'; 2 | import 'package:music_app/src/models/album.dart'; 3 | import 'package:music_app/src/models/playback.dart'; 4 | import 'package:music_app/src/models/playerstate.dart'; 5 | import 'package:rxdart/rxdart.dart'; 6 | import 'package:shared_preferences/shared_preferences.dart'; 7 | import 'dart:convert'; 8 | 9 | class MusicPlayerBloc { 10 | BehaviorSubject> _songs$; 11 | BehaviorSubject> _albums$; 12 | BehaviorSubject> _playerState$; 13 | BehaviorSubject, List>> 14 | _playlist$; //key is normal, value is shuffle 15 | BehaviorSubject _position$; 16 | BehaviorSubject> _playback$; 17 | BehaviorSubject> _favorites$; 18 | BehaviorSubject _isAudioSeeking$; 19 | MusicFinder _audioPlayer; 20 | Song _defaultSong; 21 | 22 | BehaviorSubject> get songs$ => _songs$; 23 | BehaviorSubject> get albums$ => _albums$; 24 | BehaviorSubject> get playerState$ => 25 | _playerState$; 26 | BehaviorSubject get position$ => _position$; 27 | BehaviorSubject> get playback$ => _playback$; 28 | BehaviorSubject> get favorites$ => _favorites$; 29 | 30 | MusicPlayerBloc() { 31 | _initDeafultSong(); 32 | _initStreams(); 33 | _initObservers(); 34 | _initAudioPlayer(); 35 | } 36 | 37 | Future fetchSongs() async { 38 | await MusicFinder.allSongs().then( 39 | (data) { 40 | _songs$.add(data); 41 | }, 42 | ); 43 | } 44 | 45 | void playMusic(Song song) { 46 | _audioPlayer.play(song.uri); 47 | updatePlayerState(PlayerState.playing, song); 48 | } 49 | 50 | void pauseMusic(Song song) { 51 | _audioPlayer.pause(); 52 | updatePlayerState(PlayerState.paused, song); 53 | } 54 | 55 | void stopMusic() { 56 | _audioPlayer.stop(); 57 | } 58 | 59 | void updatePlayerState(PlayerState state, Song song) { 60 | _playerState$.add(MapEntry(state, song)); 61 | } 62 | 63 | void updatePosition(Duration duration) { 64 | _position$.add(duration); 65 | } 66 | 67 | void updatePlaylist(List normalPlaylist) { 68 | List _shufflePlaylist = []..addAll(normalPlaylist); 69 | _shufflePlaylist.shuffle(); 70 | _playlist$.add(MapEntry(normalPlaylist, _shufflePlaylist)); 71 | } 72 | 73 | void _updateAlbums(List songs) { 74 | Map _albumsMap = {}; 75 | for (Song song in songs) { 76 | if (_albumsMap[song.albumId] == null) { 77 | _albumsMap[song.albumId] = Album.fromSong(song); 78 | } 79 | } 80 | final List _albums = _albumsMap.values.toList(); 81 | _albums$.add(_albums); 82 | } 83 | 84 | void playNextSong() { 85 | if (_playerState$.value.key == PlayerState.stopped) { 86 | return; 87 | } 88 | final Song _currentSong = _playerState$.value.value; 89 | final bool _isShuffle = _playback$.value.contains(Playback.shuffle); 90 | final List _playlist = 91 | _isShuffle ? _playlist$.value.value : _playlist$.value.key; 92 | int _index = _playlist.indexOf(_currentSong); 93 | if (_index == _playlist.length - 1) { 94 | _index = 0; 95 | } else { 96 | _index++; 97 | } 98 | stopMusic(); 99 | playMusic(_playlist[_index]); 100 | } 101 | 102 | void playPreviousSong() { 103 | if (_playerState$.value.key == PlayerState.stopped) { 104 | return; 105 | } 106 | final Song _currentSong = _playerState$.value.value; 107 | final bool _isShuffle = _playback$.value.contains(Playback.shuffle); 108 | final List _playlist = 109 | _isShuffle ? _playlist$.value.value : _playlist$.value.key; 110 | int _index = _playlist.indexOf(_currentSong); 111 | if (_index == 0) { 112 | _index = _playlist.length - 1; 113 | } else { 114 | _index--; 115 | } 116 | stopMusic(); 117 | playMusic(_playlist[_index]); 118 | } 119 | 120 | void _playSameSong() { 121 | final Song _currentSong = _playerState$.value.value; 122 | stopMusic(); 123 | playMusic(_currentSong); 124 | } 125 | 126 | void _onSongComplete() { 127 | final List _playback = _playback$.value; 128 | if (_playback.contains(Playback.repeatSong)) { 129 | _playSameSong(); 130 | return; 131 | } 132 | playNextSong(); 133 | } 134 | 135 | void audioSeek(double seconds) { 136 | _audioPlayer.seek(seconds); 137 | } 138 | 139 | void addToFavorites(Song song) async { 140 | List _favorites = _favorites$.value; 141 | _favorites.add(song); 142 | _favorites$.add(_favorites); 143 | await saveFavorites(); 144 | } 145 | 146 | void removeFromFavorites(Song song) async { 147 | List _favorites = _favorites$.value; 148 | _favorites.remove(song); 149 | _favorites$.add(_favorites); 150 | await saveFavorites(); 151 | } 152 | 153 | void invertSeekingState() { 154 | final _value = _isAudioSeeking$.value; 155 | _isAudioSeeking$.add(!_value); 156 | } 157 | 158 | void updatePlayback(Playback playback) { 159 | List _value = playback$.value; 160 | if (playback == Playback.shuffle) { 161 | final List _normalPlaylist = _playlist$.value.key; 162 | updatePlaylist(_normalPlaylist); 163 | } 164 | _value.add(playback); 165 | _playback$.add(_value); 166 | } 167 | 168 | void removePlayback(Playback playback) { 169 | List _value = playback$.value; 170 | _value.remove(playback); 171 | _playback$.add(_value); 172 | } 173 | 174 | Future saveFavorites() async { 175 | SharedPreferences _prefs = await SharedPreferences.getInstance(); 176 | final List _favorites = _favorites$.value; 177 | List _encodedStrings = []; 178 | for (Song song in _favorites) { 179 | _encodedStrings.add(_encodeSongToJson(song)); 180 | } 181 | _prefs.setStringList("favorites", _encodedStrings); 182 | } 183 | 184 | void retrieveFavorites() async { 185 | SharedPreferences _prefs = await SharedPreferences.getInstance(); 186 | final List _fetchedSongs = _songs$.value; 187 | List _savedStrings = _prefs.getStringList("favorites") ?? []; 188 | List _favorites = []; 189 | for (String data in _savedStrings) { 190 | final Song song = _decodeSongFromJson(data); 191 | for (var fetchedSong in _fetchedSongs) { 192 | if (song.id == fetchedSong.id) { 193 | _favorites.add(fetchedSong); 194 | } 195 | } 196 | } 197 | _favorites$.add(_favorites); 198 | } 199 | 200 | String _encodeSongToJson(Song song) { 201 | final _songMap = songToMap(song); 202 | final data = json.encode(_songMap); 203 | return data; 204 | } 205 | 206 | Song _decodeSongFromJson(String ecodedSong) { 207 | final _songMap = json.decode(ecodedSong); 208 | final Song _song = Song.fromMap(_songMap); 209 | return _song; 210 | } 211 | 212 | Map songToMap(Song song) { 213 | Map _map = {}; 214 | _map["album"] = song.album; 215 | _map["id"] = song.id; 216 | _map["artist"] = song.artist; 217 | _map["title"] = song.title; 218 | _map["albumId"] = song.albumId; 219 | _map["duration"] = song.duration; 220 | _map["uri"] = song.uri; 221 | _map["albumArt"] = song.albumArt; 222 | return _map; 223 | } 224 | 225 | void _initDeafultSong() { 226 | _defaultSong = Song( 227 | null, 228 | " ", 229 | " ", 230 | " ", 231 | null, 232 | null, 233 | null, 234 | null, 235 | ); 236 | } 237 | 238 | void _initObservers() { 239 | _songs$.listen( 240 | (List songs) { 241 | _updateAlbums(songs); 242 | }, 243 | ); // push albums from songs 244 | } 245 | 246 | void _initStreams() { 247 | _isAudioSeeking$ = BehaviorSubject.seeded(false); 248 | _songs$ = BehaviorSubject>(); 249 | _albums$ = BehaviorSubject>(); 250 | _position$ = BehaviorSubject(); 251 | _playlist$ = BehaviorSubject, List>>(); 252 | _playback$ = BehaviorSubject>.seeded([]); 253 | _favorites$ = BehaviorSubject>.seeded([]); 254 | _playerState$ = BehaviorSubject>.seeded( 255 | MapEntry( 256 | PlayerState.stopped, 257 | _defaultSong, 258 | ), 259 | ); 260 | } 261 | 262 | void _initAudioPlayer() { 263 | _audioPlayer = MusicFinder(); 264 | _audioPlayer.setPositionHandler( 265 | (Duration duration) { 266 | final bool _isAudioSeeking = _isAudioSeeking$.value; 267 | if (!_isAudioSeeking) { 268 | updatePosition(duration); 269 | } 270 | }, 271 | ); 272 | _audioPlayer.setCompletionHandler( 273 | () { 274 | _onSongComplete(); 275 | }, 276 | ); 277 | } 278 | 279 | void dispose() { 280 | stopMusic(); 281 | _isAudioSeeking$.close(); 282 | _songs$.close(); 283 | _albums$.close(); 284 | _playerState$.close(); 285 | _playlist$.close(); 286 | _position$.close(); 287 | _playback$.close(); 288 | _favorites$.close(); 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /lib/src/blocs/permissions.dart: -------------------------------------------------------------------------------- 1 | import 'package:rxdart/rxdart.dart'; 2 | import 'package:permission_handler/permission_handler.dart'; 3 | 4 | class PermissionsBloc { 5 | BehaviorSubject _storagePermissionStatus$; 6 | 7 | BehaviorSubject get storagePermissionStatus$ => 8 | _storagePermissionStatus$; 9 | 10 | PermissionsBloc() { 11 | _storagePermissionStatus$ = BehaviorSubject(); 12 | requestStoragePermission(); 13 | } 14 | 15 | Future requestStoragePermission() async { 16 | Map _permission = 17 | await PermissionHandler().requestPermissions( 18 | [ 19 | PermissionGroup.storage, 20 | ], 21 | ); 22 | final PermissionStatus _state = _permission.values.toList()[0]; 23 | 24 | _storagePermissionStatus$.add(_state); 25 | } 26 | 27 | void dispose() { 28 | _storagePermissionStatus$.close(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/common/empty_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | 4 | class EmptyScreen extends StatelessWidget { 5 | final String _text; 6 | 7 | EmptyScreen({@required String text}) : _text = text; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Padding( 12 | padding: const EdgeInsets.symmetric(horizontal: 22.0), 13 | child: Column( 14 | mainAxisAlignment: MainAxisAlignment.center, 15 | children: [ 16 | Icon( 17 | Icons.sentiment_dissatisfied, 18 | size: 150, 19 | color: Color(0xFF4D6B9C), 20 | ), 21 | Divider( 22 | height: 18, 23 | color: Colors.transparent, 24 | ), 25 | Text( 26 | _text, 27 | style: TextStyle( 28 | fontSize: 24, 29 | fontWeight: FontWeight.w500, 30 | color: Color(0xFFADB9CD), 31 | ), 32 | textAlign: TextAlign.center, 33 | ) 34 | ], 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/common/music_icons.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PlayIcon extends StatelessWidget { 4 | final Color _color; 5 | 6 | PlayIcon({@required Color color}) : _color = color; 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | final double _radius = 55; 11 | return Container( 12 | width: _radius, 13 | height: _radius, 14 | decoration: BoxDecoration( 15 | color: Colors.transparent, 16 | border: Border.all( 17 | color: _color, 18 | ), 19 | borderRadius: BorderRadius.circular( 20 | _radius, 21 | ), 22 | ), 23 | child: Center( 24 | child: Icon( 25 | Icons.play_arrow, 26 | color: _color, 27 | size: 32.0, 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | 34 | class PauseIcon extends StatelessWidget { 35 | final Color _color; 36 | 37 | PauseIcon({@required Color color}) : _color = color; 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | final double _radius = 55; 42 | return Container( 43 | width: _radius, 44 | height: _radius, 45 | decoration: BoxDecoration( 46 | color: Colors.transparent, 47 | border: Border.all( 48 | color: _color, 49 | ), 50 | borderRadius: BorderRadius.circular( 51 | _radius, 52 | ), 53 | ), 54 | child: Center( 55 | child: Icon( 56 | Icons.pause, 57 | color: _color, 58 | size: 32.0, 59 | ), 60 | ), 61 | ); 62 | } 63 | } 64 | 65 | class ShowIcon extends StatelessWidget { 66 | final Color _color; 67 | 68 | ShowIcon({@required Color color}) : _color = color; 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | final double _radius = 32; 73 | return Container( 74 | width: _radius, 75 | height: _radius, 76 | decoration: BoxDecoration( 77 | color: Colors.transparent, 78 | border: Border.all( 79 | color: _color, 80 | ), 81 | borderRadius: BorderRadius.circular( 82 | _radius, 83 | ), 84 | ), 85 | child: Center( 86 | child: Icon( 87 | Icons.keyboard_arrow_up, 88 | color: _color, 89 | size: 22.0, 90 | ), 91 | ), 92 | ); 93 | } 94 | } 95 | 96 | class HideIcon extends StatelessWidget { 97 | final Color _color; 98 | 99 | HideIcon({@required Color color}) : _color = color; 100 | 101 | @override 102 | Widget build(BuildContext context) { 103 | final double _radius = 32; 104 | return Container( 105 | width: _radius, 106 | height: _radius, 107 | decoration: BoxDecoration( 108 | color: Colors.transparent, 109 | border: Border.all( 110 | color: _color, 111 | ), 112 | borderRadius: BorderRadius.circular( 113 | _radius, 114 | ), 115 | ), 116 | child: Center( 117 | child: Icon( 118 | Icons.keyboard_arrow_down, 119 | color: _color, 120 | size: 22.0, 121 | ), 122 | ), 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/src/models/album.dart: -------------------------------------------------------------------------------- 1 | import 'package:flute_music_player/flute_music_player.dart'; 2 | 3 | class Album { 4 | int _id; 5 | String _name; 6 | String _art; 7 | String _artist; 8 | 9 | int get id => _id; 10 | String get name => _name; 11 | String get art => _art; 12 | String get artist => _artist; 13 | 14 | Album(this._id, this._name, this._art, this._artist); 15 | 16 | factory Album.fromSong(Song song) { 17 | return Album(song.albumId, song.album, song.albumArt, song.artist); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/models/playback.dart: -------------------------------------------------------------------------------- 1 | enum Playback { 2 | repeatSong, 3 | shuffle, 4 | } 5 | -------------------------------------------------------------------------------- /lib/src/models/playerstate.dart: -------------------------------------------------------------------------------- 1 | enum PlayerState { 2 | playing, 3 | paused, 4 | stopped, 5 | } 6 | -------------------------------------------------------------------------------- /lib/src/root.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:music_app/src/blocs/global.dart'; 3 | import 'package:music_app/src/ui/music_homepage/music_homepage.dart'; 4 | import 'package:permission_handler/permission_handler.dart'; 5 | import 'package:provider/provider.dart'; 6 | 7 | class ChillifyApp extends StatelessWidget { 8 | final GlobalBloc _globalBloc = GlobalBloc(); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Provider( 13 | builder: (BuildContext context) { 14 | _globalBloc.permissionsBloc.storagePermissionStatus$.listen( 15 | (data) { 16 | if (data == PermissionStatus.granted) { 17 | _globalBloc.musicPlayerBloc.fetchSongs().then( 18 | (_) { 19 | _globalBloc.musicPlayerBloc.retrieveFavorites(); 20 | }, 21 | ); 22 | } 23 | }, 24 | ); 25 | return _globalBloc; 26 | }, 27 | dispose: (BuildContext context, GlobalBloc value) => value.dispose(), 28 | child: MaterialApp( 29 | debugShowCheckedModeBanner: false, 30 | theme: ThemeData( 31 | sliderTheme: SliderThemeData( 32 | trackHeight: 1, 33 | ), 34 | ), 35 | home: SafeArea( 36 | child: StreamBuilder( 37 | stream: _globalBloc.permissionsBloc.storagePermissionStatus$, 38 | builder: (BuildContext context, 39 | AsyncSnapshot snapshot) { 40 | if (!snapshot.hasData) { 41 | return Container( 42 | height: double.infinity, 43 | width: double.infinity, 44 | color: Colors.white, 45 | child: Center( 46 | child: CircularProgressIndicator(), 47 | ), 48 | ); 49 | } 50 | final PermissionStatus _status = snapshot.data; 51 | if (_status == PermissionStatus.denied) { 52 | _globalBloc.permissionsBloc.requestStoragePermission(); 53 | return Container( 54 | height: double.infinity, 55 | width: double.infinity, 56 | color: Colors.white, 57 | child: Center( 58 | child: CircularProgressIndicator(), 59 | ), 60 | ); 61 | } else { 62 | return MusicHomepage(); 63 | } 64 | }, 65 | ), 66 | ), 67 | ), 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/ui/albums/album_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/painting.dart'; 3 | import 'package:music_app/src/models/album.dart'; 4 | import 'package:music_app/src/ui/now_playing/empty_album_art.dart'; 5 | 6 | class AlbumTile extends StatelessWidget { 7 | final Album _album; 8 | 9 | AlbumTile({@required Album album}) : _album = album; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final double _tileHeight = MediaQuery.of(context).size.height / 3; 14 | final double _radius = 10; 15 | 16 | return Stack( 17 | children: [ 18 | _album.art != null 19 | ? Container( 20 | height: _tileHeight, 21 | width: double.infinity, 22 | decoration: BoxDecoration( 23 | image: DecorationImage( 24 | image: AssetImage(_album.art), 25 | fit: BoxFit.fill, 26 | ), 27 | borderRadius: BorderRadius.circular(_radius), 28 | ), 29 | ) 30 | : EmptyAlbumArtContainer( 31 | albumArtSize: _tileHeight, 32 | iconSize: _tileHeight / 2, 33 | radius: _radius, 34 | ), 35 | Container( 36 | height: _tileHeight, 37 | width: double.infinity, 38 | decoration: BoxDecoration( 39 | borderRadius: BorderRadius.circular(_radius), 40 | gradient: LinearGradient( 41 | begin: Alignment.bottomCenter, 42 | end: Alignment.topCenter, 43 | stops: [ 44 | 0.0, 45 | 0.45, 46 | ], 47 | colors: [ 48 | Colors.black.withOpacity(0.8), 49 | Colors.transparent, 50 | ], 51 | ), 52 | ), 53 | child: Row( 54 | crossAxisAlignment: CrossAxisAlignment.end, 55 | mainAxisAlignment: MainAxisAlignment.center, 56 | children: [ 57 | Flexible( 58 | flex: 9, 59 | child: Padding( 60 | padding: const EdgeInsets.all(8.0), 61 | child: Container( 62 | height: 50.0, 63 | child: Column( 64 | mainAxisAlignment: MainAxisAlignment.end, 65 | crossAxisAlignment: CrossAxisAlignment.start, 66 | children: [ 67 | Text( 68 | _album.name, 69 | style: TextStyle( 70 | fontSize: 14.0, 71 | color: Colors.white, 72 | fontWeight: FontWeight.w600, 73 | ), 74 | maxLines: 1, 75 | overflow: TextOverflow.ellipsis, 76 | ), 77 | Divider( 78 | height: 5, 79 | color: Colors.transparent, 80 | ), 81 | Text( 82 | parseArtists().toUpperCase(), 83 | style: TextStyle( 84 | fontSize: 10, 85 | color: Colors.white, 86 | letterSpacing: 1, 87 | ), 88 | maxLines: 1, 89 | overflow: TextOverflow.ellipsis, 90 | ), 91 | ], 92 | ), 93 | ), 94 | ), 95 | ), 96 | Flexible( 97 | flex: 2, 98 | child: Padding( 99 | padding: const EdgeInsets.only(right: 8, top: 8.0), 100 | child: Container( 101 | height: 50.0, 102 | child: Icon( 103 | Icons.play_arrow, 104 | color: Colors.white, 105 | size: 24.0, 106 | ), 107 | ), 108 | ), 109 | ), 110 | ], 111 | ), 112 | ), 113 | ], 114 | ); 115 | } 116 | 117 | String parseArtists() { 118 | return _album.artist.split(";").reduce((String a, String b) { 119 | return a + " & " + b; 120 | }); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/src/ui/albums/albums_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:music_app/src/blocs/global.dart'; 3 | import 'package:music_app/src/common/empty_screen.dart'; 4 | import 'package:music_app/src/models/album.dart'; 5 | import 'package:music_app/src/ui/albums/album_tile.dart'; 6 | import 'package:music_app/src/ui/albums/specific_album_screen.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class AlbumsScreen extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | GlobalBloc _globalBloc = Provider.of(context); 13 | return Scaffold( 14 | body: StreamBuilder>( 15 | stream: _globalBloc.musicPlayerBloc.albums$, 16 | builder: (BuildContext context, AsyncSnapshot> snapshot) { 17 | if (!snapshot.hasData) { 18 | return Center( 19 | child: CircularProgressIndicator(), 20 | ); 21 | } 22 | 23 | final List _albums = snapshot.data; 24 | if (_albums.length == 0) { 25 | return EmptyScreen( 26 | text: "You do not have any albums on your device.", 27 | ); 28 | } 29 | 30 | return GridView.builder( 31 | key: PageStorageKey("Albums Screen"), 32 | padding: EdgeInsets.only( 33 | left: 16.0, right: 16.0, top: 16.0, bottom: 150), 34 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 35 | crossAxisCount: 2, 36 | mainAxisSpacing: 8.0, 37 | crossAxisSpacing: 8.0, 38 | ), 39 | physics: BouncingScrollPhysics(), 40 | itemCount: _albums.length, 41 | itemBuilder: (BuildContext context, int index) { 42 | return GestureDetector( 43 | onTap: () { 44 | Navigator.push( 45 | context, 46 | MaterialPageRoute( 47 | builder: (context) => Provider.value( 48 | value: _albums[index], 49 | child: SpecificAlbumScreen(), 50 | ), 51 | )); 52 | }, 53 | child: AlbumTile( 54 | album: _albums[index], 55 | ), 56 | ); 57 | }, 58 | ); 59 | }, 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/ui/albums/specific_album_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flute_music_player/flute_music_player.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:music_app/src/blocs/global.dart'; 4 | import 'package:music_app/src/models/album.dart'; 5 | import 'package:music_app/src/models/playerstate.dart'; 6 | import 'package:music_app/src/ui/all_songs/song_tile.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class SpecificAlbumScreen extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | final Album _album = Provider.of(context); 13 | final GlobalBloc _globalBloc = Provider.of(context); 14 | 15 | return SafeArea( 16 | child: Scaffold( 17 | appBar: AppBar( 18 | leading: IconButton( 19 | icon: Icon( 20 | Icons.arrow_back, 21 | color: Color(0xFF274D85), 22 | size: 35, 23 | ), 24 | onPressed: () => Navigator.pop(context), 25 | ), 26 | title: Text( 27 | _album.name, 28 | style: TextStyle( 29 | color: Color(0xFF274D85), 30 | fontWeight: FontWeight.bold, 31 | fontSize: 24, 32 | ), 33 | ), 34 | bottom: PreferredSize( 35 | preferredSize: Size.fromHeight(2), 36 | child: Padding( 37 | padding: const EdgeInsets.symmetric(horizontal: 12.0), 38 | child: Container( 39 | width: double.infinity, 40 | height: 2, 41 | color: Color(0xFFD9EAF1), 42 | ), 43 | ), 44 | ), 45 | elevation: 0.0, 46 | backgroundColor: Colors.transparent, 47 | ), 48 | body: StreamBuilder>( 49 | stream: _globalBloc.musicPlayerBloc.songs$, 50 | builder: (BuildContext context, AsyncSnapshot> snapshot) { 51 | if (!snapshot.hasData) { 52 | return Center( 53 | child: CircularProgressIndicator(), 54 | ); 55 | } 56 | final List _allSongs = snapshot.data; 57 | List _albumSongs = []; 58 | for (var song in _allSongs) { 59 | if (song.albumId == _album.id) { 60 | _albumSongs.add(song); 61 | } 62 | } 63 | 64 | return ListView.builder( 65 | key: UniqueKey(), 66 | padding: const EdgeInsets.only(bottom: 16.0), 67 | physics: BouncingScrollPhysics(), 68 | itemCount: _albumSongs.length, 69 | itemExtent: 110, 70 | itemBuilder: (BuildContext context, int index) { 71 | return StreamBuilder>( 72 | stream: _globalBloc.musicPlayerBloc.playerState$, 73 | builder: (BuildContext context, 74 | AsyncSnapshot> snapshot) { 75 | if (!snapshot.hasData) { 76 | return Container(); 77 | } 78 | final PlayerState _state = snapshot.data.key; 79 | final Song _currentSong = snapshot.data.value; 80 | final bool _isSelectedSong = 81 | _currentSong == _albumSongs[index]; 82 | return GestureDetector( 83 | onTap: () { 84 | _globalBloc.musicPlayerBloc.updatePlaylist(_albumSongs); 85 | switch (_state) { 86 | case PlayerState.playing: 87 | if (_isSelectedSong) { 88 | _globalBloc.musicPlayerBloc 89 | .pauseMusic(_currentSong); 90 | } else { 91 | _globalBloc.musicPlayerBloc.stopMusic(); 92 | _globalBloc.musicPlayerBloc.playMusic( 93 | _albumSongs[index], 94 | ); 95 | } 96 | break; 97 | case PlayerState.paused: 98 | if (_isSelectedSong) { 99 | _globalBloc.musicPlayerBloc 100 | .playMusic(_albumSongs[index]); 101 | } else { 102 | _globalBloc.musicPlayerBloc.stopMusic(); 103 | _globalBloc.musicPlayerBloc.playMusic( 104 | _albumSongs[index], 105 | ); 106 | } 107 | break; 108 | case PlayerState.stopped: 109 | _globalBloc.musicPlayerBloc 110 | .playMusic(_albumSongs[index]); 111 | break; 112 | default: 113 | break; 114 | } 115 | }, 116 | child: SongTile( 117 | song: _albumSongs[index], 118 | ), 119 | ); 120 | }, 121 | ); 122 | }, 123 | ); 124 | }, 125 | ), 126 | ), 127 | ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/src/ui/all_songs/all_songs_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flute_music_player/flute_music_player.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:music_app/src/blocs/global.dart'; 6 | import 'package:music_app/src/common/empty_screen.dart'; 7 | import 'package:music_app/src/models/playerstate.dart'; 8 | import 'package:music_app/src/ui/all_songs/song_tile.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class AllSongsScreen extends StatelessWidget { 12 | AllSongsScreen({ 13 | Key key, 14 | }) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final GlobalBloc _globalBloc = Provider.of(context); 19 | return Scaffold( 20 | body: StreamBuilder>( 21 | stream: _globalBloc.musicPlayerBloc.songs$, 22 | builder: (BuildContext context, AsyncSnapshot> snapshot) { 23 | if (!snapshot.hasData) { 24 | return Center( 25 | child: CircularProgressIndicator(), 26 | ); 27 | } 28 | final List _songs = snapshot.data; 29 | if (_songs.length == 0) { 30 | return EmptyScreen( 31 | text: "You do not have any songs on your device.", 32 | ); 33 | } 34 | return ListView.builder( 35 | key: PageStorageKey("All Songs"), 36 | padding: const EdgeInsets.only(bottom: 150.0), 37 | physics: BouncingScrollPhysics(), 38 | itemCount: _songs.length, 39 | itemExtent: 110, 40 | itemBuilder: (BuildContext context, int index) { 41 | return StreamBuilder>( 42 | stream: _globalBloc.musicPlayerBloc.playerState$, 43 | builder: (BuildContext context, 44 | AsyncSnapshot> snapshot) { 45 | if (!snapshot.hasData) { 46 | return Container(); 47 | } 48 | final PlayerState _state = snapshot.data.key; 49 | final Song _currentSong = snapshot.data.value; 50 | final bool _isSelectedSong = _currentSong == _songs[index]; 51 | return GestureDetector( 52 | onTap: () { 53 | _globalBloc.musicPlayerBloc.updatePlaylist(_songs); 54 | switch (_state) { 55 | case PlayerState.playing: 56 | if (_isSelectedSong) { 57 | _globalBloc.musicPlayerBloc 58 | .pauseMusic(_currentSong); 59 | } else { 60 | _globalBloc.musicPlayerBloc.stopMusic(); 61 | _globalBloc.musicPlayerBloc.playMusic( 62 | _songs[index], 63 | ); 64 | } 65 | break; 66 | case PlayerState.paused: 67 | if (_isSelectedSong) { 68 | _globalBloc.musicPlayerBloc 69 | .playMusic(_songs[index]); 70 | } else { 71 | _globalBloc.musicPlayerBloc.stopMusic(); 72 | _globalBloc.musicPlayerBloc.playMusic( 73 | _songs[index], 74 | ); 75 | } 76 | break; 77 | case PlayerState.stopped: 78 | _globalBloc.musicPlayerBloc.playMusic(_songs[index]); 79 | break; 80 | default: 81 | break; 82 | } 83 | }, 84 | child: SongTile( 85 | song: _songs[index], 86 | ), 87 | ); 88 | }, 89 | ); 90 | }, 91 | ); 92 | }, 93 | ), 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/src/ui/all_songs/song_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flute_music_player/flute_music_player.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:music_app/src/blocs/global.dart'; 4 | import 'package:music_app/src/common/music_icons.dart'; 5 | import 'package:music_app/src/models/playerstate.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | class SongTile extends StatelessWidget { 9 | final Song _song; 10 | String _artists; 11 | String _duration; 12 | SongTile({Key key, @required Song song}) 13 | : _song = song, 14 | super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final GlobalBloc _globalBloc = Provider.of(context); 19 | parseArtists(); 20 | parseDuration(); 21 | return StreamBuilder>( 22 | stream: _globalBloc.musicPlayerBloc.playerState$, 23 | builder: (BuildContext context, 24 | AsyncSnapshot> snapshot) { 25 | if (!snapshot.hasData) { 26 | return Container(); 27 | } 28 | 29 | final PlayerState _state = snapshot.data.key; 30 | final Song _currentSong = snapshot.data.value; 31 | final bool _isSelectedSong = _song == _currentSong; 32 | return AnimatedContainer( 33 | duration: Duration(milliseconds: 250), 34 | height: 110, 35 | width: double.infinity, 36 | decoration: BoxDecoration( 37 | gradient: _isSelectedSong 38 | ? LinearGradient( 39 | colors: [ 40 | Color(0xFFDDEAF2).withOpacity(0.7), 41 | Colors.white, 42 | ], 43 | ) 44 | : null, 45 | ), 46 | child: Padding( 47 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 48 | child: Column( 49 | mainAxisAlignment: MainAxisAlignment.center, 50 | children: [ 51 | Divider( 52 | color: Colors.transparent, 53 | height: 5, 54 | ), 55 | Row( 56 | children: [ 57 | Flexible( 58 | flex: 2, 59 | child: Container( 60 | width: double.infinity, 61 | alignment: Alignment.centerLeft, 62 | child: AnimatedCrossFade( 63 | duration: Duration( 64 | milliseconds: 150, 65 | ), 66 | firstChild: PauseIcon( 67 | color: Color(0xFF6D84C1), 68 | ), 69 | secondChild: PlayIcon( 70 | color: Color(0xFFA1AFBC), 71 | ), 72 | crossFadeState: 73 | _isSelectedSong && _state == PlayerState.playing 74 | ? CrossFadeState.showFirst 75 | : CrossFadeState.showSecond, 76 | ), 77 | ), 78 | ), 79 | Flexible( 80 | flex: 8, 81 | child: Padding( 82 | padding: const EdgeInsets.only(left: 8.0), 83 | child: Container( 84 | width: double.infinity, 85 | child: Column( 86 | mainAxisAlignment: MainAxisAlignment.center, 87 | crossAxisAlignment: CrossAxisAlignment.start, 88 | children: [ 89 | Text( 90 | _song.title, 91 | style: TextStyle( 92 | fontSize: 20, 93 | color: Color(0xFF4D6B9C), 94 | fontWeight: FontWeight.w600, 95 | ), 96 | maxLines: 1, 97 | overflow: TextOverflow.ellipsis, 98 | ), 99 | Divider( 100 | height: 10, 101 | color: Colors.transparent, 102 | ), 103 | Text( 104 | _artists.toUpperCase(), 105 | style: TextStyle( 106 | fontSize: 16, 107 | color: Color(0xFFADB9CD), 108 | letterSpacing: 1, 109 | ), 110 | maxLines: 1, 111 | overflow: TextOverflow.ellipsis, 112 | ), 113 | ], 114 | ), 115 | ), 116 | ), 117 | ), 118 | Flexible( 119 | flex: 2, 120 | child: Container( 121 | width: double.infinity, 122 | alignment: Alignment.centerRight, 123 | child: Text( 124 | _duration, 125 | style: TextStyle( 126 | fontSize: 16, 127 | fontWeight: FontWeight.bold, 128 | color: Color(0xFF94A6C5), 129 | ), 130 | maxLines: 1, 131 | overflow: TextOverflow.ellipsis, 132 | ), 133 | ), 134 | ), 135 | ], 136 | ), 137 | _isSelectedSong 138 | ? Row( 139 | children: [ 140 | Flexible( 141 | flex: 2, 142 | child: Container( 143 | width: double.infinity, 144 | ), 145 | ), 146 | Flexible( 147 | flex: 14, 148 | child: StreamBuilder( 149 | stream: _globalBloc.musicPlayerBloc.position$, 150 | builder: (BuildContext context, 151 | AsyncSnapshot snapshot) { 152 | if (!snapshot.hasData) { 153 | return Slider( 154 | value: 0, 155 | onChanged: (double value) => null, 156 | activeColor: Colors.transparent, 157 | inactiveColor: Colors.transparent, 158 | ); 159 | } 160 | final Duration _currentDuration = 161 | snapshot.data; 162 | final int _millseconds = 163 | _currentDuration.inMilliseconds; 164 | final int _songDurationInMilliseconds = 165 | _currentSong.duration; 166 | return Slider( 167 | min: 0, 168 | max: _songDurationInMilliseconds.toDouble(), 169 | value: _songDurationInMilliseconds > 170 | _millseconds 171 | ? _millseconds.toDouble() 172 | : _songDurationInMilliseconds 173 | .toDouble(), 174 | onChangeStart: (double value) => _globalBloc 175 | .musicPlayerBloc 176 | .invertSeekingState(), 177 | onChanged: (double value) { 178 | final Duration _duration = Duration( 179 | milliseconds: value.toInt(), 180 | ); 181 | _globalBloc.musicPlayerBloc 182 | .updatePosition(_duration); 183 | }, 184 | onChangeEnd: (double value) { 185 | _globalBloc.musicPlayerBloc 186 | .invertSeekingState(); 187 | _globalBloc.musicPlayerBloc 188 | .audioSeek(value / 1000); 189 | }, 190 | activeColor: Colors.blue, 191 | inactiveColor: Color(0xFFCEE3EE), 192 | ); 193 | }, 194 | ), 195 | ), 196 | ], 197 | ) 198 | : Container(), 199 | ], 200 | ), 201 | ), 202 | ); 203 | }); 204 | } 205 | 206 | void parseDuration() { 207 | final double _temp = _song.duration / 1000; 208 | final int _minutes = (_temp / 60).floor(); 209 | final int _seconds = (((_temp / 60) - _minutes) * 60).round(); 210 | if (_seconds.toString().length != 1) { 211 | _duration = _minutes.toString() + ":" + _seconds.toString(); 212 | } else { 213 | _duration = _minutes.toString() + ":0" + _seconds.toString(); 214 | } 215 | } 216 | 217 | void parseArtists() { 218 | _artists = _song.artist.split(";").reduce((String a, String b) { 219 | return a + " & " + b; 220 | }); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /lib/src/ui/favorites/favorites_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flute_music_player/flute_music_player.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:music_app/src/blocs/global.dart'; 4 | import 'package:music_app/src/common/empty_screen.dart'; 5 | import 'package:music_app/src/models/playerstate.dart'; 6 | import 'package:music_app/src/ui/all_songs/song_tile.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class FavoritesScreen extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | final GlobalBloc _globalBloc = Provider.of(context); 13 | return Scaffold( 14 | body: StreamBuilder>( 15 | stream: _globalBloc.musicPlayerBloc.favorites$, 16 | builder: (BuildContext context, AsyncSnapshot> snapshot) { 17 | if (!snapshot.hasData) { 18 | return Center( 19 | child: CircularProgressIndicator(), 20 | ); 21 | } 22 | final List _songs = snapshot.data; 23 | if (_songs.length == 0) { 24 | return EmptyScreen( 25 | text: "You do not have any songs as your favorites.", 26 | ); 27 | } 28 | return ListView.builder( 29 | key: PageStorageKey("Favorites"), 30 | padding: const EdgeInsets.only(bottom: 150.0), 31 | physics: BouncingScrollPhysics(), 32 | itemCount: _songs.length, 33 | itemExtent: 110, 34 | itemBuilder: (BuildContext context, int index) { 35 | return StreamBuilder>( 36 | stream: _globalBloc.musicPlayerBloc.playerState$, 37 | builder: (BuildContext context, 38 | AsyncSnapshot> snapshot) { 39 | if (!snapshot.hasData) { 40 | return Container(); 41 | } 42 | final PlayerState _state = snapshot.data.key; 43 | final Song _currentSong = snapshot.data.value; 44 | final bool _isSelectedSong = _currentSong == _songs[index]; 45 | return GestureDetector( 46 | onTap: () { 47 | _globalBloc.musicPlayerBloc.updatePlaylist(_songs); 48 | switch (_state) { 49 | case PlayerState.playing: 50 | if (_isSelectedSong) { 51 | _globalBloc.musicPlayerBloc 52 | .pauseMusic(_currentSong); 53 | } else { 54 | _globalBloc.musicPlayerBloc.stopMusic(); 55 | _globalBloc.musicPlayerBloc.playMusic( 56 | _songs[index], 57 | ); 58 | } 59 | break; 60 | case PlayerState.paused: 61 | if (_isSelectedSong) { 62 | _globalBloc.musicPlayerBloc 63 | .playMusic(_songs[index]); 64 | } else { 65 | _globalBloc.musicPlayerBloc.stopMusic(); 66 | _globalBloc.musicPlayerBloc.playMusic( 67 | _songs[index], 68 | ); 69 | } 70 | break; 71 | case PlayerState.stopped: 72 | _globalBloc.musicPlayerBloc.playMusic(_songs[index]); 73 | break; 74 | default: 75 | break; 76 | } 77 | }, 78 | child: SongTile( 79 | song: _songs[index], 80 | ), 81 | ); 82 | }, 83 | ); 84 | }, 85 | ); 86 | }, 87 | ), 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/src/ui/music_homepage/bottom_panel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flute_music_player/flute_music_player.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:music_app/src/blocs/global.dart'; 4 | import 'package:music_app/src/common/music_icons.dart'; 5 | import 'package:music_app/src/models/playerstate.dart'; 6 | import 'package:provider/provider.dart'; 7 | import 'package:sliding_up_panel/sliding_up_panel.dart'; 8 | 9 | class BottomPanel extends StatelessWidget { 10 | final PanelController _controller; 11 | 12 | BottomPanel({@required PanelController controller}) 13 | : _controller = controller; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | final GlobalBloc _globalBloc = Provider.of(context); 18 | return Container( 19 | height: double.infinity, 20 | width: double.infinity, 21 | alignment: Alignment.bottomCenter, 22 | child: StreamBuilder>( 23 | stream: _globalBloc.musicPlayerBloc.playerState$, 24 | builder: (BuildContext context, 25 | AsyncSnapshot> snapshot) { 26 | if (!snapshot.hasData) { 27 | return Container(); 28 | } 29 | 30 | final PlayerState _state = snapshot.data.key; 31 | final Song _currentSong = snapshot.data.value; 32 | final String _artists = getArtists(_currentSong); 33 | 34 | return Padding( 35 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 36 | child: Column( 37 | mainAxisAlignment: MainAxisAlignment.end, 38 | children: [ 39 | Row( 40 | mainAxisAlignment: MainAxisAlignment.center, 41 | children: [ 42 | Flexible( 43 | flex: 2, 44 | child: GestureDetector( 45 | onTap: () { 46 | if (_currentSong.uri == null) { 47 | return; 48 | } 49 | if (PlayerState.paused == _state) { 50 | _globalBloc.musicPlayerBloc.playMusic(_currentSong); 51 | } else { 52 | _globalBloc.musicPlayerBloc 53 | .pauseMusic(_currentSong); 54 | } 55 | }, 56 | child: Container( 57 | width: double.infinity, 58 | alignment: Alignment.centerLeft, 59 | child: _state == PlayerState.playing 60 | ? PauseIcon( 61 | color: Colors.white, 62 | ) 63 | : PlayIcon( 64 | color: Colors.white, 65 | ), 66 | ), 67 | ), 68 | ), 69 | Flexible( 70 | flex: 8, 71 | child: Padding( 72 | padding: const EdgeInsets.only(left: 8.0), 73 | child: Container( 74 | width: double.infinity, 75 | child: Column( 76 | mainAxisAlignment: MainAxisAlignment.center, 77 | crossAxisAlignment: CrossAxisAlignment.start, 78 | children: [ 79 | Text( 80 | _currentSong.title, 81 | style: TextStyle( 82 | fontSize: 20, 83 | color: Colors.white, 84 | fontWeight: FontWeight.w600, 85 | ), 86 | maxLines: 1, 87 | overflow: TextOverflow.ellipsis, 88 | ), 89 | Divider( 90 | height: 10, 91 | color: Colors.transparent, 92 | ), 93 | Text( 94 | _artists.toUpperCase(), 95 | style: TextStyle( 96 | fontSize: 16, 97 | color: Colors.white, 98 | letterSpacing: 1, 99 | ), 100 | maxLines: 1, 101 | overflow: TextOverflow.ellipsis, 102 | ), 103 | ], 104 | ), 105 | ), 106 | ), 107 | ), 108 | Flexible( 109 | flex: 1, 110 | child: Container( 111 | width: double.infinity, 112 | alignment: Alignment.centerRight, 113 | child: GestureDetector( 114 | onTap: () => _controller.open(), 115 | child: ShowIcon( 116 | color: Colors.white, 117 | ), 118 | ), 119 | ), 120 | ), 121 | ], 122 | ), 123 | Row( 124 | children: [ 125 | Flexible( 126 | flex: 2, 127 | child: Container( 128 | width: double.infinity, 129 | ), 130 | ), 131 | Flexible( 132 | flex: 12, 133 | child: StreamBuilder( 134 | stream: _globalBloc.musicPlayerBloc.position$, 135 | builder: (BuildContext context, 136 | AsyncSnapshot snapshot) { 137 | if (_state == PlayerState.stopped || 138 | !snapshot.hasData) { 139 | return Slider( 140 | value: 0, 141 | onChanged: (double value) => null, 142 | activeColor: Colors.transparent, 143 | inactiveColor: Colors.transparent, 144 | ); 145 | } 146 | final Duration _currentDuration = snapshot.data; 147 | final int _millseconds = 148 | _currentDuration.inMilliseconds; 149 | final int _songDurationInMilliseconds = 150 | _currentSong.duration; 151 | return Slider( 152 | min: 0, 153 | max: _songDurationInMilliseconds.toDouble(), 154 | value: _songDurationInMilliseconds > _millseconds 155 | ? _millseconds.toDouble() 156 | : _songDurationInMilliseconds.toDouble(), 157 | onChangeStart: (double value) => _globalBloc 158 | .musicPlayerBloc 159 | .invertSeekingState(), 160 | onChanged: (double value) { 161 | final Duration _duration = Duration( 162 | milliseconds: value.toInt(), 163 | ); 164 | _globalBloc.musicPlayerBloc 165 | .updatePosition(_duration); 166 | }, 167 | onChangeEnd: (double value) { 168 | _globalBloc.musicPlayerBloc 169 | .invertSeekingState(); 170 | _globalBloc.musicPlayerBloc 171 | .audioSeek(value / 1000); 172 | }, 173 | activeColor: Colors.white, 174 | inactiveColor: Colors.white.withOpacity(0.5), 175 | ); 176 | }), 177 | ), 178 | ], 179 | ), 180 | ], 181 | ), 182 | ); 183 | }, 184 | ), 185 | ); 186 | } 187 | 188 | String getArtists(Song song) { 189 | return song.artist.split(";").reduce((String a, String b) { 190 | return a + " & " + b; 191 | }); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /lib/src/ui/music_homepage/music_homepage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:music_app/src/blocs/global.dart'; 6 | import 'package:music_app/src/ui/albums/albums_screen.dart'; 7 | import 'package:music_app/src/ui/all_songs/all_songs_screen.dart'; 8 | import 'package:music_app/src/ui/favorites/favorites_screen.dart'; 9 | import 'package:music_app/src/ui/music_homepage/bottom_panel.dart'; 10 | import 'package:music_app/src/ui/now_playing/now_playing_screen.dart'; 11 | import 'package:music_app/src/ui/search/search_screen.dart'; 12 | import 'package:provider/provider.dart'; 13 | import 'package:sliding_up_panel/sliding_up_panel.dart'; 14 | 15 | class MusicHomepage extends StatefulWidget { 16 | @override 17 | _MusicHomepageState createState() => _MusicHomepageState(); 18 | } 19 | 20 | class _MusicHomepageState extends State { 21 | PanelController _panelController; 22 | 23 | @override 24 | void initState() { 25 | _panelController = PanelController(); 26 | 27 | super.initState(); 28 | } 29 | 30 | @override 31 | void dispose() { 32 | _panelController.close(); 33 | super.dispose(); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | final double _radius = 25.0; 39 | return WillPopScope( 40 | onWillPop: () { 41 | if (!_panelController.isPanelClosed()) { 42 | _panelController.close(); 43 | } else { 44 | _showExitDialog(); 45 | } 46 | return Future.value(false); 47 | }, 48 | child: Scaffold( 49 | body: SlidingUpPanel( 50 | panel: ClipRRect( 51 | borderRadius: BorderRadius.only( 52 | topLeft: Radius.circular(_radius), 53 | topRight: Radius.circular(_radius), 54 | ), 55 | child: NowPlayingScreen(controller: _panelController), 56 | ), 57 | controller: _panelController, 58 | minHeight: 115, 59 | maxHeight: MediaQuery.of(context).size.height, 60 | borderRadius: BorderRadius.only( 61 | topLeft: Radius.circular(_radius), 62 | topRight: Radius.circular(_radius), 63 | ), 64 | collapsed: Container( 65 | width: double.infinity, 66 | height: double.infinity, 67 | decoration: BoxDecoration( 68 | borderRadius: BorderRadius.only( 69 | topLeft: Radius.circular(_radius), 70 | topRight: Radius.circular(_radius), 71 | ), 72 | gradient: LinearGradient( 73 | begin: Alignment.topLeft, 74 | end: Alignment.bottomRight, 75 | stops: [ 76 | 0.0, 77 | 0.7, 78 | ], 79 | colors: [ 80 | Color(0xFF47ACE1), 81 | Color(0xFFDF5F9D), 82 | ], 83 | ), 84 | ), 85 | child: BottomPanel(controller: _panelController), 86 | ), 87 | body: DefaultTabController( 88 | length: 3, 89 | initialIndex: 0, 90 | child: Scaffold( 91 | appBar: AppBar( 92 | automaticallyImplyLeading: false, 93 | title: Text( 94 | "Chillify", 95 | style: TextStyle( 96 | color: Color(0xFF274D85), 97 | fontWeight: FontWeight.bold, 98 | fontSize: 24, 99 | ), 100 | ), 101 | bottom: TabBar( 102 | indicatorColor: Color(0xFFD9EAF1), 103 | labelColor: Color(0xFF274D85), 104 | unselectedLabelColor: Color(0xFF274D85).withOpacity(0.6), 105 | tabs: [ 106 | Padding( 107 | padding: const EdgeInsets.only(bottom: 8.0), 108 | child: Text( 109 | "Songs", 110 | style: TextStyle(fontSize: 20.0), 111 | ), 112 | ), 113 | Padding( 114 | padding: const EdgeInsets.only(bottom: 8.0), 115 | child: Text( 116 | "Albums", 117 | style: TextStyle(fontSize: 20.0), 118 | ), 119 | ), 120 | Padding( 121 | padding: const EdgeInsets.only(bottom: 8.0), 122 | child: Text( 123 | "Favorites", 124 | style: TextStyle(fontSize: 20.0), 125 | ), 126 | ), 127 | ], 128 | ), 129 | actions: [ 130 | Padding( 131 | padding: const EdgeInsets.only(right: 8.0), 132 | child: IconButton( 133 | onPressed: () { 134 | Navigator.push( 135 | context, 136 | MaterialPageRoute( 137 | builder: (context) => SearchScreen(), 138 | ), 139 | ); 140 | }, 141 | icon: Icon( 142 | Icons.search, 143 | color: Color(0xFF274D85), 144 | size: 35, 145 | ), 146 | ), 147 | ) 148 | ], 149 | elevation: 0.0, 150 | backgroundColor: Colors.transparent, 151 | ), 152 | body: TabBarView( 153 | key: UniqueKey(), 154 | physics: BouncingScrollPhysics(), 155 | children: [ 156 | AllSongsScreen(), 157 | AlbumsScreen(), 158 | FavoritesScreen(), 159 | ], 160 | ), 161 | ), 162 | ), 163 | ), 164 | ), 165 | ); 166 | } 167 | 168 | void _showExitDialog() { 169 | final GlobalBloc _globalBloc = Provider.of(context); 170 | showDialog( 171 | context: context, 172 | builder: (BuildContext context) { 173 | return AlertDialog( 174 | title: Text( 175 | "Chillify", 176 | style: TextStyle( 177 | fontSize: 20, 178 | ), 179 | ), 180 | content: Text( 181 | "Are you sure you want to close the app?", 182 | style: TextStyle( 183 | fontSize: 18, 184 | ), 185 | ), 186 | actions: [ 187 | FlatButton( 188 | textColor: Color(0xFFDF5F9D), 189 | onPressed: () { 190 | Navigator.pop(context); 191 | }, 192 | child: Text("NO"), 193 | ), 194 | FlatButton( 195 | textColor: Color(0xFFDF5F9D), 196 | onPressed: () { 197 | SystemChannels.platform.invokeMethod('SystemNavigator.pop'); 198 | _globalBloc.dispose(); 199 | }, 200 | child: Text("YES"), 201 | ), 202 | ], 203 | ); 204 | }, 205 | ); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /lib/src/ui/now_playing/album_art_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flute_music_player/flute_music_player.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class AlbumArtContainer extends StatelessWidget { 5 | const AlbumArtContainer({ 6 | Key key, 7 | @required double radius, 8 | @required double albumArtSize, 9 | @required Song currentSong, 10 | }) : _radius = radius, 11 | _albumArtSize = albumArtSize, 12 | _currentSong = currentSong, 13 | super(key: key); 14 | 15 | final double _radius; 16 | final double _albumArtSize; 17 | final Song _currentSong; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return ClipRRect( 22 | borderRadius: BorderRadius.circular(_radius), 23 | child: Stack( 24 | children: [ 25 | Container( 26 | width: double.infinity, 27 | height: _albumArtSize, 28 | child: FadeInImage( 29 | placeholder: AssetImage(_currentSong.albumArt), 30 | image: AssetImage( 31 | _currentSong.albumArt, 32 | ), 33 | fit: BoxFit.fill, 34 | ), 35 | ), 36 | Opacity( 37 | opacity: 0.55, 38 | child: Container( 39 | width: double.infinity, 40 | height: _albumArtSize, 41 | decoration: BoxDecoration( 42 | gradient: LinearGradient( 43 | begin: Alignment.bottomLeft, 44 | end: Alignment.topRight, 45 | stops: [ 46 | 0.0, 47 | 0.85, 48 | ], 49 | colors: [ 50 | Color(0xFF47ACE1), 51 | Color(0xFFDF5F9D), 52 | ], 53 | ), 54 | ), 55 | ), 56 | ), 57 | ], 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/ui/now_playing/empty_album_art.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class EmptyAlbumArtContainer extends StatelessWidget { 4 | const EmptyAlbumArtContainer({ 5 | Key key, 6 | @required double radius, 7 | @required double albumArtSize, 8 | @required double iconSize, 9 | }) : _radius = radius, 10 | _albumArtSize = albumArtSize, 11 | _iconSize = iconSize, 12 | super(key: key); 13 | 14 | final double _radius; 15 | final double _albumArtSize; 16 | final double _iconSize; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return ClipRRect( 21 | borderRadius: BorderRadius.circular(_radius), 22 | child: Stack( 23 | children: [ 24 | Container( 25 | width: double.infinity, 26 | height: _albumArtSize, 27 | color: Colors.grey[400], 28 | child: Center( 29 | child: Icon( 30 | Icons.music_note, 31 | size: _iconSize, 32 | color: Colors.black, 33 | ), 34 | ), 35 | ), 36 | Opacity( 37 | opacity: 0.55, 38 | child: Container( 39 | width: double.infinity, 40 | height: _albumArtSize, 41 | decoration: BoxDecoration( 42 | gradient: LinearGradient( 43 | begin: Alignment.bottomLeft, 44 | end: Alignment.topRight, 45 | stops: [ 46 | 0.0, 47 | 0.85, 48 | ], 49 | colors: [ 50 | Color(0xFF47ACE1), 51 | Color(0xFFDF5F9D), 52 | ], 53 | ), 54 | ), 55 | ), 56 | ), 57 | ], 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/ui/now_playing/music_board_controls.dart: -------------------------------------------------------------------------------- 1 | import 'package:flute_music_player/flute_music_player.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/painting.dart'; 4 | import 'package:music_app/src/blocs/global.dart'; 5 | import 'package:music_app/src/models/playerstate.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | class MusicBoardControls extends StatelessWidget { 9 | @override 10 | Widget build(BuildContext context) { 11 | final GlobalBloc _globalBloc = Provider.of(context); 12 | return Container( 13 | height: 100, 14 | width: double.infinity, 15 | child: Stack( 16 | children: [ 17 | Align( 18 | alignment: Alignment.center, 19 | child: Container( 20 | width: 245, 21 | height: 60, 22 | decoration: BoxDecoration( 23 | borderRadius: BorderRadius.circular(30), 24 | color: Color(0xFFDCE4F4), 25 | boxShadow: [ 26 | BoxShadow( 27 | color: Colors.black.withOpacity(0.15), 28 | blurRadius: 20, 29 | offset: Offset(2, 1.5), 30 | ), 31 | ], 32 | ), 33 | child: Row( 34 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 35 | children: [ 36 | Padding( 37 | padding: const EdgeInsets.only(left: 16.0), 38 | child: GestureDetector( 39 | onTap: () => 40 | _globalBloc.musicPlayerBloc.playPreviousSong(), 41 | child: Icon( 42 | Icons.fast_rewind, 43 | color: Color(0xFF7B92CA), 44 | size: 40, 45 | ), 46 | ), 47 | ), 48 | Padding( 49 | padding: const EdgeInsets.only(right: 16.0), 50 | child: GestureDetector( 51 | onTap: () => _globalBloc.musicPlayerBloc.playNextSong(), 52 | child: Icon( 53 | Icons.fast_forward, 54 | color: Color(0xFF7B92CA), 55 | size: 40, 56 | ), 57 | ), 58 | ), 59 | ], 60 | ), 61 | ), 62 | ), 63 | Align( 64 | alignment: Alignment.center, 65 | child: StreamBuilder>( 66 | stream: _globalBloc.musicPlayerBloc.playerState$, 67 | builder: (BuildContext context, 68 | AsyncSnapshot> snapshot) { 69 | if (!snapshot.hasData) { 70 | return Container(); 71 | } 72 | final PlayerState _state = snapshot.data.key; 73 | final Song _currentSong = snapshot.data.value; 74 | return GestureDetector( 75 | onTap: () { 76 | if (_currentSong.uri == null) { 77 | return; 78 | } 79 | if (PlayerState.paused == _state) { 80 | _globalBloc.musicPlayerBloc.playMusic(_currentSong); 81 | } else { 82 | _globalBloc.musicPlayerBloc.pauseMusic(_currentSong); 83 | } 84 | }, 85 | child: Container( 86 | width: 100, 87 | height: 100, 88 | decoration: BoxDecoration( 89 | shape: BoxShape.circle, 90 | color: Colors.white, 91 | boxShadow: [ 92 | BoxShadow( 93 | color: Colors.black.withOpacity(0.15), 94 | blurRadius: 30, 95 | offset: Offset(2, 1.5), 96 | ), 97 | ], 98 | ), 99 | child: Center( 100 | child: AnimatedCrossFade( 101 | duration: Duration(milliseconds: 200), 102 | crossFadeState: _state == PlayerState.playing 103 | ? CrossFadeState.showFirst 104 | : CrossFadeState.showSecond, 105 | firstChild: Icon( 106 | Icons.pause, 107 | size: 50, 108 | color: Color(0xFF7B92CA), 109 | ), 110 | secondChild: Icon( 111 | Icons.play_arrow, 112 | size: 50, 113 | color: Color(0xFF7B92CA), 114 | ), 115 | ), 116 | ), 117 | ), 118 | ); 119 | }), 120 | ), 121 | ], 122 | ), 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/src/ui/now_playing/now_playing_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flute_music_player/flute_music_player.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:music_app/src/blocs/global.dart'; 4 | import 'package:music_app/src/common/music_icons.dart'; 5 | import 'package:music_app/src/models/playerstate.dart'; 6 | import 'package:music_app/src/ui/now_playing/album_art_container.dart'; 7 | import 'package:music_app/src/ui/now_playing/empty_album_art.dart'; 8 | import 'package:music_app/src/ui/now_playing/music_board_controls.dart'; 9 | import 'package:music_app/src/ui/now_playing/now_playing_slider.dart'; 10 | import 'package:music_app/src/ui/now_playing/preferences_board.dart'; 11 | import 'package:provider/provider.dart'; 12 | import 'package:sliding_up_panel/sliding_up_panel.dart'; 13 | 14 | class NowPlayingScreen extends StatelessWidget { 15 | final PanelController _controller; 16 | 17 | NowPlayingScreen({@required PanelController controller}) 18 | : _controller = controller; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | final GlobalBloc _globalBloc = Provider.of(context); 23 | final double _radius = 25.0; 24 | final double _screenHeight = MediaQuery.of(context).size.height; 25 | final double _albumArtSize = _screenHeight / 2.1; 26 | return Scaffold( 27 | body: Column( 28 | mainAxisAlignment: MainAxisAlignment.start, 29 | children: [ 30 | Container( 31 | width: double.infinity, 32 | height: _albumArtSize + 50, 33 | child: Stack( 34 | children: [ 35 | StreamBuilder>( 36 | stream: _globalBloc.musicPlayerBloc.playerState$, 37 | builder: (BuildContext context, 38 | AsyncSnapshot> snapshot) { 39 | if (!snapshot.hasData || 40 | snapshot.data.value.albumArt == null) { 41 | return EmptyAlbumArtContainer( 42 | radius: _radius, 43 | albumArtSize: _albumArtSize, 44 | iconSize: _albumArtSize / 2, 45 | ); 46 | } 47 | 48 | final Song _currentSong = snapshot.data.value; 49 | return AlbumArtContainer( 50 | radius: _radius, 51 | albumArtSize: _albumArtSize, 52 | currentSong: _currentSong, 53 | ); 54 | }, 55 | ), 56 | Align( 57 | alignment: Alignment.bottomCenter, 58 | child: MusicBoardControls(), 59 | ), 60 | ], 61 | ), 62 | ), 63 | Divider( 64 | color: Colors.transparent, 65 | height: _screenHeight / 15, 66 | ), 67 | PreferencesBoard(), 68 | Divider( 69 | color: Colors.transparent, 70 | height: _screenHeight / 15, 71 | ), 72 | Padding( 73 | padding: const EdgeInsets.symmetric(horizontal: 22.0), 74 | child: Row( 75 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 76 | crossAxisAlignment: CrossAxisAlignment.center, 77 | children: [ 78 | Flexible( 79 | flex: 12, 80 | child: Container( 81 | child: StreamBuilder>( 82 | stream: _globalBloc.musicPlayerBloc.playerState$, 83 | builder: (BuildContext context, 84 | AsyncSnapshot> snapshot) { 85 | if (!snapshot.hasData) { 86 | return Container(); 87 | } 88 | if (snapshot.data.key == PlayerState.stopped) { 89 | return Container(); 90 | } 91 | final Song _currentSong = snapshot.data.value; 92 | 93 | final String _artists = _currentSong.artist 94 | .split(";") 95 | .reduce((String a, String b) { 96 | return a + " & " + b; 97 | }); 98 | return Column( 99 | mainAxisAlignment: MainAxisAlignment.center, 100 | crossAxisAlignment: CrossAxisAlignment.start, 101 | children: [ 102 | Text( 103 | _currentSong.album.toUpperCase() + 104 | " • " + 105 | _artists.toUpperCase(), 106 | style: TextStyle( 107 | fontSize: 16, 108 | color: Color(0xFFADB9CD), 109 | letterSpacing: 1, 110 | ), 111 | maxLines: 1, 112 | overflow: TextOverflow.ellipsis, 113 | ), 114 | Divider( 115 | height: 5, 116 | color: Colors.transparent, 117 | ), 118 | Text( 119 | _currentSong.title, 120 | style: TextStyle( 121 | fontSize: 30, 122 | color: Color(0xFF4D6B9C), 123 | fontWeight: FontWeight.w600, 124 | ), 125 | maxLines: 1, 126 | overflow: TextOverflow.ellipsis, 127 | ), 128 | ], 129 | ); 130 | }, 131 | ), 132 | ), 133 | ), 134 | Flexible( 135 | flex: 2, 136 | child: GestureDetector( 137 | onTap: () => _controller.close(), 138 | child: HideIcon( 139 | color: Color(0xFF90A4D4), 140 | ), 141 | ), 142 | ) 143 | ], 144 | ), 145 | ), 146 | Divider( 147 | color: Colors.transparent, 148 | height: _screenHeight / 22, 149 | ), 150 | Padding( 151 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 152 | child: Column( 153 | children: [ 154 | Padding( 155 | padding: const EdgeInsets.only(right: 12.0), 156 | child: Align( 157 | alignment: Alignment.centerRight, 158 | child: StreamBuilder>( 159 | stream: _globalBloc.musicPlayerBloc.playerState$, 160 | builder: (BuildContext context, 161 | AsyncSnapshot> 162 | snapshot) { 163 | if (!snapshot.hasData) { 164 | return Text( 165 | "0:00", 166 | style: TextStyle( 167 | fontSize: 16, 168 | fontWeight: FontWeight.w700, 169 | color: Color(0xFFADB9CD), 170 | letterSpacing: 1, 171 | ), 172 | maxLines: 1, 173 | overflow: TextOverflow.ellipsis, 174 | ); 175 | } 176 | final Song _currentSong = snapshot.data.value; 177 | final PlayerState _state = snapshot.data.key; 178 | if (_state == PlayerState.stopped) { 179 | return Text( 180 | "0:00", 181 | style: TextStyle( 182 | fontSize: 16, 183 | fontWeight: FontWeight.w700, 184 | color: Color(0xFFADB9CD), 185 | letterSpacing: 1, 186 | ), 187 | maxLines: 1, 188 | overflow: TextOverflow.ellipsis, 189 | ); 190 | } 191 | return Text( 192 | getDuration(_currentSong), 193 | style: TextStyle( 194 | fontSize: 16, 195 | fontWeight: FontWeight.w700, 196 | color: Color(0xFFADB9CD), 197 | letterSpacing: 1, 198 | ), 199 | maxLines: 1, 200 | overflow: TextOverflow.ellipsis, 201 | ); 202 | }), 203 | ), 204 | ), 205 | NowPlayingSlider(), 206 | ], 207 | ), 208 | ), 209 | ], 210 | ), 211 | ); 212 | } 213 | 214 | String getDuration(Song _song) { 215 | final double _temp = _song.duration / 1000; 216 | final int _minutes = (_temp / 60).floor(); 217 | final int _seconds = (((_temp / 60) - _minutes) * 60).round(); 218 | if (_seconds.toString().length != 1) { 219 | return _minutes.toString() + ":" + _seconds.toString(); 220 | } else { 221 | return _minutes.toString() + ":0" + _seconds.toString(); 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /lib/src/ui/now_playing/now_playing_slider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flute_music_player/flute_music_player.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:music_app/src/blocs/global.dart'; 4 | import 'package:music_app/src/models/playerstate.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:rxdart/rxdart.dart'; 7 | 8 | class NowPlayingSlider extends StatelessWidget { 9 | @override 10 | Widget build(BuildContext context) { 11 | final GlobalBloc _globalBloc = Provider.of(context); 12 | 13 | return StreamBuilder>>( 14 | stream: Observable.combineLatest2(_globalBloc.musicPlayerBloc.position$, 15 | _globalBloc.musicPlayerBloc.playerState$, (a, b) => MapEntry(a, b)), 16 | builder: (BuildContext context, 17 | AsyncSnapshot>> 18 | snapshot) { 19 | if (!snapshot.hasData) { 20 | return Slider( 21 | value: 0, 22 | onChanged: (double value) => null, 23 | activeColor: Colors.blue, 24 | inactiveColor: Color(0xFFCEE3EE), 25 | ); 26 | } 27 | if (snapshot.data.value.key == PlayerState.stopped) { 28 | return Slider( 29 | value: 0, 30 | onChanged: (double value) => null, 31 | activeColor: Colors.blue, 32 | inactiveColor: Color(0xFFCEE3EE), 33 | ); 34 | } 35 | final Duration _currentDuration = snapshot.data.key; 36 | final Song _currentSong = snapshot.data.value.value; 37 | final int _millseconds = _currentDuration.inMilliseconds; 38 | final int _songDurationInMilliseconds = _currentSong.duration; 39 | return Slider( 40 | min: 0, 41 | max: _songDurationInMilliseconds.toDouble(), 42 | value: _songDurationInMilliseconds > _millseconds 43 | ? _millseconds.toDouble() 44 | : _songDurationInMilliseconds.toDouble(), 45 | onChangeStart: (double value) => 46 | _globalBloc.musicPlayerBloc.invertSeekingState(), 47 | onChanged: (double value) { 48 | final Duration _duration = Duration( 49 | milliseconds: value.toInt(), 50 | ); 51 | _globalBloc.musicPlayerBloc.updatePosition(_duration); 52 | }, 53 | onChangeEnd: (double value) { 54 | _globalBloc.musicPlayerBloc.invertSeekingState(); 55 | _globalBloc.musicPlayerBloc.audioSeek(value / 1000); 56 | }, 57 | activeColor: Colors.blue, 58 | inactiveColor: Color(0xFFCEE3EE), 59 | ); 60 | }, 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/ui/now_playing/preferences_board.dart: -------------------------------------------------------------------------------- 1 | import 'package:flute_music_player/flute_music_player.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:music_app/src/blocs/global.dart'; 4 | import 'package:music_app/src/models/playback.dart'; 5 | import 'package:music_app/src/models/playerstate.dart'; 6 | import 'package:provider/provider.dart'; 7 | import 'package:rxdart/rxdart.dart'; 8 | 9 | class PreferencesBoard extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | final GlobalBloc _globalBloc = Provider.of(context); 13 | return Row( 14 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 15 | crossAxisAlignment: CrossAxisAlignment.center, 16 | children: [ 17 | StreamBuilder, List>>( 18 | stream: Observable.combineLatest2( 19 | _globalBloc.musicPlayerBloc.playerState$, 20 | _globalBloc.musicPlayerBloc.favorites$, 21 | (a, b) => MapEntry(a, b), 22 | ), 23 | builder: (BuildContext context, 24 | AsyncSnapshot, List>> 25 | snapshot) { 26 | if (!snapshot.hasData) { 27 | return Icon( 28 | Icons.favorite, 29 | size: 35, 30 | color: Color(0xFFC7D2E3), 31 | ); 32 | } 33 | final PlayerState _state = snapshot.data.key.key; 34 | if (_state == PlayerState.stopped) { 35 | return Icon( 36 | Icons.favorite, 37 | size: 35, 38 | color: Color(0xFFC7D2E3), 39 | ); 40 | } 41 | final Song _currentSong = snapshot.data.key.value; 42 | final List _favorites = snapshot.data.value; 43 | final bool _isFavorited = _favorites.contains(_currentSong); 44 | return IconButton( 45 | onPressed: () { 46 | if (_isFavorited) { 47 | _globalBloc.musicPlayerBloc.removeFromFavorites(_currentSong); 48 | } else { 49 | _globalBloc.musicPlayerBloc.addToFavorites(_currentSong); 50 | } 51 | }, 52 | icon: Icon( 53 | Icons.favorite, 54 | size: 35, 55 | color: !_isFavorited ? Color(0xFFC7D2E3) : Color(0xFF7B92CA), 56 | ), 57 | ); 58 | }, 59 | ), 60 | StreamBuilder>( 61 | stream: _globalBloc.musicPlayerBloc.playback$, 62 | builder: 63 | (BuildContext context, AsyncSnapshot> snapshot) { 64 | if (!snapshot.hasData) { 65 | return Icon( 66 | Icons.loop, 67 | size: 35, 68 | color: Color(0xFFC7D2E3), 69 | ); 70 | } 71 | final List _playbackList = snapshot.data; 72 | final bool _isSelected = 73 | _playbackList.contains(Playback.repeatSong); 74 | return IconButton( 75 | onPressed: () { 76 | if (!_isSelected) { 77 | _globalBloc.musicPlayerBloc 78 | .updatePlayback(Playback.repeatSong); 79 | } else { 80 | _globalBloc.musicPlayerBloc 81 | .removePlayback(Playback.repeatSong); 82 | } 83 | }, 84 | icon: Icon( 85 | Icons.loop, 86 | size: 35, 87 | color: !_isSelected ? Color(0xFFC7D2E3) : Color(0xFF7B92CA), 88 | ), 89 | ); 90 | }, 91 | ), 92 | StreamBuilder>( 93 | stream: _globalBloc.musicPlayerBloc.playback$, 94 | builder: 95 | (BuildContext context, AsyncSnapshot> snapshot) { 96 | if (!snapshot.hasData) { 97 | return Icon( 98 | Icons.loop, 99 | size: 35, 100 | color: Color(0xFFC7D2E3), 101 | ); 102 | } 103 | final List _playbackList = snapshot.data; 104 | final bool _isSelected = _playbackList.contains(Playback.shuffle); 105 | return IconButton( 106 | onPressed: () { 107 | if (!_isSelected) { 108 | _globalBloc.musicPlayerBloc.updatePlayback(Playback.shuffle); 109 | } else { 110 | _globalBloc.musicPlayerBloc.removePlayback(Playback.shuffle); 111 | } 112 | }, 113 | icon: Icon( 114 | Icons.shuffle, 115 | size: 35, 116 | color: !_isSelected ? Color(0xFFC7D2E3) : Color(0xFF7B92CA), 117 | ), 118 | ); 119 | }, 120 | ), 121 | ], 122 | ); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/src/ui/search/search_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flute_music_player/flute_music_player.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:music_app/src/blocs/global.dart'; 4 | import 'package:music_app/src/models/playerstate.dart'; 5 | import 'package:music_app/src/ui/all_songs/song_tile.dart'; 6 | import 'package:music_app/src/ui/search/search_screen_bloc.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class SearchScreen extends StatefulWidget { 10 | @override 11 | _SearchScreenState createState() => _SearchScreenState(); 12 | } 13 | 14 | class _SearchScreenState extends State { 15 | TextEditingController _textEditingController; 16 | SearchScreenBloc _searchScreenBloc; 17 | 18 | @override 19 | void initState() { 20 | _textEditingController = TextEditingController(); 21 | _searchScreenBloc = SearchScreenBloc(); 22 | super.initState(); 23 | } 24 | 25 | @override 26 | void dispose() { 27 | _textEditingController.dispose(); 28 | _searchScreenBloc.dispose(); 29 | super.dispose(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | final GlobalBloc _globalBloc = Provider.of(context); 35 | return SafeArea( 36 | child: Scaffold( 37 | resizeToAvoidBottomPadding: false, 38 | appBar: AppBar( 39 | backgroundColor: Colors.transparent, 40 | elevation: 0.0, 41 | leading: IconButton( 42 | icon: Icon( 43 | Icons.arrow_back, 44 | color: Color(0xFF274D85), 45 | size: 35, 46 | ), 47 | onPressed: () => Navigator.pop(context), 48 | ), 49 | title: StreamBuilder>( 50 | stream: _globalBloc.musicPlayerBloc.songs$, 51 | builder: (BuildContext context, AsyncSnapshot snapshot) { 52 | final List _songs = snapshot.data; 53 | return TextField( 54 | controller: _textEditingController, 55 | cursorColor: Color(0xFF274D85), 56 | decoration: InputDecoration( 57 | disabledBorder: UnderlineInputBorder( 58 | borderSide: BorderSide( 59 | color: Color(0xFFD9EAF1).withOpacity(0.7), 60 | ), 61 | ), 62 | enabledBorder: UnderlineInputBorder( 63 | borderSide: BorderSide( 64 | color: Color(0xFFD9EAF1).withOpacity(0.7), 65 | ), 66 | ), 67 | ), 68 | style: TextStyle( 69 | color: Color(0xFF274D85), 70 | fontSize: 22.0, 71 | ), 72 | autofocus: true, 73 | onChanged: (String value) { 74 | _searchScreenBloc.updateFilteredSongs(value, _songs); 75 | }, 76 | ); 77 | }, 78 | ), 79 | ), 80 | body: StreamBuilder>( 81 | stream: _searchScreenBloc.filteredSongs$, 82 | builder: (BuildContext context, AsyncSnapshot snapshot) { 83 | if (!snapshot.hasData) { 84 | return Center( 85 | child: CircularProgressIndicator(), 86 | ); 87 | } 88 | final List _filteredSongs = snapshot.data; 89 | 90 | if (_filteredSongs.length == 0) { 91 | return Center( 92 | child: Text( 93 | "Enter proper keywords to start searching", 94 | style: TextStyle( 95 | fontSize: 22.0, 96 | color: Color(0xFF274D85), 97 | ), 98 | textAlign: TextAlign.center, 99 | ), 100 | ); 101 | } 102 | 103 | return ListView.builder( 104 | key: UniqueKey(), 105 | padding: const EdgeInsets.only(bottom: 30.0), 106 | physics: BouncingScrollPhysics(), 107 | itemCount: _filteredSongs.length, 108 | itemExtent: 110, 109 | itemBuilder: (BuildContext context, int index) { 110 | return StreamBuilder>( 111 | stream: _globalBloc.musicPlayerBloc.playerState$, 112 | builder: (BuildContext context, 113 | AsyncSnapshot> snapshot) { 114 | if (!snapshot.hasData) { 115 | return Container(); 116 | } 117 | final PlayerState _state = snapshot.data.key; 118 | final Song _currentSong = snapshot.data.value; 119 | final bool _isSelectedSong = 120 | _currentSong == _filteredSongs[index]; 121 | return GestureDetector( 122 | onTap: () { 123 | _globalBloc.musicPlayerBloc 124 | .updatePlaylist(_filteredSongs); 125 | switch (_state) { 126 | case PlayerState.playing: 127 | if (_isSelectedSong) { 128 | _globalBloc.musicPlayerBloc 129 | .pauseMusic(_currentSong); 130 | } else { 131 | _globalBloc.musicPlayerBloc.stopMusic(); 132 | _globalBloc.musicPlayerBloc.playMusic( 133 | _filteredSongs[index], 134 | ); 135 | } 136 | break; 137 | case PlayerState.paused: 138 | if (_isSelectedSong) { 139 | _globalBloc.musicPlayerBloc 140 | .playMusic(_filteredSongs[index]); 141 | } else { 142 | _globalBloc.musicPlayerBloc.stopMusic(); 143 | _globalBloc.musicPlayerBloc.playMusic( 144 | _filteredSongs[index], 145 | ); 146 | } 147 | break; 148 | case PlayerState.stopped: 149 | _globalBloc.musicPlayerBloc 150 | .playMusic(_filteredSongs[index]); 151 | break; 152 | default: 153 | break; 154 | } 155 | }, 156 | child: SongTile( 157 | song: _filteredSongs[index], 158 | ), 159 | ); 160 | }, 161 | ); 162 | }, 163 | ); 164 | }, 165 | ), 166 | ), 167 | ); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /lib/src/ui/search/search_screen_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flute_music_player/flute_music_player.dart'; 2 | import 'package:rxdart/rxdart.dart'; 3 | 4 | class SearchScreenBloc { 5 | BehaviorSubject> _filteredSongs$; 6 | 7 | BehaviorSubject> get filteredSongs$ => _filteredSongs$; 8 | 9 | SearchScreenBloc() { 10 | _filteredSongs$ = BehaviorSubject>.seeded([]); 11 | } 12 | 13 | void updateFilteredSongs(String filter, List songs) { 14 | final _phrase = filter.replaceAll(" ", "").toLowerCase(); 15 | List _filteredSongs = []; 16 | if (_phrase.length == 0) { 17 | _filteredSongs$.add(_filteredSongs); 18 | return; 19 | } 20 | for (Song song in songs) { 21 | final _songName = song.title.replaceAll(" ", "").toLowerCase(); 22 | final _albumName = song.album.replaceAll(" ", "").toLowerCase(); 23 | final _artistNames = 24 | song.artist.replaceAll(" ", "").replaceAll(";", "").toLowerCase(); 25 | final _songString = _songName + _albumName + _artistNames; 26 | 27 | if (_songString.contains(_phrase)) { 28 | _filteredSongs.add(song); 29 | } 30 | } 31 | 32 | if (_filteredSongs.length == 0) { 33 | _filteredSongs$.add(null); 34 | } 35 | 36 | _filteredSongs$.add(_filteredSongs); 37 | } 38 | 39 | void dispose() { 40 | _filteredSongs$.close(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.2.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.0.4" 18 | charcode: 19 | dependency: transitive 20 | description: 21 | name: charcode 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.2" 25 | collection: 26 | dependency: transitive 27 | description: 28 | name: collection 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.14.11" 32 | cupertino_icons: 33 | dependency: "direct main" 34 | description: 35 | name: cupertino_icons 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "0.1.2" 39 | flute_music_player: 40 | dependency: "direct main" 41 | description: 42 | name: flute_music_player 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "0.0.6" 46 | flutter: 47 | dependency: "direct main" 48 | description: flutter 49 | source: sdk 50 | version: "0.0.0" 51 | flutter_test: 52 | dependency: "direct dev" 53 | description: flutter 54 | source: sdk 55 | version: "0.0.0" 56 | matcher: 57 | dependency: transitive 58 | description: 59 | name: matcher 60 | url: "https://pub.dartlang.org" 61 | source: hosted 62 | version: "0.12.5" 63 | meta: 64 | dependency: transitive 65 | description: 66 | name: meta 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "1.1.6" 70 | path: 71 | dependency: transitive 72 | description: 73 | name: path 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "1.6.2" 77 | pedantic: 78 | dependency: transitive 79 | description: 80 | name: pedantic 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "1.7.0" 84 | permission_handler: 85 | dependency: "direct main" 86 | description: 87 | name: permission_handler 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "2.1.2" 91 | provider: 92 | dependency: "direct main" 93 | description: 94 | name: provider 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "2.0.1+1" 98 | quiver: 99 | dependency: transitive 100 | description: 101 | name: quiver 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "2.0.3" 105 | rxdart: 106 | dependency: "direct main" 107 | description: 108 | name: rxdart 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "0.22.0" 112 | shared_preferences: 113 | dependency: "direct main" 114 | description: 115 | name: shared_preferences 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "0.5.3+1" 119 | sky_engine: 120 | dependency: transitive 121 | description: flutter 122 | source: sdk 123 | version: "0.0.99" 124 | sliding_up_panel: 125 | dependency: "direct main" 126 | description: 127 | name: sliding_up_panel 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "0.3.4" 131 | source_span: 132 | dependency: transitive 133 | description: 134 | name: source_span 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.5.5" 138 | stack_trace: 139 | dependency: transitive 140 | description: 141 | name: stack_trace 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "1.9.3" 145 | stream_channel: 146 | dependency: transitive 147 | description: 148 | name: stream_channel 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "2.0.0" 152 | string_scanner: 153 | dependency: transitive 154 | description: 155 | name: string_scanner 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "1.0.4" 159 | term_glyph: 160 | dependency: transitive 161 | description: 162 | name: term_glyph 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "1.1.0" 166 | test_api: 167 | dependency: transitive 168 | description: 169 | name: test_api 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "0.2.5" 173 | typed_data: 174 | dependency: transitive 175 | description: 176 | name: typed_data 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "1.1.6" 180 | vector_math: 181 | dependency: transitive 182 | description: 183 | name: vector_math 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "2.0.8" 187 | sdks: 188 | dart: ">=2.2.2 <3.0.0" 189 | flutter: "1.7.8+hotfix.4" 190 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: music_app 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.5.0+2 15 | 16 | environment: 17 | sdk: '>=2.1.0 <3.0.0' 18 | flutter: 1.7.8+hotfix.4 19 | 20 | dependencies: 21 | flutter: 22 | sdk: flutter 23 | cupertino_icons: ^0.1.2 24 | provider: 2.0.1+1 25 | flute_music_player: 0.0.6 26 | rxdart: 0.22.0 27 | sliding_up_panel: 0.3.4 28 | permission_handler: 2.1.2 29 | shared_preferences: 0.5.3+1 30 | 31 | dev_dependencies: 32 | flutter_test: 33 | sdk: flutter 34 | 35 | # For information on the generic Dart part of this file, see the 36 | # following page: https://www.dartlang.org/tools/pub/pubspec 37 | 38 | # The following section is specific to Flutter. 39 | flutter: 40 | # The following line ensures that the Material Icons font is 41 | # included with your application, so that you can use the icons in 42 | # the material Icons class. 43 | uses-material-design: true 44 | # To add assets to your application, add an assets section, like this: 45 | # assets: 46 | # - images/a_dot_burr.jpeg 47 | # - images/a_dot_ham.jpeg 48 | # An image asset can refer to one or more resolution-specific "variants", see 49 | # https://flutter.dev/assets-and-images/#resolution-aware. 50 | # For details regarding adding assets from package dependencies, see 51 | # https://flutter.dev/assets-and-images/#from-packages 52 | # To add custom fonts to your application, add a fonts section here, 53 | # in this "flutter" section. Each entry in this list should have a 54 | # "family" key with the font family name, and a "fonts" key with a 55 | # list giving the asset and other descriptors for the font. For 56 | # example: 57 | # fonts: 58 | # - family: Schyler 59 | # fonts: 60 | # - asset: fonts/Schyler-Regular.ttf 61 | # - asset: fonts/Schyler-Italic.ttf 62 | # style: italic 63 | # - family: Trajan Pro 64 | # fonts: 65 | # - asset: fonts/TrajanPro.ttf 66 | # - asset: fonts/TrajanPro_Bold.ttf 67 | # weight: 700 68 | # 69 | # For details regarding fonts from package dependencies, 70 | # see https://flutter.dev/custom-fonts/#from-packages 71 | -------------------------------------------------------------------------------- /second_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/second_screen.png -------------------------------------------------------------------------------- /third_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KarimElghamry/chillify/544a3853169d032f801dcbfeeee51ee924f99589/third_screen.png --------------------------------------------------------------------------------