├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── 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-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── .gitignore ├── Podfile.lock └── Podfile ├── imgs ├── IMG_1557.jpg ├── IMG_1558.jpg ├── IMG_1559.jpg ├── IMG_1560.jpg ├── IMG_1561.jpg ├── IMG_1562.jpg ├── IMG_1563.jpg ├── IMG_1564.jpg ├── IMG_1567.jpg ├── IMG_1568.jpg ├── IMG_1570.jpg ├── IMG_1571.jpg ├── IMG_1572.jpg ├── IMG_1573.jpg ├── download.png └── RPReplay_Final1586507210_1589979488572640.mp4 ├── android ├── gradle.properties ├── .gitignore ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── 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 │ │ │ │ └── drawable │ │ │ │ │ └── launch_background.xml │ │ │ ├── kotlin │ │ │ │ └── djy │ │ │ │ │ └── flutter │ │ │ │ │ └── music │ │ │ │ │ └── dong │ │ │ │ │ └── flutter_music │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── build.gradle ├── lib ├── http_request │ ├── config.dart │ └── http_request_manager.dart ├── pages │ ├── library_page │ │ ├── new_library_page │ │ │ ├── new_library_title_widget.dart │ │ │ └── new_library_page.dart │ │ ├── library_empty_widget.dart │ │ ├── library_page.dart │ │ ├── library_delete_button_widget.dart │ │ └── library_list_widget.dart │ ├── music_play_media_page │ │ ├── animation │ │ │ ├── music_bottom_animation.dart │ │ │ └── music_translation_animation.dart │ │ ├── music_play_bottom_widget.dart │ │ ├── music_paly_coverimage_widget.dart │ │ ├── music_play_meida_page.dart │ │ ├── music_play_control_widget.dart │ │ ├── music_play_slider_widget.dart │ │ └── music_play_info_widget.dart │ ├── person_page │ │ ├── person_aboutour_item_widget.dart │ │ ├── person_switch_item_widget.dart │ │ └── person_page.dart │ ├── recommend_page │ │ ├── recomment_more_item_widget.dart │ │ ├── recomment_item_widget.dart │ │ └── recommend_page.dart │ ├── search_page │ │ ├── search_list_widget.dart │ │ ├── search_page.dart │ │ ├── music_album_card_widget.dart │ │ ├── music_album_item_widget.dart │ │ ├── search_hot_widget.dart │ │ └── search_app_bar.dart │ ├── album_page │ │ ├── album_page.dart │ │ └── album_header_widget.dart │ ├── browse_page │ │ ├── browse_page.dart │ │ └── browse_banner_widget.dart │ ├── login_page │ │ ├── login_page.dart │ │ └── login_password_page.dart │ └── music_list_page │ │ └── music_list_page.dart ├── common │ ├── dialog │ │ └── loading_dialog.dart │ ├── state │ │ ├── user_state.dart │ │ └── theme_state.dart │ ├── screen_adapter.dart │ ├── music_store.dart │ ├── inner_shadow.dart │ └── music_global.dart ├── models │ ├── song_detail_model.dart │ ├── song_list_model.dart │ ├── search_hot_model.dart │ ├── user_model.dart │ ├── play_list_model.dart │ └── track_list_model.dart ├── public_widget │ ├── music_gestureDetector.dart │ ├── music_title_widget.dart │ ├── music_button.dart │ ├── future_builder_widget.dart │ ├── music_item_widget.dart │ ├── music_activityIndicator.dart │ └── music_submit_button.dart ├── routers │ ├── router_page_name.dart │ └── router.dart ├── main.dart ├── tabbar │ ├── tababr_page.dart │ ├── tabbar_items │ │ └── music_tab_item.dart │ └── bottom_tabbar.dart └── base_music │ ├── music_scaffold.dart │ └── music_app_bar.dart ├── .metadata ├── .gitignore ├── test └── widget_test.dart ├── pubspec.yaml └── README.md /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /imgs/IMG_1557.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/IMG_1557.jpg -------------------------------------------------------------------------------- /imgs/IMG_1558.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/IMG_1558.jpg -------------------------------------------------------------------------------- /imgs/IMG_1559.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/IMG_1559.jpg -------------------------------------------------------------------------------- /imgs/IMG_1560.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/IMG_1560.jpg -------------------------------------------------------------------------------- /imgs/IMG_1561.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/IMG_1561.jpg -------------------------------------------------------------------------------- /imgs/IMG_1562.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/IMG_1562.jpg -------------------------------------------------------------------------------- /imgs/IMG_1563.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/IMG_1563.jpg -------------------------------------------------------------------------------- /imgs/IMG_1564.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/IMG_1564.jpg -------------------------------------------------------------------------------- /imgs/IMG_1567.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/IMG_1567.jpg -------------------------------------------------------------------------------- /imgs/IMG_1568.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/IMG_1568.jpg -------------------------------------------------------------------------------- /imgs/IMG_1570.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/IMG_1570.jpg -------------------------------------------------------------------------------- /imgs/IMG_1571.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/IMG_1571.jpg -------------------------------------------------------------------------------- /imgs/IMG_1572.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/IMG_1572.jpg -------------------------------------------------------------------------------- /imgs/IMG_1573.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/IMG_1573.jpg -------------------------------------------------------------------------------- /imgs/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/download.png -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /imgs/RPReplay_Final1586507210_1589979488572640.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/imgs/RPReplay_Final1586507210_1589979488572640.mp4 -------------------------------------------------------------------------------- /lib/http_request/config.dart: -------------------------------------------------------------------------------- 1 | class HttpConfig{ 2 | static const String baseURL = "http://47.94.5.242:3000"; 3 | static const int timeout = 50000; 4 | 5 | } 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/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/coder-dongjiayi/flutter_music/HEAD/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/coder-dongjiayi/flutter_music/HEAD/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/coder-dongjiayi/flutter_music/HEAD/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/coder-dongjiayi/flutter_music/HEAD/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/coder-dongjiayi/flutter_music/HEAD/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/coder-dongjiayi/flutter_music/HEAD/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/coder-dongjiayi/flutter_music/HEAD/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/coder-dongjiayi/flutter_music/HEAD/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/coder-dongjiayi/flutter_music/HEAD/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/coder-dongjiayi/flutter_music/HEAD/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/coder-dongjiayi/flutter_music/HEAD/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/coder-dongjiayi/flutter_music/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-dongjiayi/flutter_music/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/pages/library_page/new_library_page/new_library_title_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | class NewLibraryTitleWidget extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | 8 | return Container(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /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-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.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: 27321ebbad34b0a3fafe99fac037102196d655ff 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | PreviewsEnabled 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/djy/flutter/music/dong/flutter_music/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package djy.flutter.music.dong.flutter_music 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /lib/common/dialog/loading_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | 4 | class LoadingDialog extends Dialog{ 5 | @override 6 | Widget build(BuildContext context) { 7 | // TODO: implement build 8 | return Material( 9 | type: MaterialType.transparency, 10 | child: Center( 11 | child: CupertinoActivityIndicator( 12 | radius: 20, 13 | ), 14 | ), 15 | ); 16 | 17 | } 18 | 19 | static void show (BuildContext context){ 20 | 21 | showDialog( 22 | context: context, 23 | barrierDismissible: false, 24 | builder: (BuildContext context){ 25 | return LoadingDialog(); 26 | } 27 | ); 28 | 29 | } 30 | } -------------------------------------------------------------------------------- /lib/common/state/user_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:flutter_music/common/music_global.dart'; 4 | import 'package:flutter_music/models/user_model.dart'; 5 | class UserSate extends ChangeNotifier{ 6 | 7 | 8 | UserModel get user => MusicGlobal.userModel; 9 | 10 | bool get isLogin => user != null; 11 | 12 | setUser(UserModel userModel){ 13 | MusicGlobal.userModel = userModel; 14 | MusicGlobal.saveUserInfo(userModel); 15 | notifyListeners(); 16 | } 17 | 18 | logoOut(){ 19 | MusicGlobal.logout(); 20 | notifyListeners(); 21 | } 22 | static UserSate of(context){ 23 | 24 | return Provider.of(context,listen: false); 25 | } 26 | } -------------------------------------------------------------------------------- /lib/models/song_detail_model.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter_music/models/track_list_model.dart'; 3 | 4 | class SongDetailModel{ 5 | 6 | String description; 7 | String coverImgUrl; 8 | String name; 9 | List tags; 10 | 11 | List tracks; 12 | SongDetailModel({ 13 | this.description, 14 | this.coverImgUrl, 15 | this.name, 16 | this.tags, 17 | this.tracks 18 | }); 19 | 20 | SongDetailModel.fromJson(Map json){ 21 | description = json["description"] ?? ""; 22 | coverImgUrl = json["coverImgUrl"]; 23 | name = json["name"]; 24 | 25 | 26 | if(json["tracks"] != null){ 27 | tracks = List(); 28 | json["tracks"].forEach((v){ 29 | tracks.add(TrackItemModel.fromJson(v)); 30 | }); 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /lib/public_widget/music_gestureDetector.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | import 'package:flutter_vibrate/flutter_vibrate.dart'; 4 | 5 | typedef GestureTapCallback = void Function(); 6 | class MusicGestureDetector extends StatelessWidget { 7 | 8 | MusicGestureDetector({ 9 | Key key, 10 | this.onTap, 11 | this.child 12 | 13 | }) :super(key: key); 14 | 15 | final GestureTapCallback onTap; 16 | final Widget child; 17 | @override 18 | Widget build(BuildContext context) { 19 | return GestureDetector( 20 | child: child, 21 | onTap: (){ 22 | if(onTap != null){ 23 | if(MusicStore.isVibrate){ 24 | Vibrate.feedback(FeedbackType.impact); 25 | } 26 | 27 | onTap(); 28 | } 29 | }, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/pages/music_play_media_page/animation/music_bottom_animation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | class MusicBottomAnimation extends StatelessWidget { 3 | 4 | MusicBottomAnimation({ 5 | Key key, 6 | this.child, 7 | this.animationController 8 | 9 | }) : super (key :key); 10 | final Widget child; 11 | 12 | final AnimationController animationController; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | Animation _translationAnimation = Tween(begin: 0.0,end: 40.0).animate(animationController); 17 | 18 | return AnimatedBuilder( 19 | animation: _translationAnimation, 20 | builder: (context,_){ 21 | return Positioned( 22 | bottom: _translationAnimation.value, 23 | right: 0, 24 | left: 0, 25 | child: child, 26 | ); 27 | }, 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/public_widget/music_title_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | 4 | class MusicTitleWidget extends StatelessWidget { 5 | MusicTitleWidget({ 6 | Key key, 7 | this.title, 8 | this.padding : const EdgeInsets.fromLTRB(20, 20, 20, 20) 9 | 10 | }) : super(key:key); 11 | final String title; 12 | final EdgeInsets padding; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Padding( 17 | padding: padding, 18 | child: Row( 19 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 20 | children: [ 21 | Text("$title", 22 | style: TextStyle(color: MusicStore.Theme(context).titleColor,fontSize: 17)), 23 | Icon(Icons.arrow_forward,size: 20,color: MusicStore.Theme(context).goldenColor) 24 | ], 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | -------------------------------------------------------------------------------- /lib/models/song_list_model.dart: -------------------------------------------------------------------------------- 1 | 2 | class SongListModel{ 3 | 4 | List songList; 5 | 6 | SongListModel({this.songList}); 7 | 8 | SongListModel.fromJson(Map json){ 9 | if(json["result"] != null){ 10 | 11 | songList = new List(); 12 | json["result"].forEach((v){ 13 | SongItemModel itemModel = SongItemModel.fromJson(v); 14 | songList.add(itemModel); 15 | }); 16 | } 17 | 18 | } 19 | } 20 | class SongItemModel{ 21 | int id; 22 | String name; 23 | String copywriter; 24 | String picUrl; 25 | 26 | 27 | SongItemModel({ 28 | this.id, 29 | this.name, 30 | this.copywriter, 31 | this.picUrl, 32 | 33 | }); 34 | 35 | SongItemModel.fromJson(Map json){ 36 | id = json["id"]; 37 | name = json["name"]; 38 | copywriter = json["copywriter"]; 39 | picUrl = json["picUrl"]; 40 | 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | pubspec.lock 40 | .vscode/ 41 | android/.project 42 | android/.settings/ 43 | android/app/.classpath 44 | android/app/.project 45 | android/app/.settings/org.eclipse.buildship.core.prefs 46 | -------------------------------------------------------------------------------- /lib/pages/music_play_media_page/animation/music_translation_animation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class MusicTranslationAnimation extends StatelessWidget { 4 | MusicTranslationAnimation({ 5 | Key key, 6 | this.child, 7 | this.animationController, 8 | this.begin : 40, 9 | this.end : 0 10 | }) : super (key : key); 11 | 12 | final Widget child; 13 | final double begin; 14 | final double end; 15 | 16 | final AnimationController animationController; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | Animation _translationAnimation = Tween(begin: begin,end: end).animate(animationController); 21 | 22 | return AnimatedBuilder( 23 | animation: _translationAnimation, 24 | builder: (context,_){ 25 | return Padding( 26 | padding: EdgeInsets.only(top: _translationAnimation.value), 27 | child: child, 28 | ); 29 | }, 30 | ); 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /lib/pages/person_page/person_aboutour_item_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | class PersonAboutourItemWidget extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Container( 7 | margin: EdgeInsets.fromLTRB(20, 100, 20, 20), 8 | padding: EdgeInsets.fromLTRB(20, 12, 10, 12), 9 | decoration: BoxDecoration( 10 | borderRadius: BorderRadius.circular(10), 11 | color: MusicStore.Theme(context).theme, 12 | boxShadow: MusicStore.boxShow(context, -10,10) 13 | ), 14 | child: Row( 15 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 16 | children: [ 17 | Text("关于我们" ,style: TextStyle(color: MusicStore.Theme(context).titleColor),), 18 | Icon(Icons.keyboard_arrow_right,color: MusicStore.Theme(context).titleColor,) 19 | ], 20 | ) 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/routers/router_page_name.dart: -------------------------------------------------------------------------------- 1 | class RouterPageName{ 2 | 3 | 4 | 5 | //根路径 6 | static const String initialRoute = "/"; 7 | 8 | 9 | 10 | static const String LibraryPage = "/library_page"; 11 | 12 | static const String CommendPage = "/commend_page"; 13 | 14 | static const String BrowsePage = "/browse_page"; 15 | 16 | //歌单列表页面 17 | static const String AlbumPage = "/album_page"; 18 | 19 | //播放歌曲页面 20 | static const String MusicPlayMeidaPage = "/music_play_meida_page"; 21 | 22 | static const String NewLibraryPage = "/new_library_page"; 23 | 24 | //歌曲列表页面 25 | static const String MusicListPage = "/music_list_page"; 26 | 27 | //登录页面 28 | static const String LoginPage = "/login_page"; 29 | 30 | //输入密码页面 31 | static const String LoginPasswordPage = "/login_password_page"; 32 | 33 | //个人中心 34 | static const String PersonPage = "/person_page"; 35 | 36 | //搜索页面 37 | static const String SearchPage = "/search_page"; 38 | 39 | //搜索结果页面 40 | static const String SearchResultPage = "/search_result_page"; 41 | } -------------------------------------------------------------------------------- /lib/models/search_hot_model.dart: -------------------------------------------------------------------------------- 1 | class SearchHotListModel { 2 | 3 | List hotList; 4 | SearchHotListModel({ 5 | this.hotList 6 | }); 7 | SearchHotListModel.fromJson(Map json){ 8 | if(json["data"] != null){ 9 | 10 | hotList = new List(); 11 | json["data"].forEach((v){ 12 | SearchHotItemModel itemModel = SearchHotItemModel.fromJson(v); 13 | hotList.add(itemModel); 14 | }); 15 | 16 | 17 | } 18 | } 19 | } 20 | 21 | class SearchHotItemModel{ 22 | String searchWord; 23 | int score; 24 | String content; 25 | String iconUrl; 26 | String alg; 27 | 28 | SearchHotItemModel({ 29 | this.searchWord, 30 | this.score, 31 | this.content, 32 | this.iconUrl, 33 | this.alg 34 | }); 35 | 36 | SearchHotItemModel.fromJson(Map json){ 37 | 38 | searchWord = json["searchWord"]; 39 | score = json["score"]; 40 | content = json["content"]; 41 | iconUrl = json["iconUrl"]; 42 | alg = json["alg"]; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /lib/models/user_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class UserModel { 4 | int userId; 5 | String avatarUrl; 6 | String nickname; 7 | String backgroundUrl; 8 | String token; 9 | UserModel({ 10 | this.userId, 11 | this.avatarUrl, 12 | this.nickname, 13 | this.backgroundUrl}); 14 | 15 | UserModel.fromJson(Map json){ 16 | 17 | if(json["profile"] != null){ 18 | userId = json["profile"]["userId"]; 19 | nickname = json["profile"]["nickname"]; 20 | backgroundUrl = json["profile"]["backgroundUrl"]; 21 | avatarUrl = json["profile"]["avatarUrl"]; 22 | } 23 | token = json["token"]; 24 | 25 | 26 | } 27 | String toJson() { 28 | final Map data = new Map(); 29 | data['userId'] = this.userId; 30 | data['nickname'] = this.nickname; 31 | data["backgroundUrl"] = this.backgroundUrl; 32 | data["avatarUrl"] = this.avatarUrl; 33 | 34 | final Map result = new Map(); 35 | result["profile"] = data; 36 | result["token"] = this.token; 37 | return json.encode(result); 38 | 39 | } 40 | } -------------------------------------------------------------------------------- /lib/pages/music_play_media_page/music_play_bottom_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_global.dart'; 3 | import 'package:flutter_music/public_widget/music_button.dart'; 4 | 5 | class MusicPlayBottomWidget extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return Row( 9 | mainAxisAlignment: MainAxisAlignment.center, 10 | children: [ 11 | MusicButton( 12 | normalIconData: Icons.video_library, 13 | size: 15, 14 | margin: EdgeInsets.only(right: 30), 15 | padding: EdgeInsets.fromLTRB(20, 20, 20, 20), 16 | onTap: (selected){ 17 | 18 | 19 | }, 20 | ), 21 | MusicButton( 22 | normalIconData: Icons.format_list_bulleted, 23 | size: 15, 24 | padding: EdgeInsets.fromLTRB(20, 20, 20, 20), 25 | margin: EdgeInsets.only(left: 30), 26 | onTap: (selected){ 27 | Navigator.of(context).pushNamed(RouterPageName.MusicListPage); 28 | 29 | }, 30 | ) 31 | ], 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_music/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /lib/pages/recommend_page/recomment_more_item_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | 4 | class CommentMoreItemWidget extends StatelessWidget { 5 | CommentMoreItemWidget({ 6 | Key key, 7 | this.title 8 | 9 | }):super(key:key); 10 | 11 | final String title; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Container( 16 | margin: EdgeInsets.fromLTRB(20, 20, 20, 10), 17 | padding: EdgeInsets.fromLTRB(15, 15, 15, 15), 18 | decoration: BoxDecoration( 19 | borderRadius: BorderRadius.circular(10), 20 | color: MusicStore.Theme(context).theme, 21 | boxShadow: MusicStore.boxShow(context, -10, 10) 22 | 23 | ), 24 | child: Row( 25 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 26 | children: [ 27 | Expanded( 28 | flex: 1, 29 | child: Text("${title}",style: TextStyle(fontSize: 14,color: MusicStore.Theme(context).titleColor),), 30 | ), 31 | Icon(Icons.keyboard_arrow_right,size: 20,color: MusicStore.Theme(context).titleColor) 32 | ], 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/models/play_list_model.dart: -------------------------------------------------------------------------------- 1 | 2 | class PlayListModel{ 3 | 4 | List playlist; 5 | 6 | PlayListModel({this.playlist}); 7 | 8 | PlayListModel.fromJson(Map json,String keyString){ 9 | if(json[keyString] != null){ 10 | 11 | playlist = new List(); 12 | json[keyString].forEach((v){ 13 | PlayItemModel itemModel = PlayItemModel.fromJson(v); 14 | playlist.add(itemModel); 15 | }); 16 | 17 | 18 | } 19 | } 20 | } 21 | 22 | class PlayItemModel{ 23 | 24 | String coverImgUrl; 25 | 26 | String name; 27 | 28 | int id; 29 | int trackCount; 30 | int playCount; 31 | String blurPicUrl; 32 | 33 | String description; 34 | 35 | bool selected; 36 | 37 | bool slideEnd; 38 | 39 | PlayItemModel({ 40 | this.coverImgUrl, 41 | this.name, 42 | this.id, 43 | this.selected, 44 | this.trackCount, 45 | this.playCount, 46 | this.blurPicUrl 47 | }); 48 | PlayItemModel.fromJson(Map json){ 49 | selected = false; 50 | slideEnd = false; 51 | coverImgUrl = json["coverImgUrl"]; 52 | 53 | name = json["name"]; 54 | id = json["id"]; 55 | description = json["description"] ?? ""; 56 | trackCount = json["trackCount"]; 57 | playCount = json["playCount"]; 58 | blurPicUrl = json["blurPicUrl"]; 59 | } 60 | 61 | 62 | } -------------------------------------------------------------------------------- /lib/common/screen_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | class ScreenAdapter{ 5 | static init(context){ 6 | var ratio = 2.0; 7 | 8 | var screen_width = MediaQuery.of(context).size.width; 9 | 10 | var screen_height = MediaQuery.of(context).size.height; 11 | 12 | 13 | ScreenUtil.instance = ScreenUtil(width: screen_width*ratio,height: screen_height*ratio,allowFontScaling: true)..init(context); 14 | 15 | 16 | //iphone5s 17 | // ScreenUtil.instance = ScreenUtil(width: 640,height: 1136)..init(context); 18 | 19 | //iphone6s 20 | //ScreenUtil.instance = ScreenUtil(width: 750,height: 1334)..init(context); 21 | 22 | //iphone7pluse 23 | 24 | // ScreenUtil.instance = ScreenUtil(width: 818,height: 1472,allowFontScaling: true)..init(context); 25 | 26 | //iphoneX 及以上设备 27 | // ScreenUtil.instance = ScreenUtil(width: 828,height: 1792,allowFontScaling: true)..init(context); 28 | 29 | 30 | 31 | } 32 | 33 | 34 | static getStatusBarHeight(){ 35 | return ScreenUtil.statusBarHeight; 36 | } 37 | 38 | static setHeight(double value){ 39 | return ScreenUtil.getInstance().setHeight(value); 40 | } 41 | 42 | static setWidth(double value){ 43 | return ScreenUtil.getInstance().setWidth(value); 44 | } 45 | 46 | static getScreenHeight(){ 47 | return ScreenUtil.screenWidthDp; 48 | } 49 | 50 | static getScreenWidth(){ 51 | return ScreenUtil.screenHeightDp; 52 | } 53 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | import 'package:flutter_music/routers/router.dart'; 4 | import 'package:flutter_music/common/music_global.dart'; 5 | import 'package:flutter_music/common/state/theme_state.dart'; 6 | import 'package:flutter_music/common/state/music_global_play_list_state.dart'; 7 | import 'package:flutter_music/common/state/user_state.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | 11 | void main(){ 12 | WidgetsFlutterBinding.ensureInitialized(); 13 | MusicGlobal.init().then((value){ 14 | runApp(MyApp()); 15 | }); 16 | } 17 | 18 | class MyApp extends StatelessWidget { 19 | // This widget is the root of your application. 20 | @override 21 | Widget build(BuildContext context) { 22 | 23 | return MultiProvider( 24 | providers: [ 25 | 26 | ChangeNotifierProvider(create: (_)=> UserSate()), 27 | ChangeNotifierProvider(create: (_)=>ThemeState()), 28 | ChangeNotifierProvider(create: (_)=>MusicGlobalPlayListState( 29 | platform: Theme.of(context).platform 30 | )) 31 | ], 32 | child: Consumer( 33 | builder: (context,state,widget){ 34 | 35 | return MaterialApp( 36 | 37 | 38 | color: state.theme, 39 | initialRoute: MusicRouter.initialRoute, 40 | 41 | 42 | onGenerateRoute: MusicRouter.generateRoute, 43 | 44 | theme: ThemeData(primaryColor:state.theme), 45 | 46 | ); 47 | }, 48 | ) 49 | 50 | 51 | ); 52 | 53 | } 54 | 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/pages/music_play_media_page/music_paly_coverimage_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | import 'package:cached_network_image/cached_network_image.dart'; 4 | class MusicPlayCoverimageWidget extends StatelessWidget { 5 | 6 | MusicPlayCoverimageWidget({ 7 | Key key, 8 | this.width, 9 | this.marginTop : 0, 10 | this.animationController, 11 | this.coverImageUrl 12 | }) : super (key : key); 13 | 14 | final double width; 15 | final double marginTop; 16 | final AnimationController animationController; 17 | final String coverImageUrl; 18 | @override 19 | Widget build(BuildContext context) { 20 | return Container( 21 | width: width, 22 | height: width, 23 | margin: EdgeInsets.only(top: marginTop), 24 | 25 | decoration: ShapeDecoration( 26 | color: MusicStore.Theme(context).topShadowColor, 27 | shape: CircleBorder(), 28 | shadows: MusicStore.boxShow(context,-10, 10) 29 | ), 30 | //margin: EdgeInsets.fromLTRB(60, 60, 60, 60), 31 | 32 | padding: EdgeInsets.fromLTRB(5, 5, 5, 5), 33 | child: animationController == null ? _clipOval() : RotationTransition( 34 | alignment: Alignment.center, 35 | turns: animationController, 36 | child: _clipOval() 37 | ) 38 | 39 | ); 40 | } 41 | 42 | Widget _clipOval(){ 43 | 44 | return ClipOval( 45 | child: CachedNetworkImage( 46 | imageUrl: coverImageUrl, 47 | fit: BoxFit.cover, 48 | placeholder: (context,url){ 49 | return Icon(Icons.music_note,size: width,color:MusicStore.Theme(context).bottomShadowColor); 50 | }, 51 | ) 52 | 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/pages/search_page/search_list_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | import 'package:flutter_music/http_request/music_api.dart'; 4 | import 'package:flutter_music/pages/search_page/search_page.dart'; 5 | import 'package:flutter_music/public_widget/future_builder_widget.dart'; 6 | 7 | class SearchListWidget extends StatelessWidget { 8 | 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | 13 | var _searchKey = Provider.of(context,listen: false).searchKeyWord; 14 | return FutureBuilderWidget( 15 | future: MusicApi.searchSuggest(_searchKey), 16 | successBuilder: (BuildContext context, AsyncSnapshot snapshot){ 17 | 18 | 19 | return ListView.separated( 20 | key: ValueKey(_searchKey), 21 | padding: EdgeInsets.only(left: 20, top: 20,right: 10), 22 | itemCount: snapshot.data.length, 23 | itemBuilder: (context,index){ 24 | 25 | String result = snapshot.data[index]["keyword"]; 26 | 27 | 28 | return _item(context,result); 29 | }, 30 | separatorBuilder: (context,index){ 31 | return Divider(color: MusicStore.Theme(context).topShadowColor,height: 2,); 32 | }, 33 | ); 34 | }, 35 | 36 | ); 37 | } 38 | 39 | Widget _item(context,result){ 40 | 41 | 42 | return MusicGestureDetector( 43 | onTap: (){ 44 | Navigator.of(context).pushNamed(RouterPageName.SearchResultPage,arguments: result); 45 | 46 | }, 47 | child: Padding( 48 | padding: EdgeInsets.fromLTRB(0, 15, 0, 15), 49 | child: Text("$result",style: TextStyle(color: MusicStore.Theme(context).titleColor),), 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/pages/search_page/search_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | import 'package:flutter_music/pages/search_page/search_app_bar.dart'; 4 | import 'package:flutter_music/pages/search_page/search_hot_widget.dart'; 5 | import 'package:flutter_music/pages/search_page/search_list_widget.dart'; 6 | 7 | class SearchChangeState extends ChangeNotifier{ 8 | 9 | String searchKeyWord = ""; 10 | 11 | void searchChangeValue(value){ 12 | searchKeyWord = value; 13 | notifyListeners(); 14 | } 15 | } 16 | 17 | class SearchPage extends StatefulWidget { 18 | @override 19 | _SearchPageState createState() => _SearchPageState(); 20 | } 21 | 22 | class _SearchPageState extends State { 23 | 24 | 25 | 26 | @override 27 | void initState() { 28 | // TODO: implement initState 29 | 30 | super.initState(); 31 | } 32 | @override 33 | void dispose() { 34 | // TODO: implement dispose 35 | super.dispose(); 36 | 37 | } 38 | 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | ScreenAdapter.init(context); 43 | return MultiProvider( 44 | providers: [ 45 | ChangeNotifierProvider(create: (_)=> SearchChangeState()), 46 | ], 47 | child: MusicScaffold( 48 | appBar: SearchAppBar( 49 | isGoback: false, 50 | onChange: ( BuildContext context,String value){ 51 | 52 | Provider.of(context,listen: false).searchChangeValue(value); 53 | }, 54 | 55 | ), 56 | 57 | body: Consumer( 58 | builder: (context,state,_){ 59 | 60 | return state.searchKeyWord.length == 0 ? SearchHotWidget() : SearchListWidget(); 61 | }, 62 | ), 63 | 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /lib/models/track_list_model.dart: -------------------------------------------------------------------------------- 1 | class TrackListModel{ 2 | 3 | List trackList; 4 | TrackListModel.fromJson(Map json){ 5 | 6 | if(json["playlist"]["tracks"] != null){ 7 | trackList = new List(); 8 | json["playlist"]["tracks"].forEach((v){ 9 | TrackItemModel itemModel = TrackItemModel.fromJson(v); 10 | trackList.add(itemModel); 11 | }); 12 | } 13 | } 14 | 15 | 16 | } 17 | 18 | class TrackItemModel{ 19 | String name; 20 | int id; 21 | List arList; 22 | 23 | MusicItemModel musicItemModel; 24 | 25 | TrackArAlModel al; 26 | 27 | TrackItemModel({ 28 | this.name, 29 | this.arList, 30 | this.al, 31 | this.musicItemModel 32 | }); 33 | TrackItemModel.fromJson(Map json){ 34 | 35 | 36 | name = json["name"]; 37 | id = json["id"]; 38 | al = TrackArAlModel.fromJson(json["al"]); 39 | if(json["ar"] != null){ 40 | arList = new List(); 41 | json["ar"].forEach((v){ 42 | TrackArAlModel itemModel = TrackArAlModel.fromJson(v); 43 | arList.add(itemModel); 44 | }); 45 | 46 | } 47 | } 48 | } 49 | 50 | class TrackArAlModel{ 51 | int id; 52 | String name; 53 | String picUrl; 54 | 55 | TrackArAlModel({ 56 | this.id, 57 | this.name, 58 | this.picUrl 59 | }); 60 | TrackArAlModel.fromJson(Map json){ 61 | id = json["id"]; 62 | name = json["name"]; 63 | picUrl = json["picUrl"]; 64 | 65 | } 66 | 67 | } 68 | 69 | class MusicItemModel{ 70 | int id; 71 | String url; 72 | int size; 73 | MusicItemModel({ 74 | this.id, 75 | this.url : "", 76 | this.size 77 | }); 78 | MusicItemModel.fromJson(Map json){ 79 | this.id = json["id"]; 80 | this.url = json["url"]; 81 | this.size = json["size"]; 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_music 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UIBackgroundModes 31 | 32 | audio 33 | 34 | UILaunchStoryboardName 35 | LaunchScreen 36 | UIMainStoryboardFile 37 | Main 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | UIViewControllerBasedStatusBarAppearance 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /lib/pages/search_page/music_album_card_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cached_network_image/cached_network_image.dart'; 3 | import 'package:flutter_music/common/music_store.dart'; 4 | class MusicVideoCardWidget extends StatelessWidget { 5 | MusicVideoCardWidget({ 6 | Key key, 7 | this.coverImageUrl, 8 | this.title 9 | }):super(key :key); 10 | final String coverImageUrl; 11 | final String title; 12 | @override 13 | Widget build(BuildContext context) { 14 | ScreenAdapter.init(context); 15 | return Container( 16 | margin: EdgeInsets.fromLTRB(0, 10, 20, 10), 17 | padding: EdgeInsets.fromLTRB(2, 2, 2, 2), 18 | width: ScreenAdapter.setWidth(280), 19 | decoration: BoxDecoration( 20 | borderRadius: BorderRadius.circular(8), 21 | color: MusicStore.Theme(context).theme, 22 | boxShadow: MusicStore.boxShow(context, -2, 8) 23 | ), 24 | child: Column( 25 | children: [ 26 | _coverImage(), 27 | _title(context) 28 | ], 29 | ), 30 | 31 | ); 32 | } 33 | Widget _title(context){ 34 | 35 | return Padding( 36 | padding: EdgeInsets.only(top: 8,left: 5,right: 5), 37 | child: Text( 38 | "$title", 39 | maxLines: 1, 40 | style: TextStyle( 41 | fontSize: 17, 42 | fontWeight: FontWeight.w500, 43 | color: MusicStore.Theme(context).titleColor), 44 | overflow: TextOverflow.ellipsis, 45 | ), 46 | ); 47 | } 48 | Widget _coverImage(){ 49 | return SizedBox( 50 | width: ScreenAdapter.setWidth(280), 51 | height:ScreenAdapter.setWidth(220), 52 | child: ClipRRect( 53 | borderRadius: BorderRadius.only(topLeft: Radius.circular(8),topRight: Radius.circular(8)), 54 | child: CachedNetworkImage( 55 | imageUrl: "$coverImageUrl", 56 | fit: BoxFit.cover, 57 | )), 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/pages/music_play_media_page/music_play_meida_page.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_music/base_music/music_app_bar.dart'; 4 | import 'package:flutter_music/common/music_store.dart'; 5 | import 'package:flutter_music/http_request/music_api.dart'; 6 | 7 | 8 | import 'package:flutter_music/common/state/music_global_play_list_state.dart'; 9 | import 'package:flutter_music/pages/music_play_media_page/music_play_body_widget.dart'; 10 | class MusicPlayMeidaPage extends StatefulWidget { 11 | 12 | 13 | @override 14 | _MusicPlayMeidaPageState createState() => _MusicPlayMeidaPageState(); 15 | } 16 | 17 | class _MusicPlayMeidaPageState extends State with TickerProviderStateMixin { 18 | 19 | 20 | 21 | Future _future; 22 | @override 23 | void initState() { 24 | // TODO: implement initState 25 | super.initState(); 26 | List trackList = MusicStore.MusicPlayList(context).currentPlayList; 27 | 28 | _future = MusicApi.musicMp3Item(trackList); 29 | 30 | } 31 | 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | 36 | return MusicScaffold( 37 | showFloatingActionButton: false, 38 | appBar: MusicAppBar( 39 | title: "", 40 | leftIconData: Icons.keyboard_arrow_left, 41 | rightIconData: Icons.more_horiz, 42 | leftOnTap: (){ 43 | Navigator.of(context).pop(); 44 | }, 45 | ), 46 | 47 | body: SafeArea( 48 | child: FutureBuilderWidget>( 49 | future: _future, 50 | successBuilder: (BuildContext context, AsyncSnapshot> snapshot){ 51 | Widget musicBody = MusicPlayBodyWidget(); 52 | MusicStore.MusicPlayList(context).music_play(); 53 | 54 | return musicBody; 55 | }, 56 | ) 57 | ) 58 | ); 59 | 60 | 61 | } 62 | 63 | 64 | 65 | } 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /lib/tabbar/tababr_page.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// @Author: isanwenyu@163.com 3 | /// @Date: 2020-05-21 15:31:18 4 | /// @LastEditors: zhuyuanbao 5 | /// @LastEditTime: 2020-05-21 15:34:38 6 | /// @Description: 7 | /// 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_music/base_music/music_scaffold.dart'; 10 | import 'package:flutter_music/common/music_store.dart'; 11 | import 'package:flutter_music/pages/library_page/library_page.dart'; 12 | import 'package:flutter_music/pages/recommend_page/recommend_page.dart'; 13 | import 'package:flutter_music/pages/browse_page/browse_page.dart'; 14 | import 'package:flutter_music/tabbar/bottom_tabbar.dart'; 15 | import 'package:flutter_music/common/screen_adapter.dart'; 16 | 17 | class TabbarPage extends StatefulWidget { 18 | @override 19 | _TabbarPageState createState() => _TabbarPageState(); 20 | } 21 | 22 | class _TabbarPageState extends State { 23 | 24 | var _currentIndex = 0; 25 | 26 | var _pageController; 27 | 28 | 29 | final List _pageList = [ 30 | LibraryPage(), 31 | CommendPage(), 32 | BrowsePage() 33 | ]; 34 | @override 35 | void initState() { 36 | // TODO: implement initState 37 | super.initState(); 38 | _pageController = PageController(initialPage: _currentIndex); 39 | } 40 | 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | 45 | 46 | ScreenAdapter.init(context); 47 | return MusicScaffold( 48 | body: PageView( 49 | physics: NeverScrollableScrollPhysics(), 50 | controller:_pageController, 51 | children: _pageList 52 | ), 53 | bottomNavigationBar: BottomTabar( 54 | currentIndex: _currentIndex, 55 | onTap: (index){ 56 | setState(() { 57 | _currentIndex = index; 58 | _pageController.animateToPage(index, 59 | duration: Duration(milliseconds: 300), curve: Curves.ease); 60 | }); 61 | 62 | }, 63 | ), 64 | ); 65 | 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /lib/common/music_store.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter_music/common/music_global.dart'; 5 | import 'package:flutter_music/common/state/music_global_play_list_state.dart'; 6 | import 'package:flutter_music/common/state/theme_state.dart'; 7 | import 'package:flutter_music/common/state/user_state.dart'; 8 | export 'package:provider/provider.dart'; 9 | 10 | export 'package:cached_network_image/cached_network_image.dart'; 11 | export 'package:flutter_music/common/screen_adapter.dart'; 12 | export 'package:flutter_music/public_widget/music_gestureDetector.dart'; 13 | export 'package:flutter_music/common/state/music_global_play_list_state.dart'; 14 | export 'package:flutter_music/base_music/music_scaffold.dart'; 15 | export 'package:flutter_music/routers/router_page_name.dart'; 16 | export 'package:flutter_music/common/state/music_global_play_list_state.dart'; 17 | export 'package:flutter_music/common/dialog/loading_dialog.dart'; 18 | export 'package:flutter_flexible_toast/flutter_flexible_toast.dart'; 19 | export 'package:flutter_music/common/state/user_state.dart'; 20 | 21 | 22 | class MusicStore { 23 | 24 | /// 主题 25 | static ThemeState Theme(context) => ThemeState.of(context); 26 | /// 用户信息 27 | static UserSate User(context) => UserSate.of(context); 28 | 29 | /// 当前播放列表 30 | static MusicGlobalPlayListState MusicPlayList(context) => MusicGlobalPlayListState.of(context); 31 | 32 | /// 是否有触摸反馈 33 | static bool get isVibrate => MusicGlobal.isVibrate; 34 | 35 | 36 | static saveVibrate(bool isVibrate){ 37 | MusicGlobal.saveVibrateInfo(isVibrate); 38 | } 39 | 40 | static List boxShow(BuildContext context, 41 | double top, 42 | 43 | double bottom){ 44 | return [ 45 | BoxShadow( 46 | color: MusicStore.Theme(context).bottomShadowColor, 47 | offset: Offset(bottom,bottom), 48 | blurRadius: bottom), 49 | BoxShadow(color: MusicStore.Theme(context).topShadowColor, offset: Offset(top,top), blurRadius:top.abs()*2+1.0) 50 | ]; 51 | } 52 | 53 | 54 | } -------------------------------------------------------------------------------- /lib/pages/person_page/person_switch_item_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | 4 | import 'package:flutter_music/common/music_store.dart'; 5 | 6 | typedef ValueChanged = void Function(bool value); 7 | 8 | class PersonSwitchItemWidget extends StatefulWidget { 9 | PersonSwitchItemWidget({ 10 | Key key, 11 | this.title, 12 | this.margin : const EdgeInsets.fromLTRB(20, 0, 20, 0), 13 | this.onChanged, 14 | this.value : false 15 | 16 | }):super(key : key); 17 | final String title; 18 | final EdgeInsets margin; 19 | final ValueChanged onChanged; 20 | 21 | var value; 22 | 23 | @override 24 | _PersonSwitchItemWidgetState createState() => _PersonSwitchItemWidgetState(); 25 | } 26 | 27 | class _PersonSwitchItemWidgetState extends State { 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return _switchItem(); 32 | } 33 | Widget _switchItem(){ 34 | return Container( 35 | margin: widget.margin, 36 | padding: EdgeInsets.fromLTRB(20, 5, 10, 5), 37 | decoration: BoxDecoration( 38 | borderRadius: BorderRadius.circular(10), 39 | color: MusicStore.Theme(context).theme, 40 | boxShadow:MusicStore.boxShow(context, -10, 10)), 41 | child: Row( 42 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 43 | 44 | children: [ 45 | 46 | Text(widget.title ,style: TextStyle(color: MusicStore.Theme(context).titleColor),), 47 | 48 | 49 | CupertinoSwitch( 50 | activeColor: Colors.red, 51 | value: widget.value, 52 | onChanged: (state){ 53 | if(widget.onChanged != null){ 54 | widget.onChanged(state); 55 | } 56 | setState(() { 57 | widget.value = !widget.value; 58 | }); 59 | }, 60 | ), 61 | 62 | ], 63 | ) 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/pages/library_page/library_empty_widget.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// @Author: isanwenyu@163.com 3 | /// @Date: 2020-05-21 15:31:18 4 | /// @LastEditors: zhuyuanbao 5 | /// @LastEditTime: 2020-05-21 15:34:30 6 | /// @Description: 7 | /// 8 | 9 | 10 | import 'package:flutter/material.dart'; 11 | import 'package:flutter_music/common/music_store.dart'; 12 | import 'package:flutter_vibrate/flutter_vibrate.dart'; 13 | 14 | class LibraryEmptyWidget extends StatelessWidget { 15 | @override 16 | Widget build(BuildContext context) { 17 | return Center( 18 | 19 | child: Column( 20 | mainAxisAlignment: MainAxisAlignment.center, 21 | children: [ 22 | _text1(context), 23 | _text2(context), 24 | _createPlayList(context) 25 | ], 26 | ), 27 | ); 28 | } 29 | 30 | Widget _text1(BuildContext context){ 31 | return Text("欢迎来到Muisc",style: TextStyle(fontSize: 14,color: MusicStore.Theme(context).titleColor),); 32 | 33 | } 34 | Widget _text2(BuildContext context){ 35 | 36 | return Padding( 37 | padding: EdgeInsets.only(top: 5,bottom: 30), 38 | child: Text("开始创建你的歌单吧",style: TextStyle(fontSize: 12,color: MusicStore.Theme(context).titleColor),), 39 | ); 40 | 41 | } 42 | 43 | Widget _createPlayList(BuildContext context){ 44 | return GestureDetector( 45 | onTap: (){ 46 | Vibrate.feedback(FeedbackType.selection); 47 | Navigator.of(context).pushNamed("/new_library_page"); 48 | }, 49 | child: Container( 50 | width: double.infinity, 51 | alignment: Alignment.center, 52 | padding: EdgeInsets.only(top: 15,bottom: 15), 53 | margin: EdgeInsets.fromLTRB(20, 30, 20, 0), 54 | 55 | decoration: BoxDecoration( 56 | borderRadius: BorderRadius.circular(10), 57 | color: MusicStore.Theme(context).theme, 58 | boxShadow: MusicStore.boxShow(context, -10,10) 59 | 60 | ), 61 | child: Text("创建歌单",style: TextStyle(fontSize: 14,color: MusicStore.Theme(context).titleColor),), 62 | ), 63 | ); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /lib/pages/library_page/new_library_page/new_library_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | import 'package:flutter_music/base_music/music_app_bar.dart'; 4 | 5 | 6 | 7 | class NewLibraryPage extends StatefulWidget { 8 | @override 9 | _NewLibraryPageState createState() => _NewLibraryPageState(); 10 | } 11 | 12 | class _NewLibraryPageState extends State { 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | backgroundColor: MusicStore.Theme(context).theme, 17 | appBar: MusicAppBar( 18 | leftIconData: Icons.arrow_back_ios, 19 | rightIconData: Icons.check, 20 | leftOnTap: (){ 21 | Navigator.of(context).pop(); 22 | }, 23 | title: "新建歌单", 24 | ), 25 | body: Center( 26 | child: Column( 27 | mainAxisAlignment: MainAxisAlignment.center, 28 | 29 | children: [ 30 | _icon(context) 31 | ], 32 | ), 33 | ) 34 | ); 35 | } 36 | 37 | 38 | 39 | 40 | Widget _icon(BuildContext context) { 41 | return Container( 42 | 43 | margin: EdgeInsets.fromLTRB(10, 10, 10, 10), 44 | padding:EdgeInsets.fromLTRB(15, 15, 15, 15), 45 | decoration: BoxDecoration( 46 | 47 | borderRadius: BorderRadius.circular(15), 48 | border: Border.all(color: MusicStore.Theme(context).theme,width: 1), 49 | color: MusicStore.Theme 50 | (context) 51 | .theme, 52 | boxShadow: MusicStore.boxShow(context, -10, 10), 53 | gradient: LinearGradient( 54 | begin: Alignment.topLeft, 55 | end: Alignment.bottomRight, 56 | colors: [ 57 | MusicStore.Theme(context).bottomShadowColor, 58 | 59 | Colors.white 60 | ], 61 | 62 | 63 | ) 64 | ), 65 | child: Icon(Icons.play_arrow, size: 20, color: MusicStore.Theme 66 | (context) 67 | .titleColor), 68 | ); 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /lib/tabbar/tabbar_items/music_tab_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | 4 | class MusicTabItem extends StatelessWidget { 5 | MusicTabItem({ 6 | Key key, 7 | this.index, 8 | this.animation, 9 | 10 | this.animationController, 11 | this.title, 12 | this.iconData, 13 | this.normalColor, 14 | this.selectedColor, 15 | this.colorTween, 16 | this.onTap 17 | }) : super(key:key); 18 | 19 | final int index; 20 | 21 | final IconData iconData; 22 | 23 | final Color normalColor; 24 | 25 | final Color selectedColor; 26 | 27 | final ColorTween colorTween; 28 | 29 | final String title; 30 | 31 | final ValueChanged onTap; 32 | 33 | final Animation animation; 34 | 35 | final AnimationController animationController; 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | 40 | 41 | Animation colorAnimation = Tween(begin: 0.0,end: 1.0).animate(animationController); 42 | 43 | final iconColor = colorTween.evaluate(colorAnimation); 44 | 45 | return MusicGestureDetector( 46 | onTap: (){ 47 | onTap(index); 48 | }, 49 | child: Container( 50 | padding: EdgeInsets.fromLTRB(15, 8, 15, 8), 51 | decoration: BoxDecoration( 52 | borderRadius: BorderRadius.circular(10), 53 | color: MusicStore.Theme(context).theme, 54 | boxShadow: MusicStore.boxShow(context, -2, 5) 55 | ), 56 | child: Row( 57 | children: [ 58 | Icon(iconData,color: iconColor), 59 | SizedBox( 60 | width: animation.value, 61 | child: Text( 62 | "${title}", 63 | style: TextStyle( 64 | color:MusicStore.Theme(context).tabItemSelectedColor, 65 | fontSize: 14, 66 | ), 67 | textAlign:TextAlign.center, 68 | maxLines: 1, 69 | 70 | overflow: TextOverflow.clip) 71 | 72 | ) 73 | ], 74 | ), 75 | ), 76 | ); 77 | } 78 | } 79 | 80 | 81 | -------------------------------------------------------------------------------- /lib/pages/music_play_media_page/music_play_control_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:audioplayers/audioplayers.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_music/common/state/music_global_play_list_state.dart'; 4 | import 'package:flutter_music/public_widget/music_button.dart'; 5 | import 'package:flutter_music/common/music_store.dart'; 6 | typedef GestureTapCallback = void Function(); 7 | 8 | typedef GestureStateTapCallback = void Function(bool selected); 9 | 10 | class MusicPlayControlWidget extends StatelessWidget { 11 | 12 | 13 | 14 | MusicPlayControlWidget({ 15 | Key key, 16 | this.previousTap, 17 | this.nextTap, 18 | this.stateTap 19 | }) : super(key : key); 20 | 21 | final GestureTapCallback previousTap; 22 | final GestureTapCallback nextTap; 23 | final GestureStateTapCallback stateTap; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | MusicGlobalPlayListState musicGlobalPlayListState = MusicStore.MusicPlayList(context); 28 | 29 | return Padding( 30 | padding: EdgeInsets.only(top: 30), 31 | child: Row( 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | children: [ 34 | MusicButton( 35 | normalIconData:Icons.skip_previous, 36 | padding: EdgeInsets.fromLTRB(15, 15, 15, 15), 37 | onTap: (selected){ 38 | previousTap(); 39 | }, 40 | size: 30, 41 | ), 42 | MusicButton( 43 | 44 | selected: musicGlobalPlayListState.playerState == AudioPlayerState.PAUSED ? true : false, 45 | selectedIconData:Icons.play_arrow, 46 | normalIconData: Icons.pause, 47 | onTap: (selected){ 48 | stateTap(selected); 49 | }, 50 | padding: EdgeInsets.fromLTRB(15, 15, 15, 15), 51 | margin: EdgeInsets.only(left: 50,right: 50), 52 | size: 30, 53 | ), 54 | MusicButton( 55 | normalIconData:Icons.skip_next, 56 | padding: EdgeInsets.fromLTRB(15, 15, 15, 15), 57 | size: 30, 58 | onTap: (selected){ 59 | nextTap(); 60 | }, 61 | ), 62 | ], 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "djy.flutter.music.dong.flutter_music" 42 | minSdkVersion 18 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.1.1' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 67 | } 68 | -------------------------------------------------------------------------------- /lib/http_request/http_request_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_music/http_request/config.dart'; 2 | import 'package:dio/dio.dart'; 3 | 4 | class HttpRequestManager{ 5 | 6 | factory HttpRequestManager() => _getInstance(); 7 | 8 | static HttpRequestManager get instance => _getInstance(); 9 | 10 | static HttpRequestManager _instance; 11 | 12 | HttpRequestManager._internal(); 13 | 14 | static HttpRequestManager _getInstance(){ 15 | if(_instance == null){ 16 | _instance = new HttpRequestManager._internal(); 17 | } 18 | return _instance; 19 | } 20 | 21 | Map _publicParams; 22 | 23 | void registerPublicParams(Map params){ 24 | _publicParams = params; 25 | } 26 | 27 | static final BaseOptions baseOptions = BaseOptions( 28 | baseUrl: HttpConfig.baseURL, 29 | connectTimeout: HttpConfig.timeout 30 | ); 31 | 32 | 33 | 34 | static final Dio dio = Dio(baseOptions); 35 | 36 | 37 | static Future request(String url, 38 | { 39 | String method = "post", 40 | Map params, 41 | Interceptor inter 42 | } ) async { 43 | 44 | params = params ?? Map(); 45 | 46 | final options = Options(method: method); 47 | // 全局拦截器 48 | // 创建默认的全局拦截器 49 | Interceptor dInter = InterceptorsWrapper( 50 | onRequest: (RequestOptions warpperOptions) { 51 | 52 | // warpperOptions.queryParameters = HttpRequestManager.instance._publicParams; 53 | 54 | return warpperOptions; 55 | }, 56 | onResponse: (response) { 57 | 58 | return response; 59 | }, 60 | onError: (err) { 61 | 62 | return err; 63 | } 64 | ); 65 | 66 | 67 | List inters = [dInter]; 68 | 69 | 70 | // 请求单独拦截器 71 | if (inter != null) { 72 | 73 | inters.add(inter); 74 | } 75 | 76 | // 统一添加到拦截器中 77 | dio.interceptors.addAll(inters); 78 | 79 | //添加公共参数 80 | if(HttpRequestManager.instance._publicParams != null){ 81 | params.addAll(HttpRequestManager.instance._publicParams); 82 | 83 | } 84 | 85 | try { 86 | Response response = await dio.request(url, queryParameters: params, options: options); 87 | 88 | return response.data; 89 | } on DioError catch(e) { 90 | return Future.error(e); 91 | } 92 | 93 | } 94 | 95 | 96 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/pages/library_page/library_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_music/common/music_store.dart'; 4 | 5 | import 'package:flutter_music/base_music/music_app_bar.dart'; 6 | 7 | import 'package:flutter_music/pages/library_page/library_list_widget.dart'; 8 | import 'package:flutter_music/public_widget/music_submit_button.dart'; 9 | import 'package:flutter_music/pages/library_page/library_state/library_list_state.dart'; 10 | 11 | class LibraryPage extends StatefulWidget { 12 | @override 13 | _LibraryPageState createState() => _LibraryPageState(); 14 | } 15 | 16 | class _LibraryPageState extends State with AutomaticKeepAliveClientMixin{ 17 | 18 | @override 19 | // TODO: implement wantKeepAlive 20 | bool get wantKeepAlive => true; 21 | 22 | 23 | @override 24 | void initState() { 25 | // TODO: implement initState 26 | super.initState(); 27 | 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | 33 | 34 | return MultiProvider( 35 | providers: [ 36 | ChangeNotifierProvider( 37 | 38 | create: (context)=>LibraryListState(), 39 | 40 | ) 41 | ], 42 | child: Builder( 43 | builder: (context){ 44 | 45 | 46 | return Selector( 47 | selector: (context,userState){ 48 | return userState.isLogin; 49 | }, 50 | shouldRebuild: (pre,next){ 51 | return pre != next; 52 | }, 53 | builder: (context,isLogin,_){ 54 | 55 | return MusicScaffold( 56 | showFloatingActionButton: false, 57 | appBar: MusicAppBar( 58 | title: "歌单", 59 | rightIconData: isLogin == false ? null : Icons.edit, 60 | rightSelectedIconData: Icons.delete_sweep , 61 | rightOnTap: (){ 62 | LibraryListState.libraryState(context).deleteAction(); 63 | 64 | }, 65 | ), 66 | body: isLogin == false ? _unLogin() : LibraryListWidget() , 67 | ); 68 | }, 69 | ); 70 | 71 | }, 72 | ) 73 | 74 | ); 75 | } 76 | 77 | 78 | Widget _unLogin(){ 79 | return Center( 80 | child: MusicSubmitButton( 81 | margin: EdgeInsets.fromLTRB(20, 0, 20, 0), 82 | onTap: (state){ 83 | Navigator.of(context).pushNamed(RouterPageName.LoginPage); 84 | }, 85 | title: "登录查看歌单", 86 | ), 87 | 88 | ); 89 | } 90 | 91 | @override 92 | void dispose() { 93 | // TODO: implement dispose 94 | 95 | super.dispose(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/pages/library_page/library_delete_button_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | import 'package:flutter_music/pages/library_page/library_state/library_list_state.dart'; 4 | 5 | 6 | class LibraryDeleteButtonWidget extends StatelessWidget { 7 | LibraryDeleteButtonWidget({ 8 | Key key, 9 | this.index 10 | 11 | }): super(key : key); 12 | 13 | final int index; 14 | @override 15 | Widget build(BuildContext context) { 16 | 17 | 18 | return MusicGestureDetector( 19 | onTap: (){ 20 | LibraryListState.updateDeleteState(context, index); 21 | }, 22 | child: _deleteButton(context), 23 | ); 24 | } 25 | Widget _deleteButton(BuildContext context){ 26 | return Container( 27 | color: MusicStore.Theme(context).theme, 28 | 29 | alignment: Alignment.centerLeft, 30 | width: double.infinity, 31 | height: ScreenAdapter.setHeight(180), 32 | margin: EdgeInsets.only(left: 20,right: 20,top: 10,bottom: 10), 33 | padding: EdgeInsets.only(left: 0,), 34 | child: Container( 35 | width: ScreenAdapter.setWidth(30), 36 | height: ScreenAdapter.setHeight(30), 37 | margin: EdgeInsets.only(left: 5), 38 | padding: EdgeInsets.fromLTRB(5, 5, 5, 5), 39 | child: Selector( 40 | builder: (context,selected,_){ 41 | 42 | return Container( 43 | width: ScreenAdapter.setWidth(20), 44 | height: ScreenAdapter.setWidth(20), 45 | decoration: BoxDecoration( 46 | color: selected == false ? Colors.white : Colors.red, 47 | borderRadius: BorderRadius.circular(5) 48 | ), 49 | 50 | ); 51 | }, 52 | selector: (BuildContext context,LibraryListState state){ 53 | return state.dataSource[index].selected; 54 | }, 55 | shouldRebuild: (pre,next){ 56 | return pre != next; 57 | }, 58 | ), 59 | decoration: BoxDecoration( 60 | borderRadius: BorderRadius.circular(ScreenAdapter.setWidth(15)), 61 | color: MusicStore.Theme 62 | (context) 63 | .theme, 64 | boxShadow: MusicStore.boxShow(context,-3, 3), 65 | gradient:LinearGradient( 66 | begin: Alignment.topLeft, 67 | end: Alignment.bottomRight, 68 | colors: [ 69 | MusicStore.Theme(context).bottomShadowColor, 70 | MusicStore.Theme(context).topShadowColor 71 | ], 72 | 73 | ) 74 | ), 75 | ), 76 | ); 77 | } 78 | } 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /lib/pages/search_page/music_album_item_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | class MusicAlbumItemWidget extends StatelessWidget { 4 | MusicAlbumItemWidget({ 5 | Key key, 6 | this.coverImageUrl, 7 | this.title, 8 | this.subTitle 9 | 10 | }):super(key : key); 11 | final String coverImageUrl; 12 | final String title; 13 | final String subTitle; 14 | @override 15 | Widget build(BuildContext context) { 16 | ScreenAdapter.init(context); 17 | return Container( 18 | margin: EdgeInsets.only(left: 20, right: 20, top: 10, bottom: 20), 19 | child: Row( 20 | children: [ 21 | _itemCover(context), 22 | Expanded( 23 | flex: 1, 24 | child: _itemTitle(context,title,subTitle), 25 | ) 26 | ], 27 | ), 28 | ); 29 | } 30 | 31 | Widget _itemTitle(context,title,subtTitle) { 32 | return Padding( 33 | padding: EdgeInsets.only(left: 20, right: 10), 34 | child: Column( 35 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 36 | crossAxisAlignment: CrossAxisAlignment.start, 37 | children: [ 38 | Text( 39 | "$title", 40 | maxLines: 2, 41 | style: TextStyle( 42 | fontSize: 17, 43 | fontWeight: FontWeight.w500, 44 | color: MusicStore.Theme(context).titleColor), 45 | overflow: TextOverflow.ellipsis, 46 | ), 47 | Padding( 48 | padding: EdgeInsets.only(top: 10), 49 | child: Text( 50 | "$subtTitle", 51 | maxLines: 1, 52 | overflow: TextOverflow.ellipsis, 53 | style: TextStyle( 54 | fontSize: 10, 55 | color: MusicStore.Theme(context).subtTitleColor), 56 | )) 57 | ], 58 | ), 59 | ); 60 | } 61 | 62 | 63 | Widget _itemCover(context) { 64 | 65 | return Container( 66 | width: ScreenAdapter.setWidth(180), 67 | height: ScreenAdapter.setHeight(180), 68 | margin: EdgeInsets.fromLTRB(0, 0, 0, 0), 69 | padding: EdgeInsets.fromLTRB(2, 2, 2, 2), 70 | decoration: BoxDecoration( 71 | borderRadius: BorderRadius.circular(10), 72 | color: MusicStore.Theme(context).topShadowColor, 73 | boxShadow: MusicStore.boxShow(context, -10, 10)), 74 | child: ClipRRect( 75 | borderRadius: BorderRadius.circular(10), 76 | child: CachedNetworkImage( 77 | imageUrl: "$coverImageUrl", 78 | fit: BoxFit.cover, 79 | ))); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/pages/album_page/album_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/pages/album_page/album_header_widget.dart'; 3 | import 'package:flutter_music/common/music_store.dart'; 4 | import 'package:flutter_music/base_music/music_app_bar.dart'; 5 | import 'package:flutter_music/http_request/music_api.dart'; 6 | import 'package:flutter_music/public_widget/music_item_widget.dart'; 7 | class AlbumPage extends StatefulWidget { 8 | AlbumPage({ 9 | Key key, 10 | this.id 11 | }) : super(key : key); 12 | final int id; 13 | @override 14 | _AlbumPageState createState() => _AlbumPageState(); 15 | } 16 | 17 | class _AlbumPageState extends State { 18 | 19 | 20 | Future _future; 21 | 22 | @override 23 | void initState() { 24 | // TODO: implement initState 25 | super.initState(); 26 | _future = MusicApi.songDetail(widget.id); 27 | } 28 | @override 29 | Widget build(BuildContext context) { 30 | 31 | return MusicScaffold( 32 | 33 | appBar: MusicAppBar( 34 | leftIconData: Icons.keyboard_arrow_left, 35 | leftOnTap: (){ 36 | Navigator.of(context).pop(); 37 | } 38 | ), 39 | body: FutureBuilderWidget( 40 | future: _future, 41 | successBuilder: (BuildContext context, AsyncSnapshot snapshot){ 42 | SongDetailModel model = snapshot.data; 43 | return ListView.builder( 44 | itemCount: model.tracks.length +1, 45 | itemBuilder: (context,index){ 46 | 47 | if(index == 0){ 48 | return AlbumHeaderWidget( 49 | title: model.name, 50 | desc: model.description, 51 | coverImageUrl: model.coverImgUrl, 52 | ); 53 | } 54 | var tracks = model.tracks; 55 | var trackIndex = index-1; 56 | 57 | var id = tracks[trackIndex].id; 58 | var title = tracks[trackIndex].name; 59 | var subtTitle = tracks[trackIndex].arList.first.name + tracks[trackIndex].al.name; 60 | var coverImageUrl = tracks[trackIndex].al.picUrl; 61 | 62 | return MusicItemWidget( 63 | onTap: (index){ 64 | 65 | MusicStore.MusicPlayList(context).updatePlayList(tracks, index); 66 | Navigator.of(context).pushNamed( 67 | RouterPageName.MusicPlayMeidaPage 68 | ); 69 | }, 70 | id: id, 71 | index: trackIndex, 72 | title: title, 73 | subtTitle:subtTitle, 74 | coverImageUrl:coverImageUrl, 75 | ); 76 | } 77 | ); 78 | } 79 | ) 80 | 81 | ); 82 | 83 | 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/common/state/theme_state.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:flutter_music/common/music_global.dart'; 6 | 7 | class ThemeState extends ChangeNotifier{ 8 | 9 | 10 | 11 | Color _theme = MusicGlobal.theme; 12 | 13 | bool get isDarkTheme => _theme == MusicGlobal.light ? false : true; 14 | 15 | Color get theme => _theme; 16 | 17 | 18 | Color get indicatorColor{ 19 | if(theme == MusicGlobal.light){ 20 | return Color(0xFF3C3C44); 21 | } 22 | return Colors.white; 23 | } 24 | 25 | Color get titleColor{ 26 | if(theme == MusicGlobal.light){ 27 | return MusicGlobal.lightTitleColor; 28 | } 29 | return MusicGlobal.darkTitleColor; 30 | } 31 | 32 | Color get bottomShadowColor { 33 | if(theme== MusicGlobal.light){ 34 | return MusicGlobal.lightBottomShadowColor; 35 | } 36 | return MusicGlobal.darkBottomShadowColor; 37 | } 38 | 39 | Color get topShadowColor{ 40 | if(theme == MusicGlobal.light){ 41 | return MusicGlobal.lightTopShadowColor; 42 | } 43 | return MusicGlobal.darkTopShadowColor; 44 | } 45 | Color get textFieldColor{ 46 | if(theme == MusicGlobal.light){ 47 | return Color.fromRGBO(0, 0, 0, 1.0); 48 | } 49 | return Color.fromRGBO(255, 255, 255, 1.0); 50 | } 51 | 52 | 53 | Color get tabItemNormalColor{ 54 | if(theme == MusicGlobal.light){ 55 | return Color.fromRGBO(222, 227, 233, 1.0); 56 | } 57 | return MusicGlobal.darkTitleColor; 58 | } 59 | 60 | Color get tabItemSelectedColor{ 61 | if(theme == MusicGlobal.light){ 62 | return MusicGlobal.lightTitleColor; 63 | } 64 | return MusicGlobal.goldenColor; 65 | } 66 | 67 | Color get sliderColor{ 68 | if(theme == MusicGlobal.light){ 69 | return Color.fromRGBO(151, 160, 235, 1.0); 70 | } 71 | return Colors.red; 72 | } 73 | Color get sliderThemeColor{ 74 | if(theme == MusicGlobal.light){ 75 | return Color.fromRGBO(151, 160, 235, 1.0); 76 | } 77 | return Colors.white; 78 | } 79 | Color get sliderOverlayColor{ 80 | if(theme == MusicGlobal.light){ 81 | return Colors.white; 82 | } 83 | return Colors.red; 84 | } 85 | Color get subtTitleColor => MusicGlobal.subTitleColor; 86 | 87 | Color get goldenColor => MusicGlobal.goldenColor; 88 | 89 | 90 | switchTheme(){ 91 | if(_theme == MusicGlobal.light){ 92 | _theme = MusicGlobal.dark; 93 | }else{ 94 | _theme = MusicGlobal.light; 95 | } 96 | MusicGlobal.saveTheme(_theme); 97 | 98 | notifyListeners(); 99 | } 100 | 101 | 102 | 103 | 104 | static ThemeState of(context){ 105 | 106 | return Provider.of(context,listen: false); 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /lib/pages/browse_page/browse_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/base_music/music_app_bar.dart'; 3 | import 'package:flutter_music/common/music_store.dart'; 4 | import 'package:flutter_music/pages/browse_page/browse_banner_widget.dart'; 5 | import 'package:flutter_music/public_widget/music_item_widget.dart'; 6 | import 'package:flutter_music/http_request/music_api.dart'; 7 | import 'package:flutter_music/public_widget/music_title_widget.dart'; 8 | class BrowsePage extends StatefulWidget { 9 | @override 10 | _BrowsePageState createState() => _BrowsePageState(); 11 | } 12 | 13 | class _BrowsePageState extends State with AutomaticKeepAliveClientMixin{ 14 | 15 | @override 16 | // TODO: implement wantKeepAlive 17 | bool get wantKeepAlive => true; 18 | Future _future; 19 | void initState() { 20 | // TODO: implement initState 21 | super.initState(); 22 | _future = MusicApi.newSongList(); 23 | } 24 | @override 25 | void dispose() { 26 | // TODO: implement dispose 27 | super.dispose(); 28 | 29 | } 30 | 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | 35 | 36 | return MusicScaffold( 37 | showFloatingActionButton: false, 38 | appBar: MusicAppBar( 39 | title: "浏览", 40 | rightIconData: Icons.search, 41 | rightOnTap: (){ 42 | Navigator.of(context).pushNamed(RouterPageName.SearchPage); 43 | }, 44 | ), 45 | body: FutureBuilderWidget>( 46 | future:_future, 47 | successBuilder: (BuildContext context, AsyncSnapshot> snapshot){ 48 | 49 | 50 | return ListView.builder( 51 | padding: EdgeInsets.only(bottom: 20), 52 | itemCount: 10 + 2, 53 | itemBuilder: (BuildContext context, int index){ 54 | 55 | if(index == 0){ 56 | return BrowseBannerWidget(); 57 | }else if(index == 1){ 58 | return MusicTitleWidget( 59 | title: "新歌", 60 | ); 61 | } 62 | 63 | TrackItemModel itemModel = snapshot.data[index-2]; 64 | 65 | return MusicItemWidget( 66 | onTap: (_){ 67 | 68 | MusicStore.MusicPlayList(context).updatePlayList(snapshot.data, index-2); 69 | 70 | Navigator.of(context).pushNamed( 71 | RouterPageName.MusicPlayMeidaPage 72 | ); 73 | }, 74 | index: itemModel.id, 75 | title: itemModel.name, 76 | subtTitle: itemModel.arList.first.name + "-" + itemModel.al.name, 77 | coverImageUrl: itemModel.al.picUrl, 78 | 79 | ); 80 | } 81 | ); 82 | }, 83 | ), 84 | ); 85 | 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /lib/pages/recommend_page/recomment_item_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | import 'package:flutter_music/public_widget/music_title_widget.dart'; 4 | import 'package:flutter_music/public_widget/music_gestureDetector.dart'; 5 | typedef GestureTapCallback = void Function(); 6 | 7 | class CommentItemWidget extends StatelessWidget { 8 | 9 | CommentItemWidget({ 10 | Key key, 11 | this.title, 12 | this.imageUrl1, 13 | this.imageUrl2, 14 | this.leftOnTap, 15 | this.rightOnTap 16 | 17 | }): super(key:key); 18 | 19 | final String title; 20 | final String imageUrl1; 21 | final String imageUrl2; 22 | 23 | final GestureTapCallback leftOnTap; 24 | final GestureTapCallback rightOnTap; 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Padding( 29 | padding: EdgeInsets.only(top: 30,left: 20,right: 20), 30 | child: Column( 31 | children: [ 32 | MusicTitleWidget( 33 | title: title, 34 | padding: EdgeInsets.fromLTRB(0, 0, 0, 0), 35 | ), 36 | _itemList(context) 37 | ], 38 | ), 39 | ); 40 | } 41 | 42 | Widget _itemList(BuildContext context){ 43 | 44 | return Row( 45 | children: [ 46 | Expanded( 47 | flex: 1, 48 | child: Container( 49 | margin: EdgeInsets.only(right:6), 50 | child: MusicGestureDetector( 51 | onTap: (){ 52 | leftOnTap(); 53 | }, 54 | child: _item(context,imageUrl1), 55 | ), 56 | ) 57 | ), 58 | Expanded( 59 | flex: 1, 60 | child: Container( 61 | margin: EdgeInsets.only(left: 6), 62 | child: MusicGestureDetector( 63 | onTap: (){ 64 | rightOnTap(); 65 | }, 66 | child: _item(context,imageUrl2), 67 | ), 68 | ), 69 | ) 70 | ], 71 | ); 72 | } 73 | 74 | Widget _item(BuildContext context,imageUrl){ 75 | return Container( 76 | 77 | margin: EdgeInsets.only(top: 15), 78 | padding: EdgeInsets.fromLTRB(3, 3, 3, 3), 79 | 80 | decoration: BoxDecoration( 81 | 82 | borderRadius: BorderRadius.circular(10), 83 | color: MusicStore.Theme(context).topShadowColor, 84 | boxShadow: MusicStore.boxShow(context, -10, 10) 85 | ), 86 | child: AspectRatio( 87 | aspectRatio: 1.3, 88 | child: ClipRRect( 89 | borderRadius: BorderRadius.circular(5), 90 | child: CachedNetworkImage( 91 | imageUrl: "$imageUrl", 92 | fit: BoxFit.cover, 93 | ) 94 | ), 95 | ), 96 | 97 | ); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /lib/pages/search_page/search_hot_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | import 'package:flutter_music/http_request/music_api.dart'; 4 | 5 | 6 | class SearchHotWidget extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | 10 | return FutureBuilderWidget>( 11 | future: MusicApi.searchHot(), 12 | successBuilder: (BuildContext context, AsyncSnapshot> snapshot){ 13 | 14 | List result = snapshot.data; 15 | 16 | return Container( 17 | margin: EdgeInsets.fromLTRB(20, 5, 20, 20), 18 | child: CustomScrollView( 19 | 20 | slivers: [ 21 | SliverToBoxAdapter( 22 | child: _title(context), 23 | ), 24 | SliverGrid( 25 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 26 | mainAxisSpacing: 20, 27 | crossAxisSpacing: 20, 28 | crossAxisCount: 2, 29 | childAspectRatio: 4.5 30 | ), 31 | delegate: 32 | SliverChildBuilderDelegate((BuildContext context, int index) { 33 | SearchHotItemModel itemModel = result[index]; 34 | 35 | 36 | return MusicGestureDetector( 37 | onTap: (){ 38 | Navigator.of(context).pushNamed(RouterPageName.SearchResultPage,arguments: itemModel.searchWord); 39 | 40 | }, 41 | child: _hotItem(context,result[index]), 42 | ); 43 | }, childCount: result.length 44 | ) 45 | ) 46 | ], 47 | ), 48 | ); 49 | }, 50 | ); 51 | } 52 | 53 | 54 | Widget _title(context){ 55 | return Padding( 56 | padding: EdgeInsets.fromLTRB(0, 20, 20, 20), 57 | child: Text("热搜榜", 58 | maxLines: 1, 59 | overflow: TextOverflow.ellipsis, 60 | style: TextStyle( 61 | fontSize: 20, 62 | color: MusicStore.Theme(context).titleColor, 63 | fontWeight: FontWeight.w500)), 64 | ); 65 | } 66 | 67 | Widget _hotItem(context,SearchHotItemModel itemModel){ 68 | return Container( 69 | alignment: Alignment.center, 70 | padding: EdgeInsets.fromLTRB(8, 5, 8, 5), 71 | decoration: BoxDecoration( 72 | borderRadius: BorderRadius.circular(15), 73 | color: MusicStore.Theme(context).theme, 74 | boxShadow: MusicStore.boxShow(context, -3, 5)), 75 | child: Text(itemModel.searchWord, 76 | maxLines: 1, 77 | overflow: TextOverflow.ellipsis, 78 | style: TextStyle( 79 | color: MusicStore.Theme(context).titleColor, 80 | fontWeight: FontWeight.w500)), 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_music 2 | description: A new Flutter application. 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.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.1.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^0.1.2 26 | provider: ^4.0.4 27 | shared_preferences: ^0.5.6+3 28 | flutter_swiper: ^1.1.6 29 | flutter_screenutil: ^0.5.3 30 | cached_network_image: ^2.0.0 31 | flutter_vibrate: ^1.0.0 32 | audioplayers: ^0.15.1 33 | dio: ^3.0.9 34 | apple_sign_in: ^0.1.0 35 | flutter_secure_storage: ^3.3.3 36 | before_after: ^1.0.1 37 | flutter_flexible_toast: ^0.1.4 38 | 39 | dev_dependencies: 40 | flutter_test: 41 | sdk: flutter 42 | 43 | 44 | # For information on the generic Dart part of this file, see the 45 | # following page: https://dart.dev/tools/pub/pubspec 46 | 47 | # The following section is specific to Flutter. 48 | flutter: 49 | 50 | # The following line ensures that the Material Icons font is 51 | # included with your application, so that you can use the icons in 52 | # the material Icons class. 53 | uses-material-design: true 54 | 55 | # To add assets to your application, add an assets section, like this: 56 | # assets: 57 | # - images/a_dot_burr.jpeg 58 | # - images/a_dot_ham.jpeg 59 | 60 | # An image asset can refer to one or more resolution-specific "variants", see 61 | # https://flutter.dev/assets-and-images/#resolution-aware. 62 | 63 | # For details regarding adding assets from package dependencies, see 64 | # https://flutter.dev/assets-and-images/#from-packages 65 | 66 | # To add custom fonts to your application, add a fonts section here, 67 | # in this "flutter" section. Each entry in this list should have a 68 | # "family" key with the font family name, and a "fonts" key with a 69 | # list giving the asset and other descriptors for the font. For 70 | # example: 71 | # fonts: 72 | # - family: Schyler 73 | # fonts: 74 | # - asset: fonts/Schyler-Regular.ttf 75 | # - asset: fonts/Schyler-Italic.ttf 76 | # style: italic 77 | # - family: Trajan Pro 78 | # fonts: 79 | # - asset: fonts/TrajanPro.ttf 80 | # - asset: fonts/TrajanPro_Bold.ttf 81 | # weight: 700 82 | # 83 | # For details regarding fonts from package dependencies, 84 | # see https://flutter.dev/custom-fonts/#from-packages 85 | -------------------------------------------------------------------------------- /lib/common/inner_shadow.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | import 'dart:ui' as ui; 4 | 5 | class InnerShadow extends SingleChildRenderObjectWidget { 6 | const InnerShadow({ 7 | Key key, 8 | this.color, 9 | this.blur, 10 | this.offset, 11 | Widget child, 12 | }) : super(key: key, child: child); 13 | 14 | final Color color; 15 | final double blur; 16 | final Offset offset; 17 | 18 | @override 19 | RenderInnerShadow createRenderObject(BuildContext context) { 20 | return RenderInnerShadow() 21 | ..color = color 22 | ..blur = blur 23 | ..offset = offset; 24 | } 25 | 26 | @override 27 | void updateRenderObject( 28 | BuildContext context, RenderInnerShadow renderObject) { 29 | renderObject 30 | ..color = color 31 | ..blur = blur 32 | ..offset = offset; 33 | } 34 | } 35 | 36 | class RenderInnerShadow extends RenderProxyBox { 37 | RenderInnerShadow({ 38 | RenderBox child, 39 | }) : super(child); 40 | 41 | @override 42 | bool get alwaysNeedsCompositing => child != null; 43 | 44 | Color _color; 45 | double _blur; 46 | Offset _offset; 47 | 48 | Color get color => _color; 49 | set color(Color value) { 50 | if (_color == value) return; 51 | _color = value; 52 | markNeedsPaint(); 53 | } 54 | 55 | double get blur => _blur; 56 | set blur(double value) { 57 | if (_blur == value) return; 58 | _blur = value; 59 | markNeedsPaint(); 60 | } 61 | 62 | Offset get offset => _offset; 63 | set offset(Offset value) { 64 | if (_offset == value) return; 65 | _offset = value; 66 | markNeedsPaint(); 67 | } 68 | 69 | @override 70 | void paint(PaintingContext context, Offset offset) { 71 | if (child != null) { 72 | var layerPaint = Paint()..color = Colors.white; 73 | 74 | var canvas = context.canvas; 75 | canvas.saveLayer(offset & size, layerPaint); 76 | context.paintChild(child, offset); 77 | var shadowPaint = Paint() 78 | ..blendMode = ui.BlendMode.srcATop 79 | ..imageFilter = ui.ImageFilter.blur(sigmaX: blur, sigmaY: blur) 80 | ..colorFilter = ui.ColorFilter.mode(color, ui.BlendMode.srcIn); 81 | canvas.saveLayer(offset & size, shadowPaint); 82 | 83 | // Invert the alpha to compute inner part. 84 | var invertPaint = Paint() 85 | ..colorFilter = const ui.ColorFilter.matrix([ 86 | 1, 87 | 0, 88 | 0, 89 | 0, 90 | 0, 91 | 0, 92 | 1, 93 | 0, 94 | 0, 95 | 0, 96 | 0, 97 | 0, 98 | 1, 99 | 0, 100 | 0, 101 | 0, 102 | 0, 103 | 0, 104 | -1, 105 | 255, 106 | ]); 107 | canvas.saveLayer(offset & size, invertPaint); 108 | canvas.translate(_offset.dx, _offset.dy); 109 | context.paintChild(child, offset); 110 | context.canvas.restore(); 111 | context.canvas.restore(); 112 | context.canvas.restore(); 113 | } 114 | } 115 | 116 | @override 117 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { 118 | if (child != null) visitor(child); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/base_music/music_scaffold.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/screen_adapter.dart'; 3 | import 'package:flutter_music/common/music_store.dart'; 4 | import 'package:flutter_music/base_music/music_bottom_play.dart'; 5 | 6 | class MusicScaffold extends StatefulWidget { 7 | MusicScaffold( 8 | {Key key, 9 | this.appBar, 10 | this.body, 11 | this.bottomNavigationBar, 12 | this.showFloatingActionButton: true}) 13 | : super(key: key); 14 | 15 | final Widget body; 16 | final Widget bottomNavigationBar; 17 | final PreferredSizeWidget appBar; 18 | //是否显示最下面的播放条 默认是显示的 19 | final bool showFloatingActionButton; 20 | @override 21 | _MusicScaffoldState createState() => _MusicScaffoldState(); 22 | } 23 | 24 | class _MusicScaffoldState extends State 25 | with TickerProviderStateMixin { 26 | AnimationController _animationController; 27 | MusicGlobalPlayListState musicGlobalPlayListState; 28 | @override 29 | void initState() { 30 | // TODO: implement initState 31 | super.initState(); 32 | musicGlobalPlayListState = MusicStore.MusicPlayList(context); 33 | _animationController = 34 | AnimationController(duration: Duration(seconds: 25), vsync: this); 35 | 36 | if(musicGlobalPlayListState.playerState == AudioPlayerState.PAUSED){ 37 | _animationController.stop(); 38 | }else{ 39 | _animationController.repeat(); 40 | } 41 | musicGlobalPlayListState.onPlayerStateChanged.listen((state){ 42 | if(this.mounted == false)return; 43 | 44 | if(state == AudioPlayerState.PAUSED){ 45 | _animationController.stop(); 46 | }else{ 47 | _animationController.repeat(); 48 | } 49 | }); 50 | 51 | } 52 | 53 | @override 54 | void dispose() { 55 | _animationController.dispose(); 56 | // TODO: implement dispose 57 | super.dispose(); 58 | } 59 | 60 | @override 61 | Widget build(BuildContext context) { 62 | ScreenAdapter.init(context); 63 | 64 | 65 | int listCount = musicGlobalPlayListState.currentPlayList != null ? musicGlobalPlayListState.currentPlayList.length : 0; 66 | 67 | 68 | return Scaffold( 69 | backgroundColor: MusicStore.Theme(context).theme, 70 | appBar: widget.appBar, 71 | body: SafeArea( 72 | child: GestureDetector( 73 | 74 | onTap: (){ 75 | FocusScope.of(context).requestFocus(FocusNode()); 76 | }, 77 | child: Padding( 78 | padding: EdgeInsets.only( 79 | bottom: (listCount == 0 || widget.bottomNavigationBar != null) 80 | ? 0 81 | : ScreenAdapter.setHeight(90)), 82 | child: widget.body, 83 | ), 84 | ), 85 | ), 86 | bottomNavigationBar: widget.bottomNavigationBar, 87 | floatingActionButton: _floatingWidget(listCount), 88 | floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, 89 | ); 90 | } 91 | 92 | Widget _floatingWidget(listCount) { 93 | if ( listCount== 0 || widget.showFloatingActionButton == false) { 94 | return Text(""); 95 | } 96 | 97 | return MusicBottomPlay( 98 | animationController: _animationController, 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/public_widget/music_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | import 'package:flutter_vibrate/flutter_vibrate.dart'; 4 | import 'package:cached_network_image/cached_network_image.dart'; 5 | 6 | typedef GestureTapCallback = void Function(bool selected); 7 | 8 | 9 | class MusicButton extends StatefulWidget { 10 | MusicButton({ 11 | Key key, 12 | this.selected: false, 13 | @required this.normalIconData, 14 | this.showLayer:true, 15 | this.selectedIconData, 16 | this.onTap, 17 | this.isEnable : true, 18 | this.padding : const EdgeInsets.fromLTRB(10, 10, 10, 10), 19 | this.margin : const EdgeInsets.fromLTRB(0, 0, 0, 0), 20 | this.size : 25, 21 | this.imageURL 22 | }): super(key:key); 23 | 24 | final IconData normalIconData; 25 | final IconData selectedIconData; 26 | final GestureTapCallback onTap; 27 | final EdgeInsets padding; 28 | final EdgeInsets margin; 29 | final bool isEnable; 30 | final double size; 31 | final bool showLayer; 32 | final String imageURL; 33 | bool selected; 34 | @override 35 | _MusicButtonState createState() => _MusicButtonState(); 36 | } 37 | 38 | class _MusicButtonState extends State { 39 | 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | 44 | 45 | return GestureDetector( 46 | 47 | 48 | 49 | onTap: widget.isEnable == false ? null : (){ 50 | if(MusicStore.isVibrate == true){ 51 | Vibrate.feedback(FeedbackType.impact); 52 | } 53 | 54 | if (widget.selectedIconData != null){ 55 | setState(() { 56 | 57 | widget.selected = !widget.selected; 58 | }); 59 | } 60 | if(widget.onTap != null){ 61 | widget.onTap(widget.selected); 62 | } 63 | 64 | 65 | }, 66 | child: _musicButton(context), 67 | ); 68 | } 69 | 70 | Widget _musicButton(BuildContext context) { 71 | 72 | return Container( 73 | margin: widget.margin, 74 | padding: widget.padding, 75 | decoration: BoxDecoration( 76 | borderRadius: BorderRadius.circular(15), 77 | color: MusicStore.Theme 78 | (context) 79 | .theme, 80 | boxShadow: widget.showLayer == false ? null : MusicStore.boxShow(context, -3, 3), 81 | gradient: widget.selected == false ? null : LinearGradient( 82 | begin: Alignment.topLeft, 83 | end: Alignment.bottomRight, 84 | colors: [ 85 | MusicStore.Theme(context).bottomShadowColor, 86 | MusicStore.Theme(context).topShadowColor 87 | ], 88 | 89 | ) 90 | ), 91 | 92 | child: widget.imageURL != null ? _image() : Icon(_iconData(), size: widget.size, color: MusicStore.Theme 93 | (context) 94 | .titleColor), 95 | ); 96 | } 97 | 98 | Widget _image(){ 99 | return ClipRRect( 100 | borderRadius: BorderRadius.circular(15), 101 | child: CachedNetworkImage( 102 | imageUrl: widget.imageURL, 103 | fit: BoxFit.cover, 104 | )); 105 | } 106 | 107 | IconData _iconData(){ 108 | 109 | if(widget.selectedIconData != null){ 110 | return widget.selected == true ? widget.selectedIconData : widget.normalIconData; 111 | } 112 | return widget.normalIconData; 113 | } 114 | } 115 | 116 | 117 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - apple_sign_in (0.0.1): 3 | - Flutter 4 | - audioplayers (0.0.1): 5 | - Flutter 6 | - Flutter (1.0.0) 7 | - flutter_flexible_toast (0.0.1): 8 | - Flutter 9 | - flutter_secure_storage (3.3.1): 10 | - Flutter 11 | - flutter_vibrate (0.0.1): 12 | - Flutter 13 | - FMDB (2.7.5): 14 | - FMDB/standard (= 2.7.5) 15 | - FMDB/standard (2.7.5) 16 | - path_provider (0.0.1): 17 | - Flutter 18 | - path_provider_macos (0.0.1): 19 | - Flutter 20 | - shared_preferences (0.0.1): 21 | - Flutter 22 | - shared_preferences_macos (0.0.1): 23 | - Flutter 24 | - shared_preferences_web (0.0.1): 25 | - Flutter 26 | - sqflite (0.0.1): 27 | - Flutter 28 | - FMDB (~> 2.7.2) 29 | 30 | DEPENDENCIES: 31 | - apple_sign_in (from `.symlinks/plugins/apple_sign_in/ios`) 32 | - audioplayers (from `.symlinks/plugins/audioplayers/ios`) 33 | - Flutter (from `Flutter`) 34 | - flutter_flexible_toast (from `.symlinks/plugins/flutter_flexible_toast/ios`) 35 | - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) 36 | - flutter_vibrate (from `.symlinks/plugins/flutter_vibrate/ios`) 37 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 38 | - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) 39 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 40 | - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) 41 | - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) 42 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 43 | 44 | SPEC REPOS: 45 | https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git: 46 | - FMDB 47 | 48 | EXTERNAL SOURCES: 49 | apple_sign_in: 50 | :path: ".symlinks/plugins/apple_sign_in/ios" 51 | audioplayers: 52 | :path: ".symlinks/plugins/audioplayers/ios" 53 | Flutter: 54 | :path: Flutter 55 | flutter_flexible_toast: 56 | :path: ".symlinks/plugins/flutter_flexible_toast/ios" 57 | flutter_secure_storage: 58 | :path: ".symlinks/plugins/flutter_secure_storage/ios" 59 | flutter_vibrate: 60 | :path: ".symlinks/plugins/flutter_vibrate/ios" 61 | path_provider: 62 | :path: ".symlinks/plugins/path_provider/ios" 63 | path_provider_macos: 64 | :path: ".symlinks/plugins/path_provider_macos/ios" 65 | shared_preferences: 66 | :path: ".symlinks/plugins/shared_preferences/ios" 67 | shared_preferences_macos: 68 | :path: ".symlinks/plugins/shared_preferences_macos/ios" 69 | shared_preferences_web: 70 | :path: ".symlinks/plugins/shared_preferences_web/ios" 71 | sqflite: 72 | :path: ".symlinks/plugins/sqflite/ios" 73 | 74 | SPEC CHECKSUMS: 75 | apple_sign_in: 7716c7ddfa195aeab7dec0dc374ef4ff45d1adb4 76 | audioplayers: 84f968cea3f2deab00ec4f8ff53358b3c0b3992c 77 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 78 | flutter_flexible_toast: 0547e740cae0c33bb7c51bcd931233f4584e1143 79 | flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec 80 | flutter_vibrate: c0ca4ac994ff97c3f147f69865458bbc0cc3508b 81 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 82 | path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d 83 | path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 84 | shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01 85 | shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 86 | shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 87 | sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 88 | 89 | PODFILE CHECKSUM: 15f7e0ddf8dcfcd97e0855c96c0f533b1a099c78 90 | 91 | COCOAPODS: 1.9.1 92 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/pages/search_page/search_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_music/common/music_store.dart'; 5 | 6 | import 'package:flutter_music/public_widget/music_button.dart'; 7 | 8 | typedef ValueChanged = void Function(BuildContext context,String value); 9 | class SearchAppBar extends StatefulWidget implements PreferredSizeWidget{ 10 | SearchAppBar({ 11 | Key key, 12 | this.onChange, 13 | this.searchWord, 14 | this.isGoback : false 15 | 16 | }) : preferredSize = Size.fromHeight(kToolbarHeight), super(key :key); 17 | final Size preferredSize; 18 | final ValueChanged onChange; 19 | final String searchWord; 20 | final bool isGoback; 21 | @override 22 | _SearchAppBarState createState() => _SearchAppBarState(); 23 | } 24 | 25 | class _SearchAppBarState extends State { 26 | 27 | TextEditingController editingController; 28 | @override 29 | void initState() { 30 | // TODO: implement initState 31 | super.initState(); 32 | editingController = TextEditingController(); 33 | editingController.text = widget.searchWord; 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | ScreenAdapter.init(context); 39 | return SafeArea( 40 | child:Row( 41 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 42 | children: [ 43 | Expanded( 44 | flex: 1, 45 | child: _search(context), 46 | ), 47 | Padding( 48 | padding: EdgeInsets.only(right: 20), 49 | child: widget.isGoback == false ? MusicGestureDetector( 50 | onTap: (){ 51 | Navigator.of(context).pop(); 52 | }, 53 | child: Text("取消",style:TextStyle(color: MusicStore.Theme(context).titleColor,fontSize: 16),), 54 | ) : MusicButton( 55 | normalIconData: Icons.chevron_left, 56 | onTap: (selected){ 57 | Navigator.of(context).pop(); 58 | }, 59 | ) 60 | 61 | ) 62 | ], 63 | ) 64 | ); 65 | } 66 | Widget _search(context){ 67 | return Container( 68 | 69 | alignment: Alignment.center, 70 | decoration: BoxDecoration( 71 | borderRadius: BorderRadius.circular(8), 72 | color:MusicStore.Theme(context).theme, 73 | border: Border.all(color: MusicStore.Theme(context).topShadowColor,width: 2), 74 | boxShadow: MusicStore.boxShow(context, -5, 5) 75 | 76 | ), 77 | 78 | margin: EdgeInsets.fromLTRB(15, 0, 15, 0), 79 | padding: EdgeInsets.fromLTRB(10, 0, 0, 0), 80 | child: TextField( 81 | controller: editingController, 82 | autofocus: widget.isGoback == true ? false : true, 83 | onChanged: (value){ 84 | widget.onChange(context,value); 85 | }, 86 | 87 | style: TextStyle(color: MusicStore.Theme(context).titleColor,fontSize: 18), 88 | onSubmitted: (string){ 89 | 90 | Navigator.of(context).pushNamed(RouterPageName.SearchResultPage,arguments: string); 91 | 92 | }, 93 | decoration: InputDecoration( 94 | suffixIcon: Icon(Icons.search,color: MusicStore.Theme(context).titleColor,), 95 | border: InputBorder.none, 96 | hintText: "请输入搜索内容", 97 | hintStyle: TextStyle(color: MusicStore.Theme(context).subtTitleColor) 98 | ) 99 | 100 | ), 101 | 102 | ); 103 | } 104 | } 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /lib/pages/person_page/person_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/base_music/music_app_bar.dart'; 3 | import 'package:flutter_music/common/music_store.dart'; 4 | import 'package:flutter_music/http_request/music_api.dart'; 5 | import 'package:flutter_music/public_widget/music_submit_button.dart'; 6 | import 'package:flutter_music/pages/person_page/person_switch_item_widget.dart'; 7 | import 'package:flutter_music/pages/person_page/person_aboutour_item_widget.dart'; 8 | class PersonPage extends StatefulWidget { 9 | @override 10 | _PersonPageState createState() => _PersonPageState(); 11 | } 12 | 13 | class _PersonPageState extends State { 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | 18 | return MusicScaffold( 19 | appBar: MusicAppBar( 20 | title: MusicStore.User(context).user.nickname, 21 | rightIconData: Icons.chevron_left, 22 | rightOnTap: (){ 23 | Navigator.of(context).pop(); 24 | }, 25 | ), 26 | 27 | body: Stack( 28 | children: [ 29 | ListView( 30 | children: [ 31 | 32 | _text("默认情况下触摸按钮会有震动反馈,您可以手动关闭震动效果"), 33 | PersonSwitchItemWidget( 34 | onChanged: (state){ 35 | MusicStore.saveVibrate(state); 36 | }, 37 | value: MusicStore.isVibrate, 38 | margin: EdgeInsets.fromLTRB(20, 20, 20, 0), 39 | title: "触摸反馈", 40 | ), 41 | _text("主题模式并不会跟随系统的变化而变化,需要您手动进行设置"), 42 | PersonSwitchItemWidget( 43 | onChanged: (state){ 44 | MusicStore.Theme(context).switchTheme(); 45 | }, 46 | value: MusicStore.Theme(context).isDarkTheme, 47 | margin: EdgeInsets.fromLTRB(20, 20, 20, 0), 48 | title: "深色模式", 49 | ), 50 | 51 | PersonAboutourItemWidget() 52 | 53 | ], 54 | ), 55 | Positioned( 56 | right: 0, 57 | left: 0, 58 | bottom: 0, 59 | child: _exitBottom(), 60 | ) 61 | ], 62 | ), 63 | ); 64 | } 65 | 66 | 67 | 68 | Widget _text(text){ 69 | return Padding( 70 | padding: EdgeInsets.fromLTRB(25, 50, 20, 0), 71 | child: Text("$text", 72 | textAlign: TextAlign.left, 73 | style: TextStyle(color: MusicStore.Theme(context).subtTitleColor,fontSize: 11), 74 | ), 75 | ); 76 | } 77 | 78 | Widget _exitBottom(){ 79 | return MusicSubmitButton( 80 | onTap: (state){ 81 | state.requestFuture(MusicApi.logout()); 82 | }, 83 | successCallback: (succcess){ 84 | if(succcess == 1){ 85 | MusicStore.User(context).logoOut(); 86 | 87 | MusicStore.MusicPlayList(context).music_clear(); 88 | 89 | Navigator.of(context).pop(); 90 | }else{ 91 | FlutterFlexibleToast.showToast( 92 | message: "退出失败,请重试", 93 | toastLength: Toast.LENGTH_SHORT, 94 | toastGravity: ToastGravity.TOP, 95 | backgroundColor: Colors.red, 96 | icon: ICON.WARNING, 97 | timeInSeconds: 2); 98 | } 99 | }, 100 | margin: EdgeInsets.fromLTRB(20, 20, 20, 40), 101 | backGroundColor: Colors.red, 102 | titleStyle: TextStyle(color: Colors.white,fontSize: 17,fontWeight: FontWeight.w600), 103 | title: "退出登录", 104 | loadingText: "正在退出...", 105 | loadingStyle: TextStyle(color: Colors.white,fontSize: 17,fontWeight: FontWeight.w600), 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git' 6 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 7 | 8 | project 'Runner', { 9 | 'Debug' => :debug, 10 | 'Profile' => :release, 11 | 'Release' => :release, 12 | } 13 | 14 | def parse_KV_file(file, separator='=') 15 | file_abs_path = File.expand_path(file) 16 | if !File.exists? file_abs_path 17 | return []; 18 | end 19 | generated_key_values = {} 20 | skip_line_start_symbols = ["#", "/"] 21 | File.foreach(file_abs_path) do |line| 22 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 23 | plugin = line.split(pattern=separator) 24 | if plugin.length == 2 25 | podname = plugin[0].strip() 26 | path = plugin[1].strip() 27 | podpath = File.expand_path("#{path}", file_abs_path) 28 | generated_key_values[podname] = podpath 29 | else 30 | puts "Invalid plugin specification: #{line}" 31 | end 32 | end 33 | generated_key_values 34 | end 35 | 36 | target 'Runner' do 37 | use_frameworks! 38 | use_modular_headers! 39 | 40 | # Flutter Pod 41 | 42 | copied_flutter_dir = File.join(__dir__, 'Flutter') 43 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 44 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 45 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 46 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 47 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 48 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 49 | 50 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 51 | unless File.exist?(generated_xcode_build_settings_path) 52 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 53 | end 54 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 55 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 56 | 57 | unless File.exist?(copied_framework_path) 58 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 59 | end 60 | unless File.exist?(copied_podspec_path) 61 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 62 | end 63 | end 64 | 65 | # Keep pod path relative so it can be checked into Podfile.lock. 66 | pod 'Flutter', :path => 'Flutter' 67 | 68 | # Plugin Pods 69 | 70 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 71 | # referring to absolute paths on developers' machines. 72 | system('rm -rf .symlinks') 73 | system('mkdir -p .symlinks/plugins') 74 | plugin_pods = parse_KV_file('../.flutter-plugins') 75 | plugin_pods.each do |name, path| 76 | symlink = File.join('.symlinks', 'plugins', name) 77 | File.symlink(path, symlink) 78 | pod name, :path => File.join(symlink, 'ios') 79 | end 80 | end 81 | 82 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 83 | install! 'cocoapods', :disable_input_output_paths => true 84 | 85 | post_install do |installer| 86 | installer.pods_project.targets.each do |target| 87 | target.build_configurations.each do |config| 88 | config.build_settings['ENABLE_BITCODE'] = 'NO' 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/pages/library_page/library_list_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter_music/common/music_global.dart'; 4 | import 'package:flutter_music/common/music_store.dart'; 5 | import 'package:flutter_music/public_widget/future_builder_widget.dart'; 6 | import 'package:flutter_music/pages/library_page/library_empty_widget.dart'; 7 | import 'package:flutter_music/models/play_list_model.dart'; 8 | import 'package:flutter_music/http_request/music_api.dart'; 9 | import 'package:flutter_music/common/screen_adapter.dart'; 10 | import 'package:flutter_music/pages/library_page/library_item_widget.dart'; 11 | import 'package:flutter_music/pages/library_page/library_state/library_list_state.dart'; 12 | 13 | class LibraryListWidget extends StatefulWidget { 14 | LibraryListWidget({Key key}) : super(key: key); 15 | 16 | 17 | @override 18 | _LibraryListWidgetState createState() => _LibraryListWidgetState(); 19 | } 20 | 21 | class _LibraryListWidgetState extends State 22 | with TickerProviderStateMixin { 23 | 24 | Future _future; 25 | 26 | @override 27 | void initState() { 28 | // TODO: implement initState 29 | super.initState(); 30 | 31 | _future = MusicApi.requestPlayList(); 32 | LibraryListState.libraryState(context).initEditAnimation(this); 33 | 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | 39 | ScreenAdapter.init(context); 40 | 41 | 42 | return FutureBuilderWidget>( 43 | future: _future, 44 | emptydBuilder: 45 | (BuildContext context, AsyncSnapshot> snapshot) { 46 | return LibraryEmptyWidget(); 47 | }, 48 | isEmptyBuilder: 49 | (BuildContext context, AsyncSnapshot> snapshot) { 50 | return snapshot.data.length == 0 ? true : false; 51 | }, 52 | successBuilder: 53 | (BuildContext context, AsyncSnapshot> snapshot) { 54 | LibraryListState.initDataSource(context, snapshot.data); 55 | 56 | return _selector(context); 57 | }, 58 | ); 59 | } 60 | 61 | Widget _selector(BuildContext context) { 62 | List dataSource = LibraryListState.getDataSource(context); 63 | return Selector( 64 | builder: (context, state, _) { 65 | 66 | return _listView(dataSource); 67 | }, 68 | selector: (context, state) { 69 | return state.dataSource.length; 70 | }, 71 | shouldRebuild: (pre, next) { 72 | 73 | return pre != next; 74 | }, 75 | ); 76 | } 77 | 78 | 79 | 80 | Widget _listView(List dataSource) { 81 | 82 | return Builder(builder: (context) { 83 | 84 | return dataSource.length == 0 85 | ? LibraryEmptyWidget() 86 | : ListView.builder( 87 | padding: EdgeInsets.only(top: 30), 88 | 89 | itemCount: dataSource.length, 90 | 91 | itemBuilder: (context,index){ 92 | PlayItemModel itemModel = dataSource[index]; 93 | 94 | return LibraryItemWidget( 95 | 96 | animation: LibraryListState.libraryState(context).editAnimation, 97 | onTap: (index) { 98 | 99 | Navigator.of(context).pushNamed(RouterPageName.AlbumPage, arguments: itemModel.id); 100 | 101 | }, 102 | index: index, 103 | coverImage: itemModel.coverImgUrl, 104 | name: itemModel.name, 105 | desc: itemModel.description, 106 | 107 | ); 108 | } 109 | ); 110 | 111 | }); 112 | } 113 | 114 | @override 115 | void dispose() { 116 | // TODO: implement dispose 117 | 118 | super.dispose(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/public_widget/future_builder_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_music/public_widget/music_activityIndicator.dart'; 4 | import 'package:flutter_music/common/music_store.dart'; 5 | 6 | 7 | typedef AsyncWidgetBuilder = Widget Function(BuildContext context, AsyncSnapshot snapshot); 8 | 9 | typedef EmptyWidgetBuilder = bool Function(BuildContext context, AsyncSnapshot snapshot); 10 | 11 | class FutureBuilderWidget extends StatefulWidget { 12 | FutureBuilderWidget({ 13 | Key key, 14 | this.future, 15 | this.emptydBuilder, 16 | this.activityIndicator, 17 | EmptyWidgetBuilder isEmptyBuilder, 18 | this.successBuilder, 19 | AsyncWidgetBuilder fieldBuilder 20 | 21 | 22 | }) : _isEmptyBuilder = isEmptyBuilder , 23 | _fieldBuilder = fieldBuilder, 24 | super(key : key); 25 | 26 | /// 异步函数 27 | final Future future; 28 | 29 | /// 请求成功回调 30 | final AsyncWidgetBuilder successBuilder; 31 | 32 | /// 请求失败回调 33 | final AsyncWidgetBuilder _fieldBuilder; 34 | 35 | /// 用于显示空白页面的widget 36 | final AsyncWidgetBuilder emptydBuilder; 37 | 38 | /// 指示器 不传的话 会有默认的大菊花指示器 39 | final Widget activityIndicator; 40 | 41 | /// 是否需要显示空白页面 42 | final EmptyWidgetBuilder _isEmptyBuilder; 43 | @override 44 | _FutureBuilderWidgetState createState() => _FutureBuilderWidgetState(); 45 | } 46 | 47 | class _FutureBuilderWidgetState extends State> { 48 | 49 | 50 | @override 51 | void initState() { 52 | // TODO: implement initState 53 | 54 | super.initState(); 55 | 56 | } 57 | 58 | /// MARK:空白页面 59 | Widget _empty(BuildContext context,AsyncSnapshot snapshot){ 60 | 61 | Widget emptyWidget = widget.emptydBuilder(context,snapshot); 62 | 63 | return Center( 64 | child: emptyWidget == null ? Text("暂时没有数据哦") : emptyWidget 65 | ); 66 | } 67 | 68 | /// MARK: 错误信息页面 69 | Widget _error(BuildContext context,AsyncSnapshot snapshot){ 70 | 71 | 72 | return Center( 73 | child: widget._fieldBuilder == null ? 74 | Padding( 75 | padding: EdgeInsets.only(left: 20,right: 20), 76 | child: Text("请求失败:"+snapshot.error.toString(),style: TextStyle(color: Colors.red),), 77 | ) 78 | : widget._fieldBuilder(context,snapshot), 79 | ); 80 | } 81 | 82 | ///MARK:指示器 83 | Widget _activityIndicator(){ 84 | return Builder( 85 | builder: (context){ 86 | return Center( 87 | 88 | child: widget.activityIndicator != null ? widget.activityIndicator : MusicActivityIndicator( 89 | color: MusicStore.Theme(context).titleColor, 90 | radius: 20, 91 | ), 92 | ); 93 | }, 94 | ); 95 | } 96 | 97 | @override 98 | Widget build(BuildContext context) { 99 | 100 | 101 | return FutureBuilder( 102 | future: widget.future, 103 | 104 | builder: (BuildContext context, AsyncSnapshot snapshot){ 105 | 106 | if(snapshot.connectionState == ConnectionState.done){ 107 | 108 | if(snapshot.hasError){ 109 | 110 | return _error(context,snapshot); 111 | 112 | 113 | }else{ 114 | 115 | bool isShowEmpty = false; 116 | 117 | if(widget._isEmptyBuilder != null){ 118 | isShowEmpty = widget._isEmptyBuilder(context,snapshot); 119 | } 120 | 121 | Widget builderWidget = widget.successBuilder(context,snapshot); 122 | 123 | return isShowEmpty == true ? _empty(context,snapshot) : builderWidget; 124 | 125 | } 126 | 127 | }else{ 128 | 129 | return _activityIndicator(); 130 | } 131 | 132 | }, 133 | ); 134 | } 135 | } 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /lib/base_music/music_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | import 'package:flutter_music/public_widget/music_button.dart'; 4 | 5 | 6 | typedef GestureTapCallback = void Function(); 7 | 8 | class MusicAppBar extends StatefulWidget implements PreferredSizeWidget{ 9 | 10 | MusicAppBar({ 11 | Key key, 12 | this.title, 13 | 14 | this.leftIconData, 15 | this.rightIconData, 16 | this.rightSelectedIconData, 17 | this.leftOnTap, 18 | this.rightOnTap, 19 | this.rightImageURL 20 | }) : preferredSize = Size.fromHeight(kToolbarHeight), super(key:key); 21 | 22 | 23 | final GestureTapCallback leftOnTap; 24 | final GestureTapCallback rightOnTap; 25 | 26 | final Size preferredSize; 27 | final String title; 28 | final IconData leftIconData; 29 | final IconData rightIconData; 30 | 31 | final IconData rightSelectedIconData; 32 | final String rightImageURL; 33 | 34 | 35 | @override 36 | _MusicAppBarState createState() => _MusicAppBarState(); 37 | } 38 | 39 | class _MusicAppBarState extends State { 40 | @override 41 | Widget build(BuildContext context) { 42 | 43 | List _list = List(); 44 | if(widget.leftIconData != null){ 45 | _list.add(_leftItem()); 46 | } 47 | if(widget.title != null){ 48 | _list.add(_title()); 49 | } 50 | 51 | 52 | if(widget.rightIconData != null){ 53 | _list.add(_rightItem()); 54 | } 55 | if(widget.rightImageURL != null){ 56 | _list.add(_image()); 57 | } 58 | 59 | return SafeArea( 60 | child: Container( 61 | padding: EdgeInsets.only(left: 20,right: 20), 62 | color: MusicStore.Theme(context).theme, 63 | child: Row( 64 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 65 | children: _list 66 | ), 67 | ), 68 | ); 69 | 70 | 71 | } 72 | //keyboard_arrow_left 73 | 74 | Widget _leftItem(){ 75 | 76 | if(widget.leftIconData == null){ 77 | return Text(""); 78 | } 79 | return MusicButton( 80 | normalIconData: widget.leftIconData, 81 | onTap: (selected){ 82 | if(widget.leftIconData != null){ 83 | widget.leftOnTap(); 84 | } 85 | }, 86 | ); 87 | } 88 | 89 | Widget _image(){ 90 | return MusicGestureDetector( 91 | onTap: (){ 92 | if(widget.rightOnTap != null){ 93 | widget.rightOnTap(); 94 | } 95 | }, 96 | child: Container( 97 | width: ScreenAdapter.setWidth(80), 98 | height: ScreenAdapter.setHeight(80), 99 | margin: EdgeInsets.fromLTRB(6, 6, 6, 6), 100 | decoration: BoxDecoration( 101 | borderRadius: BorderRadius.circular(40), 102 | color: MusicStore.Theme(context).theme, 103 | boxShadow: MusicStore.boxShow(context, -5, 5) 104 | ), 105 | child: ClipRRect( 106 | borderRadius: BorderRadius.circular(37), 107 | child: CachedNetworkImage( 108 | imageUrl: widget.rightImageURL, 109 | fit: BoxFit.cover, 110 | )) 111 | ) 112 | ); 113 | } 114 | 115 | Widget _rightItem(){ 116 | if (widget.rightIconData == null && widget.rightImageURL == null){ 117 | return Text(""); 118 | } 119 | return MusicButton( 120 | imageURL: widget.rightImageURL, 121 | normalIconData: widget.rightIconData, 122 | selectedIconData: widget.rightSelectedIconData, 123 | onTap: (selected){ 124 | if(widget.rightOnTap != null){ 125 | widget.rightOnTap(); 126 | } 127 | }, 128 | ); 129 | } 130 | 131 | 132 | Widget _title(){ 133 | return Text("${widget.title}",style: TextStyle(fontSize: 25,color: MusicStore.Theme(context).titleColor,fontWeight: FontWeight.w500),); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /lib/pages/music_play_media_page/music_play_slider_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | 4 | class MusicPlaySliderWidget extends StatefulWidget { 5 | @override 6 | _MusicPlaySliderWidgetState createState() => _MusicPlaySliderWidgetState(); 7 | } 8 | 9 | class _MusicPlaySliderWidgetState extends State { 10 | 11 | double changeValue = 0.0; 12 | bool startSlider = false; 13 | MusicGlobalPlayListState musicGlobalPlayListState; 14 | @override 15 | Widget build(BuildContext context) { 16 | musicGlobalPlayListState = MusicStore.MusicPlayList(context); 17 | return Padding( 18 | padding: EdgeInsets.fromLTRB(30, 50, 30, 30), 19 | child: Column( 20 | children: [ 21 | _slider(context), 22 | _time(context) 23 | ], 24 | ), 25 | ); 26 | } 27 | 28 | Widget _time(context){ 29 | 30 | return Padding( 31 | padding: EdgeInsets.fromLTRB(10, 0, 10, 0), 32 | child: Row( 33 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 34 | children: [ 35 | 36 | /// 当前时间 37 | Selector( 38 | builder: (context,positionText,_){ 39 | 40 | return Text(positionText,style: TextStyle(fontSize: 12,color: Color.fromRGBO(165, 171, 191, 1.0))); 41 | }, 42 | selector: (context,state){ 43 | return state.positionText; 44 | }, 45 | shouldRebuild: (pre,next){ 46 | return pre != next; 47 | }, 48 | ), 49 | /// 总时长 50 | Selector( 51 | builder: (context,durationText,_){ 52 | 53 | return Text(durationText,style: TextStyle(fontSize: 12,color: Color.fromRGBO(165, 171, 191, 1.0))); 54 | }, 55 | selector: (context,state){ 56 | return state.durationText; 57 | }, 58 | shouldRebuild: (pre,next){ 59 | return pre != next; 60 | }, 61 | 62 | ) 63 | 64 | ], 65 | ), 66 | ); 67 | } 68 | 69 | Widget _slider(context){ 70 | return SliderTheme( 71 | 72 | data: SliderTheme.of(context).copyWith( 73 | activeTrackColor: MusicStore.Theme(context).sliderColor, 74 | inactiveTrackColor: MusicStore.Theme(context).bottomShadowColor, 75 | trackHeight: 3, 76 | overlayColor:MusicStore.Theme(context).sliderOverlayColor, 77 | thumbColor:MusicStore.Theme(context).sliderThemeColor, 78 | overlayShape:RoundSliderOverlayShape(//可继承SliderComponentShape自定义形状 79 | overlayRadius: 10, //滑块外圈大小 80 | 81 | ), 82 | thumbShape: RoundSliderThumbShape(//可继承SliderComponentShape自定义形状 83 | disabledThumbRadius: 5, //禁用是滑块大小 84 | enabledThumbRadius: 5, //滑块大小 85 | ), 86 | 87 | ), 88 | child: Selector( 89 | builder: (context,progress,_){ 90 | 91 | if(startSlider == false){ 92 | changeValue = progress; 93 | } 94 | 95 | return Slider( 96 | onChangeStart: (double){ 97 | startSlider = true; 98 | 99 | }, 100 | onChangeEnd: (double value){ 101 | 102 | musicGlobalPlayListState.music_seek(value); 103 | 104 | startSlider = false; 105 | 106 | }, 107 | onChanged: (double value) { 108 | 109 | setState(() { 110 | changeValue = value; 111 | }); 112 | 113 | 114 | }, 115 | value: changeValue, 116 | ); 117 | }, 118 | selector: (context,state){ 119 | return state.playProgress; 120 | }, 121 | shouldRebuild: (pre,next){ 122 | return pre != next; 123 | }, 124 | ) 125 | 126 | ); 127 | } 128 | } 129 | 130 | -------------------------------------------------------------------------------- /lib/pages/album_page/album_header_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:flutter_music/public_widget/music_gestureDetector.dart'; 5 | class _IntroductionState extends ChangeNotifier{ 6 | 7 | // 0 折叠 1展开 8 | int mainLine = 3; 9 | 10 | void updateState(){ 11 | mainLine = mainLine == 3 ? 10000 : 3; 12 | 13 | notifyListeners(); 14 | } 15 | 16 | } 17 | 18 | class AlbumHeaderWidget extends StatelessWidget { 19 | AlbumHeaderWidget({ 20 | Key key, 21 | this.coverImageUrl, 22 | this.title, 23 | this.desc 24 | 25 | }) : super(key : key); 26 | 27 | final String coverImageUrl; 28 | final String title; 29 | final String desc; 30 | @override 31 | Widget build(BuildContext context) { 32 | ScreenAdapter.init(context); 33 | return Padding( 34 | padding: EdgeInsets.only(top: 20,left: 20,right: 20,bottom: 20), 35 | child: Column( 36 | children: [ 37 | _songHeader(context), 38 | _introduction() 39 | ], 40 | ), 41 | ); 42 | } 43 | Widget _introduction(){ 44 | 45 | return ChangeNotifierProvider( 46 | create: (context) => _IntroductionState(), 47 | child: Padding( 48 | padding: EdgeInsets.only(top: 20), 49 | 50 | child: Builder( 51 | builder: (context){ 52 | return MusicGestureDetector( 53 | onTap: (){ 54 | Provider.of<_IntroductionState>(context,listen: false).updateState(); 55 | 56 | }, 57 | child: Consumer<_IntroductionState>( 58 | builder: (context,state,_){ 59 | return Row( 60 | children: [ 61 | Expanded( 62 | flex: 1, 63 | child: Text( 64 | "$desc", 65 | textAlign:TextAlign.left, 66 | style: TextStyle(fontSize: 12,color: MusicStore.Theme(context).subtTitleColor), 67 | maxLines: state.mainLine, 68 | overflow: TextOverflow.ellipsis, 69 | ), 70 | ) 71 | ], 72 | ); 73 | }, 74 | ) 75 | ); 76 | }, 77 | ), 78 | ), 79 | ); 80 | 81 | 82 | } 83 | 84 | Widget _songHeader(context){ 85 | return Row( 86 | crossAxisAlignment: CrossAxisAlignment.center, 87 | children: [ 88 | Expanded( 89 | flex: 1, 90 | child: _cover(context), 91 | ), 92 | Expanded( 93 | flex: 1, 94 | child: _title(context), 95 | ) 96 | ], 97 | ); 98 | } 99 | 100 | Widget _title(context){ 101 | 102 | return Padding( 103 | padding: EdgeInsets.only(top: 0), 104 | child: Text( 105 | "$title", 106 | 107 | 108 | style: TextStyle(fontSize: 24,fontWeight: FontWeight.w600,color: MusicStore.Theme(context).titleColor), 109 | 110 | ), 111 | ); 112 | } 113 | 114 | Widget _cover(context){ 115 | String _coverImageURL = coverImageUrl + "?param="+"316"+"y"+"240"; 116 | return Container( 117 | width: ScreenAdapter.setWidth(316), 118 | height: ScreenAdapter.setHeight(240), 119 | margin: EdgeInsets.fromLTRB(0, 0, 20, 0), 120 | padding: EdgeInsets.fromLTRB(5, 5, 5, 5), 121 | decoration: BoxDecoration( 122 | borderRadius: BorderRadius.circular(10), 123 | color: MusicStore.Theme(context).topShadowColor, 124 | boxShadow: MusicStore.boxShow(context, -10, 10) 125 | ), 126 | 127 | child: ClipRRect( 128 | borderRadius: BorderRadius.circular(10), 129 | child: CachedNetworkImage( 130 | imageUrl: "$_coverImageURL", 131 | fit: BoxFit.cover, 132 | ), 133 | ), 134 | ); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /lib/routers/router.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_music/pages/search_page/search_result_page.dart'; 6 | import 'package:flutter_music/pages/login_page/login_password_page.dart'; 7 | import 'package:flutter_music/tabbar/tababr_page.dart'; 8 | import 'package:flutter_music/pages/library_page/new_library_page/new_library_page.dart'; 9 | import 'package:flutter_music/pages/album_page/album_page.dart'; 10 | import 'package:flutter_music/routers/router_page_name.dart'; 11 | import 'package:flutter_music/pages/music_play_media_page/music_play_meida_page.dart'; 12 | import 'package:flutter_music/pages/music_list_page/music_list_page.dart'; 13 | import 'package:flutter_music/pages/login_page/login_page.dart'; 14 | import 'package:flutter_music/pages/library_page/library_page.dart'; 15 | import 'package:flutter_music/pages/recommend_page/recommend_page.dart'; 16 | import 'package:flutter_music/pages/browse_page/browse_page.dart'; 17 | import 'package:flutter_music/pages/person_page/person_page.dart'; 18 | import 'package:flutter_music/pages/search_page/search_page.dart'; 19 | class MusicRouter{ 20 | 21 | static final String initialRoute = RouterPageName.initialRoute; 22 | 23 | static final Map routers = { 24 | RouterPageName.initialRoute: (context) => TabbarPage(), 25 | 26 | "/new_library_page": (context,{arguments}) => NewLibraryPage(), 27 | 28 | RouterPageName.AlbumPage:(context) => AlbumPage(), 29 | 30 | RouterPageName.LibraryPage:(context) => LibraryPage(), 31 | 32 | RouterPageName.CommendPage:(context) => CommendPage(), 33 | 34 | RouterPageName.BrowsePage:(context) => BrowsePage(), 35 | 36 | RouterPageName.MusicPlayMeidaPage:(context) => MusicPlayMeidaPage(), 37 | 38 | RouterPageName.MusicListPage:(context) => MusicListPage(), 39 | 40 | RouterPageName.LoginPage:(context) => LoginPage(), 41 | 42 | RouterPageName.LoginPasswordPage:(context) => LoginPasswordPage(), 43 | 44 | RouterPageName.PersonPage:(context) => PersonPage(), 45 | 46 | RouterPageName.SearchPage:(context) => SearchPage(), 47 | 48 | RouterPageName.SearchResultPage:(context) => SearchResultPage() 49 | 50 | }; 51 | 52 | 53 | static const opacityCurve = const Interval(0.0, 0.75, curve: Curves.fastOutSlowIn); 54 | 55 | static final RouteFactory generateRoute = (settings) { 56 | 57 | final String name = settings.name; 58 | 59 | final Function pageContentBuilder = routers[name]; 60 | 61 | if (pageContentBuilder != null) { 62 | if (settings.arguments != null) { 63 | 64 | if(name == RouterPageName.AlbumPage){ 65 | return MaterialPageRoute( 66 | settings: RouteSettings(name: name), 67 | builder: (context) { 68 | return AlbumPage(id: settings.arguments); 69 | } 70 | ); 71 | }else if(name == RouterPageName.LoginPasswordPage){ 72 | Map map = settings.arguments; 73 | return MaterialPageRoute( 74 | settings: RouteSettings(name: name), 75 | builder: (context) { 76 | return LoginPasswordPage(nickName: map["nickName"],mobile: map["mobile"]); 77 | } 78 | ); 79 | }else if(name == RouterPageName.SearchResultPage){ 80 | return MaterialPageRoute( 81 | settings: RouteSettings(name: name), 82 | builder: (context) { 83 | return SearchResultPage(searchWord: settings.arguments); 84 | } 85 | ); 86 | } 87 | 88 | }else{ 89 | 90 | bool fullscreenDialog = false; 91 | if(name == RouterPageName.MusicListPage || name == RouterPageName.LoginPage){ 92 | fullscreenDialog = true; 93 | } 94 | final Route route = 95 | MaterialPageRoute( 96 | settings: RouteSettings(name: name), 97 | builder: (context) => pageContentBuilder(context), 98 | fullscreenDialog: fullscreenDialog 99 | ); 100 | return route; 101 | } 102 | } 103 | 104 | return null; 105 | }; 106 | } 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /lib/pages/music_play_media_page/music_play_info_widget.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_music/common/music_store.dart'; 4 | 5 | import 'package:flutter_music/common/screen_adapter.dart'; 6 | import 'package:flutter_music/http_request/music_api.dart'; 7 | import 'package:flutter_music/pages/music_play_media_page/animation/music_translation_animation.dart'; 8 | import 'package:flutter_music/pages/music_play_media_page/music_paly_coverimage_widget.dart'; 9 | import 'dart:ui'; 10 | 11 | 12 | class MusicPlayInfoWidget extends StatelessWidget { 13 | 14 | MusicPlayInfoWidget({ 15 | Key key, 16 | 17 | 18 | this.translationAnimation, 19 | 20 | this.rotationAnimationController, 21 | this.pageController 22 | 23 | }) : super (key : key); 24 | 25 | 26 | 27 | 28 | final AnimationController rotationAnimationController; 29 | 30 | final AnimationController translationAnimation; 31 | 32 | var _playerNameTop = 35.0; 33 | var _playerArtistTop = 5.0; 34 | var _playCovermarginTop = 20.0; 35 | 36 | 37 | final PageController pageController; 38 | 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | ScreenAdapter.init(context); 43 | 44 | List _tracks =MusicStore.MusicPlayList(context).currentPlayList; 45 | 46 | double _containerHeight = ScreenAdapter.getScreenWidth()/3.0 47 | + _playCovermarginTop *2 48 | + _playerNameTop 49 | + _playerArtistTop 50 | + 20 51 | + 10; 52 | 53 | return Container( 54 | 55 | height: _containerHeight, 56 | 57 | child: PageView.builder( 58 | onPageChanged: (index){ 59 | 60 | if(index < MusicStore.MusicPlayList(context).currentIndex){ 61 | MusicStore.MusicPlayList(context).music_control_previous(); 62 | 63 | }else if(index > MusicStore.MusicPlayList(context).currentIndex){ 64 | MusicStore.MusicPlayList(context).music_control_next(); 65 | } 66 | 67 | }, 68 | controller: pageController, 69 | itemCount: _tracks.length, 70 | itemBuilder: (BuildContext context, int index){ 71 | TrackItemModel itemModel = _tracks[index]; 72 | return _pageItem(itemModel.name,itemModel.arList.first.name,itemModel.al.picUrl); 73 | }), 74 | ); 75 | 76 | } 77 | 78 | Widget _pageItem(name,artist,coverImageUrl){ 79 | return Builder( 80 | builder: (context){ 81 | return Column( 82 | children: [ 83 | 84 | _playCover(context,coverImageUrl), 85 | _playerName(context,name), 86 | _playerArtist(context,artist) 87 | ], 88 | ); 89 | }, 90 | ); 91 | } 92 | 93 | Widget _playerName(context,name){ 94 | 95 | return Row( 96 | children: [ 97 | Expanded( 98 | flex: 1, 99 | child: Padding( 100 | padding: EdgeInsets.only(left: 20,right: 20), 101 | child: MusicTranslationAnimation( 102 | animationController: translationAnimation, 103 | begin: 0, 104 | end: _playerNameTop, 105 | child: Text( 106 | name, 107 | maxLines: 1, 108 | textAlign: TextAlign.center, 109 | overflow: TextOverflow.ellipsis, 110 | style: TextStyle(color: MusicStore.Theme(context).titleColor,fontSize: 17,fontWeight: FontWeight.w600), 111 | ), 112 | ), 113 | ), 114 | ) 115 | ], 116 | ); 117 | } 118 | Widget _playerArtist(context,artist){ 119 | return Padding( 120 | padding: EdgeInsets.only(top: _playerArtistTop), 121 | child: Text(artist, 122 | style: TextStyle(color: MusicStore.Theme(context).subtTitleColor,fontSize: 12), 123 | ), 124 | ); 125 | } 126 | Widget _playCover(context,coverImageUrl){ 127 | double _width = ScreenAdapter.getScreenWidth()/3.0; 128 | 129 | return MusicPlayCoverimageWidget( 130 | 131 | width: _width, 132 | coverImageUrl: coverImageUrl, 133 | marginTop: _playCovermarginTop, 134 | animationController: rotationAnimationController, 135 | ); 136 | 137 | } 138 | } 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /lib/pages/recommend_page/recommend_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/models/play_list_model.dart'; 3 | import 'package:flutter_music/models/song_list_model.dart'; 4 | import 'package:flutter_music/base_music/music_app_bar.dart'; 5 | import 'package:flutter_music/common/music_store.dart'; 6 | import 'package:flutter_music/pages/recommend_page/recomment_item_widget.dart'; 7 | import 'package:flutter_music/pages/recommend_page/recomment_more_item_widget.dart'; 8 | import 'package:flutter_music/http_request/music_api.dart'; 9 | import 'package:flutter_music/public_widget/music_title_widget.dart'; 10 | import 'package:flutter_music/public_widget/music_gestureDetector.dart'; 11 | import 'package:flutter_music/pages/album_page/album_page.dart'; 12 | 13 | class CommendPage extends StatefulWidget { 14 | @override 15 | _CommendPageState createState() => _CommendPageState(); 16 | } 17 | 18 | class _CommendPageState extends State with AutomaticKeepAliveClientMixin { 19 | 20 | @override 21 | // TODO: implement wantKeepAlive 22 | bool get wantKeepAlive => true; 23 | 24 | Future _recommendNewSongFuture = MusicApi.recommendNewSongList(); 25 | 26 | Future _choicenessSongFuture = MusicApi.choicenessSongList(); 27 | 28 | Future _recommendMoreFuture = MusicApi.recommendMoreSongList(); 29 | 30 | @override 31 | void initState() { 32 | // TODO: implement initState 33 | super.initState(); 34 | 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | 40 | bool isLogin = MusicStore.User(context).isLogin; 41 | 42 | return MusicScaffold( 43 | showFloatingActionButton: false, 44 | appBar: MusicAppBar( 45 | title: "推荐", 46 | rightImageURL: isLogin == true ? MusicStore.User(context).user.avatarUrl : null, 47 | rightIconData: isLogin == true ? null : Icons.person_pin, 48 | rightOnTap: (){ 49 | if(isLogin == false){ 50 | Navigator.of(context).pushNamed(RouterPageName.LoginPage); 51 | }else{ 52 | Navigator.of(context).pushNamed(RouterPageName.PersonPage); 53 | } 54 | 55 | }, 56 | ), 57 | 58 | body: ListView( 59 | children: [ 60 | _lastSongList("最新歌单",_recommendNewSongFuture), 61 | _lastSongList("精选歌单",_choicenessSongFuture), 62 | MusicTitleWidget( 63 | title: "更多", 64 | padding: EdgeInsets.fromLTRB(20, 20, 20, 0), 65 | ), 66 | _moreListView(context) 67 | 68 | ], 69 | ), 70 | 71 | ); 72 | } 73 | 74 | /// 最新歌单 精选歌单 75 | Widget _lastSongList(String title,Future future){ 76 | 77 | return FutureBuilderWidget>( 78 | future:future, 79 | activityIndicator: Text(""), 80 | successBuilder: (BuildContext context,AsyncSnapshot> snapshot){ 81 | 82 | 83 | return CommentItemWidget( 84 | title: "$title", 85 | imageUrl1: snapshot.data.first.coverImgUrl, 86 | imageUrl2: snapshot.data.last.coverImgUrl, 87 | leftOnTap: (){ 88 | _pushAlbumPage(snapshot.data.first.id); 89 | }, 90 | rightOnTap: (){ 91 | _pushAlbumPage(snapshot.data.last.id); 92 | }, 93 | ); 94 | }, 95 | ); 96 | } 97 | 98 | 99 | /// 更多列表 100 | Widget _moreListView(BuildContext context){ 101 | return FutureBuilderWidget>( 102 | future: _recommendMoreFuture, 103 | 104 | successBuilder: (BuildContext context,AsyncSnapshot> snapshot){ 105 | 106 | return Column( 107 | children: snapshot.data.map((value){ 108 | 109 | return MusicGestureDetector( 110 | child: CommentMoreItemWidget(title: value.name,), 111 | onTap: (){ 112 | _pushAlbumPage(value.id); 113 | }, 114 | ); 115 | 116 | 117 | 118 | }).toList(), 119 | ); 120 | }, 121 | ); 122 | } 123 | 124 | void _pushAlbumPage(id){ 125 | Navigator.push(context, MaterialPageRoute( 126 | builder: (context){ 127 | return AlbumPage(id:id); 128 | } 129 | )); 130 | } 131 | 132 | 133 | } 134 | -------------------------------------------------------------------------------- /lib/common/music_global.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:convert'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'package:flutter_music/http_request/http_request_manager.dart'; 6 | 7 | import 'package:shared_preferences/shared_preferences.dart'; 8 | 9 | import 'package:flutter_music/models/user_model.dart'; 10 | export 'package:flutter_music/routers/router_page_name.dart'; 11 | 12 | const _musicLightColor = Color.fromRGBO(241, 243, 246, 1.0); 13 | 14 | const _lightMusicTextColor = Color.fromRGBO(92, 122, 170, 1.0); 15 | const _darkMusicTextColor = Color.fromRGBO(179, 217, 226, 1.0); 16 | 17 | const _musicSubTitleColor = Color.fromRGBO(162, 173, 190, 1.0); 18 | 19 | const _lightMusicShadowColor = Color.fromRGBO(225, 234, 242, 1.0); 20 | 21 | const _darMusicShadowColor = Color.fromRGBO(38, 43, 50, 1.0); 22 | 23 | const _lightTopShadowColor = Color.fromRGBO(255, 255, 255, 1.0); 24 | 25 | const _darkTopShadowColor = Color.fromRGBO(54, 63, 69, 1.0); 26 | 27 | const _musicGoldenColor = Color.fromRGBO(170, 147, 92, 1.0); 28 | 29 | const _musicDarkColor = Color.fromRGBO(44, 53, 60, 1.0); 30 | 31 | const _musicThemes = [ 32 | 33 | _musicLightColor, //白天模式 34 | 35 | _musicDarkColor //暗黑模式 36 | 37 | ]; 38 | 39 | 40 | class MusicGlobal { 41 | 42 | static const String LOGIN_USERINFO_KEY = "login_userinfo_key"; 43 | static const String THEME_COLOR_KEY ="theme_color_key"; 44 | static const String VIBRATE_KEY = "vibrate_key"; 45 | 46 | 47 | static Color get light => _musicLightColor; 48 | 49 | static Color get dark => _musicDarkColor; 50 | 51 | 52 | static bool isVibrate = true; 53 | 54 | static Color theme = light; 55 | 56 | //文本、icon 颜色 57 | static Color get lightTitleColor => _lightMusicTextColor; 58 | 59 | static Color get darkTitleColor => _darkMusicTextColor; 60 | 61 | //副标题 62 | static Color get subTitleColor => _musicSubTitleColor; 63 | 64 | //阴影颜色 65 | static Color get lightBottomShadowColor => _lightMusicShadowColor; 66 | 67 | static Color get lightTopShadowColor => _lightTopShadowColor; 68 | 69 | static Color get darkTopShadowColor => _darkTopShadowColor; 70 | 71 | static Color get darkBottomShadowColor => _darMusicShadowColor; 72 | 73 | 74 | //金色 75 | static Color get goldenColor => _musicGoldenColor; 76 | 77 | 78 | //可选主题列表 79 | static List get themes => _musicThemes; 80 | 81 | 82 | static SharedPreferences _userPreferences; 83 | 84 | static UserModel userModel; 85 | 86 | 87 | static Future init() async { 88 | 89 | 90 | _userPreferences = await SharedPreferences.getInstance(); 91 | 92 | var userInfo = _userPreferences.getString(LOGIN_USERINFO_KEY); 93 | 94 | 95 | /// step1 读取用户信息 96 | if(userInfo != null){ 97 | 98 | Map result = json.decode(userInfo); 99 | 100 | userModel = UserModel.fromJson(result); 101 | 102 | HttpRequestManager.instance.registerPublicParams( 103 | {"uid":userModel.userId,"token":userModel.token} 104 | ); 105 | } 106 | String themeInfo = _userPreferences.getString(THEME_COLOR_KEY); 107 | 108 | /// step2 读取主题信息 109 | if(themeInfo != null){ 110 | print(themeInfo); 111 | theme = themeInfo == "light" ? light : dark; 112 | } 113 | 114 | 115 | /// stpe3 是否有触摸反馈 116 | bool _isVibrate = _userPreferences.getBool(VIBRATE_KEY); 117 | if(_isVibrate != null){ 118 | isVibrate = _isVibrate; 119 | } 120 | 121 | } 122 | /// 保存主题信息 123 | static saveTheme(Color color){ 124 | _userPreferences.setString(THEME_COLOR_KEY, color == light ? "light" : "dark"); 125 | } 126 | 127 | /// 保存用户信息 128 | static saveUserInfo(UserModel userModel) { 129 | 130 | HttpRequestManager.instance.registerPublicParams( 131 | {"uid":userModel.userId,"token":userModel.token} 132 | ); 133 | 134 | _userPreferences.setString(LOGIN_USERINFO_KEY, userModel.toJson()).whenComplete((){ 135 | 136 | }); 137 | 138 | } 139 | /// 保存震动反馈信息 140 | 141 | static saveVibrateInfo(bool vibrate){ 142 | 143 | 144 | isVibrate = vibrate; 145 | _userPreferences.setBool(VIBRATE_KEY, vibrate).whenComplete((){ 146 | 147 | }); 148 | } 149 | 150 | /// 退出登录 151 | static logout(){ 152 | userModel = null; 153 | _userPreferences.remove(LOGIN_USERINFO_KEY); 154 | } 155 | 156 | 157 | 158 | } -------------------------------------------------------------------------------- /lib/public_widget/music_item_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/common/music_store.dart'; 3 | import 'package:cached_network_image/cached_network_image.dart'; 4 | 5 | import 'package:flutter_vibrate/flutter_vibrate.dart'; 6 | import 'package:flutter_music/public_widget/music_button.dart'; 7 | 8 | typedef GestureTapCallback = void Function(int index); 9 | 10 | class MusicItemWidget extends StatelessWidget { 11 | MusicItemWidget( 12 | {Key key, 13 | this.index, 14 | this.id, 15 | this.title, 16 | this.subtTitle, 17 | this.coverImageUrl, 18 | this.onTap}) 19 | : super(key: key); 20 | 21 | final String title; 22 | final String subtTitle; 23 | final String coverImageUrl; 24 | 25 | final int id; 26 | final int index; 27 | 28 | final GestureTapCallback onTap; 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | ScreenAdapter.init(context); 33 | return GestureDetector( 34 | onTap: () { 35 | Vibrate.feedback(FeedbackType.selection); 36 | onTap(index); 37 | }, 38 | child: _musicItem(context)); 39 | } 40 | 41 | Widget _musicItem(context) { 42 | MusicGlobalPlayListState musicGlobalPlayListState = MusicStore.MusicPlayList(context); 43 | 44 | 45 | bool _selected = false; 46 | 47 | if(MusicStore.MusicPlayList(context).currentPlayList.length > 0){ 48 | _selected = musicGlobalPlayListState.currentTrackItem.id == id; 49 | } 50 | 51 | return Container( 52 | margin: EdgeInsets.only(left: 20, right: 20, top: 10, bottom: 20), 53 | decoration: 54 | BoxDecoration( 55 | color: MusicStore.Theme(context).theme, 56 | boxShadow: _selected == false ? null : MusicStore.boxShow(context, -10,10) 57 | 58 | ), 59 | child: Row( 60 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 61 | children: [ 62 | _itemCover(context,_selected), 63 | Expanded( 64 | flex: 1, 65 | child: _itemTitle(context), 66 | ), 67 | MusicButton( 68 | showLayer: !_selected, 69 | normalIconData: Icons.play_arrow, 70 | isEnable: false, 71 | padding: EdgeInsets.fromLTRB(15, 15, 15, 15), 72 | ) 73 | // _itemPlay(context) 74 | ], 75 | ), 76 | ); 77 | } 78 | 79 | Widget _itemCover(context,selected) { 80 | 81 | 82 | String _coverImageURL = coverImageUrl + "?param="+"120"+"y"+"120"; 83 | 84 | double margin = selected == false ? 0 : 5; 85 | 86 | return Container( 87 | width: ScreenAdapter.setWidth(selected==true ? 100 : 120), 88 | height: ScreenAdapter.setHeight(selected==true ? 100 : 120), 89 | margin: EdgeInsets.fromLTRB(margin, margin, margin, margin), 90 | padding: EdgeInsets.fromLTRB(2, 2, 2, 2), 91 | decoration: BoxDecoration( 92 | borderRadius: BorderRadius.circular(10), 93 | color: MusicStore.Theme(context).topShadowColor, 94 | boxShadow: MusicStore.boxShow(context, -10, 10)), 95 | child: ClipRRect( 96 | borderRadius: BorderRadius.circular(10), 97 | child: CachedNetworkImage( 98 | imageUrl: "$_coverImageURL", 99 | fit: BoxFit.cover, 100 | ))); 101 | } 102 | 103 | Widget _itemTitle(context) { 104 | return Padding( 105 | padding: EdgeInsets.only(left: 20, right: 10), 106 | child: Column( 107 | mainAxisAlignment: MainAxisAlignment.center, 108 | crossAxisAlignment: CrossAxisAlignment.start, 109 | children: [ 110 | Text( 111 | "$title", 112 | maxLines: 2, 113 | style: TextStyle( 114 | fontSize: 20, 115 | fontWeight: FontWeight.w500, 116 | color: MusicStore.Theme(context).titleColor), 117 | overflow: TextOverflow.ellipsis, 118 | ), 119 | Padding( 120 | padding: EdgeInsets.only(top: 3, right: 60), 121 | child: Text( 122 | "$subtTitle", 123 | maxLines: 1, 124 | overflow: TextOverflow.ellipsis, 125 | style: TextStyle( 126 | fontSize: 10, 127 | color: MusicStore.Theme(context).subtTitleColor), 128 | )) 129 | ], 130 | ), 131 | ); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /lib/public_widget/music_activityIndicator.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:math' as math; 6 | 7 | import 'package:flutter/widgets.dart'; 8 | 9 | 10 | 11 | const double _kDefaultIndicatorRadius = 10.0; 12 | 13 | 14 | 15 | /// An iOS-style activity indicator that spins clockwise. 16 | /// 17 | /// See also: 18 | /// 19 | /// * 20 | class MusicActivityIndicator extends StatefulWidget { 21 | /// Creates an iOS-style activity indicator that spins clockwise. 22 | const MusicActivityIndicator({ 23 | Key key, 24 | this.color = const Color.fromRGBO(0, 0, 0, 1.0), 25 | this.animating = true, 26 | this.radius = _kDefaultIndicatorRadius, 27 | }) : assert(animating != null), 28 | assert(radius != null), 29 | assert(radius > 0), 30 | super(key: key); 31 | 32 | /// Whether the activity indicator is running its animation. 33 | /// 34 | /// Defaults to true. 35 | final bool animating; 36 | 37 | /// Radius of the spinner widget. 38 | /// 39 | /// Defaults to 10px. Must be positive and cannot be null. 40 | final double radius; 41 | 42 | final Color color; 43 | 44 | 45 | @override 46 | _MusicActivityIndicatorState createState() => _MusicActivityIndicatorState(); 47 | } 48 | 49 | 50 | class _MusicActivityIndicatorState extends State with SingleTickerProviderStateMixin { 51 | AnimationController _controller; 52 | 53 | @override 54 | void initState() { 55 | super.initState(); 56 | _controller = AnimationController( 57 | duration: const Duration(seconds: 1), 58 | vsync: this, 59 | ); 60 | 61 | if (widget.animating) 62 | _controller.repeat(); 63 | } 64 | 65 | @override 66 | void didUpdateWidget(MusicActivityIndicator oldWidget) { 67 | super.didUpdateWidget(oldWidget); 68 | if (widget.animating != oldWidget.animating) { 69 | if (widget.animating) 70 | _controller.repeat(); 71 | else 72 | _controller.stop(); 73 | } 74 | } 75 | 76 | @override 77 | void dispose() { 78 | _controller.dispose(); 79 | super.dispose(); 80 | } 81 | 82 | @override 83 | Widget build(BuildContext context) { 84 | return SizedBox( 85 | height: widget.radius * 2, 86 | width: widget.radius * 2, 87 | child: CustomPaint( 88 | painter: _CupertinoActivityIndicatorPainter( 89 | position: _controller, 90 | activeColor: widget.color, 91 | radius: widget.radius, 92 | ), 93 | ), 94 | ); 95 | } 96 | } 97 | 98 | const double _kTwoPI = math.pi * 2.0; 99 | const int _kTickCount = 12; 100 | 101 | // Alpha values extracted from the native component (for both dark and light mode). 102 | // The list has a length of 12. 103 | const List _alphaValues = [147, 131, 114, 97, 81, 64, 47, 47, 47, 47, 47, 47]; 104 | 105 | class _CupertinoActivityIndicatorPainter extends CustomPainter { 106 | _CupertinoActivityIndicatorPainter({ 107 | @required this.position, 108 | @required this.activeColor, 109 | double radius, 110 | }) : tickFundamentalRRect = RRect.fromLTRBXY( 111 | -radius, 112 | radius / _kDefaultIndicatorRadius, 113 | -radius / 2.0, 114 | -radius / _kDefaultIndicatorRadius, 115 | radius / _kDefaultIndicatorRadius, 116 | radius / _kDefaultIndicatorRadius, 117 | ), 118 | super(repaint: position); 119 | 120 | final Animation position; 121 | final RRect tickFundamentalRRect; 122 | final Color activeColor; 123 | 124 | @override 125 | void paint(Canvas canvas, Size size) { 126 | final Paint paint = Paint(); 127 | 128 | canvas.save(); 129 | canvas.translate(size.width / 2.0, size.height / 2.0); 130 | 131 | final int activeTick = (_kTickCount * position.value).floor(); 132 | 133 | for (int i = 0; i < _kTickCount; ++ i) { 134 | final int t = (i + activeTick) % _kTickCount; 135 | paint.color = activeColor.withAlpha(_alphaValues[t]); 136 | canvas.drawRRect(tickFundamentalRRect, paint); 137 | canvas.rotate(-_kTwoPI / _kTickCount); 138 | } 139 | 140 | canvas.restore(); 141 | } 142 | 143 | @override 144 | bool shouldRepaint(_CupertinoActivityIndicatorPainter oldPainter) { 145 | return oldPainter.position != position || oldPainter.activeColor != activeColor; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /lib/tabbar/bottom_tabbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:math' as math; 3 | import 'package:flutter_music/common/music_store.dart'; 4 | import 'package:flutter_music/tabbar/tabbar_items/music_tab_item.dart'; 5 | class BottomTabar extends StatefulWidget { 6 | 7 | BottomTabar({Key key,this.currentIndex,this.onTap}) : super(key:key); 8 | 9 | int currentIndex; 10 | 11 | final ValueChanged onTap; 12 | 13 | @override 14 | _BottomTabarState createState() => _BottomTabarState(); 15 | } 16 | 17 | class _BottomTabarState extends State with TickerProviderStateMixin { 18 | 19 | 20 | var _itemList = [ 21 | { 22 | "title":"资料库", 23 | "iconData":Icons.format_list_bulleted, 24 | }, 25 | { 26 | "title":"推荐", 27 | "iconData":Icons.favorite, 28 | }, 29 | { 30 | "title":"浏览", 31 | "iconData":Icons.music_note, 32 | } 33 | ]; 34 | 35 | 36 | 37 | List _animationControllers = []; 38 | 39 | List _animations = []; 40 | 41 | 42 | @override 43 | void initState() { 44 | // TODO: implement initState 45 | super.initState(); 46 | _resetState(); 47 | 48 | } 49 | 50 | void _resetState() { 51 | for (AnimationController controller in _animationControllers){ 52 | controller.dispose(); 53 | } 54 | _animationControllers = List.generate(_itemList.length, (int index){ 55 | 56 | return AnimationController( 57 | duration: Duration(milliseconds: 300), 58 | vsync: this 59 | )..addListener(_rebuild); 60 | 61 | }); 62 | 63 | _animationControllers[widget.currentIndex].value = 1.0; 64 | 65 | _animations = List.generate(_itemList.length, (int index){ 66 | return Tween(begin: 0.0,end: 60.0).animate(_animationControllers[index]); 67 | }); 68 | 69 | 70 | 71 | } 72 | @override 73 | void didUpdateWidget(BottomTabar oldWidget) { 74 | // TODO: implement didUpdateWidget 75 | super.didUpdateWidget(oldWidget); 76 | 77 | 78 | if(oldWidget.currentIndex != widget.currentIndex){ 79 | 80 | _animationControllers[oldWidget.currentIndex].reverse(); 81 | 82 | _animationControllers[widget.currentIndex].forward(); 83 | } 84 | 85 | } 86 | 87 | 88 | List _createMusicTableItem(){ 89 | 90 | ColorTween colorTween = ColorTween( 91 | begin: MusicStore.Theme(context).tabItemNormalColor, 92 | end: MusicStore.Theme(context).tabItemSelectedColor 93 | ); 94 | 95 | List _list = List.generate(_itemList.length, (int index){ 96 | 97 | return MusicTabItem( 98 | index: index, 99 | animation: _animations[index], 100 | 101 | animationController: _animationControllers[index], 102 | title: _itemList[index]["title"], 103 | iconData: _itemList[index]["iconData"], 104 | normalColor: MusicStore.Theme(context).tabItemNormalColor, 105 | selectedColor:MusicStore.Theme(context).tabItemSelectedColor, 106 | colorTween: colorTween, 107 | onTap: (index){ 108 | if (widget.onTap != null) 109 | widget.onTap(index); 110 | }, 111 | ); 112 | }); 113 | return _list; 114 | } 115 | 116 | @override 117 | Widget build(BuildContext context) { 118 | 119 | final double additionalBottomPadding = math.max(MediaQuery.of(context).padding.bottom, 0.0); 120 | 121 | return Material( 122 | color: MusicStore.Theme(context).theme, 123 | child: ConstrainedBox( 124 | 125 | constraints:BoxConstraints(minHeight: kBottomNavigationBarHeight+additionalBottomPadding), 126 | child: Padding( 127 | padding: EdgeInsets.only(bottom: additionalBottomPadding), 128 | child: MediaQuery.removePadding( 129 | context: context, 130 | removeBottom: true, 131 | child: Row( 132 | mainAxisAlignment: MainAxisAlignment.spaceAround, 133 | children: _createMusicTableItem() 134 | ) 135 | ), 136 | ), 137 | 138 | ), 139 | ); 140 | } 141 | 142 | @override 143 | void dispose() { 144 | // TODO: implement dispose 145 | for (AnimationController controller in _animationControllers){ 146 | controller.dispose(); 147 | } 148 | 149 | super.dispose(); 150 | 151 | } 152 | void _rebuild() { 153 | setState(() { 154 | 155 | }); 156 | } 157 | 158 | } 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /lib/pages/browse_page/browse_banner_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_swiper/flutter_swiper.dart'; 3 | import 'package:flutter_music/common/music_store.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:cached_network_image/cached_network_image.dart'; 6 | import 'package:flutter_music/http_request/music_api.dart'; 7 | class BanerScrollState extends ChangeNotifier{ 8 | int currentIndex = 0; 9 | void setIndex(index){ 10 | currentIndex = index; 11 | notifyListeners(); 12 | } 13 | } 14 | 15 | class BrowseBannerWidget extends StatelessWidget { 16 | 17 | final Future _future = MusicApi.choicenessSongList(order: "hot",limit: 4); 18 | 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | 23 | return FutureBuilderWidget>( 24 | future: _future, 25 | 26 | successBuilder: (BuildContext context, AsyncSnapshot> snapshot){ 27 | 28 | return _banner(snapshot.data); 29 | }, 30 | ); 31 | 32 | 33 | } 34 | Widget _banner(List list){ 35 | 36 | return MultiProvider( 37 | providers: [ 38 | ChangeNotifierProvider(create: (_)=>BanerScrollState()) 39 | ], 40 | 41 | child: 42 | 43 | Consumer( 44 | builder: (context,state,_){ 45 | return Column( 46 | children: [ 47 | _swiperList(list), 48 | _swiperControlList(list) 49 | ], 50 | ); 51 | 52 | }, 53 | ) 54 | ); 55 | } 56 | 57 | ///轮播图下面的control 58 | Widget _swiperControlList(List list){ 59 | 60 | List _listWidget = List.generate(list.length , (int index){ 61 | 62 | return _swiperControlItem(index); 63 | 64 | }); 65 | 66 | return Padding( 67 | padding: EdgeInsets.only(top: 20), 68 | child: Row( 69 | mainAxisAlignment: MainAxisAlignment.center, 70 | children: _listWidget 71 | ), 72 | ); 73 | } 74 | 75 | ///每一个control 76 | 77 | Widget _swiperControlItem(index){ 78 | 79 | return Builder( 80 | 81 | builder: (context){ 82 | 83 | return Container( 84 | width: Provider.of(context).currentIndex == index ? 30 : 10, 85 | height: Provider.of(context).currentIndex == index ? 8 : 10, 86 | margin: EdgeInsets.fromLTRB(5, 0, 5, 0), 87 | decoration: BoxDecoration( 88 | borderRadius: BorderRadius.circular(5), 89 | color:index == Provider.of(context).currentIndex ? MusicStore.Theme(context).tabItemSelectedColor: Color.fromRGBO(223, 230, 235, 1.0) 90 | ), 91 | ); 92 | }, 93 | ); 94 | } 95 | 96 | /// 轮播图 97 | Widget _swiperList(List list){ 98 | return Builder( 99 | builder: (context){ 100 | return AspectRatio( 101 | aspectRatio: 1.8, 102 | child: Container( 103 | decoration: BoxDecoration( 104 | borderRadius: BorderRadius.circular(10), 105 | color: MusicStore.Theme(context).topShadowColor, 106 | boxShadow: MusicStore.boxShow(context,-15, 10) 107 | ), 108 | margin: EdgeInsets.only(left: 20,right: 20,top: 30), 109 | padding: EdgeInsets.fromLTRB(0, 5, 0, 5), 110 | child: Swiper( 111 | 112 | duration: 1000, 113 | autoplayDelay: 5000, 114 | autoplay: true, 115 | itemCount: list.length, 116 | onIndexChanged: (index){ 117 | 118 | Provider.of(context,listen: false).setIndex(index); 119 | 120 | }, 121 | itemBuilder: (context,index){ 122 | 123 | return _swiperItem(list[index].coverImgUrl); 124 | }, 125 | 126 | ) 127 | ), 128 | ); 129 | }, 130 | ); 131 | } 132 | 133 | 134 | Widget _swiperItem(imageUrl){ 135 | 136 | return Container( 137 | margin: EdgeInsets.fromLTRB(5, 0, 5, 0), 138 | decoration: BoxDecoration( 139 | borderRadius: BorderRadius.circular(8), 140 | 141 | ), 142 | child: ClipRRect( 143 | borderRadius: BorderRadius.circular(8), 144 | child: CachedNetworkImage( 145 | imageUrl: imageUrl, 146 | fit: BoxFit.cover, 147 | ) 148 | ), 149 | 150 | ); 151 | } 152 | } 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /lib/public_widget/music_submit_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter_music/common/music_store.dart'; 4 | typedef GestureTapCallback = void Function(_MusicSubmitButtonState state); 5 | 6 | typedef WaitingCallback = void Function(); 7 | 8 | typedef SuccessCallback = void Function(T); 9 | 10 | typedef FiledCallback = void Function(Object error); 11 | 12 | 13 | class MusicSubmitButton extends StatefulWidget { 14 | MusicSubmitButton({ 15 | Key key, 16 | this.isEnable : true, 17 | this.onTap, 18 | this.title, 19 | this.margin: const EdgeInsets.fromLTRB(0, 60, 0, 0), 20 | this.loadingText: "正在加载...", 21 | this.waitingCallback, 22 | this.successCallback, 23 | this.filedCallback, 24 | this.backGroundColor, 25 | this.titleStyle, 26 | this.loadingStyle 27 | 28 | }): super(key :key); 29 | 30 | final GestureTapCallback onTap; 31 | final String title; 32 | final EdgeInsets margin; 33 | final String loadingText; 34 | 35 | final WaitingCallback waitingCallback; 36 | final SuccessCallback successCallback; 37 | final FiledCallback filedCallback; 38 | final TextStyle titleStyle; 39 | final TextStyle loadingStyle; 40 | final Color backGroundColor; 41 | final bool isEnable; 42 | @override 43 | _MusicSubmitButtonState createState() => _MusicSubmitButtonState(); 44 | } 45 | 46 | class _MusicSubmitButtonState extends State> { 47 | 48 | 49 | ConnectionState connectionState = ConnectionState.none; 50 | 51 | void requestFuture(Future future){ 52 | _subscribe(future); 53 | 54 | } 55 | 56 | void _subscribe(Future future){ 57 | if(future !=null){ 58 | 59 | setState(() { 60 | connectionState = ConnectionState.waiting; 61 | }); 62 | if(widget.waitingCallback != null){ 63 | widget.waitingCallback(); 64 | } 65 | Future.delayed(Duration(milliseconds: 500), (){ 66 | future.then((T data){ 67 | 68 | if(widget.successCallback != null){ 69 | widget.successCallback(data); 70 | } 71 | 72 | setState(() { 73 | connectionState = ConnectionState.done; 74 | }); 75 | 76 | },onError: (Object error){ 77 | print(error); 78 | if(widget.filedCallback != null){ 79 | widget.filedCallback(error); 80 | } 81 | 82 | setState(() { 83 | connectionState = ConnectionState.done; 84 | }); 85 | 86 | }); 87 | 88 | }); 89 | 90 | 91 | } 92 | 93 | } 94 | 95 | 96 | Widget _activityIndicator(){ 97 | TextStyle loadingStyle = widget.loadingStyle ?? TextStyle(color: MusicStore.Theme(context).titleColor,fontWeight: FontWeight.w600,fontSize: 17); 98 | return Center( 99 | child: Row( 100 | mainAxisAlignment: MainAxisAlignment.center, 101 | children: [ 102 | CupertinoActivityIndicator( 103 | radius: 10, 104 | ), 105 | Padding( 106 | padding: EdgeInsets.only(left: 10), 107 | child: Text(widget.loadingText,style: loadingStyle,) 108 | 109 | ) 110 | ], 111 | ) 112 | ); 113 | } 114 | 115 | Widget _titleText(){ 116 | 117 | TextStyle titleStyle = widget.titleStyle ?? TextStyle(color: MusicStore.Theme(context).titleColor,fontWeight: FontWeight.w600,fontSize: 17); 118 | return Text(widget.title,textAlign: TextAlign.center, 119 | style: titleStyle,); 120 | } 121 | 122 | @override 123 | Widget build(BuildContext context) { 124 | Color backGrounColor = widget.backGroundColor ?? MusicStore.Theme(context).theme; 125 | return MusicGestureDetector( 126 | onTap: widget.isEnable == false ? null : (){ 127 | 128 | if(connectionState != ConnectionState.waiting){ 129 | widget.onTap(this); 130 | } 131 | 132 | }, 133 | child:Opacity( 134 | opacity: widget.isEnable == false ? 0.6 : 1.0, 135 | child: Container( 136 | width: double.infinity, 137 | padding: EdgeInsets.only(top: 15,bottom: 15), 138 | margin:widget.margin, 139 | child: connectionState == ConnectionState.waiting ? _activityIndicator() : _titleText(), 140 | decoration: 141 | BoxDecoration( 142 | borderRadius: BorderRadius.circular(10), 143 | color: backGrounColor, 144 | boxShadow: MusicStore.boxShow(context, -10, 10)) 145 | 146 | ), 147 | ) 148 | ); 149 | } 150 | 151 | 152 | 153 | } 154 | 155 | 156 | -------------------------------------------------------------------------------- /lib/pages/login_page/login_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/base_music/music_app_bar.dart'; 3 | import 'package:flutter_music/common/music_store.dart'; 4 | import 'package:flutter_music/http_request/music_api.dart'; 5 | import 'package:flutter_music/public_widget/music_submit_button.dart'; 6 | 7 | 8 | class LoginPage extends StatefulWidget { 9 | @override 10 | _LoginPageState createState() => _LoginPageState(); 11 | } 12 | 13 | class _LoginPageState extends State { 14 | 15 | TextEditingController editingController; 16 | bool _isEnable = false; 17 | @override 18 | void initState() { 19 | // TODO: implement initState 20 | editingController = TextEditingController(); 21 | editingController.addListener((){ 22 | _verifyMobile(editingController.text); 23 | }); 24 | super.initState(); 25 | } 26 | void dispose() { 27 | // TODO: implement dispose 28 | super.dispose(); 29 | editingController.dispose(); 30 | } 31 | 32 | void _verifyMobile(String mobile){ 33 | if(mobile.length ==0) return; 34 | 35 | if(mobile.substring(0,1) == "1" && mobile.length == 11){ 36 | setState(() { 37 | _isEnable = true; 38 | }); 39 | }else{ 40 | setState(() { 41 | _isEnable = false; 42 | }); 43 | } 44 | } 45 | @override 46 | Widget build(BuildContext context) { 47 | ScreenAdapter.init(context); 48 | return MusicScaffold( 49 | 50 | appBar: MusicAppBar( 51 | rightIconData: Icons.clear, 52 | title: "手机号登录", 53 | rightOnTap: (){ 54 | Navigator.of(context).pop(); 55 | }, 56 | ), 57 | body: Padding( 58 | 59 | padding: EdgeInsets.fromLTRB(20, 40, 20, 20), 60 | child: Column( 61 | crossAxisAlignment: CrossAxisAlignment.start, 62 | children: [ 63 | Text("请使用网易云音乐注册的手机号进行登录",style: TextStyle(color: MusicStore.Theme(context).subtTitleColor,fontSize: 12),), 64 | 65 | _mobile(context), 66 | 67 | MusicSubmitButton( 68 | 69 | isEnable: _isEnable, 70 | 71 | successCallback: (String nickName){ 72 | if(nickName == null){ 73 | FlutterFlexibleToast.showToast( 74 | message: "手机号未注册", 75 | toastLength: Toast.LENGTH_SHORT, 76 | toastGravity: ToastGravity.TOP, 77 | backgroundColor: Colors.red, 78 | icon: ICON.WARNING, 79 | timeInSeconds: 2); 80 | 81 | } else{ 82 | Navigator.of(context).pushNamed(RouterPageName.LoginPasswordPage,arguments: {"nickName":nickName,"mobile":editingController.text}); 83 | } 84 | }, 85 | 86 | filedCallback: (Object error){ 87 | 88 | }, 89 | 90 | onTap: (state){ 91 | state.requestFuture(MusicApi.existenceMobile(editingController.text)); 92 | }, 93 | loadingText: "正在验证手机号", 94 | title: "下一步", 95 | ) 96 | 97 | ], 98 | ), 99 | ) 100 | ); 101 | } 102 | 103 | Widget _mobile(context){ 104 | return Container( 105 | margin: EdgeInsets.only(top: 20), 106 | width: double.infinity, 107 | //height: ScreenAdapter.setHeight(100), 108 | decoration: BoxDecoration( 109 | 110 | border: Border( 111 | bottom: BorderSide(width: 1,color: MusicStore.Theme(context).topShadowColor) 112 | ) 113 | ), 114 | child: Row( 115 | children: [ 116 | Padding( 117 | padding: EdgeInsets.only(right: 10), 118 | child: Text("+86",style: TextStyle(fontSize: 20,color: MusicStore.Theme(context).textFieldColor),), 119 | ), 120 | Expanded( 121 | flex: 1, 122 | child: TextField( 123 | controller: editingController, 124 | 125 | autofocus: true, 126 | style: TextStyle(fontSize: 18,color: MusicStore.Theme(context).textFieldColor), 127 | keyboardType: TextInputType.number, 128 | decoration:InputDecoration( 129 | 130 | hintText: "请输入手机号", 131 | hintStyle: TextStyle(color: MusicStore.Theme(context).textFieldColor), 132 | border: InputBorder.none 133 | ) 134 | ), 135 | ) 136 | ], 137 | ), 138 | ); 139 | } 140 | } 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iMusic 2 | 3 | ### 视频演示 4 | [哔哩哔哩](https://www.bilibili.com/video/BV1X54y1D7eb) 5 | 6 | ### 页面效果预览 7 | 8 | | | | | | 9 | | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | 10 | | | | | | 11 | | | | | | 12 | 13 | 14 | 15 | ## 安装体验 16 | * Android 可以扫描下面的二维码进行下载安装,苹果因为分发限制暂时不能通过扫码的方式进行安装。如果恰好此时github不显示图片了 你可以点击[这里](http://d.6short.com/rq64)进行下载安装。因为没有绑定域名可能会有风险提示,点击继续访问就可以了。 17 | ![avatar](./imgs/download.png) 18 | 19 | 20 | 21 | ## 前言 22 | 23 | * flutter 是谷歌2017年推出的一套跨平台解决方案。目前已经支持iOS Android 和 web 端。macOs 也已经被提上了日程,而且谷歌未来的操作系统FuchsiaOS极大的可能 前端也是用flutter实现 ,flutter 野心很大,有想统一大前端的趋势。 24 | 25 | 26 | ## 运行环境 27 | * ``` Flutter 1.12.13``` 28 | * ```Dart 2.7.0``` 29 | 30 | ## 如何开始 31 | 1. ```git clone https://github.com/SHIMLY-GitHub/flutter_music.git``` 32 | 2. 部署接口地址 ```https://binaryify.github.io/NeteaseCloudMusicApi/#/?id=%e5%ae%89%e8%a3%85``` 33 | 3. 配置服务器地址 ``` lib/http_request/config.dat``` 更改baseURL 为第二步 中配置的接口地址 34 | 4. 在命令行执行```flutter run``` 默认情况下是debug模式。 如果你想获得更好的体验可以执行```flutter run --release``` release模式下会比debug模式运行更流畅 35 | 36 | ## 依赖简介 37 | * ```provider``` 状态管理 38 | * ```shared_preferences``` 存储用户信息 39 | * ```flutter_swiper``` 轮播图 40 | * ```flutter_screenutil``` 屏幕适配 41 | * ``cached_network_image`` 这个网络图片缓存框架 既有动画又有本地缓存 系统自带的要么有缓存没动画 要么有动画没缓存 42 | * ``vibrate`` 震动反馈 43 | * ```audioplayers``` 音频播放 44 | * ```flutter_flexible_toast``` 信息提示 45 | 46 | ## 对于Flutter的一些看法 47 | * 我是一个iOS开发者,刚刚学习flutter 的时候确实不太适应这种声明式UI布局的理念,但是随着不断学习的深入 我发现之前在iOS上特别复杂的布局 在Flutter上变的如此简单,当你熟悉了以后会发现你的布局效率大大的提升。在学习的过程中 也踩了不少坑,最近写了这个简单的项目 就是想给打算入坑Flutter 但是又不知道如何开始的同学一些思路,在编写代码的过程中 对于widget的命名 我尽量避免了一些iOS平台专有的控件名称比如 ```cell``` ```tableView``` (因为这样对Andoroid的同学很不友好) 在文件命名和方法命名上 我翻阅了Flutter的源代码 尽量和系统保持统一。因为功能不多,也没有复杂的业务逻辑 ,更没有复杂的布局 特别适合新手入门学习 接下来我可能会加入一些UITest 完善一下整个的开发流程 48 | 49 | * 关于Flutter的优势以及优点不在赘述,请前往Flutter官网自行查看,我主要说一下Flutter目前存在一些不足之处 50 | 51 | 1. 热重载 是Flutter特别推崇的一个功能,但是在实际开发中 当你的布局改动特别大 或者有新建文件的时候 热重载就会失效,我是用的AndroidStudio 有时候莫名其妙的热重载就不好使了 只能重启,重新编译,但是Flutter在iOS平台上重新编译一次时间还不短。 52 | 3. AndroidStudio有个Bug Flutter中的bulid 方法莫名其妙的会多执行一次 。 53 | 54 | 3. 关于Flutter中的回调地狱,其实我并不想把这个作为不足之处列出来,因为在我看来,这并不是什么特别大的问题 但是大多数人都特别排斥这种操作,所以我就简单说一下,Flutter上有一个库[nested ](https://pub.dev/packages/nested)还有我个人写了一个工具类[如何优雅的处理flutter中层级其嵌套的问题](https://github.com/SHIMLY-GitHub/flutter_widget_extension)倒是可以解决一些回调地狱的问题,但是我觉得可以通过封装``widget`` 提取``widget`` 来减少层级套用,如果你写Object-C代码 手写布局的话 你都写一个方法里 不封装 ,不抽取, 不加注释,其实这样的代码 看起来并不一定比回调地狱好多少。回调地狱的存在恰好可以让你审视自己写的代码 是否可读性强 是否易于维护。 55 | 56 | * 关于 Flutter 在iOS平台的表现能力,虽然Flutter绕过了js桥接带来的性能损耗 直接使用对应平台的GPU进行渲染,其性能接近原生。就实际来看 也确实如此,不过在iOS平台 实话实说 总感觉跟原生还有有点差距,但这并不是说Flutter的性能比原生要差很多,更多的原因可能是因为iOS平台的动画效果 相对更加流畅,所以给人感觉原生效果似乎性能更好。我知道的商业项目大规模应用Flutter 有阿里旗下的闲鱼,还有京东旗下的芬香 但是闲鱼使用的Flutter 引擎是 经过改造,优化以后的 ,并不是github上开源的,至于芬香 一直听说京东有关于Flutter有深度的定制化 不知道京东用的是不是github上开源的。但是 不管怎样这两款App都很有参考意义。 57 | 58 | * 最后关于Flutter的发展 从目前社区活跃度以及中国互联网公司对Flutter的支持来看 大有劲头,Flutter是不是前端的未来 现在不好说 ,但是前端编程大统一 一定是未来,对于这种声明式UI 个人比较看好,因为很多在命令式UI上特别复杂的布局 会变的非常简单。而且谷歌在打造自己的新一代操作系统``Fuchsia ``前端也是使用Flutter。据 坊间传闻,这个操作系统还有可能取代Android 如果这样发展的话Flutter将大有可为。至于swiftUI 他跟Flutter很像,不过目前还不成熟 社区支持也不完善, 但是以苹果技术社区的力量,以及中国互联网公司KPI的诱惑,一旦swiftUI 成熟起来,相关的配套库就会像雨后春笋一样崛起,如果swiftUI 在将来也可以跨平台了 对于开发者和一些中小型公司来说 真的是善莫大焉。总之 不管是Flutter 还是 swiftUI 未来可期。 59 | 60 | 61 | 62 | 63 | ​ 64 | 65 | ## 最后感谢UI设计师和接口开发团队 66 | 1. [無我](https://www.ui.cn/detail/518851.html) 67 | 2. [网易云api](https://github.com/Binaryify/NeteaseCloudMusicApi) 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /lib/pages/login_page/login_password_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_music/base_music/music_app_bar.dart'; 3 | import 'package:flutter_music/common/music_store.dart'; 4 | import 'package:flutter_music/http_request/music_api.dart'; 5 | import 'package:flutter_music/models/user_model.dart'; 6 | import 'package:flutter_music/public_widget/music_submit_button.dart'; 7 | 8 | 9 | class LoginPasswordPage extends StatefulWidget { 10 | LoginPasswordPage({ 11 | Key key, 12 | this.nickName, 13 | this.mobile 14 | 15 | }): super(key:key); 16 | 17 | final String nickName; 18 | final String mobile; 19 | 20 | @override 21 | _LoginPasswordPageState createState() => _LoginPasswordPageState(); 22 | } 23 | 24 | class _LoginPasswordPageState extends State { 25 | 26 | 27 | TextEditingController editingController; 28 | 29 | bool _isEnable = false; 30 | @override 31 | void initState() { 32 | // TODO: implement initState 33 | 34 | 35 | super.initState(); 36 | editingController = TextEditingController(); 37 | 38 | editingController.addListener((){ 39 | if(editingController.text.length > 0 && _isEnable == false){ 40 | setState(() { 41 | _isEnable = true; 42 | }); 43 | } 44 | if(editingController.text.length == 0){ 45 | setState(() { 46 | _isEnable = false; 47 | }); 48 | } 49 | }); 50 | } 51 | 52 | @override 53 | void dispose() { 54 | // TODO: implement dispose 55 | super.dispose(); 56 | editingController.dispose(); 57 | } 58 | @override 59 | Widget build(BuildContext context) { 60 | return MusicScaffold( 61 | appBar: MusicAppBar( 62 | title: "手机号登录", 63 | rightIconData: Icons.chevron_left, 64 | rightOnTap: (){ 65 | Navigator.of(context).pop(); 66 | }, 67 | ), 68 | body: Padding( 69 | padding: EdgeInsets.fromLTRB(20, 40, 20, 20), 70 | child: Column( 71 | crossAxisAlignment: CrossAxisAlignment.start, 72 | children: [ 73 | Text(widget.nickName + " 你好",style: TextStyle(color: MusicStore.Theme(context).subtTitleColor,fontSize: 12),), 74 | 75 | _password(context), 76 | MusicSubmitButton( 77 | isEnable: _isEnable, 78 | 79 | successCallback: (userModel){ 80 | 81 | if(userModel == null){ 82 | FlutterFlexibleToast.showToast( 83 | message: "密码错误", 84 | toastLength: Toast.LENGTH_SHORT, 85 | toastGravity: ToastGravity.TOP, 86 | backgroundColor: Colors.red, 87 | icon: ICON.WARNING, 88 | timeInSeconds: 2); 89 | }else{ 90 | FlutterFlexibleToast.showToast( 91 | message: "欢迎来到iMusic", 92 | toastLength: Toast.LENGTH_SHORT, 93 | toastGravity: ToastGravity.CENTER, 94 | backgroundColor: Colors.greenAccent, 95 | icon: ICON.SUCCESS, 96 | timeInSeconds: 5); 97 | 98 | MusicStore.User(context).setUser(userModel); 99 | 100 | Navigator.of(context).popUntil(ModalRoute.withName(RouterPageName.initialRoute)); 101 | } 102 | 103 | 104 | }, 105 | 106 | 107 | onTap: (state){ 108 | state.requestFuture(MusicApi.login(widget.mobile, editingController.text)); 109 | 110 | }, 111 | title: "登录", 112 | loadingText: "正在登录...", 113 | 114 | ) 115 | ], 116 | ), 117 | ), 118 | ); 119 | } 120 | 121 | Widget _password(context){ 122 | return Container( 123 | margin: EdgeInsets.only(top: 20), 124 | width: double.infinity, 125 | //height: ScreenAdapter.setHeight(100), 126 | decoration: BoxDecoration( 127 | 128 | border: Border( 129 | bottom: BorderSide(width: 1,color: MusicStore.Theme(context).topShadowColor) 130 | ) 131 | ), 132 | child: TextField( 133 | 134 | controller: editingController, 135 | autofocus: true, 136 | obscureText:true, 137 | style: TextStyle(fontSize: 18,color: MusicStore.Theme(context).textFieldColor), 138 | 139 | decoration:InputDecoration( 140 | hintText: "请输入密码", 141 | hintStyle: TextStyle(color: MusicStore.Theme(context).textFieldColor), 142 | border: InputBorder.none 143 | ) 144 | ), 145 | ); 146 | } 147 | } 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /lib/pages/music_list_page/music_list_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:audioplayers/audioplayers.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_music/base_music/music_app_bar.dart'; 4 | import 'package:flutter_music/common/music_store.dart'; 5 | import 'package:flutter_music/common/screen_adapter.dart'; 6 | import 'package:flutter_music/public_widget/music_item_widget.dart'; 7 | import 'package:flutter_music/pages/music_play_media_page/music_paly_coverimage_widget.dart'; 8 | class MusicListPage extends StatefulWidget { 9 | @override 10 | _MusicListPageState createState() => _MusicListPageState(); 11 | } 12 | 13 | class _MusicListPageState extends State with TickerProviderStateMixin{ 14 | 15 | 16 | MusicGlobalPlayListState _musicGlobalPlayListState; 17 | AnimationController _rotationAnimationController; 18 | 19 | @override 20 | void initState() { 21 | // TODO: implement initState 22 | super.initState(); 23 | _musicGlobalPlayListState = MusicStore.MusicPlayList(context); 24 | 25 | _rotationAnimationController = AnimationController(duration: Duration(seconds: 25),vsync: this); 26 | 27 | if(_musicGlobalPlayListState.playerState == AudioPlayerState.PAUSED){ 28 | _rotationAnimationController.stop(); 29 | }else{ 30 | _rotationAnimationController.repeat(); 31 | } 32 | _musicGlobalPlayListState.onPlayerStateChanged.listen((state){ 33 | if(this.mounted == false) return; 34 | 35 | if(state == AudioPlayerState.PAUSED){ 36 | _rotationAnimationController.stop(); 37 | }else{ 38 | _rotationAnimationController.repeat(); 39 | } 40 | }); 41 | 42 | } 43 | @override 44 | void dispose() { 45 | _rotationAnimationController.dispose(); 46 | // TODO: implement dispose 47 | super.dispose(); 48 | } 49 | @override 50 | Widget build(BuildContext context) { 51 | 52 | ScreenAdapter.init(context); 53 | double _width = ScreenAdapter.getScreenWidth()/4.0; 54 | 55 | 56 | return Scaffold( 57 | backgroundColor: MusicStore.Theme(context).theme, 58 | appBar: MusicAppBar( 59 | title: "播放列表", 60 | leftIconData: Icons.keyboard_arrow_down, 61 | rightIconData: Icons.more_horiz, 62 | leftOnTap: (){ 63 | Navigator.of(context).pop(); 64 | }, 65 | ), 66 | body: SafeArea( 67 | child: Stack( 68 | children: [ 69 | _playCoverImage(_width), 70 | Selector( 71 | builder: (context,currentIndex,_){ 72 | return _playList(_width); 73 | }, 74 | selector: (context,state){ 75 | return state.currentIndex; 76 | }, 77 | shouldRebuild: (pre,next){ 78 | return pre != next; 79 | }, 80 | ) 81 | 82 | ], 83 | ), 84 | ) 85 | ); 86 | } 87 | 88 | Widget _playList(top){ 89 | return Container( 90 | margin: EdgeInsets.only(top: top+40), 91 | child: ListView.builder( 92 | 93 | itemCount: _musicGlobalPlayListState.currentPlayList.length, 94 | itemBuilder: (context,index){ 95 | TrackItemModel itemModel = _musicGlobalPlayListState.currentPlayList[index]; 96 | 97 | var title = itemModel.name; 98 | var subtTitle = itemModel.arList.first.name + itemModel.al.name; 99 | var coverImageUrl = itemModel.al.picUrl; 100 | 101 | return MusicItemWidget( 102 | onTap: (selectedIndex){ 103 | _musicGlobalPlayListState.updatePlayItem(selectedIndex); 104 | }, 105 | index: index, 106 | id: itemModel.id, 107 | title: title, 108 | subtTitle: subtTitle, 109 | coverImageUrl: coverImageUrl, 110 | 111 | ); 112 | } 113 | ), 114 | ); 115 | } 116 | 117 | 118 | Widget _playCoverImage(width){ 119 | 120 | 121 | return Positioned( 122 | top: 0, 123 | left: 0, 124 | right: 0, 125 | child: Center( 126 | child: Selector( 127 | selector: (context,state){ 128 | return state.currentIndex; 129 | }, 130 | builder: (context,currentIndex,_){ 131 | return MusicPlayCoverimageWidget( 132 | animationController: _rotationAnimationController, 133 | width: width, 134 | marginTop: 20, 135 | coverImageUrl: _musicGlobalPlayListState.currentTrackItem.al.picUrl, 136 | ); 137 | }, 138 | shouldRebuild: (pre,next){ 139 | return pre != next; 140 | }, 141 | 142 | ) 143 | ), 144 | ); 145 | } 146 | } 147 | --------------------------------------------------------------------------------