├── 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 | 
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 |
--------------------------------------------------------------------------------