├── .flutter-plugins-dependencies ├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── android ├── app │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── darren │ │ │ └── com │ │ │ └── example │ │ │ └── flutterflowermusic │ │ │ ├── App.java │ │ │ ├── FlutterPluginBasicTest.java │ │ │ ├── MainActivity.java │ │ │ ├── constant │ │ │ └── MusicConstants.java │ │ │ ├── mediasession │ │ │ └── MediaSessionManager.java │ │ │ ├── model │ │ │ └── Song.java │ │ │ └── tools │ │ │ └── MusicStreamTool.java │ │ └── res │ │ ├── drawable │ │ └── launch_background.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── images ├── disc.png ├── ic_rand.svg ├── ic_spen.svg ├── is_single.svg ├── nologin.png ├── placehoder_img.png ├── play_needle.png └── play_needle2.png ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Flutter.podspec │ ├── Release.xcconfig │ └── flutter_export_environment.sh ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── BackgroundTools.swift │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── GoogleService-Info.plist │ ├── Info.plist │ ├── MusicStreamTool.swift │ └── Runner-Bridging-Header.h ├── lib ├── api │ ├── httpUtil.dart │ └── netUtils.dart ├── app.dart ├── base │ ├── app_config.dart │ ├── base.dart │ ├── base2.dart │ └── const_config.dart ├── data │ ├── base.dart │ ├── base.g.dart │ ├── comment.dart │ ├── comment.g.dart │ ├── song.dart │ ├── song.g.dart │ ├── user.dart │ └── user.g.dart ├── main.dart ├── main │ ├── dialog │ │ └── dialog.dart │ ├── refresh │ │ ├── classic_indicator.dart │ │ ├── default_constants.dart │ │ ├── footer_indicator.dart │ │ ├── indicator_config.dart │ │ ├── indicator_wrap.dart │ │ ├── pull_to_refresh.dart │ │ ├── refresh_physics.dart │ │ └── smart_refresher.dart │ └── tap │ │ ├── opacity_tap_widget.dart │ │ └── tap_widget.dart ├── main_provide.dart ├── model │ ├── home_repository.dart │ ├── mine_respository.dart │ └── old_repository.dart ├── tools │ ├── app_tool.dart │ ├── audio_tool.dart │ ├── mutual_tool.dart │ ├── player_tool.dart │ └── user_tool.dart ├── utils │ └── common_util.dart ├── view │ ├── home │ │ ├── comment_page.dart │ │ └── home_page.dart │ ├── mine │ │ ├── advice_page.dart │ │ ├── author_page.dart │ │ ├── collection_page.dart │ │ ├── login_page.dart │ │ ├── mine_page.dart │ │ ├── register_protocol_page.dart │ │ ├── reset_password_page.dart │ │ └── setting_page.dart │ ├── old │ │ ├── old_page.dart │ │ └── olddetail_page.dart │ └── player │ │ ├── full_player_page.dart │ │ ├── mini_player_page.dart │ │ └── music_list_page.dart └── viewmodel │ ├── home │ ├── comment_provide.dart │ └── home_provide.dart │ ├── mine │ ├── advice_provide.dart │ ├── collection_provide.dart │ ├── login_provide.dart │ ├── mine_provide.dart │ ├── regiest_provide.dart │ ├── reset_password_provide.dart │ └── setting_provide.dart │ ├── old │ └── old_provide.dart │ └── player │ └── player_provide.dart ├── pubspec.yaml ├── screenShots ├── IMG_6994(20191205-090511).JPG ├── QRCode_258.png ├── Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.151.png ├── Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.18.png ├── Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.21.png ├── Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.25.png ├── Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.28.png ├── Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.35.png ├── Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.38.png ├── Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.44.png ├── Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.53.png └── 屏幕快照 2019-05-06 下午8.29.23.png └── test └── widget_test.dart /.flutter-plugins-dependencies: -------------------------------------------------------------------------------- 1 | {"_info":"// This is a generated file; do not edit or check into version control.","dependencyGraph":[{"name":"audioplayers","dependencies":["path_provider"]},{"name":"flutter_webview_plugin","dependencies":[]},{"name":"fluttertoast","dependencies":[]},{"name":"multi_image_picker","dependencies":[]},{"name":"path_provider","dependencies":[]},{"name":"shared_preferences","dependencies":[]},{"name":"sqflite","dependencies":[]},{"name":"url_launcher","dependencies":[]},{"name":"webview_flutter","dependencies":[]}]} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 72 | -------------------------------------------------------------------------------- /.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: 5391447fae6209bb21a89e6a5a6583cac1af9b4b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Darren-chenchen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 备注: 3 | 4 | ### 2019年谷歌推出Provider,代替 Provide 成为官方推荐的状态管理方式,Provide已经不再维护,所有项目已经逐渐替换成了Provider,项目默认分支是provider,可下载该分支查看。 5 | 6 | ## 开发环境 flutter v1.12.13+hotfix.5(如有报错可能跟flutter版本相关,可根据个人的flutter版本进行调整) 7 | 8 | 9 | 10 | 11 | 12 | # 阅读之前 13 | 14 | ### 1、部分截图 15 | 16 | 17 | ![()](https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/master/screenShots/Simulator%20Screen%20Shot%20-%20iPhone%20X%CA%80%20-%202019-05-06%20at%2018.23.151.png) 18 | ![](https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/master/screenShots/Simulator%20Screen%20Shot%20-%20iPhone%20X%CA%80%20-%202019-05-06%20at%2018.23.18.png) 19 | ![](https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/master/screenShots/Simulator%20Screen%20Shot%20-%20iPhone%20X%CA%80%20-%202019-05-06%20at%2018.23.21.png) 20 | ![](https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/master/screenShots/Simulator%20Screen%20Shot%20-%20iPhone%20X%CA%80%20-%202019-05-06%20at%2018.23.25.png) 21 | 22 | 23 | 24 | 25 | # 安卓请扫码下载体验,ios没有证书,无法下载。密码123 26 | 27 | ![](https://github.com/Darren-chenchen/flutter_flowermusic/blob/provider/screenShots/QRCode_258.png?raw=true) 28 | 29 | 30 | # 项目结构 31 | 32 | ![(logo)](https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/master/screenShots/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-05-06%20%E4%B8%8B%E5%8D%888.29.23.png) 33 | 34 | # 该项目的特点 35 | 36 | ### 1、使用mvvm架构编写。 [MVVM架构在Flutter中的简单实践](https://www.jianshu.com/p/43eb17163468) 37 | ### 2、Provider和RxDart 的使用 38 | 39 | 40 | # 部分封装介绍 41 | 42 | ### 1、refresh组件:刷新组件是在pull_to_refresh的基础上进行的再次封装,该库本身是存在一些问题的,所以就自己改了一下使用。希望该库持续更新,还有其他的刷新库,这里就不详细说了。主要是我们在使用时最好能够读懂别人的组件库的代码,这样才能更好的解决问题。也是一种学习方式。[pull__to__refresh](https://github.com/peng8350/flutter_pulltorefresh) 43 | 44 | ### 2、OpacityTapWidget组件:OpacityTapWidget组件解决了2个问题: 45 | 46 | 1)点击效果:点击时child有一个透明度的变化 47 | 48 | 2)点击的热区问题: OpacityTapWidget内部设置padding增加了点击的热区。 49 | 50 | ``` 51 | new OpacityTapWidget( 52 | onTap: () { 53 | Navigator.of(context).pop(); 54 | }, 55 | child: new Icon(Icons.close, color: Colors.white,size: 27,), 56 | ) 57 | ``` 58 | 59 | ### 3、TapWidget组件:和OpacityTapWidget不一样的是TapWidget点击的效果是背景颜色的变化。 60 | 61 | # 部分第三方库的封装与介绍 62 | 63 | ### 1.dio 网络请求封装: [Dio](https://github.com/flutterchina/dio/blob/master/README-ZH.md) 64 | 65 | - Dio初始化 66 | 67 | ``` 68 | dio = new Dio() 69 | ..options = BaseOptions( 70 | baseUrl: AppConfig.baseUrl, 71 | connectTimeout: 30000, 72 | receiveTimeout: 30000) 73 | ..interceptors.add(HeaderInterceptor()); 74 | ..interceptors.add(LogInterceptor(responseBody: true, requestBody: true)); 75 | ``` 76 | 77 | - 拦截器 78 | 79 | ``` 80 | class HeaderInterceptor extends Interceptor { 81 | @override 82 | onRequest(RequestOptions options) { 83 | final token = AppConfig.userTools.getUserToken(); 84 | if (token != null && token.length > 0) { 85 | options.headers.putIfAbsent('Authorization', () => 'Bearer' + ' ' + token); 86 | } 87 | // if (options.uri.path.indexOf('api/user/advice/Imgs') > 0 || options.uri.path.indexOf('api/user/uploadUserHeader') > 0) { // 上传图片 88 | // options.headers.putIfAbsent('Content-Type', () => 'multipart/form-data'); 89 | // print('上传图片'); 90 | // } else { 91 | // } 92 | // options.headers.putIfAbsent('Content-Type', () => 'application/json;charset=UTF-8'); 93 | 94 | return super.onRequest(options); 95 | } 96 | } 97 | ``` 98 | 99 | ### 2. [rxdart](https://github.com/ReactiveX/rxdart) 100 | 101 | - 属性监听 102 | 103 | ``` 104 | 方式1: 105 | final subjectMore = new BehaviorSubject.seeded(false); 106 | 方式2: 107 | final subjectMore = new BehaviorSubject(); 108 | 109 | subjectMore.value = false 110 | _provide.subjectMore.listen((hasMore) { 111 | }); 112 | ``` 113 | 方式1与方式2的不同是,方式1再初始化时就会触发,监听者会在初始化时监听到false参数。 114 | 115 | ### 3.flutter_svg 初始化svg格式的图片 116 | 117 | ``` 118 | new SvgPicture.asset("images/is_single.svg", width: 28, height: 28); 119 | ``` 120 | 121 | ### 4.shared_preferences 数据存储 122 | 123 | 由于数据的存储和获取是异步的,但是在项目中使用同步的方法获取用户信息就很是有必要,所以该项目再初始化之前就初始化了shared_preferences,解决了在项目中使用同步的方法获取用户信息这个问题。 124 | 125 | ``` 126 | void main() async { 127 | /// 先初始化shared_preferences 128 | await AppConfig.init(); 129 | runApp(MyApp()); 130 | } 131 | ``` 132 | 133 | # 最后 134 | 135 | ### 1、建议大家把重点放在项目架构的优化上面(mvvm)。 136 | 137 | ### 2、该项目只供学习交流使用,严禁用于商业用途..... 138 | 139 | ### 有任何问题或者需要服务器端代码可进群:625200323 140 | ![()](https://github.com/Darren-chenchen/flutter_flowermusic/blob/provider/screenShots/IMG_6994(20191205-090511).JPG?raw=true) 141 | 142 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "darren.com.example.flutterflowermusic" 37 | minSdkVersion 19 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | multiDexEnabled true 42 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 43 | 44 | } 45 | 46 | buildTypes { 47 | release { 48 | // TODO: Add your own signing config for the release build. 49 | // Signing with the debug keys for now, so `flutter run --release` works. 50 | signingConfig signingConfigs.debug 51 | } 52 | } 53 | } 54 | 55 | flutter { 56 | source '../..' 57 | } 58 | 59 | dependencies { 60 | implementation 'androidx.multidex:multidex:2.0.0' 61 | implementation 'com.google.android.material:material:1.0.0' 62 | implementation 'com.android.support:support-v4:24.2.1' 63 | 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.1.0' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' 67 | } 68 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 25 | 26 | 27 | 30 | 31 | 38 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /android/app/src/main/java/darren/com/example/flutterflowermusic/App.java: -------------------------------------------------------------------------------- 1 | package darren.com.example.flutterflowermusic; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import androidx.multidex.MultiDex; 7 | 8 | import io.flutter.app.FlutterApplication; 9 | 10 | public class App extends FlutterApplication { 11 | 12 | private static Context context; 13 | 14 | @Override 15 | public void onCreate() { 16 | super.onCreate(); 17 | context = this; 18 | } 19 | /** 20 | * 获取全局上下文*/ 21 | public static Context getContext(){ 22 | return context; 23 | } 24 | 25 | @Override 26 | protected void attachBaseContext(Context base) { 27 | super.attachBaseContext(base); 28 | MultiDex.install(this); 29 | Log.d("222", "22222"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android/app/src/main/java/darren/com/example/flutterflowermusic/MainActivity.java: -------------------------------------------------------------------------------- 1 | package darren.com.example.flutterflowermusic; 2 | 3 | import android.graphics.Color; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | import android.view.View; 8 | 9 | import io.flutter.app.FlutterActivity; 10 | import io.flutter.plugin.common.MethodCall; 11 | import io.flutter.plugin.common.MethodChannel; 12 | import io.flutter.plugin.common.PluginRegistry; 13 | import io.flutter.plugins.GeneratedPluginRegistrant; 14 | 15 | public class MainActivity extends FlutterActivity { 16 | 17 | MethodChannel channel; 18 | public String CHANNEL = "darren.com.example.flutterFlowermusic/mutual"; 19 | 20 | FlutterPluginBasicTest pluginBasicTest = new FlutterPluginBasicTest(); 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | GeneratedPluginRegistrant.registerWith(this); 26 | 27 | this.setTitleTransparent(); 28 | this.registerFor(this); 29 | Log.d("1111", "1111"); 30 | } 31 | 32 | public void registerFor(PluginRegistry registrar) { 33 | pluginBasicTest.registerWith(registrar.registrarFor(CHANNEL), this); 34 | } 35 | 36 | private void setTitleTransparent() { 37 | if (Build.VERSION.SDK_INT >= 21) { 38 | View decorView = getWindow().getDecorView(); 39 | int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 40 | | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; 41 | decorView.setSystemUiVisibility(option); 42 | getWindow().setStatusBarColor(Color.TRANSPARENT); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /android/app/src/main/java/darren/com/example/flutterflowermusic/constant/MusicConstants.java: -------------------------------------------------------------------------------- 1 | package darren.com.example.flutterflowermusic.constant; 2 | 3 | /** 4 | * Created by ZerdoorPHPDC on 2017/12/28 0028. 5 | */ 6 | 7 | public class MusicConstants { 8 | 9 | public static final String PARAM_MUSIC_PATH = "PARAM_MUSIC_PATH"; 10 | 11 | public static final String MUSIC_ACTICON_START_PLAY = "MUSIC_ACTICON_START_PLAY"; 12 | public static final String MUSIC_ACTICON_RESET_START_PLAY = "MUSIC_ACTICON_RESET_START_PLAY"; 13 | public static final String MUSIC_ACTICON_CONTINUE_PLAY = "MUSIC_ACTICON_CONTINUE_PLAY"; 14 | public static final String MUSIC_ACTICON_PAUSE_PLAY = "MUSIC_ACTICON_PAUSE_PLAY"; 15 | 16 | public static final String MUSIC_ACTICON_PLAY_NEXT = "MUSIC_ACTICON_PLAY_NEXT"; 17 | public static final String MUSIC_ACTICON_PLAY_PRE = "MUSIC_ACTICON_PLAY_PRE"; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /android/app/src/main/java/darren/com/example/flutterflowermusic/mediasession/MediaSessionManager.java: -------------------------------------------------------------------------------- 1 | package darren.com.example.flutterflowermusic.mediasession; 2 | 3 | import android.util.Log; 4 | 5 | import darren.com.example.flutterflowermusic.App; 6 | import darren.com.example.flutterflowermusic.tools.MusicStreamTool; 7 | 8 | import android.support.v4.media.MediaMetadataCompat; 9 | import android.support.v4.media.session.MediaSessionCompat; 10 | import android.support.v4.media.session.PlaybackStateCompat; 11 | 12 | 13 | public class MediaSessionManager { 14 | 15 | // 静态内部类单例模式 16 | private MediaSessionManager(){ 17 | initSession(); 18 | } 19 | public static synchronized MediaSessionManager share() { 20 | MediaSessionManager tools = MediaSessionManagerHolder.instance; 21 | return tools; 22 | } 23 | public static class MediaSessionManagerHolder { 24 | 25 | private static final MediaSessionManager instance = new MediaSessionManager(); 26 | } 27 | 28 | private static final String MY_MEDIA_ROOT_ID = "MediaSessionManager"; 29 | 30 | MediaSessionCompat mMediaSession; 31 | private PlaybackStateCompat.Builder stateBuilder; 32 | private MusicStreamTool musicPlayService = MusicStreamTool.share(); 33 | 34 | // 定义回调函数 35 | public interface MediaSessionManagerNextCallBack { 36 | public void nextAction(); 37 | } 38 | public interface MediaSessionManagerPreCallBack { 39 | public void preAction(); 40 | } 41 | 42 | MediaSessionManagerNextCallBack nextCallBack; 43 | public void setNextCallBack(MediaSessionManagerNextCallBack callBack) { 44 | this.nextCallBack = callBack; 45 | } 46 | MediaSessionManagerPreCallBack preCallBack; 47 | public void setPreCallBack(MediaSessionManagerPreCallBack callBack) { 48 | this.preCallBack = callBack; 49 | } 50 | 51 | public void initSession() { 52 | try { 53 | Log.d("11111", "------"); 54 | mMediaSession = new MediaSessionCompat(App.getContext(), MY_MEDIA_ROOT_ID); 55 | mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); 56 | stateBuilder = new PlaybackStateCompat.Builder() 57 | .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE 58 | | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS); 59 | mMediaSession.setPlaybackState(stateBuilder.build()); 60 | mMediaSession.setCallback(sessionCb); 61 | mMediaSession.setActive(true); 62 | } catch (Exception e) { 63 | } 64 | } 65 | 66 | public void updatePlaybackState() { 67 | int state = 0; 68 | if (MusicStreamTool.share().currentState == MusicStreamTool.MusicStreamToolState.isPlaying) { 69 | state = PlaybackStateCompat.STATE_PLAYING; 70 | } else { 71 | state = PlaybackStateCompat.STATE_PAUSED; 72 | } 73 | long position = 0; 74 | if (MusicStreamTool.share().currentState == MusicStreamTool.MusicStreamToolState.isPlaying) { 75 | position = MusicStreamTool.share().player.getCurrentPosition(); 76 | } 77 | if (stateBuilder != null) { 78 | stateBuilder.setState(state, position, 1.0f); 79 | mMediaSession.setPlaybackState(stateBuilder.build()); 80 | } 81 | } 82 | 83 | public void updateLocMsg() { 84 | try { 85 | //同步歌曲信息 86 | MediaMetadataCompat.Builder md = new MediaMetadataCompat.Builder(); 87 | md.putString(MediaMetadataCompat.METADATA_KEY_TITLE, MusicStreamTool.share().currentSong.title); 88 | md.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, MusicStreamTool.share().currentSong.singer); 89 | md.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, MusicStreamTool.share().currentSong.singer); 90 | if (MusicStreamTool.share().currentState == MusicStreamTool.MusicStreamToolState.isPlaying) { 91 | md.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, MusicStreamTool.share().player.getDuration()); 92 | } 93 | mMediaSession.setMetadata(md.build()); 94 | } catch (Exception e) { 95 | } 96 | 97 | } 98 | 99 | private MediaSessionCompat.Callback sessionCb = new MediaSessionCompat.Callback() { 100 | @Override 101 | public void onPlay() { 102 | super.onPlay(); 103 | Log.d("--", "onPlay"); 104 | MusicStreamTool.share().playMusic(); 105 | } 106 | 107 | @Override 108 | public void onPause() { 109 | super.onPause(); 110 | Log.d("--", "onPause"); 111 | MusicStreamTool.share().pauseMusic(); 112 | } 113 | 114 | @Override 115 | public void onSkipToNext() { 116 | super.onSkipToNext(); 117 | if (MediaSessionManager.this.nextCallBack != null) { 118 | MediaSessionManager.this.nextCallBack.nextAction(); 119 | } 120 | } 121 | 122 | @Override 123 | public void onSkipToPrevious() { 124 | super.onSkipToPrevious(); 125 | if (MediaSessionManager.this.preCallBack != null) { 126 | MediaSessionManager.this.preCallBack.preAction(); 127 | } 128 | } 129 | 130 | }; 131 | 132 | public void release() { 133 | mMediaSession.setCallback(null); 134 | mMediaSession.setActive(false); 135 | mMediaSession.release(); 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /android/app/src/main/java/darren/com/example/flutterflowermusic/model/Song.java: -------------------------------------------------------------------------------- 1 | package darren.com.example.flutterflowermusic.model; 2 | 3 | public class Song { 4 | public String _id = ""; 5 | public String imgUrl = ""; 6 | public String lrcUrl = ""; 7 | public String size = ""; 8 | public String singer = ""; 9 | public String songUrl = ""; 10 | public String title = ""; 11 | public String duration = ""; 12 | } 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | android.enableJetifier=true 2 | android.useAndroidX=true 3 | org.gradle.jvmargs=-Xmx1536M 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /images/disc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/images/disc.png -------------------------------------------------------------------------------- /images/ic_rand.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/ic_spen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/is_single.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/nologin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/images/nologin.png -------------------------------------------------------------------------------- /images/placehoder_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/images/placehoder_img.png -------------------------------------------------------------------------------- /images/play_needle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/images/play_needle.png -------------------------------------------------------------------------------- /images/play_needle2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/images/play_needle2.png -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Flutter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE: This podspec is NOT to be published. It is only used as a local source! 3 | # 4 | 5 | Pod::Spec.new do |s| 6 | s.name = 'Flutter' 7 | s.version = '1.0.0' 8 | s.summary = 'High-performance, high-fidelity mobile apps.' 9 | s.description = <<-DESC 10 | Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. 11 | DESC 12 | s.homepage = 'https://flutter.io' 13 | s.license = { :type => 'MIT' } 14 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } 15 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } 16 | s.ios.deployment_target = '8.0' 17 | s.vendored_frameworks = 'Flutter.framework' 18 | end 19 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/chenliangchenliang/Flutter/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/chenliangchenliang/Desktop/03-项目/我的项目/flower_music/flutter/flutter_flowermusic" 5 | export "FLUTTER_TARGET=/Users/chenliangchenliang/Desktop/03-项目/我的项目/flower_music/flutter/flutter_flowermusic/lib/main.dart" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "SYMROOT=${SOURCE_ROOT}/../build/ios" 8 | export "FLUTTER_FRAMEWORK_DIR=/Users/chenliangchenliang/Flutter/flutter/bin/cache/artifacts/engine/ios" 9 | export "FLUTTER_BUILD_NAME=1.0.0" 10 | export "FLUTTER_BUILD_NUMBER=1" 11 | export "TRACK_WIDGET_CREATION=true" 12 | -------------------------------------------------------------------------------- /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 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | pods_ary = [] 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) { |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | pods_ary.push({:name => podname, :path => podpath}); 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | } 32 | return pods_ary 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | 38 | pod 'StreamingKit', '~> 0.1' 39 | 40 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 41 | # referring to absolute paths on developers' machines. 42 | system('rm -rf .symlinks') 43 | system('mkdir -p .symlinks/plugins') 44 | 45 | # Flutter Pods 46 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 47 | if generated_xcode_build_settings.empty? 48 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 49 | end 50 | generated_xcode_build_settings.map { |p| 51 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 52 | symlink = File.join('.symlinks', 'flutter') 53 | File.symlink(File.dirname(p[:path]), symlink) 54 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 55 | end 56 | } 57 | 58 | # Plugin Pods 59 | plugin_pods = parse_KV_file('../.flutter-plugins') 60 | plugin_pods.map { |p| 61 | symlink = File.join('.symlinks', 'plugins', p[:name]) 62 | File.symlink(p[:path], symlink) 63 | pod p[:name], :path => File.join(symlink, 'ios') 64 | } 65 | 66 | end 67 | 68 | post_install do |installer| 69 | installer.pods_project.targets.each do |target| 70 | target.build_configurations.each do |config| 71 | config.build_settings['ENABLE_BITCODE'] = 'NO' 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | import AVFoundation 4 | let HOST_URL = "http://chenliang.yishouhaoge.cn/#/" 5 | 6 | @UIApplicationMain 7 | @objc class AppDelegate: FlutterAppDelegate, FlutterStreamHandler { 8 | var eventSink: FlutterEventSink? 9 | 10 | override func application( 11 | _ application: UIApplication, 12 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? 13 | ) -> Bool { 14 | GeneratedPluginRegistrant.register(with: self) 15 | UIApplication.shared.beginReceivingRemoteControlEvents() 16 | 17 | let name = "darren.com.example.flutterFlowermusic/mutual" 18 | let event = "darren.com.example.flutterFlowermusic/event" 19 | let controller: FlutterViewController = window.rootViewController as! FlutterViewController 20 | let channel = FlutterMethodChannel(name: name, binaryMessenger: controller.binaryMessenger) 21 | channel.setMethodCallHandler { (call, result) in 22 | 23 | if "share" == call.method { 24 | self.share(data: call.arguments) 25 | } 26 | if "beginPlay" == call.method { 27 | self.beginPlay(data: call.arguments) 28 | } 29 | if "pause" == call.method { 30 | MusicStreamTool.share.pauseMusic() 31 | } 32 | if "resume" == call.method { 33 | MusicStreamTool.share.playMusic() 34 | } 35 | if "seek" == call.method { 36 | let timer = call.arguments as? Int ?? 0 37 | MusicStreamTool.share.seekMusic(playOffset: Float(timer)) 38 | } 39 | if "GoodComment" == call.method { // 好评 40 | self.goodComment() 41 | } 42 | } 43 | let evenChannal = FlutterEventChannel.init(name: event, binaryMessenger: controller.binaryMessenger) 44 | evenChannal.setStreamHandler(self) 45 | 46 | // 注册后台播放 47 | setupActive() 48 | 49 | /// 原生传值给flutter 50 | setupDataToFlutter() 51 | 52 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 53 | } 54 | 55 | // 五星好评 56 | func goodComment() { 57 | var urlStr = "" 58 | urlStr = "https://www.darrenblog.cn/#/detail/5cd281787bb725236692b6c2" 59 | let url = URL.init(string: urlStr)! 60 | if #available(iOS 10.0, *) { 61 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 62 | } else { 63 | UIApplication.shared.openURL(url) 64 | } 65 | } 66 | 67 | func share(data: Any?) { 68 | let music = data as? [String : Any] 69 | if music != nil { 70 | let shareText = "this song is so nice" 71 | let str = HOST_URL + "chenliang/music/single?id=\(music!["_id"] ?? "")" 72 | if let url = URL(string: str) { 73 | let img: UIImage = UIImage(named:"Icon-60") ?? UIImage() 74 | let shareItems:Array = [shareText, url, img] as [Any] 75 | let activityVC = UIActivityViewController.init(activityItems: shareItems, applicationActivities: nil) 76 | let controller: FlutterViewController = window.rootViewController as! FlutterViewController 77 | controller.present(activityVC, animated: true, completion: nil) 78 | } 79 | } 80 | } 81 | func setupDataToFlutter() { 82 | MusicStreamTool.share.currentPlayTimerClouse = { (timer, totalTimer) in 83 | // 防止服务器没有返回duration 84 | if (self.eventSink != nil) { 85 | self.eventSink!("progress" + "&" + "\(timer)+\(totalTimer)") 86 | } 87 | } 88 | MusicStreamTool.share.playStatusClouse = {(state) in 89 | print("--------------") 90 | print(state) 91 | if state == MusicStreamToolState.beginPlay { 92 | if (self.eventSink != nil) { 93 | self.eventSink!("state" + "&" + "beginPlay") 94 | } 95 | } 96 | if state == MusicStreamToolState.isPlaying { 97 | if (self.eventSink != nil) { 98 | self.eventSink!("state" + "&" + "isPlaying") 99 | } 100 | } 101 | if state == MusicStreamToolState.isCacheing { 102 | if (self.eventSink != nil) { 103 | self.eventSink!("state" + "&" + "isCacheing") 104 | } 105 | } 106 | if state == MusicStreamToolState.isPaued { 107 | if (self.eventSink != nil) { 108 | self.eventSink!("state" + "&" + "playPause") 109 | } 110 | } 111 | // 自然停止,自动播放下一首 112 | if state == MusicStreamToolState.isEnd { 113 | if (self.eventSink != nil) { 114 | self.eventSink!("state" + "&" + "playEnd") 115 | } 116 | } 117 | // 点击下一首播放的状态是stop 118 | if state == MusicStreamToolState.isStoped { 119 | if (self.eventSink != nil) { 120 | self.eventSink!("state" + "&" + "playStop") 121 | } 122 | } 123 | 124 | } 125 | } 126 | 127 | func setupActive() { 128 | let session = AVAudioSession.sharedInstance() 129 | do { 130 | try session.setActive(true) 131 | if #available(iOS 11.0, *) { 132 | try session.setCategory(AVAudioSessionCategoryPlayback, mode: AVAudioSessionModeDefault, routeSharingPolicy: AVAudioSessionRouteSharingPolicy.default, options: AVAudioSessionCategoryOptions.init(rawValue: 0)) 133 | } else { 134 | // Fallback on earlier versions 135 | try session.setCategory(AVAudioSessionCategoryPlayback, with: AVAudioSessionCategoryOptions.init(rawValue: 0)) 136 | } 137 | } catch { 138 | print(error) 139 | } 140 | } 141 | 142 | 143 | /// ios 给flutter传递数据建立桥梁 144 | func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { 145 | self.eventSink = events 146 | return nil 147 | } 148 | func onCancel(withArguments arguments: Any?) -> FlutterError? { 149 | self.eventSink = nil 150 | return nil 151 | } 152 | 153 | func beginPlay(data: Any?) { 154 | let music = data as? [String : Any] 155 | MusicStreamTool.share.model = music ?? ["":""] 156 | } 157 | 158 | override func remoteControlReceived(with event: UIEvent?) { 159 | if event?.type == UIEventType.remoteControl { 160 | switch event!.subtype { 161 | case .remoteControlPreviousTrack: 162 | print("上一首") 163 | if (self.eventSink != nil) { 164 | self.eventSink!("preMusic" + "&" + "111") 165 | } 166 | case .remoteControlNextTrack: 167 | print("下一首") 168 | if (self.eventSink != nil) { 169 | self.eventSink!("nextMusic" + "&" + "111") 170 | } 171 | case .remoteControlPlay: 172 | print(">") 173 | MusicStreamTool.share.playMusic() 174 | case .remoteControlPause: 175 | print("||") 176 | MusicStreamTool.share.pauseMusic() 177 | default: 178 | break 179 | } 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/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/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/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/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/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/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/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/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/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/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/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/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/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/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/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/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/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/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/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/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/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/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/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/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/BackgroundTools.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BackgroundTools.swift 3 | // XFMusic 4 | // 5 | // Created by darren on 2017/10/12. 6 | // Copyright © 2017年 陈亮陈亮. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MediaPlayer 11 | 12 | class BackgroundTools: UIResponder { 13 | static let share = BackgroundTools() 14 | 15 | var settings = [String : Any]() 16 | 17 | override init() { 18 | super.init() 19 | UIApplication.shared.beginReceivingRemoteControlEvents() 20 | self.becomeFirstResponder() 21 | } 22 | 23 | func setOnceInfo(img:UIImage, currentTimer: Int) { 24 | //大标题 - 小标题 - 歌曲总时长 - 歌曲当前播放时长 - 封面 25 | self.settings = [MPMediaItemPropertyTitle: MusicStreamTool.share.model["title"] ?? "" , 26 | MPMediaItemPropertyArtist: MusicStreamTool.share.model["singer"] ?? "" , 27 | MPMediaItemPropertyPlaybackDuration: MusicStreamTool.share.getAssetDuration(), 28 | MPNowPlayingInfoPropertyElapsedPlaybackTime: currentTimer, 29 | MPMediaItemPropertyArtwork: MPMediaItemArtwork(image: img)] 30 | MPNowPlayingInfoCenter.default().setValue(settings, forKey: "nowPlayingInfo") 31 | } 32 | 33 | // 设置后台播放信息 34 | func setCurrentTimer(currentTimer: Int) { 35 | //大标题 - 小标题 - 歌曲总时长 - 歌曲当前播放时长 - 封面 36 | self.settings[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTimer 37 | MPNowPlayingInfoCenter.default().setValue(settings, forKey: "nowPlayingInfo") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 570107869449-8lusefnqv2mhi04ehsisd851jbbfedj7.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.570107869449-8lusefnqv2mhi04ehsisd851jbbfedj7 9 | API_KEY 10 | AIzaSyAV14i4oRfR3vp7iyfgtKz99jucbjGu7J0 11 | GCM_SENDER_ID 12 | 570107869449 13 | PLIST_VERSION 14 | 1 15 | BUNDLE_ID 16 | darren.com.example.flutterFlowermusic 17 | PROJECT_ID 18 | fluttermusic-c69f9 19 | STORAGE_BUCKET 20 | fluttermusic-c69f9.appspot.com 21 | IS_ADS_ENABLED 22 | 23 | IS_ANALYTICS_ENABLED 24 | 25 | IS_APPINVITE_ENABLED 26 | 27 | IS_GCM_ENABLED 28 | 29 | IS_SIGNIN_ENABLED 30 | 31 | GOOGLE_APP_ID 32 | 1:570107869449:ios:aa232e8d359c3f51 33 | DATABASE_URL 34 | https://fluttermusic-c69f9.firebaseio.com 35 | 36 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_flowermusic 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 | io.flutter.embedded_views_preview 31 | GADApplicationIdentifier 32 | ca-app-pub-7554307620787158~7792716260 33 | NSCameraUsageDescription 34 | Example usage description 35 | NSPhotoLibraryUsageDescription 36 | Example usage description 37 | UIBackgroundModes 38 | 39 | audio 40 | remote-notification 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | UIViewControllerBasedStatusBarAppearance 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lib/api/httpUtil.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:dio/dio.dart'; 3 | import 'package:flutter_flowermusic/base/app_config.dart'; 4 | 5 | class HttpUtil { 6 | static HttpUtil instance; 7 | Dio dio; 8 | BaseOptions options; 9 | 10 | static HttpUtil getInstance() { 11 | print('getInstance'); 12 | if (instance == null) { 13 | instance = new HttpUtil(); 14 | } 15 | return instance; 16 | } 17 | HttpUtil() { 18 | dio = new Dio() 19 | ..options = BaseOptions( 20 | baseUrl: AppConfig.baseUrl, 21 | connectTimeout: 30000, 22 | receiveTimeout: 30000) 23 | ..interceptors.add(HeaderInterceptor()); 24 | // ..interceptors.add(LogInterceptor(responseBody: true, requestBody: true)); 25 | } 26 | } 27 | 28 | class HeaderInterceptor extends Interceptor { 29 | @override 30 | onRequest(RequestOptions options) { 31 | final token = AppConfig.userTools.getUserToken(); 32 | if (token != null && token.length > 0) { 33 | options.headers.putIfAbsent('Authorization', () => 'Bearer' + ' ' + token); 34 | } 35 | // if (options.uri.path.indexOf('api/user/advice/Imgs') > 0 || options.uri.path.indexOf('api/user/uploadUserHeader') > 0) { // 上传图片 36 | // options.headers.putIfAbsent('Content-Type', () => 'multipart/form-data'); 37 | // print('上传图片'); 38 | // } else { 39 | // } 40 | // options.headers.putIfAbsent('Content-Type', () => 'application/json;charset=UTF-8'); 41 | 42 | return super.onRequest(options); 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /lib/api/netUtils.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:async'; 3 | import 'package:flutter_flowermusic/data/base.dart'; 4 | import 'package:fluttertoast/fluttertoast.dart'; 5 | import 'package:rxdart/rxdart.dart'; 6 | import 'httpUtil.dart'; 7 | 8 | Observable get(String url, {Map params}) => 9 | Observable.fromFuture(_get(url, params: params)).delay(Duration(milliseconds: 500)).asBroadcastStream(); 10 | 11 | Future _get(String url, {Map params}) async { 12 | var response = await HttpUtil().dio.get(url, queryParameters: params); 13 | var res = BaseResponse.fromJson(response.data); 14 | if (res.success == false) { 15 | Fluttertoast.showToast( 16 | msg: res.message 17 | ); 18 | } 19 | return res; 20 | } 21 | 22 | 23 | Observable post(String url,{dynamic body,Map queryParameters}) => 24 | Observable.fromFuture(_post(url, body,queryParameters: queryParameters)).delay(Duration(milliseconds: 500)).asBroadcastStream(); 25 | 26 | Future _post(String url, dynamic body,{ Map queryParameters}) async { 27 | var response = await HttpUtil().dio.post(url, data: body, queryParameters: queryParameters); 28 | var res = BaseResponse.fromJson(response.data); 29 | if (res.success == false) { 30 | Fluttertoast.showToast( 31 | msg: res.message, 32 | gravity: ToastGravity.CENTER 33 | ); 34 | } 35 | return res; 36 | } 37 | -------------------------------------------------------------------------------- /lib/app.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_flowermusic/main_provide.dart'; 4 | import 'package:flutter_flowermusic/view/home/home_page.dart'; 5 | import 'package:flutter_flowermusic/view/mine/mine_page.dart'; 6 | import 'package:flutter_flowermusic/view/old/old_page.dart'; 7 | import 'package:flutter_flowermusic/view/player/mini_player_page.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | import 'base/base2.dart'; 11 | 12 | class App extends PageProvideNode2 { 13 | 14 | App() { 15 | // mProviders.add(MainProvide.instance); 16 | // mProviders = MainProvide.instance; 17 | } 18 | @override 19 | Widget buildContent(BuildContext context) { 20 | // TODO: implement buildContent 21 | return _AppContentPage(); 22 | } 23 | } 24 | 25 | class _AppContentPage extends StatefulWidget { 26 | @override 27 | State createState() { 28 | return _AppState(); 29 | } 30 | } 31 | 32 | class _AppState extends State<_AppContentPage> with TickerProviderStateMixin<_AppContentPage> { 33 | 34 | MainProvide _provide; 35 | TabController controller; 36 | HomePage _home = HomePage(); 37 | OldPage _old = OldPage(); 38 | MinePage _mine = MinePage(); 39 | MiniPlayerPage _miniPage = MiniPlayerPage(); 40 | 41 | Animation _animationMini; 42 | AnimationController _miniController; 43 | final _tranTween = new Tween(begin: 1, end: 0); 44 | 45 | @override 46 | // TODO: implement wantKeepAlive 47 | bool get wantKeepAlive => true; 48 | 49 | @override 50 | void initState() { 51 | // TODO: implement initState 52 | super.initState(); 53 | 54 | _provide = MainProvide.instance; 55 | 56 | controller = new TabController(length: 3, vsync: this); 57 | 58 | _miniController = new AnimationController( 59 | duration: const Duration(milliseconds: 500), 60 | vsync: this, 61 | ); 62 | _animationMini = new CurvedAnimation(parent: _miniController, curve: Curves.linear); 63 | } 64 | 65 | @override 66 | void dispose() { 67 | super.dispose(); 68 | print("app释放"); 69 | } 70 | 71 | ontap(int index) { 72 | _provide.currentIndex = index; 73 | controller.animateTo(index, 74 | duration: const Duration(milliseconds: 300), 75 | curve: Curves.ease); 76 | } 77 | 78 | @override 79 | Widget build(BuildContext context) { 80 | return ChangeNotifierProvider( 81 | builder: (context) => _provide, 82 | child: new Scaffold( 83 | body: new Stack( 84 | alignment: AlignmentDirectional.bottomEnd, 85 | overflow: Overflow.visible, 86 | children: [ 87 | _initTabBarView(), 88 | _initMiniPlayer() 89 | ], 90 | ), 91 | bottomNavigationBar: _initBottomNavigationBar() 92 | ), 93 | ); 94 | } 95 | 96 | // TabBarView存在页面释放问题 97 | // Widget _initTabBarView() { 98 | // return new TabBarView( 99 | // controller: controller, 100 | // physics: NeverScrollableScrollPhysics(), 101 | // children: [ 102 | // _home, 103 | // _old, 104 | // _mine, 105 | // ], 106 | // ); 107 | // } 108 | 109 | Widget _initTabBarView() { 110 | return Consumer( 111 | builder : (BuildContext context, MainProvide mainProvider, Widget child) { 112 | return IndexedStack( 113 | index: _provide.currentIndex, 114 | children: [ 115 | _home, 116 | _old, 117 | _mine 118 | ], 119 | ); 120 | } 121 | ); 122 | } 123 | 124 | Widget _initMiniPlayer() { 125 | return Consumer( 126 | builder: (BuildContext context, MainProvide mainProvider, Widget child) { 127 | return Visibility( 128 | visible: mainProvider.showMini, 129 | child: new FadeTransition( 130 | opacity: _tranTween.animate(_animationMini), 131 | child: new Container( 132 | width: 80, 133 | height: 110, 134 | child: _miniPage, 135 | ), 136 | ), 137 | ); 138 | } 139 | ); 140 | } 141 | 142 | Widget _initBottomNavigationBar() { 143 | return Theme( 144 | data: new ThemeData( 145 | canvasColor: Colors.white, // BottomNavigationBar背景色 146 | textTheme: Theme.of(context).textTheme.copyWith(caption: TextStyle(color: Colors.grey)) 147 | ), 148 | child: Consumer( 149 | builder: (BuildContext context, MainProvide mainProvider, Widget child) { 150 | return BottomNavigationBar( 151 | fixedColor: Colors.red, 152 | currentIndex: mainProvider.currentIndex, 153 | onTap: ontap, 154 | type: BottomNavigationBarType.fixed, 155 | items: [ 156 | new BottomNavigationBarItem( 157 | icon: new Icon(Icons.music_video), 158 | title: new Text('推荐')), 159 | new BottomNavigationBarItem( 160 | icon: new Icon(Icons.music_note), 161 | title: new Text('经典')), 162 | new BottomNavigationBarItem( 163 | icon: new Icon(Icons.people), 164 | title: new Text('我的')) 165 | ]); 166 | } 167 | ) 168 | ); 169 | } 170 | } -------------------------------------------------------------------------------- /lib/base/app_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_flowermusic/tools/app_tool.dart'; 4 | import 'package:flutter_flowermusic/tools/user_tool.dart'; 5 | 6 | class AppConfig { 7 | 8 | static const baseUrl = 'http://chenliang.yishouhaoge.cn/'; 9 | 10 | static const primaryColor = Color(0xFF5DBE82); //主题色 11 | 12 | static const disabledMainColor = Color.fromRGBO(97, 190, 130, 0.5); 13 | static const textColor = Color(0xFF333333); 14 | static const grayTextColor = Color(0xFF71747E); 15 | static const backgroundColor = Color(0xFFF5F5F8); 16 | static const divider = Color(0xFFdddddd); 17 | 18 | // 交互相关 19 | static const platform = const MethodChannel('darren.com.example.flutterFlowermusic/mutual'); 20 | 21 | /// Construct a color from a hex code string, of the format #RRGGBB. 22 | static Color hexToColor(String code) { 23 | return new Color(int.parse(code.substring(1, 7), radix: 16) + 0xFF000000); 24 | } 25 | 26 | static Widget getPlaceHoder([width, height]) { 27 | return new Container( 28 | width: width, 29 | height: height, 30 | child: new Image.asset('images/placehoder_img.png',fit: BoxFit.cover,)); 31 | } 32 | 33 | static Widget getUserPlaceHoder(width, height) { 34 | return new Container( 35 | width: width, 36 | height: height, 37 | child: new Image.asset('images/nologin.png')); 38 | } 39 | 40 | static Widget getLoadingPlaceHoder(width, height) { 41 | return new SizedBox( 42 | width: width, 43 | height: height, 44 | child: const CircularProgressIndicator(strokeWidth: 2.0) 45 | ); 46 | } 47 | 48 | //debug:false release: true 49 | static const bool inProduction = const bool.fromEnvironment("dart.vm.product"); 50 | 51 | static Widget initLoading(bool showEmpty, [String emptyText = '暂无数据']) { 52 | return new Center( 53 | child: showEmpty ? _initEmpty(emptyText) : SizedBox( 54 | width: 35.0, 55 | height: 35.0, 56 | child: const CircularProgressIndicator(strokeWidth: 2.0) 57 | ), 58 | ); 59 | } 60 | 61 | static Widget _initEmpty(String emptyText) { 62 | return new Container( 63 | padding: EdgeInsets.fromLTRB(0, 80, 0, 0), 64 | child: new Column( 65 | children: [ 66 | new Icon(Icons.hourglass_empty, color: Colors.grey,size: 60,), 67 | new Container(height: 10,), 68 | new Text(emptyText) 69 | ], 70 | ), 71 | ); 72 | } 73 | 74 | static UserTools userTools; 75 | static AppTools appTools; 76 | 77 | static init() async { 78 | userTools = await UserTools.getInstance(); 79 | appTools = await AppTools.getInstance(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/base/base.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:async'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:rxdart/rxdart.dart'; 6 | 7 | /// BaseProvide 8 | class BaseProvide with ChangeNotifier { 9 | 10 | CompositeSubscription compositeSubscription = CompositeSubscription(); 11 | 12 | 13 | /// add [StreamSubscription] to [compositeSubscription] 14 | /// 15 | /// 在 [dispose]的时候能进行取消 16 | addSubscription(StreamSubscription subscription){ 17 | compositeSubscription.add(subscription); 18 | } 19 | 20 | @override 21 | void dispose() { 22 | super.dispose(); 23 | compositeSubscription.dispose(); 24 | } 25 | } 26 | 27 | 28 | /// page的基类 [PageProvideNode] 29 | /// 30 | /// 隐藏了 [ProviderNode] 的调用 31 | abstract class PageProvideNode extends StatelessWidget { 32 | /// The values made available to the [child]. 33 | final BaseProvide mProviders = BaseProvide(); 34 | 35 | 36 | Widget buildContent(BuildContext context); 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return ChangeNotifierProvider.value( 41 | value: mProviders, 42 | child: buildContent(context), 43 | ); 44 | } 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /lib/base/base2.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:async'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_flowermusic/main/dialog/dialog.dart'; 6 | import 'package:flutter_flowermusic/viewmodel/home/home_provide.dart'; 7 | import 'package:provider/provider.dart'; 8 | import 'package:rxdart/rxdart.dart'; 9 | 10 | 11 | 12 | abstract class PageProvideNode2 extends StatelessWidget { 13 | /// The values made available to the [child]. 14 | BaseProvide2 mProviders = BaseProvide2(); 15 | 16 | Widget buildContent(BuildContext context); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return ChangeNotifierProvider.value( 21 | value: mProviders, 22 | child: buildContent(context), 23 | ); 24 | } 25 | } 26 | 27 | /// BaseProvide 28 | class BaseProvide2 with ChangeNotifier { 29 | 30 | CompositeSubscription compositeSubscription = CompositeSubscription(); 31 | 32 | 33 | /// add [StreamSubscription] to [compositeSubscription] 34 | /// 35 | /// 在 [dispose]的时候能进行取消 36 | addSubscription(StreamSubscription subscription){ 37 | compositeSubscription.add(subscription); 38 | } 39 | 40 | @override 41 | void dispose() { 42 | super.dispose(); 43 | compositeSubscription.dispose(); 44 | } 45 | } -------------------------------------------------------------------------------- /lib/base/const_config.dart: -------------------------------------------------------------------------------- 1 | class ConstConfig { 2 | // 用户数据 3 | static String CURRENT_USERDATA = '_currentUserData_'; 4 | /// 搜索历史 5 | static String SEARCH_HISTORY_LIST = '_searchHistory_'; 6 | /// 歌曲模式 7 | static String MUSIC_MODE = '_musicmode_'; 8 | 9 | } -------------------------------------------------------------------------------- /lib/data/base.dart: -------------------------------------------------------------------------------- 1 | //library baseresponse; 2 | 3 | import 'package:json_annotation/json_annotation.dart'; 4 | 5 | part 'base.g.dart'; 6 | 7 | @JsonSerializable() 8 | class BaseResponse { 9 | int code; 10 | int total; 11 | int totalPage; 12 | dynamic data; 13 | String message; 14 | bool success; 15 | 16 | BaseResponse({this.code, this.total, this.totalPage, this.data, this.message, this.success}); 17 | 18 | factory BaseResponse.fromJson(Map json) => 19 | _$BaseResponseFromJson(json); 20 | 21 | Map toJson() => 22 | _$BaseResponseToJson(this); 23 | } 24 | 25 | @JsonSerializable() 26 | class CommonResponse { 27 | int code; 28 | dynamic data; 29 | String message; 30 | bool success; 31 | 32 | CommonResponse(); 33 | 34 | factory CommonResponse.fromJson(Map json) => _$CommonResponseFromJson(json); 35 | 36 | Map toJson() => _$CommonResponseToJson(this); 37 | } 38 | -------------------------------------------------------------------------------- /lib/data/base.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'base.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | BaseResponse _$BaseResponseFromJson(Map json) { 10 | return BaseResponse( 11 | code: json['code'] as int, 12 | total: json['total'] as int, 13 | totalPage: json['totalPage'] as int, 14 | data: json['data'], 15 | message: json['message'] as String, 16 | success: json['success'] as bool); 17 | } 18 | 19 | Map _$BaseResponseToJson(BaseResponse instance) => 20 | { 21 | 'code': instance.code, 22 | 'total': instance.total, 23 | 'totalPage': instance.totalPage, 24 | 'data': instance.data, 25 | 'message': instance.message, 26 | 'success': instance.success 27 | }; 28 | 29 | CommonResponse _$CommonResponseFromJson(Map json) { 30 | return CommonResponse() 31 | ..code = json['code'] as int 32 | ..data = json['data'] 33 | ..message = json['message'] as String 34 | ..success = json['success'] as bool; 35 | } 36 | 37 | Map _$CommonResponseToJson(CommonResponse instance) => 38 | { 39 | 'code': instance.code, 40 | 'data': instance.data, 41 | 'message': instance.message, 42 | 'success': instance.success 43 | }; 44 | -------------------------------------------------------------------------------- /lib/data/comment.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter_flowermusic/data/user.dart'; 3 | import 'package:json_annotation/json_annotation.dart'; 4 | 5 | part 'comment.g.dart'; 6 | 7 | @JsonSerializable() 8 | class Comment { 9 | User user; 10 | String content; 11 | String creatDateStr; 12 | int niceCount; 13 | String songId; 14 | @JsonKey(name: '_id', nullable: true) 15 | String id; 16 | 17 | Comment({this.user, this.content, this.creatDateStr, this.niceCount, this.songId}); 18 | 19 | factory Comment.fromJson(Map json) => 20 | _$CommentFromJson(json); 21 | 22 | Map toJson() => 23 | _$CommentToJson(this); 24 | } -------------------------------------------------------------------------------- /lib/data/comment.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'comment.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Comment _$CommentFromJson(Map json) { 10 | return Comment( 11 | user: json['user'] == null 12 | ? null 13 | : User.fromJson(json['user'] as Map), 14 | content: json['content'] as String, 15 | creatDateStr: json['creatDateStr'] as String, 16 | niceCount: json['niceCount'] as int, 17 | songId: json['songId'] as String) 18 | ..id = json['_id'] as String; 19 | } 20 | 21 | Map _$CommentToJson(Comment instance) => { 22 | 'user': instance.user, 23 | 'content': instance.content, 24 | 'creatDateStr': instance.creatDateStr, 25 | 'niceCount': instance.niceCount, 26 | 'songId': instance.songId, 27 | '_id': instance.id 28 | }; 29 | -------------------------------------------------------------------------------- /lib/data/song.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'song.g.dart'; 4 | 5 | @JsonSerializable() 6 | class Song { 7 | 8 | String _id; 9 | String imgUrl; 10 | String lrcUrl; 11 | String size; 12 | String singer; 13 | String songUrl; 14 | String title; 15 | String duration; 16 | String imgUrl_s; 17 | String desc; 18 | bool isFav; 19 | 20 | @JsonKey(name: '_id', nullable: true) 21 | String id; 22 | 23 | /// 自定义属性 24 | bool isExpaned = false; 25 | 26 | Song(); 27 | 28 | factory Song.fromJson(Map json) => 29 | _$SongFromJson(json); 30 | 31 | Map toJson() => 32 | _$SongToJson(this); 33 | } 34 | -------------------------------------------------------------------------------- /lib/data/song.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'song.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Song _$SongFromJson(Map json) { 10 | return Song() 11 | ..imgUrl = json['imgUrl'] as String 12 | ..lrcUrl = json['lrcUrl'] as String 13 | ..size = json['size'] as String 14 | ..singer = json['singer'] as String 15 | ..songUrl = json['songUrl'] as String 16 | ..title = json['title'] as String 17 | ..duration = json['duration'] as String 18 | ..imgUrl_s = json['imgUrl_s'] as String 19 | ..desc = json['desc'] as String 20 | ..isFav = json['isFav'] as bool 21 | ..id = json['_id'] as String 22 | ..isExpaned = json['isExpaned'] as bool; 23 | } 24 | 25 | Map _$SongToJson(Song instance) => { 26 | 'imgUrl': instance.imgUrl, 27 | 'lrcUrl': instance.lrcUrl, 28 | 'size': instance.size, 29 | 'singer': instance.singer, 30 | 'songUrl': instance.songUrl, 31 | 'title': instance.title, 32 | 'duration': instance.duration, 33 | 'imgUrl_s': instance.imgUrl_s, 34 | 'desc': instance.desc, 35 | 'isFav': instance.isFav, 36 | '_id': instance.id, 37 | 'isExpaned': instance.isExpaned 38 | }; 39 | -------------------------------------------------------------------------------- /lib/data/user.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'user.g.dart'; 4 | 5 | @JsonSerializable() 6 | class User { 7 | 8 | String userId; 9 | String userName; 10 | String passWord; 11 | String creatDateStr; 12 | String creatDate; 13 | String token; 14 | String email; 15 | @JsonKey(nullable: true) 16 | String userPic = ''; 17 | User(); 18 | 19 | factory User.fromJson(Map json) => 20 | _$UserFromJson(json); 21 | 22 | Map toJson() => 23 | _$UserToJson(this); 24 | } 25 | -------------------------------------------------------------------------------- /lib/data/user.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'user.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | User _$UserFromJson(Map json) { 10 | return User() 11 | ..userId = json['userId'] as String 12 | ..userName = json['userName'] as String 13 | ..passWord = json['passWord'] as String 14 | ..creatDateStr = json['creatDateStr'] as String 15 | ..creatDate = json['creatDate'] as String 16 | ..token = json['token'] as String 17 | ..email = json['email'] as String 18 | ..userPic = json['userPic'] as String; 19 | } 20 | 21 | Map _$UserToJson(User instance) => { 22 | 'userId': instance.userId, 23 | 'userName': instance.userName, 24 | 'passWord': instance.passWord, 25 | 'creatDateStr': instance.creatDateStr, 26 | 'creatDate': instance.creatDate, 27 | 'token': instance.token, 28 | 'email': instance.email, 29 | 'userPic': instance.userPic 30 | }; 31 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_flowermusic/app.dart'; 3 | import 'package:flutter_flowermusic/base/app_config.dart'; 4 | import 'package:provider/provider.dart'; 5 | 6 | void main() async { 7 | WidgetsFlutterBinding.ensureInitialized(); 8 | Provider.debugCheckInvalidValueType = null; 9 | await AppConfig.init(); 10 | runApp(MyApp()); 11 | } 12 | 13 | class MyApp extends StatelessWidget { 14 | // This widget is the root of your application. 15 | @override 16 | Widget build(BuildContext context) { 17 | return MaterialApp( 18 | home: new App(), 19 | debugShowCheckedModeBanner: false, // 去除debug旗标 20 | theme: new ThemeData( 21 | primaryColor: new Color.fromRGBO(255, 255, 255, 1), 22 | highlightColor: AppConfig.backgroundColor, 23 | splashColor: AppConfig.backgroundColor, 24 | hintColor: Colors.grey, 25 | scaffoldBackgroundColor:new Color.fromRGBO(255, 255, 255, 1), //设置页面背景颜色 26 | // bottomAppBarColor: new Color.fromRGBO(19, 35, 63, 1), //设置底部导航的背景色 27 | // backgroundColor: new Color.fromRGBO(19, 35, 63, 1), 28 | // indicatorColor: new Color.fromRGBO(19, 35, 63, 1), //设置tab指示器颜色 29 | 30 | primaryIconTheme: new IconThemeData(color: Colors.black),//主要icon样式,如头部返回icon按钮 31 | iconTheme: new IconThemeData(size: 18.0, color: Colors.white), //设置icon样式 32 | 33 | textTheme: new TextTheme( 34 | title: new TextStyle( 35 | color: Colors.white, 36 | fontSize: 16, 37 | fontWeight: FontWeight.normal 38 | ), 39 | subtitle: new TextStyle( 40 | color: Colors.white, 41 | fontSize: 14, 42 | fontWeight: FontWeight.normal 43 | ) 44 | ), 45 | primaryTextTheme: new TextTheme( //设置文本样式 46 | title: new TextStyle( 47 | color: Colors.black, 48 | fontSize: 18.0, 49 | fontWeight: FontWeight.bold)), 50 | 51 | tabBarTheme: new TabBarTheme( 52 | labelColor: Colors.black, 53 | unselectedLabelColor: Colors.grey) 54 | ), 55 | ); 56 | } 57 | } -------------------------------------------------------------------------------- /lib/main/dialog/dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | Future _showAlert({BuildContext context, Widget child}) => showDialog( 5 | context: context, 6 | barrierDismissible: false, 7 | builder: (BuildContext context) => child, 8 | ); 9 | /** 10 | * onlyPositive : 只有确定按钮 11 | */ 12 | Future showAlert(BuildContext context, {String title, String negativeText = "取消", String positiveText = "确定", bool onlyPositive = false}) => 13 | _showAlert( 14 | context: context, 15 | child: CupertinoAlertDialog( 16 | title: Text(title), 17 | actions: _buildAlertActions(context, onlyPositive, negativeText, positiveText), 18 | ), 19 | ); 20 | 21 | List _buildAlertActions(BuildContext context, bool onlyPositive, String negativeText, String positiveText) { 22 | if (onlyPositive) { 23 | return [ 24 | CupertinoDialogAction( 25 | child: Text( 26 | positiveText, 27 | style: TextStyle(fontSize: 18.0), 28 | ), 29 | isDefaultAction: true, 30 | onPressed: () { 31 | Navigator.pop(context, true); 32 | }, 33 | ), 34 | ]; 35 | } else { 36 | return [ 37 | CupertinoDialogAction( 38 | child: Text( 39 | negativeText, 40 | style: TextStyle(color: Color(0xFF71747E), fontSize: 18.0), 41 | ), 42 | isDestructiveAction: true, 43 | onPressed: () { 44 | Navigator.pop(context, false); 45 | }, 46 | ), 47 | CupertinoDialogAction( 48 | child: Text( 49 | positiveText, 50 | style: TextStyle(fontSize: 18.0), 51 | ), 52 | isDefaultAction: true, 53 | onPressed: () { 54 | Navigator.pop(context, true); 55 | }, 56 | ), 57 | ]; 58 | } 59 | } 60 | 61 | 62 | /// 显示loading框 , 隐藏调用 Navigator.pop(context) 63 | Future _showLoadingDialog(BuildContext c, LoadingDialog loading, {bool cancelable = true}) => 64 | showDialog(context: c, barrierDismissible: cancelable, builder: (BuildContext c) => loading); 65 | 66 | class LoadingDialog extends CupertinoAlertDialog { 67 | BuildContext currentContext; 68 | 69 | show(BuildContext context) { 70 | showing = true; 71 | _showLoadingDialog(context, this).then((r) { 72 | showing = false; 73 | }); 74 | } 75 | 76 | bool showing; 77 | 78 | hide(BuildContext context) { 79 | if (showing) { 80 | Navigator.removeRoute(context, ModalRoute.of(currentContext)); 81 | } 82 | } 83 | 84 | @override 85 | Widget build(BuildContext context) { 86 | currentContext = context; 87 | return WillPopScope( 88 | onWillPop: () => Future.value(!bool.fromEnvironment("dart.vm.product")), 89 | child: LayoutBuilder( 90 | builder: (BuildContext context, BoxConstraints constraints) { 91 | return Center( 92 | child: Container( 93 | width: 120, 94 | height: 120, 95 | color: Colors.transparent, 96 | child: const Center( 97 | child: SizedBox( 98 | width: 45.0, 99 | height: 45.0, 100 | child: const CircularProgressIndicator(strokeWidth: 2.0) 101 | ), 102 | ), 103 | ), 104 | ); 105 | }, 106 | ), 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/main/refresh/classic_indicator.dart: -------------------------------------------------------------------------------- 1 | /** 2 | Author: Jpeng 3 | Email: peng8350@gmail.com 4 | createTime:2018-05-14 17:39 5 | */ 6 | 7 | import 'package:flutter/material.dart' hide RefreshIndicator; 8 | import 'package:flutter/widgets.dart'; 9 | import 'package:flutter_flowermusic/main/refresh/smart_refresher.dart'; 10 | 11 | enum IconPosition { left, right, top, bottom } 12 | 13 | class ClassicIndicator extends Indicator { 14 | final String releaseText, 15 | idleText, 16 | refreshingText, 17 | completeText, 18 | failedText, 19 | noDataText; 20 | 21 | final Widget releaseIcon, 22 | idleIcon, 23 | refreshingIcon, 24 | completeIcon, 25 | failedIcon, 26 | noMoreIcon; 27 | 28 | final double height; 29 | 30 | final double spacing; 31 | 32 | final IconPosition iconPos; 33 | 34 | final TextStyle textStyle; 35 | 36 | const ClassicIndicator({ 37 | @required RefreshStatus mode = RefreshStatus.init, 38 | Key key, 39 | this.textStyle: const TextStyle(color: const Color(0xff555555)), 40 | this.releaseText: '松手即可刷新', 41 | this.refreshingText: '刷新中...', 42 | this.completeText: '刷新完成', 43 | this.noDataText: '没有更多数据了', 44 | this.height: 60.0, 45 | this.noMoreIcon: const Icon(Icons.clear, color: Colors.grey), 46 | this.failedText: '刷新失败', 47 | this.idleText: '下拉刷新', 48 | this.iconPos: IconPosition.left, 49 | this.spacing: 15.0, 50 | this.refreshingIcon: const CircularProgressIndicator(strokeWidth: 2.0), 51 | this.failedIcon: const Icon(Icons.clear, color: Colors.grey), 52 | this.completeIcon: const Icon(Icons.done, color: Colors.grey), 53 | this.idleIcon = const Icon(Icons.arrow_downward, color: Colors.grey), 54 | this.releaseIcon = const Icon(Icons.arrow_upward, color: Colors.grey), 55 | }) : super(key: key, mode: mode); 56 | 57 | @override 58 | State createState() { 59 | // TODO: implement createState 60 | return new _ClassicIndicatorState(); 61 | } 62 | } 63 | 64 | class _ClassicIndicatorState extends State { 65 | Widget _buildText() { 66 | return new Text( 67 | widget.mode == RefreshStatus.canRefresh 68 | ? widget.releaseText 69 | : widget.mode == RefreshStatus.completed 70 | ? widget.completeText 71 | : widget.mode == RefreshStatus.failed 72 | ? widget.failedText 73 | : widget.mode == RefreshStatus.refreshing 74 | ? widget.refreshingText 75 | : widget.mode == RefreshStatus.noMore 76 | ? widget.noDataText 77 | : widget.mode == RefreshStatus.init 78 | ? '' 79 | : widget.idleText, 80 | style: widget.textStyle); 81 | } 82 | 83 | Widget _buildIcon() { 84 | Widget icon = widget.mode == RefreshStatus.canRefresh 85 | ? widget.releaseIcon 86 | : widget.mode == RefreshStatus.noMore 87 | ? widget.noMoreIcon 88 | : widget.mode == RefreshStatus.idle 89 | ? widget.idleIcon 90 | : widget.mode == RefreshStatus.completed 91 | ? widget.completeIcon 92 | : widget.mode == RefreshStatus.failed 93 | ? widget.failedIcon 94 | : widget.mode == RefreshStatus.init 95 | ? new Container() 96 | : new SizedBox( 97 | width: 15.0, 98 | height: 15.0, 99 | child: widget.refreshingIcon, 100 | ); 101 | return icon; 102 | } 103 | 104 | @override 105 | Widget build(BuildContext context) { 106 | // TODO: implement buildContent 107 | Widget textWidget = _buildText(); 108 | Widget iconWidget = _buildIcon(); 109 | List children = [ 110 | iconWidget, 111 | new Container( 112 | width: widget.spacing, 113 | height: widget.spacing, 114 | ), 115 | textWidget 116 | ]; 117 | Widget container = (widget.iconPos == IconPosition.top || 118 | widget.iconPos == IconPosition.bottom) 119 | ? new Column( 120 | mainAxisAlignment: MainAxisAlignment.center, 121 | verticalDirection: widget.iconPos == IconPosition.top 122 | ? VerticalDirection.down 123 | : VerticalDirection.up, 124 | children: children, 125 | ) 126 | : new Row( 127 | textDirection: widget.iconPos == IconPosition.right 128 | ? TextDirection.rtl 129 | : TextDirection.ltr, 130 | mainAxisAlignment: MainAxisAlignment.center, 131 | children: children, 132 | ); 133 | return new Container( 134 | height: widget.height+0.0, 135 | child: new Center( 136 | child: container, 137 | ), 138 | ); 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /lib/main/refresh/default_constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_flowermusic/main/refresh/smart_refresher.dart'; 3 | 4 | /* 5 | Author: Jpeng 6 | Email: peng8350@gmail.com 7 | createTime:2018-05-17 10:39 8 | */ 9 | 10 | 11 | typedef void OnRefresh(); 12 | typedef void FooterRefresh(); 13 | typedef void OnOffsetChange(bool up, double offset); 14 | typedef Widget IndicatorBuilder(BuildContext context, RefreshStatus mode); 15 | 16 | const int default_completeDuration = 800; 17 | 18 | const double default_refresh_triggerDistance = 100.0; 19 | 20 | const double default_load_triggerDistance = 15.0; 21 | 22 | const double default_height = 60.0; 23 | 24 | const double default_headerExceed = 100.0; 25 | 26 | const double default_footerExceed = 100.0; 27 | 28 | const bool default_AutoLoad = true; 29 | 30 | const bool default_enablePullDown = true; 31 | 32 | const bool default_enablePullUp = false; 33 | 34 | const bool default_BottomWhenBuild = true; 35 | 36 | const bool default_enableOverScroll = true; 37 | 38 | const int spaceAnimateMill=300; 39 | 40 | const double minSpace = 0.000001; -------------------------------------------------------------------------------- /lib/main/refresh/footer_indicator.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart' hide RefreshIndicator; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_flowermusic/main/refresh/smart_refresher.dart'; 5 | 6 | class FooterIndicator extends Indicator { 7 | final String releaseText, 8 | idleText, 9 | refreshingText, 10 | completeText, 11 | failedText, 12 | noDataText; 13 | 14 | final Widget releaseIcon, 15 | refreshingIcon, 16 | completeIcon, 17 | failedIcon, 18 | noMoreIcon; 19 | 20 | final double height; 21 | 22 | final double spacing; 23 | 24 | final TextStyle textStyle; 25 | 26 | const FooterIndicator( { 27 | @required RefreshStatus mode = RefreshStatus.init, 28 | Key key, 29 | this.textStyle: const TextStyle(color: const Color(0xff555555)), 30 | this.refreshingText: '加载中...', 31 | this.noDataText: '没有更多数据了', 32 | this.height: 60.0, 33 | this.releaseText: '', 34 | this.completeText: '', 35 | this.noMoreIcon: const Icon(Icons.clear, color: Colors.grey), 36 | this.failedText: '加载失败', 37 | this.idleText: '加载中...', 38 | this.spacing: 15.0, 39 | this.refreshingIcon: const CircularProgressIndicator(strokeWidth: 2.0), 40 | this.failedIcon: const Icon(Icons.clear, color: Colors.grey), 41 | this.completeIcon: const Icon(Icons.done, color: Colors.grey), 42 | this.releaseIcon = const Icon(Icons.arrow_upward, color: Colors.grey), 43 | }) : super(key: key, mode: mode); 44 | 45 | @override 46 | State createState() { 47 | // TODO: implement createState 48 | return new _FooterIndicatorState(); 49 | } 50 | } 51 | 52 | class _FooterIndicatorState extends State { 53 | Widget _buildText() { 54 | return new Text( 55 | widget.mode == RefreshStatus.canRefresh 56 | ? widget.releaseText 57 | : widget.mode == RefreshStatus.completed 58 | ? widget.completeText 59 | : widget.mode == RefreshStatus.failed 60 | ? widget.failedText 61 | : widget.mode == RefreshStatus.refreshing 62 | ? widget.refreshingText 63 | : widget.mode == RefreshStatus.noMore 64 | ? widget.noDataText 65 | : widget.mode == RefreshStatus.init 66 | ? '' 67 | : widget.idleText, 68 | style: widget.textStyle); 69 | } 70 | 71 | Widget _buildIcon() { 72 | Widget icon = widget.mode == RefreshStatus.canRefresh 73 | ? widget.releaseIcon 74 | : widget.mode == RefreshStatus.noMore 75 | ? widget.noMoreIcon 76 | : widget.mode == RefreshStatus.completed 77 | ? widget.completeIcon 78 | : widget.mode == RefreshStatus.failed 79 | ? widget.failedIcon 80 | : widget.mode == RefreshStatus.init 81 | ? new Container() 82 | : new SizedBox( 83 | width: 15.0, 84 | height: 15.0, 85 | child: widget.refreshingIcon, 86 | ); 87 | return icon; 88 | } 89 | 90 | @override 91 | Widget build(BuildContext context) { 92 | // TODO: implement buildContent 93 | Widget textWidget = _buildText(); 94 | Widget iconWidget = _buildIcon(); 95 | List children = [ 96 | iconWidget, 97 | new Container( 98 | width: widget.spacing, 99 | height: widget.spacing, 100 | ), 101 | textWidget 102 | ]; 103 | Widget container = new Row( 104 | textDirection: TextDirection.ltr, 105 | mainAxisAlignment: MainAxisAlignment.center, 106 | children: children, 107 | ); 108 | return new Container( 109 | height: widget.height+0.0, 110 | child: new Center( 111 | child: container, 112 | ), 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/main/refresh/indicator_config.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Jpeng 3 | Email: peng8350@gmail.com 4 | createTime:2018-05-14 15:39 5 | */ 6 | 7 | import 'default_constants.dart'; 8 | 9 | /* 10 | * This will use to configure the Wrapper action 11 | */ 12 | abstract class Config { 13 | // How many distances should be dragged to trigger refresh 14 | final double triggerDistance; 15 | 16 | const Config({this.triggerDistance}); 17 | } 18 | 19 | class RefreshConfig extends Config { 20 | // display time of success or failed 21 | final int completeDuration; 22 | // emptySpace height 23 | final double height; 24 | 25 | const RefreshConfig( 26 | {this.height: default_height, 27 | double triggerDistance: default_refresh_triggerDistance, 28 | this.completeDuration: default_completeDuration}) 29 | : super(triggerDistance: triggerDistance); 30 | } 31 | 32 | class LoadConfig extends Config { 33 | // if autoLoad when touch outside 34 | final bool autoLoad; 35 | // Whether the interface is at the bottom when the interface is loaded 36 | final bool bottomWhenBuild; 37 | 38 | final bool enableOverScroll; 39 | 40 | 41 | const LoadConfig({ 42 | this.autoLoad: default_AutoLoad, 43 | this.bottomWhenBuild:default_BottomWhenBuild, 44 | this.enableOverScroll :default_enableOverScroll, 45 | double triggerDistance: default_load_triggerDistance, 46 | }) : super(triggerDistance: triggerDistance); 47 | } 48 | -------------------------------------------------------------------------------- /lib/main/refresh/pull_to_refresh.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Jpeng 3 | Email: peng8350@gmail.com 4 | createTime:2018-05-03 15:39 5 | */ 6 | 7 | library pulltorefresh; 8 | 9 | export './smart_refresher.dart'; 10 | export './classic_indicator.dart'; 11 | export './indicator_config.dart'; -------------------------------------------------------------------------------- /lib/main/refresh/refresh_physics.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Jpeng 3 | Email: peng8350@gmail.com 4 | createTime:2018-05-02 14:39 5 | */ 6 | 7 | import 'package:flutter/widgets.dart'; 8 | import 'dart:math' as math; 9 | import 'package:flutter/material.dart'; 10 | 11 | /* 12 | this class is copy from BouncingScrollPhysics, 13 | because it doesn't fit my idea, 14 | Fixed the problem that child parts could not be dragged without data. 15 | */ 16 | class RefreshScrollPhysics extends ScrollPhysics { 17 | 18 | final bool enableOverScroll; 19 | 20 | /// Creates scroll physics that bounce back from the edge. 21 | const RefreshScrollPhysics({ScrollPhysics parent,this.enableOverScroll:true}) : super(parent: parent); 22 | 23 | @override 24 | RefreshScrollPhysics applyTo(ScrollPhysics ancestor) { 25 | return new RefreshScrollPhysics(parent: buildParent(ancestor),enableOverScroll: enableOverScroll); 26 | } 27 | 28 | /// The multiple applied to overscroll to make it appear that scrolling past 29 | /// the edge of the scrollable contents is harder than scrolling the list. 30 | /// This is done by reducing the ratio of the scroll effect output vs the 31 | /// scroll gesture input. 32 | /// 33 | /// This factor starts at 0.52 and progressively becomes harder to overscroll 34 | /// as more of the area past the edge is dragged in (represented by an increasing 35 | /// `overscrollFraction` which starts at 0 when there is no overscroll). 36 | double frictionFactor(double overscrollFraction) => 37 | 0.52 * math.pow(1 - overscrollFraction, 2); 38 | 39 | @override 40 | bool shouldAcceptUserOffset(ScrollMetrics position) { 41 | // TODO: implement shouldAcceptUserOffset 42 | return true; 43 | } 44 | 45 | @override 46 | double applyPhysicsToUserOffset(ScrollMetrics position, double offset) { 47 | assert(offset != 0.0); 48 | assert(position.minScrollExtent <= position.maxScrollExtent); 49 | 50 | if (!position.outOfRange) return offset; 51 | 52 | final double overscrollPastStart = 53 | math.max(position.minScrollExtent - position.pixels, 0.0); 54 | final double overscrollPastEnd = 55 | math.max(position.pixels - position.maxScrollExtent, 0.0); 56 | final double overscrollPast = 57 | math.max(overscrollPastStart, overscrollPastEnd); 58 | final bool easing = (overscrollPastStart > 0.0 && offset < 0.0) || 59 | (overscrollPastEnd > 0.0 && offset > 0.0); 60 | 61 | final double friction = easing 62 | // Apply less resistance when easing the overscroll vs tensioning. 63 | ? frictionFactor( 64 | (overscrollPast - offset.abs()) / position.viewportDimension) 65 | : frictionFactor(overscrollPast / position.viewportDimension); 66 | final double direction = offset.sign; 67 | return direction * _applyFriction(overscrollPast, offset.abs(), friction); 68 | } 69 | 70 | static double _applyFriction( 71 | double extentOutside, double absDelta, double gamma) { 72 | 73 | assert(absDelta > 0); 74 | double total = 0.0; 75 | if (extentOutside > 0) { 76 | final double deltaToLimit = extentOutside / gamma; 77 | if (absDelta < deltaToLimit) return absDelta * gamma; 78 | total += extentOutside; 79 | absDelta -= deltaToLimit; 80 | } 81 | return total + absDelta; 82 | } 83 | 84 | @override 85 | double applyBoundaryConditions(ScrollMetrics position, double value) { 86 | if(!enableOverScroll) { 87 | if (value < position.pixels && 88 | position.pixels <= position.minScrollExtent) // underscroll 89 | return value - position.pixels; 90 | if (value < position.minScrollExtent && 91 | position.minScrollExtent < position.pixels) // hit top edge 92 | return value - position.minScrollExtent; 93 | if (position.maxScrollExtent <= position.pixels && 94 | position.pixels < value) // overscroll 95 | return value - position.pixels; 96 | 97 | if (position.pixels < position.maxScrollExtent && 98 | position.maxScrollExtent < value) // hit bottom edge 99 | return value - position.maxScrollExtent; 100 | } 101 | return 0.0; 102 | 103 | } 104 | 105 | @override 106 | Simulation createBallisticSimulation( 107 | ScrollMetrics position, double velocity) { 108 | final Tolerance tolerance = this.tolerance; 109 | if (velocity.abs() >= tolerance.velocity || position.outOfRange) { 110 | return new BouncingScrollSimulation( 111 | spring: spring, 112 | position: position.pixels, 113 | velocity: velocity * 114 | 0.91, // TODO(abarth): We should move this constant closer to the drag end. 115 | leadingExtent: position.minScrollExtent, 116 | trailingExtent: position.maxScrollExtent, 117 | tolerance: tolerance, 118 | ); 119 | } 120 | return null; 121 | } 122 | 123 | // The ballistic simulation here decelerates more slowly than the one for 124 | // ClampingScrollPhysics so we require a more deliberate input gesture 125 | // to trigger a fling. 126 | @override 127 | double get minFlingVelocity => 2.5 * 2.0; 128 | 129 | // Methodology: 130 | // 1- Use https://github.com/flutter/scroll_overlay to test with Flutter and 131 | // platform scroll views superimposed. 132 | // 2- Record incoming speed and make rapid flings in the test app. 133 | // 3- If the scrollables stopped overlapping at any moment, adjust the desired 134 | // output value of this function at that input speed. 135 | // 4- Feed new input/output set into a power curve fitter. Change function 136 | // and repeat from 2. 137 | // 5- Repeat from 2 with medium and slow flings. 138 | /// Momentum build-up function that mimics iOS's scroll speed increase with repeated flings. 139 | /// 140 | /// The velocity of the last fling is not an important factor. Existing speed 141 | /// and (related) time since last fling are factors for the velocity transfer 142 | /// calculations. 143 | @override 144 | double carriedMomentum(double existingVelocity) { 145 | return existingVelocity.sign * 146 | math.min(0.000816 * math.pow(existingVelocity.abs(), 1.967).toDouble(), 147 | 40000.0); 148 | } 149 | 150 | // Eyeballed from observation to counter the effect of an unintended scroll 151 | // from the natural motion of lifting the finger after a scroll. 152 | @override 153 | double get dragStartDistanceMotionThreshold => 3.5; 154 | } 155 | -------------------------------------------------------------------------------- /lib/main/tap/opacity_tap_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class OpacityTapWidget extends StatefulWidget { 4 | final Widget child; 5 | final Function onTap; 6 | 7 | const OpacityTapWidget({Key key, this.child, this.onTap}) : super(key: key); 8 | 9 | @override 10 | OpacityTapWidgetState createState() { 11 | return new OpacityTapWidgetState(); 12 | } 13 | } 14 | 15 | class OpacityTapWidgetState extends State { 16 | var isDown = false; 17 | @override 18 | Widget build(BuildContext context) { 19 | return GestureDetector( 20 | behavior: HitTestBehavior.opaque, 21 | child: AnimatedContainer( 22 | duration: Duration(milliseconds: 500), 23 | padding: EdgeInsets.fromLTRB(8, 8, 8, 8), 24 | child: new Opacity( 25 | opacity: isDown ? 0.5 : 1, 26 | child: widget.child, 27 | ), 28 | ), 29 | onTap: widget.onTap, 30 | onTapDown: (d) => setState(() => this.isDown = true), 31 | onTapUp: (d) => setState(() => this.isDown = false), 32 | onTapCancel: () => setState(() => this.isDown = false), 33 | ); 34 | } 35 | } -------------------------------------------------------------------------------- /lib/main/tap/tap_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TapWidget extends StatefulWidget { 4 | final Widget child; 5 | final Function onTap; 6 | 7 | const TapWidget({Key key, this.child, this.onTap}) : super(key: key); 8 | 9 | @override 10 | TapWidgetState createState() { 11 | return new TapWidgetState(); 12 | } 13 | } 14 | 15 | class TapWidgetState extends State { 16 | var isDown = false; 17 | @override 18 | Widget build(BuildContext context) { 19 | return GestureDetector( 20 | child: AnimatedContainer( 21 | duration: Duration(milliseconds: 500), 22 | foregroundDecoration: BoxDecoration( 23 | color: isDown ? Colors.white.withOpacity(0.5) : Colors.transparent, 24 | ), 25 | child: widget.child, 26 | ), 27 | onTap: widget.onTap, 28 | onTapDown: (d) => setState(() => this.isDown = true), 29 | onTapUp: (d) => setState(() => this.isDown = false), 30 | onTapCancel: () => setState(() => this.isDown = false), 31 | ); 32 | } 33 | } -------------------------------------------------------------------------------- /lib/main_provide.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'base/base2.dart'; 4 | 5 | class MainProvide extends BaseProvide2 { 6 | 7 | // 工厂模式 8 | factory MainProvide() =>_getInstance(); 9 | static MainProvide get instance => _getInstance(); 10 | static MainProvide _instance; 11 | static MainProvide _getInstance() { 12 | if (_instance == null) { 13 | _instance = new MainProvide._internal(); 14 | } 15 | return _instance; 16 | } 17 | 18 | MainProvide._internal() { 19 | // 初始化 20 | } 21 | 22 | int _currentIndex = 0; 23 | int get currentIndex => _currentIndex; 24 | set currentIndex(int currentIndex) { 25 | _currentIndex = currentIndex; 26 | notify(); 27 | } 28 | 29 | bool _showMini = false; 30 | bool get showMini => _showMini; 31 | set showMini(bool showMini) { 32 | _showMini = showMini; 33 | notify(); 34 | } 35 | 36 | notify() { 37 | notifyListeners(); 38 | } 39 | } -------------------------------------------------------------------------------- /lib/model/home_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_flowermusic/api/netUtils.dart'; 2 | import 'package:flutter_flowermusic/base/app_config.dart'; 3 | import 'package:flutter_flowermusic/data/base.dart'; 4 | import 'package:rxdart/rxdart.dart'; 5 | 6 | // 网络请求相关 7 | class HomeService { 8 | /// 获取列表 9 | Observable getSongs(dynamic request, Map query) { 10 | var url = 'api/song/list'; 11 | var response = post(url, body: request, queryParameters: query); 12 | return response; 13 | } 14 | 15 | /// 收藏歌曲 16 | Observable collectionSong(String songId) { 17 | var body = { 18 | 'userId': AppConfig.userTools.getUserId(), 19 | 'songId': songId, 20 | 'songType': 0 21 | }; 22 | var url = 'api/song/collectionSong'; 23 | var response = post(url, body: body, queryParameters: null); 24 | return response; 25 | } 26 | /// 取消收藏 27 | Observable uncollectionSong(String songId) { 28 | var body = { 29 | 'userId': AppConfig.userTools.getUserId(), 30 | 'songId': songId 31 | }; 32 | var url = 'api/song/uncollectionSong'; 33 | var response = post(url, body: body, queryParameters: null); 34 | return response; 35 | } 36 | 37 | /// 评论列表 38 | Observable commentList(int page, String songId) { 39 | var param = { 40 | 'page': page, 41 | 'pageSize': 30, 42 | 'songId': songId 43 | }; 44 | var url = 'api/song/comment/list'; 45 | var response = get(url, params: param); 46 | return response; 47 | } 48 | /// 发布评论 49 | Observable sendComment(String content, String songId) { 50 | var body = { 51 | 'content': content, 52 | 'userId': AppConfig.userTools.getUserId(), 53 | 'songId': songId 54 | }; 55 | var url = 'api/song/comment/submit'; 56 | var response = post(url, body: body); 57 | return response; 58 | } 59 | /// 点赞 60 | Observable niceComment(String commentId) { 61 | var body = { 62 | 'commentId': commentId 63 | }; 64 | var url = 'api/song/comment/nice'; 65 | var response = post(url, body: body); 66 | return response; 67 | } 68 | } 69 | 70 | 71 | class HomeRepo { 72 | final HomeService _remote = HomeService(); 73 | 74 | Observable getSongs(Map query) { 75 | return _remote.getSongs(null, query); 76 | } 77 | Observable collectionSong(String songId) { 78 | return _remote.collectionSong(songId); 79 | } 80 | Observable uncollectionSong(String songId) { 81 | return _remote.uncollectionSong(songId); 82 | } 83 | 84 | Observable commentList(int page, String songId) { 85 | return _remote.commentList(page, songId); 86 | } 87 | Observable sendComment(String content, String songId) { 88 | return _remote.sendComment(content, songId); 89 | } 90 | 91 | Observable niceComment(String commentId) { 92 | return _remote.niceComment(commentId); 93 | } 94 | } -------------------------------------------------------------------------------- /lib/model/mine_respository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter_flowermusic/api/netUtils.dart'; 3 | import 'package:flutter_flowermusic/base/app_config.dart'; 4 | import 'package:flutter_flowermusic/data/base.dart'; 5 | import 'package:rxdart/rxdart.dart'; 6 | 7 | // 网络请求相关 8 | class MineService { 9 | /// 登录 10 | Observable login(dynamic request) { 11 | var url = 'api/user/login'; 12 | var response = post(url, body: request, queryParameters: null); 13 | return response; 14 | } 15 | /// 重置密码 16 | Observable resetPassword(dynamic request) { 17 | var url = 'api/user/resetPassword'; 18 | var response = post(url, body: request, queryParameters: null); 19 | return response; 20 | } 21 | /// 获取注册协议 22 | Observable getProtocol() { 23 | var url = 'api/user/regiestRoule/get'; 24 | var response = get(url); 25 | return response; 26 | } 27 | /// 收藏列表 28 | Observable favList() { 29 | var param = { 30 | 'userId': AppConfig.userTools.getUserId(), 31 | 'songType': 3 32 | }; 33 | var url = 'api/song/collectionSongList'; 34 | var response = get(url, params: param); 35 | return response; 36 | } 37 | /// 取消收藏 38 | Observable uncollectionSong(String songId) { 39 | var body = { 40 | 'userId': AppConfig.userTools.getUserId(), 41 | 'songId': songId 42 | }; 43 | var url = 'api/song/uncollectionSong'; 44 | var response = post(url, body: body); 45 | return response; 46 | } 47 | 48 | /// 上传头像 49 | Observable uploadUserHeader(FormData body) { 50 | var param = { 51 | 'userId': AppConfig.userTools.getUserId() 52 | }; 53 | var url = 'api/user/uploadUserHeader?userId=${AppConfig.userTools.getUserId()}'; 54 | var response = post(url, body: body); 55 | return response; 56 | } 57 | 58 | /// 意见反馈 59 | Observable adviceSubmit(body) { 60 | var url = 'api/user/advice/submit'; 61 | var response = post(url, body: body); 62 | return response; 63 | } 64 | /// 上传意见返回的图片 65 | Observable uploadImg(body) { 66 | var url = 'api/user/advice/Imgs'; 67 | var response = post(url, body: body); 68 | return response; 69 | } 70 | } 71 | 72 | 73 | class MineRepo { 74 | final MineService _remote = MineService(); 75 | 76 | Observable login(dynamic request) { 77 | return _remote.login(request); 78 | } 79 | 80 | Observable resetPassword(dynamic request) { 81 | return _remote.resetPassword(request); 82 | } 83 | 84 | Observable getProtocol() { 85 | return _remote.getProtocol(); 86 | } 87 | 88 | Observable favList() { 89 | return _remote.favList(); 90 | } 91 | 92 | Observable uncollectionSong(String songId) { 93 | return _remote.uncollectionSong(songId); 94 | } 95 | 96 | Observable uploadUserHeader(FormData body) { 97 | return _remote.uploadUserHeader(body); 98 | } 99 | 100 | Observable adviceSubmit(dynamic body) { 101 | return _remote.adviceSubmit(body); 102 | } 103 | 104 | Observable uploadImg(dynamic body) { 105 | return _remote.uploadImg(body); 106 | } 107 | } -------------------------------------------------------------------------------- /lib/model/old_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_flowermusic/api/netUtils.dart'; 2 | import 'package:flutter_flowermusic/base/app_config.dart'; 3 | import 'package:flutter_flowermusic/data/base.dart'; 4 | import 'package:rxdart/rxdart.dart'; 5 | 6 | // 网络请求相关 7 | class OldService { 8 | /// 获取列表 9 | Observable getSongs(dynamic request, Map query) { 10 | var url = 'api/oldsong/list'; 11 | var response = post(url, body: request, queryParameters: query); 12 | return response; 13 | } 14 | 15 | /// 收藏歌曲 16 | Observable collectionSong(String songId) { 17 | var body = { 18 | 'userId': AppConfig.userTools.getUserId(), 19 | 'songId': songId, 20 | 'songType': 1 21 | }; 22 | var url = 'api/song/collectionSong'; 23 | var response = post(url, body: body, queryParameters: null); 24 | return response; 25 | } 26 | /// 取消收藏 27 | Observable uncollectionSong(String songId) { 28 | var body = { 29 | 'userId': AppConfig.userTools.getUserId(), 30 | 'songId': songId 31 | }; 32 | var url = 'api/song/uncollectionSong'; 33 | var response = post(url, body: body, queryParameters: null); 34 | return response; 35 | } 36 | } 37 | 38 | 39 | class OldRepo { 40 | final OldService _remote = OldService(); 41 | 42 | Observable getSongs(Map query) { 43 | return _remote.getSongs(null, query); 44 | } 45 | 46 | Observable collectionSong(String songId) { 47 | return _remote.collectionSong(songId); 48 | } 49 | Observable uncollectionSong(String songId) { 50 | return _remote.uncollectionSong(songId); 51 | } 52 | } -------------------------------------------------------------------------------- /lib/tools/app_tool.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_flowermusic/base/const_config.dart'; 2 | import 'package:flutter_flowermusic/data/song.dart'; 3 | import 'package:flutter_flowermusic/utils/common_util.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | import 'dart:convert'; 6 | 7 | 8 | class AppTools { 9 | static String SEARCH_HISTORY_LIST = '_searchHistory_'; 10 | 11 | static AppTools _instance; 12 | static Future get instance async { 13 | return await getInstance(); 14 | } 15 | static Future getInstance() async { 16 | if (_instance == null) { 17 | _instance = new AppTools(); 18 | await _instance._init(); 19 | 20 | } 21 | return _instance; 22 | } 23 | Future _init() async { 24 | _spf = await SharedPreferences.getInstance(); 25 | } 26 | static SharedPreferences _spf; 27 | 28 | static bool _beforCheck() { 29 | if (_spf == null) { 30 | return true; 31 | } 32 | return false; 33 | } 34 | 35 | /// 存储搜索关键字 36 | Future setSearchKey(String key) { 37 | if (_beforCheck()) return null; 38 | var list = this.getHistoryKeys(); 39 | if (list.contains(key)) { 40 | list.remove(key); 41 | list.insert(0, key); 42 | } else { 43 | list.insert(0, key); 44 | } 45 | return _spf.setStringList(ConstConfig.SEARCH_HISTORY_LIST, list); 46 | } 47 | /// 获取搜索历史 48 | List getHistoryKeys() { 49 | var list = _spf.getStringList(ConstConfig.SEARCH_HISTORY_LIST); 50 | if (list != null) { 51 | return list; 52 | } else { 53 | return []; 54 | } 55 | } 56 | 57 | /// 清空搜索历史 58 | Future delectSearchKey() { 59 | return _spf.setStringList(ConstConfig.SEARCH_HISTORY_LIST, []); 60 | } 61 | 62 | /// 存储歌曲播放模式 63 | Future setMusicMode(int mode) { 64 | if (_beforCheck()) return null; 65 | return _spf.setInt(ConstConfig.MUSIC_MODE, mode); 66 | } 67 | 68 | // 获取数据 69 | int getMusicMode() { 70 | var mode = _spf.getInt(ConstConfig.MUSIC_MODE); 71 | if (mode == null) { 72 | mode = 0; 73 | } 74 | return mode; 75 | } 76 | } -------------------------------------------------------------------------------- /lib/tools/audio_tool.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:audioplayers/audioplayers.dart'; 3 | import 'package:flutter_flowermusic/data/song.dart'; 4 | import 'package:rxdart/rxdart.dart'; 5 | 6 | enum AudioToolsState { 7 | beginPlay, 8 | isPlaying, 9 | isPaued, 10 | isCacheing, 11 | isStoped, 12 | isEnd, 13 | isError 14 | } 15 | 16 | class AudioTools { 17 | AudioPlayer audioPlayer = new AudioPlayer(); 18 | final stateSubject = new BehaviorSubject.seeded(AudioToolsState.isStoped); 19 | final progressSubject = new BehaviorSubject.seeded(0); 20 | final durationSubject = new BehaviorSubject.seeded(0); 21 | 22 | AudioTools() { 23 | AudioPlayer.logEnabled = false; 24 | audioPlayer.onDurationChanged.listen((Duration d) { 25 | print('Max duration: $d'); 26 | this.durationSubject.value = d.inSeconds; 27 | }); 28 | audioPlayer.onAudioPositionChanged.listen((Duration p) { 29 | this.progressSubject.value = p.inSeconds; 30 | if (p.inMilliseconds <= 200) { 31 | setPlayerState(AudioToolsState.isPlaying); 32 | } 33 | }); 34 | audioPlayer.onPlayerStateChanged.listen((AudioPlayerState s) { 35 | print('Current player state: $s'); 36 | if (s == AudioPlayerState.PAUSED) { 37 | setPlayerState(AudioToolsState.isPaued); 38 | } 39 | if (s == AudioPlayerState.STOPPED) { 40 | setPlayerState(AudioToolsState.isStoped); 41 | } 42 | }); 43 | audioPlayer.onPlayerCompletion.listen((event) { 44 | print('onPlayerCompletion'); 45 | setPlayerState(AudioToolsState.isEnd); 46 | }); 47 | audioPlayer.onPlayerError.listen((msg) { 48 | print('audioPlayer error : $msg'); 49 | setPlayerState(AudioToolsState.isError); 50 | }); 51 | } 52 | /// 播放 53 | Future play(Song song) async { 54 | if (audioPlayer.state != AudioPlayerState.STOPPED) { 55 | await audioPlayer.stop(); 56 | } 57 | var encoded = Uri.encodeFull(song.songUrl); 58 | audioPlayer.play(encoded).then((value) { 59 | if (value == 1) { 60 | setPlayerState(AudioToolsState.beginPlay); 61 | } else { 62 | } 63 | return value; 64 | }); 65 | } 66 | /// 暂停 67 | Future pause() async { 68 | audioPlayer.pause().then((value) { 69 | if (value == 1) { 70 | 71 | } else { 72 | 73 | } 74 | return value; 75 | }); 76 | } 77 | Future seek(int value) async { 78 | Duration d = Duration(seconds: value); 79 | audioPlayer.seek(d).then((value) { 80 | if (value == 1) { 81 | 82 | } else { 83 | 84 | } 85 | return value; 86 | }); 87 | } 88 | /// resume 89 | Future resume() async { 90 | audioPlayer.resume().then((value) { 91 | if (value == 1) { 92 | setPlayerState(AudioToolsState.isPlaying); 93 | } else { 94 | 95 | } 96 | return value; 97 | }); 98 | } 99 | /// 停止 100 | Future stop() async { 101 | audioPlayer.stop().then((value) { 102 | if (value == 1) { 103 | 104 | } else { 105 | 106 | } 107 | return value; 108 | }); 109 | } 110 | 111 | setPlayerState(AudioToolsState state) { 112 | stateSubject.value = state; 113 | } 114 | } -------------------------------------------------------------------------------- /lib/tools/mutual_tool.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_flowermusic/base/app_config.dart'; 4 | import 'package:flutter_flowermusic/data/song.dart'; 5 | import 'package:flutter_flowermusic/main_provide.dart'; 6 | import 'package:flutter_flowermusic/tools/audio_tool.dart'; 7 | import 'package:flutter_flowermusic/tools/player_tool.dart'; 8 | 9 | class MutualTools { 10 | // 工厂模式 11 | factory MutualTools() =>_getInstance(); 12 | static MutualTools get instance => _getInstance(); 13 | static MutualTools _instance; 14 | static MutualTools _getInstance() { 15 | if (_instance == null) { 16 | _instance = new MutualTools._internal(); 17 | } 18 | return _instance; 19 | } 20 | 21 | EventChannel eventChannel = const EventChannel("darren.com.example.flutterFlowermusic/event"); 22 | 23 | 24 | MutualTools._internal() { 25 | // 监听事件,同时发送参数12345 26 | eventChannel.receiveBroadcastStream(12345).listen(_onEvent,onError: _onError); 27 | } 28 | 29 | // 回调事件 30 | void _onEvent(Object event) { 31 | var arr = event.toString().split("&"); 32 | if (arr.first == 'progress') { 33 | var arr2 = arr.last.split('+'); 34 | var progress = arr2.first; 35 | var total = arr2.last; 36 | PlayerTools.instance.duration = int.parse(total); 37 | PlayerTools.instance.currentProgress = int.parse(progress); 38 | } 39 | if (arr.first == 'state') { 40 | if (arr.last == 'beginPlay') { 41 | PlayerTools.instance.currentState = AudioToolsState.beginPlay; 42 | 43 | } 44 | if (arr.last == 'isPlaying') { 45 | PlayerTools.instance.currentState = AudioToolsState.isPlaying; 46 | } 47 | 48 | if (arr.last == 'isCacheing') { 49 | PlayerTools.instance.currentState = AudioToolsState.isCacheing; 50 | } else { 51 | } 52 | 53 | if (arr.last == 'playPause') { 54 | PlayerTools.instance.currentState = AudioToolsState.isPaued; 55 | } 56 | if (arr.last == 'playEnd') { 57 | PlayerTools.instance.currentState = AudioToolsState.isEnd; 58 | } 59 | if (arr.last == 'isStoped') { 60 | PlayerTools.instance.currentState = AudioToolsState.isStoped; 61 | } 62 | if (arr.last == 'isError') { 63 | PlayerTools.instance.currentState = AudioToolsState.isError; 64 | } 65 | } 66 | if (arr.first == 'nextMusic') { 67 | PlayerTools.instance.nextAction(); 68 | } 69 | if (arr.first == 'preMusic') { 70 | PlayerTools.instance.preAction(); 71 | } 72 | } 73 | 74 | 75 | // 错误返回 76 | void _onError(Object error) { 77 | 78 | } 79 | 80 | // 分享 81 | share(Song song) { 82 | try{ 83 | AppConfig.platform.invokeMethod('share', song.toJson()); 84 | } catch(e){ 85 | } 86 | } 87 | // 播放歌曲 88 | beginPlay(Song song) { 89 | try{ 90 | AppConfig.platform.invokeMethod('beginPlay', song.toJson()); 91 | } catch(e){ 92 | } 93 | } 94 | // 暂停歌曲 95 | pause() { 96 | try{ 97 | AppConfig.platform.invokeMethod('pause'); 98 | } catch(e){ 99 | } 100 | } 101 | // 从暂停播放 102 | resume() { 103 | try{ 104 | AppConfig.platform.invokeMethod('resume'); 105 | } catch(e){ 106 | } 107 | } 108 | // 停止 109 | stop() { 110 | try{ 111 | AppConfig.platform.invokeMethod('stop'); 112 | } catch(e){ 113 | } 114 | } 115 | // seek 116 | seek(int value) { 117 | try{ 118 | AppConfig.platform.invokeMethod('seek', value); 119 | } catch(e){ 120 | } 121 | } 122 | // 安装apk 123 | install(String path) { 124 | try{ 125 | AppConfig.platform.invokeMethod('install', path); 126 | } catch(e){ 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /lib/tools/player_tool.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:math'; 3 | import 'package:flutter_flowermusic/base/app_config.dart'; 4 | import 'package:flutter_flowermusic/data/song.dart'; 5 | import 'package:flutter_flowermusic/main_provide.dart'; 6 | import 'package:flutter_flowermusic/tools/audio_tool.dart'; 7 | import 'package:flutter_flowermusic/utils/common_util.dart'; 8 | import 'package:rxdart/rxdart.dart'; 9 | 10 | import 'mutual_tool.dart'; 11 | 12 | class PlayerTools { 13 | final stateSubject = new BehaviorSubject.seeded(AudioToolsState.isStoped); 14 | final progressSubject = new BehaviorSubject.seeded(0); 15 | final timerDownSubject = new BehaviorSubject.seeded(''); 16 | final currentSongSubject = new BehaviorSubject.seeded(Song()); 17 | 18 | // 工厂模式 19 | factory PlayerTools() =>_getInstance(); 20 | static PlayerTools get instance => _getInstance(); 21 | static PlayerTools _instance; 22 | static PlayerTools _getInstance() { 23 | if (_instance == null) { 24 | _instance = new PlayerTools._internal(); 25 | } 26 | return _instance; 27 | } 28 | 29 | PlayerTools._internal() { 30 | this.mode = AppConfig.appTools.getMusicMode(); 31 | } 32 | 33 | List _songArr = []; 34 | List get songArr => _songArr; 35 | set songArr(List songArr) { 36 | _songArr = songArr; 37 | } 38 | 39 | int _currentPlayIndex = 0; 40 | int get currentPlayIndex => _currentPlayIndex; 41 | set currentPlayIndex(int index) { 42 | _currentPlayIndex = index; 43 | } 44 | 45 | Song _currentSong = Song(); 46 | Song get currentSong => _currentSong; 47 | set currentSong(Song currentSong) { 48 | _currentSong = currentSong; 49 | currentSongSubject.value = currentSong; 50 | } 51 | 52 | int _currentProgress = 0; 53 | int get currentProgress => _currentProgress; 54 | set currentProgress(int progress) { 55 | _currentProgress = progress; 56 | this.progressSubject.value = progress; 57 | } 58 | 59 | int _duration = 0; 60 | int get duration => _duration; 61 | set duration(int duration) { 62 | _duration = duration; 63 | } 64 | 65 | /// 0顺序 1随机 2单曲 66 | int _mode = 0; 67 | int get mode => _mode; 68 | set mode(int mode) { 69 | _mode = mode; 70 | AppConfig.appTools.setMusicMode(mode); 71 | } 72 | 73 | AudioToolsState _currentState = AudioToolsState.isStoped; 74 | AudioToolsState get currentState => _currentState; 75 | set currentState(AudioToolsState state) { 76 | _currentState = state; 77 | this.stateSubject.value = state; 78 | if (state == AudioToolsState.isEnd) { 79 | this.nextAction(true); 80 | } 81 | } 82 | 83 | // 设置数据源 84 | setSongs([List songs, int index = 0]) { 85 | 86 | this.songArr.clear(); 87 | this.songArr.addAll(songs); 88 | 89 | this.currentPlayIndex = index; 90 | if (index < songArr.length) { 91 | this.play(songArr[index]); 92 | } 93 | 94 | MainProvide.instance.showMini = true; 95 | } 96 | 97 | setIndexPlay(int index) { 98 | if (index < songArr.length) { 99 | this.play(songArr[index]); 100 | } 101 | } 102 | 103 | /// 播放 104 | play(Song song) async { 105 | this.currentSong = song; 106 | if (this.songArr.length == 0) { /// 可能是单曲 107 | this.songArr = [song]; 108 | this.currentPlayIndex = 0; 109 | MainProvide.instance.showMini = true; 110 | } 111 | this.appPlay(song); 112 | } 113 | appPlay(Song song) async { 114 | Song song_my = Song(); 115 | song_my.songUrl = song.songUrl; 116 | song_my.id = song.id; 117 | song_my.duration = song.duration; 118 | song_my.size = song.size; 119 | song_my.imgUrl = song.imgUrl; 120 | song_my.imgUrl_s = song.imgUrl_s; 121 | song_my.title = song.title; 122 | song_my.singer = song.singer; 123 | song_my.lrcUrl = song.lrcUrl; 124 | 125 | var song_url = song.songUrl; 126 | song_my.songUrl = song_url; 127 | MutualTools.instance.beginPlay(song); 128 | } 129 | /// 暂停 130 | pause() { 131 | MutualTools.instance.pause(); 132 | } 133 | resume() { 134 | MutualTools.instance.resume(); 135 | } 136 | /// 停止 137 | stop() { 138 | MutualTools.instance.stop(); 139 | } 140 | /// seek 141 | seek(int value) { 142 | MutualTools.instance.seek(value); 143 | } 144 | /// 上一首 145 | preAction() { 146 | 147 | if (this.mode == 1) { // 随机 148 | this.currentPlayIndex = Random().nextInt(this.songArr.length - 1); 149 | } else { 150 | this.currentPlayIndex -= 1; 151 | if (this.currentPlayIndex < 0) { 152 | this.currentPlayIndex = this.songArr.length - 1; 153 | } 154 | } 155 | this.play(songArr[currentPlayIndex]); 156 | } 157 | /// 下一首 158 | nextAction([bool isAutoend = false]){ 159 | if (isAutoend && this.mode == 2) { 160 | this.play(songArr[currentPlayIndex]); 161 | return; 162 | } 163 | if (this.mode == 1) { // 随机 164 | this.currentPlayIndex = Random().nextInt(this.songArr.length - 1); 165 | } else { 166 | this.currentPlayIndex += 1; 167 | if (this.currentPlayIndex >= this.songArr.length) { 168 | this.currentPlayIndex = 0; 169 | } 170 | } 171 | this.play(songArr[currentPlayIndex]); 172 | } 173 | 174 | 175 | /// 定时器 176 | Timer _countdownTimer; 177 | int _countdownNum = 0; 178 | int get countdownNum => _countdownNum; 179 | set countdownNum(int countdownNum) { 180 | _countdownNum = countdownNum; 181 | if (countdownNum > 0) { 182 | this.startTimer(); 183 | } else { 184 | this.stopTimer(); 185 | } 186 | } 187 | 188 | /// 开启定时器 189 | startTimer() { 190 | stopTimer(); 191 | _countdownTimer = new Timer.periodic(new Duration(seconds: 1), (timer) { 192 | print(timer); 193 | this.countdownNum = this.countdownNum - 1; 194 | String text = CommonUtil.dealDuration('${this.countdownNum}'); 195 | String downText = text + '后播放器关闭'; 196 | timerDownSubject.value = downText; 197 | if (this.countdownNum == 0) { 198 | this.pause(); 199 | } 200 | }); 201 | } 202 | /// 关闭定时器 203 | stopTimer() { 204 | if (_countdownTimer != null) { 205 | _countdownTimer.cancel(); 206 | _countdownTimer = null; 207 | } 208 | } 209 | } -------------------------------------------------------------------------------- /lib/tools/user_tool.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_flowermusic/base/const_config.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | import 'dart:convert'; 4 | 5 | class UserTools { 6 | static UserTools _instance; 7 | static Future get instance async { 8 | return await getInstance(); 9 | } 10 | static Future getInstance() async { 11 | if (_instance == null) { 12 | _instance = new UserTools(); 13 | await _instance._init(); 14 | 15 | } 16 | return _instance; 17 | } 18 | Future _init() async { 19 | _spf = await SharedPreferences.getInstance(); 20 | } 21 | static SharedPreferences _spf; 22 | 23 | static bool _beforCheck() { 24 | if (_spf == null) { 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | // 存储用户数据 31 | Future setUserData(Map user) { 32 | if (_beforCheck()) return null; 33 | var jsonStr = json.encode(user); 34 | return _spf.setString(ConstConfig.CURRENT_USERDATA, jsonStr); 35 | } 36 | 37 | // 获取数据 38 | dynamic getUserData() { 39 | var mapStr = _spf.getString(ConstConfig.CURRENT_USERDATA); 40 | if (mapStr != null) { 41 | var map = json.decode(mapStr); 42 | return map; 43 | } else { 44 | return null; 45 | } 46 | } 47 | 48 | String getUserId() { 49 | var userData = this.getUserData(); 50 | if (userData != null) { 51 | var user = userData as Map; 52 | return user['userId']; 53 | } else { 54 | return ''; 55 | } 56 | } 57 | 58 | String getUserToken() { 59 | var userData = this.getUserData(); 60 | if (userData != null) { 61 | var user = userData as Map; 62 | return user['token']; 63 | } else { 64 | return ''; 65 | } 66 | } 67 | 68 | // 更新用户头像 69 | Future> updateUserIcon(String iconStr) async { 70 | var userData = this.getUserData(); 71 | if (userData != null) { 72 | var user = userData as Map; 73 | user['userPic'] = iconStr; 74 | await this.setUserData(user); 75 | return user; 76 | } else { 77 | return {}; 78 | } 79 | } 80 | 81 | 82 | // 删除数据 83 | Future delectUserData() { 84 | return _spf.setString(ConstConfig.CURRENT_USERDATA, null); 85 | } 86 | } -------------------------------------------------------------------------------- /lib/utils/common_util.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | 5 | import 'package:dio/dio.dart'; 6 | import 'package:multi_image_picker/multi_image_picker.dart'; 7 | import 'package:path_provider/path_provider.dart'; 8 | import 'package:uuid/uuid.dart'; 9 | 10 | class CommonUtil { 11 | static padNum(String pad) { 12 | var num = '${(double.parse(pad) / 60).toInt()}'; 13 | var len = num.toString().length; 14 | while (len < 2) { 15 | num = '0' + num; 16 | len++; 17 | } 18 | return num; 19 | } 20 | 21 | static dealDuration(String duration) { 22 | var ge = '${(double.parse(duration) % 60).toInt()}'; 23 | var miao = '00'; 24 | if (ge.length == 1) { 25 | miao = '0' + ge; 26 | } else { 27 | miao = ge; 28 | } 29 | return padNum(duration) + ':$miao'; 30 | } 31 | 32 | static isPassword(String password) { 33 | return new RegExp(r'^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,12}$').hasMatch(password); 34 | } 35 | 36 | static isEmail(String email) { 37 | return new RegExp(r'^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$').hasMatch(email); 38 | } 39 | 40 | // 处理图片 41 | static Future clickIcon(int count) async { 42 | FormData formData; 43 | List files = []; 44 | try { 45 | List resultList = await MultiImagePicker.pickImages( 46 | maxImages: count, 47 | enableCamera: true 48 | ); 49 | if (resultList.length > 0) { 50 | for(int i = 0; i< resultList.length; i ++) { 51 | Asset asset = resultList[i]; 52 | ByteData byteData = await asset.requestThumbnail(200, 200); 53 | List imageData = byteData.buffer.asUint8List(); 54 | 55 | //获得一个uuud码用于给图片命名 56 | final String uuid = Uuid().v1(); 57 | //获得应用临时目录路径 58 | final Directory _directory = await getTemporaryDirectory(); 59 | final Directory _imageDirectory = 60 | await new Directory('${_directory.path}/image/') 61 | .create(recursive: true); 62 | var path = _imageDirectory.path; 63 | print('本次获得路径:${_imageDirectory.path}'); 64 | //将压缩的图片暂时存入应用缓存目录 65 | File imageFile = new File('${path}originalImage_$uuid.png') 66 | ..writeAsBytesSync(imageData); 67 | print(imageFile.path); 68 | var file = new UploadFileInfo(imageFile, '${path}originalImage_$uuid.png', contentType: ContentType.parse("image/png")); 69 | files.add(file); 70 | }; 71 | FormData formData = new FormData.from({ 72 | 'file': files 73 | }); 74 | return formData; 75 | } else { 76 | return formData; 77 | } 78 | } catch (e) { 79 | print(e.message); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /lib/view/mine/author_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:webview_flutter/webview_flutter.dart'; 3 | 4 | class AuthorPage extends StatefulWidget { 5 | 6 | @override 7 | _AuthorPageState createState() => _AuthorPageState(); 8 | } 9 | 10 | class _AuthorPageState extends State { 11 | @override 12 | void initState() { 13 | super.initState(); 14 | } 15 | 16 | @override 17 | void dispose() { 18 | super.dispose(); 19 | } 20 | 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | appBar: AppBar( 26 | title: Text('关于作者'), 27 | ), 28 | body: Stack( 29 | children: [ 30 | new WebView( 31 | initialUrl: 'http://www.darrenblog.cn', // 加载的url 32 | onWebViewCreated: (WebViewController web) { 33 | // webview 创建调用, 34 | web.canGoBack().then((res){ 35 | print(res); // 是否能返回上一级 36 | }); 37 | web.currentUrl().then((url){ 38 | print(url);// 返回当前url 39 | }); 40 | web.canGoForward().then((res){ 41 | print(res); //是否能前进 42 | }); 43 | }, 44 | onPageFinished: (String value) { 45 | // webview 页面加载调用 46 | print('webview 页面加载调用onPageFinished'); 47 | print(value); 48 | }, 49 | ) 50 | ], 51 | ), 52 | ); 53 | } 54 | } -------------------------------------------------------------------------------- /lib/view/mine/collection_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_flowermusic/base/app_config.dart'; 4 | import 'package:flutter_flowermusic/base/base.dart'; 5 | import 'package:flutter_flowermusic/data/song.dart'; 6 | import 'package:flutter_flowermusic/main/dialog/dialog.dart'; 7 | import 'package:flutter_flowermusic/main/refresh/smart_refresher.dart'; 8 | import 'package:flutter_flowermusic/utils/common_util.dart'; 9 | import 'package:flutter_flowermusic/viewmodel/mine/collection_provide.dart'; 10 | import 'package:provider/provider.dart'; 11 | import 'package:rxdart/rxdart.dart'; 12 | 13 | class CollectionPage extends StatefulWidget { 14 | 15 | CollectionPage(); 16 | 17 | @override 18 | State createState() { 19 | // TODO: implement createState 20 | return _CollectionContentState(); 21 | } 22 | } 23 | 24 | class _CollectionContentState extends State { 25 | 26 | CollectionProvide _provider = CollectionProvide(); 27 | RefreshController _refreshController; 28 | final _subscriptions = CompositeSubscription(); 29 | final _loading = LoadingDialog(); 30 | 31 | @override 32 | void initState() { 33 | // TODO: implement initState 34 | super.initState(); 35 | _refreshController = new RefreshController(); 36 | _loadData(); 37 | } 38 | _loadData() { 39 | var s = _provider.favList().doOnListen(() { 40 | }).doOnCancel(() { 41 | }).listen((data) { 42 | _refreshController.sendBack(true, RefreshStatus.idle); 43 | }, onError: (e) { 44 | }); 45 | _subscriptions.add(s); 46 | } 47 | _uncollectionSong(String songId) { 48 | var s = _provider.uncollectionSong(songId).doOnListen(() { 49 | _loading.show(context); 50 | }).doOnCancel(() { 51 | }).listen((data) { 52 | _loading.hide(context); 53 | }, onError: (e) { 54 | _loading.hide(context); 55 | }); 56 | _subscriptions.add(s); 57 | } 58 | @override 59 | void dispose() { 60 | super.dispose(); 61 | _subscriptions.dispose(); 62 | } 63 | @override 64 | Widget build(BuildContext context) { 65 | return ChangeNotifierProvider.value( 66 | value: _provider, 67 | child: new Scaffold( 68 | appBar: new AppBar( 69 | title: new Text('我的收藏'), 70 | centerTitle: true, 71 | ), 72 | body: _initView(), 73 | ), 74 | ); 75 | } 76 | 77 | Widget _initView() { 78 | return Selector( 79 | selector: (_, provide) => provide.dataArr.length, 80 | builder: (_, value, child) { 81 | return value > 0 ? _buildListView() : AppConfig 82 | .initLoading(_provider.showEmpty, '暂无收藏'); 83 | }, 84 | ); 85 | } 86 | 87 | Widget _buildListView() { 88 | return new SmartRefresher( 89 | child: new ListView.builder( 90 | itemCount: _provider.dataArr.length, 91 | itemBuilder: (context, i) { 92 | if (_provider.dataArr.length > 0) { 93 | return new Dismissible( 94 | key: new Key(_provider.dataArr[i].id), 95 | confirmDismiss: (DismissDirection direction) async { 96 | bool res = await showAlert(context, title: '确定要取消收藏该歌曲?', onlyPositive: false); 97 | return res; 98 | }, 99 | onDismissed: (direction) { 100 | this._uncollectionSong(_provider.dataArr[i].id); 101 | }, 102 | child: getRow(_provider.dataArr[i])); 103 | } 104 | }), 105 | controller:_refreshController, 106 | enablePullDown: true, 107 | enablePullUp: false, 108 | onHeaderRefresh: _onHeaderRefresh, 109 | ); 110 | } 111 | 112 | Widget getRow(Song song) { 113 | return new GestureDetector( 114 | onTap: () { 115 | 116 | }, 117 | child: new Column( 118 | children: [ 119 | new Container( 120 | height: 70, 121 | padding: EdgeInsets.fromLTRB(12, 8, 12, 0), 122 | color: Colors.white, 123 | child: new Row(children: [ 124 | new CachedNetworkImage( 125 | width: 70, 126 | height: 70, 127 | key: Key(song.imgUrl_s), 128 | imageUrl: song.imgUrl_s, 129 | fit: BoxFit.cover, 130 | placeholder: (context, url) => AppConfig.getPlaceHoder(70.0, 70.0), 131 | errorWidget: (context, url, error) => AppConfig.getPlaceHoder(70.0, 70.0), 132 | ), 133 | new Container( 134 | width: 8, 135 | ), 136 | new Expanded(child: new Column( 137 | crossAxisAlignment: CrossAxisAlignment.start, 138 | children: [ 139 | new Container(height: 4,), 140 | new Text(song.title, style: TextStyle(color: Colors.black,fontSize: 16,fontWeight: FontWeight.bold),textAlign: TextAlign.left), 141 | new Container(height: 8,), 142 | new Text(song.duration != '' ? ' 时长:' + CommonUtil.dealDuration(song.duration):'', style: TextStyle(color: Colors.grey, fontSize: 12),textAlign: TextAlign.left) 143 | ],)), 144 | ],),), 145 | ], 146 | ), 147 | ); 148 | } 149 | 150 | _onHeaderRefresh() { 151 | _loadData(); 152 | } 153 | } -------------------------------------------------------------------------------- /lib/view/mine/register_protocol_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_flowermusic/base/base.dart'; 3 | import 'package:flutter_flowermusic/base/base2.dart'; 4 | import 'package:flutter_flowermusic/viewmodel/mine/regiest_provide.dart'; 5 | import 'package:flutter_html/flutter_html.dart'; 6 | import 'package:provider/provider.dart'; 7 | //import 'package:provide/provide.dart'; 8 | import 'package:rxdart/rxdart.dart'; 9 | 10 | class RegiestProtocolPage extends PageProvideNode2 { 11 | 12 | RegiestProvide provider = RegiestProvide(); 13 | 14 | RegiestProtocolPage() { 15 | // mProviders.provide(Provider.value(provide)); 16 | // mProviders = provider; 17 | } 18 | 19 | @override 20 | Widget buildContent(BuildContext context) { 21 | return _RegiestProtocolContentPage(provider); 22 | } 23 | 24 | @override 25 | BaseProvide2 initProvide() { 26 | // TODO: implement initProvide 27 | return provider; 28 | } 29 | } 30 | 31 | class _RegiestProtocolContentPage extends StatefulWidget { 32 | RegiestProvide provide; 33 | 34 | _RegiestProtocolContentPage(this.provide); 35 | 36 | @override 37 | State createState() { 38 | return _RegiestProtocolContentState(); 39 | } 40 | } 41 | 42 | class _RegiestProtocolContentState extends State<_RegiestProtocolContentPage> { 43 | 44 | RegiestProvide _provide; 45 | final _subscriptions = CompositeSubscription(); 46 | 47 | @override 48 | void initState() { 49 | // TODO: implement initState 50 | super.initState(); 51 | _provide ??= widget.provide; 52 | _loadData(); 53 | } 54 | _loadData() { 55 | var s = _provide.getProtocol().doOnListen(() { 56 | }).doOnCancel(() { 57 | }).listen((data) { 58 | }, onError: (e) { 59 | }); 60 | _subscriptions.add(s); 61 | } 62 | @override 63 | void dispose() { 64 | super.dispose(); 65 | _subscriptions.dispose(); 66 | } 67 | 68 | @override 69 | Widget build(BuildContext context) { 70 | return ChangeNotifierProvider( 71 | builder: (context) => _provide, 72 | child: new Scaffold( 73 | backgroundColor: Color(0xFFFFFFFF), 74 | appBar: new AppBar( 75 | title: new Text('注册协议'), 76 | centerTitle: true, 77 | actions: [ 78 | ], 79 | ), 80 | body: _buildBody(), 81 | ), 82 | ); 83 | } 84 | 85 | Consumer _buildBody() { 86 | return Consumer( 87 | builder : (BuildContext context, RegiestProvide value, Widget child) { 88 | return new Container( 89 | padding: EdgeInsets.fromLTRB(8, 12, 8, 12), 90 | child: new ListView.builder( 91 | itemCount: 1, 92 | itemBuilder: (context, i) { 93 | return new Html(data: value.protocol, defaultTextStyle: TextStyle(color: Colors.grey, fontSize: 16, height: 1)); 94 | }) 95 | ); 96 | } 97 | ); 98 | } 99 | } -------------------------------------------------------------------------------- /lib/view/mine/reset_password_page.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_flowermusic/base/app_config.dart'; 4 | import 'package:flutter_flowermusic/main/dialog/dialog.dart'; 5 | import 'package:flutter_flowermusic/viewmodel/mine/reset_password_provide.dart'; 6 | import 'package:fluttertoast/fluttertoast.dart'; 7 | import 'package:provider/provider.dart'; 8 | import 'package:rxdart/rxdart.dart'; 9 | 10 | class ResetPasswordPage extends StatelessWidget { 11 | 12 | final provide = ResetPasswordProvide(); 13 | 14 | @override 15 | Widget buildContent(BuildContext context) { 16 | return ChangeNotifierProvider.value( 17 | value: provide, 18 | child: _ResetPasswordContentPage(provide), 19 | ); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | // TODO: implement build 25 | return buildContent(context); 26 | } 27 | } 28 | 29 | class _ResetPasswordContentPage extends StatefulWidget { 30 | ResetPasswordProvide provide; 31 | 32 | _ResetPasswordContentPage(this.provide); 33 | 34 | @override 35 | State createState() { 36 | return _ResetPasswordContentState(); 37 | } 38 | } 39 | 40 | class _ResetPasswordContentState extends State<_ResetPasswordContentPage> { 41 | ResetPasswordProvide _provide; 42 | 43 | final _subscriptions = CompositeSubscription(); 44 | 45 | final _loading = LoadingDialog(); 46 | 47 | final _userTF = TextEditingController(); 48 | final _emailTF = TextEditingController(); 49 | final _pwdTF = TextEditingController(); 50 | 51 | @override 52 | void initState() { 53 | // TODO: implement initState 54 | super.initState(); 55 | 56 | _provide ??= widget.provide; 57 | } 58 | @override 59 | Widget build(BuildContext context) { 60 | // TODO: implement build 61 | return new Scaffold( 62 | body: SingleChildScrollView( 63 | child: new Column( 64 | children: [ 65 | _setupBack(), 66 | _setupTop(), 67 | _setupTextFields(), 68 | _setupLoginBtn(), 69 | ], 70 | ), 71 | ), 72 | ); 73 | } 74 | @override 75 | void dispose() { 76 | super.dispose(); 77 | _subscriptions.dispose(); 78 | } 79 | 80 | Widget _setupBack() { 81 | return new Container( 82 | height: 160, 83 | padding: EdgeInsets.fromLTRB(6, 0, 0, 0), 84 | alignment: Alignment.centerLeft, 85 | child: new GestureDetector( 86 | onTap: () { 87 | this._goback(false); 88 | }, 89 | child: new Icon(Icons.keyboard_arrow_left, color: Colors.black, size: 40,), 90 | ), 91 | ); 92 | } 93 | 94 | Widget _setupTop() { 95 | return new Column( 96 | children: [ 97 | new Text('重置密码', style: TextStyle(fontSize: 23),), 98 | new Container(height: 60,) 99 | ], 100 | ); 101 | } 102 | 103 | _setupTextFields() { 104 | return new Column( 105 | children: _setupContent(), 106 | ); 107 | } 108 | List _setupContent() { 109 | return new List.generate( 110 | 3, 111 | (int index) => 112 | _setupItem(index)); 113 | } 114 | 115 | 116 | Widget _setupItem(int index) { 117 | return new Container( 118 | margin: EdgeInsets.fromLTRB(30, 0, 30, 0), 119 | child: new Column( 120 | children: [ 121 | new Row( 122 | children: [ 123 | new Container( 124 | width: 60, 125 | child: new Text(_provide.titles[index], style: TextStyle(fontSize: 16)), 126 | ), 127 | new Expanded( 128 | child: Consumer(builder: (build, provide, _) { 129 | return TextField( 130 | obscureText: (index == 2 && 131 | provide.passwordVisiable == false) ? true : false, 132 | style: new TextStyle(color: Colors.black), 133 | controller: index == 0 ? _userTF:index == 1 ? _emailTF:_pwdTF, 134 | decoration: InputDecoration( 135 | hintText: provide.placeHoderText[index], 136 | hintStyle: new TextStyle(color: Colors.grey), 137 | border: InputBorder.none, 138 | focusedBorder: InputBorder.none, 139 | ), 140 | onChanged: (str) { 141 | if (index == 0) { 142 | _provide.userName = str; 143 | provide.loginEnable = _provide.loginEnable; 144 | } 145 | if (index == 1) { 146 | _provide.email = str; 147 | provide.loginEnable = _provide.loginEnable; 148 | } 149 | if (index == 2) { 150 | _provide.password = str; 151 | provide.loginEnable = _provide.loginEnable; 152 | } 153 | }); 154 | }) 155 | ), 156 | 157 | index == 2 ? _setupEyeOpened() : new Container() 158 | ], 159 | ), 160 | new Divider(height: 1, color: AppConfig.divider) 161 | ], 162 | ), 163 | ); 164 | } 165 | 166 | 167 | Widget _setupEyeOpened() { 168 | print('_setupEyeOpened'); 169 | return Consumer( 170 | builder: ((build, provide, _) { 171 | return new GestureDetector( 172 | onTap: () { 173 | print(provide.passwordVisiable); 174 | provide.passwordVisiable = 175 | !provide.passwordVisiable; 176 | }, 177 | child: new Icon( 178 | provide.passwordVisiable 179 | ? Icons.remove_red_eye 180 | : Icons.panorama_fish_eye, 181 | color: Colors.black, 182 | ), 183 | ); 184 | }) 185 | ); 186 | } 187 | 188 | 189 | Widget _setupLoginBtn() { 190 | print('_setupLoginBtn'); 191 | return new Container( 192 | height: 48, 193 | width: MediaQuery.of(context).size.width - 60, 194 | margin: EdgeInsets.fromLTRB(15, 50, 15, 0), 195 | child: Consumer(builder: (build, provide, _) { 196 | return new RaisedButton( 197 | disabledColor: AppConfig.disabledMainColor, 198 | color: AppConfig.primaryColor, 199 | onPressed: provide.loginEnable ? _resetPassword : null, 200 | child: new Text('确 定', style: new TextStyle(color: Colors.white, fontSize: 18)), 201 | ); 202 | }), 203 | ); 204 | } 205 | 206 | _resetPassword() { 207 | var s = _provide.resetPassword().doOnListen(() { 208 | _loading.show(context); 209 | }).doOnCancel(() { 210 | }).listen((data) { 211 | _loading.hide(context); 212 | if (data.success) { 213 | Fluttertoast.showToast( 214 | msg: "重置成功" 215 | ); 216 | Future.delayed(Duration(seconds: 1), () { 217 | this._goback(true); 218 | }); 219 | } 220 | }, onError: (e) { 221 | _loading.hide(context); 222 | }); 223 | _subscriptions.add(s); 224 | } 225 | _goback(bool logined) { 226 | Navigator.pop(context, logined); 227 | } 228 | } -------------------------------------------------------------------------------- /lib/view/mine/setting_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_flowermusic/base/app_config.dart'; 3 | import 'package:flutter_flowermusic/tools/player_tool.dart'; 4 | import 'package:flutter_flowermusic/viewmodel/mine/setting_provide.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:rxdart/rxdart.dart'; 7 | 8 | class SettingPage extends StatelessWidget { 9 | 10 | final provide = SettingProvide(); 11 | 12 | @override 13 | Widget buildContent(BuildContext context) { 14 | return ChangeNotifierProvider.value( 15 | value: provide, 16 | child: _SettingContentPage(provide), 17 | ); 18 | } 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | // TODO: implement build 23 | return buildContent(context); 24 | } 25 | } 26 | 27 | class _SettingContentPage extends StatefulWidget { 28 | SettingProvide provide; 29 | 30 | _SettingContentPage(this.provide); 31 | 32 | @override 33 | State createState() { 34 | return _SettingContentState(); 35 | } 36 | } 37 | 38 | class _SettingContentState extends State<_SettingContentPage> { 39 | SettingProvide _provide; 40 | final _subscriptions = CompositeSubscription(); 41 | 42 | @override 43 | void initState() { 44 | // TODO: implement initState 45 | super.initState(); 46 | 47 | _provide ??= widget.provide; 48 | 49 | var s = PlayerTools.instance.timerDownSubject.listen((str) { 50 | _provide.downText = str; 51 | }); 52 | _subscriptions.add(s); 53 | } 54 | @override 55 | Widget build(BuildContext context) { 56 | // TODO: implement build 57 | return new Scaffold( 58 | appBar: new AppBar( 59 | title: new Text('设置'), 60 | centerTitle: true, 61 | ), 62 | body: _initView(), 63 | ); 64 | } 65 | 66 | @override 67 | void dispose() { 68 | _subscriptions.dispose(); 69 | super.dispose(); 70 | print("设置页面释放"); 71 | } 72 | 73 | 74 | Widget _initView() { 75 | return new Column( 76 | children: [ 77 | _initTimer() 78 | ], 79 | ); 80 | } 81 | 82 | Widget _initTimer() { 83 | return new Container( 84 | padding: EdgeInsets.fromLTRB(10, 10, 0, 0), 85 | child: new Column( 86 | children: [ 87 | new Row( 88 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 89 | children: [ 90 | new Text('定时关闭', style: TextStyle(fontSize: 16),), 91 | new Switch( 92 | value: Provider.of(context).openTimer, 93 | onChanged: (value) { 94 | _provide.openTimer = value; 95 | }) 96 | ], 97 | ), 98 | _provide.openTimer == false ? new Container():new Container( 99 | child: new Column( 100 | children: [ 101 | new Text(_provide.downText), 102 | new Slider( 103 | activeColor: AppConfig.primaryColor, 104 | inactiveColor: AppConfig.backgroundColor, 105 | min: 0.0, 106 | max: 40, 107 | value: _provide.sliderValue, 108 | onChanged: (value) {}, 109 | onChangeEnd: (endValue) { 110 | print('onChangeEnd:$endValue'); 111 | if (endValue > 0) { 112 | _provide.sliderValue = endValue; 113 | } else { 114 | _provide.sliderValue = 1; 115 | } 116 | }, 117 | ) 118 | ], 119 | ), 120 | ) 121 | ], 122 | ), 123 | ); 124 | } 125 | } -------------------------------------------------------------------------------- /lib/view/old/olddetail_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_flowermusic/data/song.dart'; 3 | import 'package:flutter_html/flutter_html.dart'; 4 | 5 | class OldDetailPage extends StatefulWidget { 6 | 7 | Song song; 8 | 9 | OldDetailPage({Key key, @required this.song}) : super(key: key); 10 | 11 | @override 12 | _OldDetailPageState createState() => new _OldDetailPageState(this.song); 13 | } 14 | 15 | class _OldDetailPageState extends State { 16 | 17 | final Song _song; 18 | 19 | _OldDetailPageState(this._song) { 20 | } 21 | 22 | @override 23 | void dispose() { 24 | super.dispose(); 25 | print("详情释放"); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return new Scaffold( 31 | backgroundColor: Color(0xFFFFFFFF), 32 | appBar: new AppBar( 33 | title: new Text(this._song.title), 34 | centerTitle: true, 35 | actions: [ 36 | ], 37 | ), 38 | body: _buildBody(), 39 | ); 40 | } 41 | 42 | Widget _buildBody() { 43 | return new Container( 44 | padding: EdgeInsets.fromLTRB(8, 12, 8, 12), 45 | child: new ListView.builder( 46 | itemCount: 1, 47 | itemBuilder: (context, i) { 48 | return new Html(data: _song.desc, defaultTextStyle: TextStyle(color: Colors.grey, fontSize: 16, height: 1)); 49 | }) 50 | ); 51 | } 52 | } -------------------------------------------------------------------------------- /lib/view/player/mini_player_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:cached_network_image/cached_network_image.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_flowermusic/base/app_config.dart'; 6 | import 'package:flutter_flowermusic/base/base.dart'; 7 | import 'package:flutter_flowermusic/tools/audio_tool.dart'; 8 | import 'package:flutter_flowermusic/tools/player_tool.dart'; 9 | import 'package:flutter_flowermusic/view/player/full_player_page.dart'; 10 | import 'package:flutter_flowermusic/viewmodel/player/player_provide.dart'; 11 | import 'package:provider/provider.dart'; 12 | 13 | 14 | class MiniPlayerPage extends PageProvideNode { 15 | PlayerProvide provide = PlayerProvide(); 16 | 17 | MiniPlayerPage() { 18 | // mProviders.provide(Provider.value(provide)); 19 | } 20 | 21 | @override 22 | Widget buildContent(BuildContext context) { 23 | return _MiniPlayerContentPage(provide); 24 | } 25 | } 26 | 27 | class _MiniPlayerContentPage extends StatefulWidget { 28 | PlayerProvide provide; 29 | _MiniPlayerContentPage(this.provide); 30 | 31 | @override 32 | State createState() { 33 | return _MiniPlayerContentState(); 34 | } 35 | } 36 | 37 | class _MiniPlayerContentState extends State<_MiniPlayerContentPage> with TickerProviderStateMixin { 38 | 39 | PlayerProvide _provide; 40 | Animation animationNeedle; 41 | Animation animationRecord; 42 | AnimationController controllerNeedle; 43 | AnimationController controllerRecord; 44 | final _rotateTween = new Tween(begin: 0.0, end: -0.02); 45 | final _commonTween = new Tween(begin: 0.0, end: 1.0); 46 | 47 | @override 48 | void initState() { 49 | // TODO: implement initState 50 | super.initState(); 51 | _provide ??= widget.provide; 52 | 53 | controllerNeedle = new AnimationController( 54 | duration: const Duration(milliseconds: 200), 55 | vsync: this, 56 | ); 57 | animationNeedle = new CurvedAnimation(parent: controllerNeedle, curve: Curves.linear); 58 | 59 | controllerRecord = new AnimationController( 60 | duration: const Duration(milliseconds: 15000), vsync: this); 61 | animationRecord = new CurvedAnimation(parent: controllerRecord, curve: Curves.linear); 62 | 63 | PlayerTools.instance.stateSubject.listen((state) { 64 | if (state == AudioToolsState.isPlaying) { 65 | controllerNeedle.forward(); 66 | controllerRecord.forward(); 67 | } else { 68 | controllerNeedle.reverse(); 69 | controllerRecord.stop(canceled: false); 70 | } 71 | }); 72 | animationRecord.addStatusListener((status) { 73 | if (status == AnimationStatus.completed && PlayerTools.instance.currentState == AudioToolsState.isPlaying) { 74 | controllerRecord.repeat(); 75 | } else if (status == AnimationStatus.dismissed) { 76 | controllerRecord.forward(); 77 | } 78 | }); 79 | } 80 | 81 | @override 82 | void dispose() { 83 | controllerNeedle.dispose(); 84 | controllerRecord.dispose(); 85 | super.dispose(); 86 | } 87 | 88 | @override 89 | Widget build(BuildContext context) { 90 | return ChangeNotifierProvider( 91 | builder: (context) => _provide, 92 | child: _buildView(), 93 | ); 94 | } 95 | 96 | Widget _buildView() { 97 | return Consumer( 98 | builder: (BuildContext context, PlayerProvide value, Widget child) { 99 | return new Container( 100 | margin: EdgeInsets.fromLTRB(0, 0, 0, 20), 101 | child: new Stack( 102 | alignment: AlignmentDirectional.topCenter, 103 | children: [ 104 | new Container( 105 | width: 80, 106 | height: 90, 107 | padding: EdgeInsets.fromLTRB(14, 30, 0, 0), 108 | child: new InkWell( 109 | onTap: _showFullPlayer, 110 | child: new Stack( 111 | alignment: AlignmentDirectional.center, 112 | children: [ 113 | new Image.asset('images/disc.png'), 114 | new ClipOval( 115 | child: new RotationTransition( 116 | turns: _commonTween.animate(animationRecord), 117 | alignment: Alignment.center, 118 | child: new CachedNetworkImage( 119 | width: 35, 120 | height: 35, 121 | key: Key(_provide.currentSong.imgUrl_s ?? ''), 122 | imageUrl: _provide.currentSong.imgUrl_s ?? 'http://www.yishouhaoge.cn:4001/1555069598726-fj_s23.jpg', 123 | fit: BoxFit.cover, 124 | placeholder: (context, url) => AppConfig.getLoadingPlaceHoder(20.0, 20.0), 125 | errorWidget: (context, url, error) => AppConfig.getPlaceHoder(35.0, 35.0), 126 | ), 127 | ), 128 | ), 129 | _provide.cacheing ? new SizedBox( 130 | width: 20, 131 | height: 20, 132 | child: const CircularProgressIndicator(strokeWidth: 3.0) 133 | ): new Container() 134 | ], 135 | ), 136 | ) 137 | ), 138 | new Container( 139 | width: 80, 140 | child: new RotationTransition( 141 | alignment: Alignment.topRight, 142 | turns: _rotateTween.animate(animationNeedle), 143 | child: new Image.asset('images/play_needle2.png', width: 80,height: 46,alignment: Alignment.topRight,), 144 | ), 145 | ) 146 | ], 147 | ), 148 | ); 149 | } 150 | ); 151 | } 152 | 153 | 154 | _showFullPlayer() { 155 | Navigator.push(context, new PageRouteBuilder( 156 | opaque: false, 157 | pageBuilder: (BuildContext context, _, __) { 158 | return FullPlayerPage(); 159 | }, 160 | transitionsBuilder: (___, Animation animation, ____, Widget child) { 161 | return new SlideTransition( 162 | position: Tween( 163 | begin: Offset(0.0, 1.0), 164 | end:Offset(0.0, 0.0) 165 | ).animate(CurvedAnimation( 166 | parent: animation, 167 | curve: Curves.fastOutSlowIn 168 | )), 169 | child: child, 170 | ); 171 | } 172 | )); 173 | } 174 | } -------------------------------------------------------------------------------- /lib/view/player/music_list_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_flowermusic/base/app_config.dart'; 3 | import 'package:flutter_flowermusic/data/song.dart'; 4 | import 'package:flutter_flowermusic/main/tap/opacity_tap_widget.dart'; 5 | import 'package:flutter_flowermusic/tools/player_tool.dart'; 6 | 7 | class MusicListPage extends StatefulWidget { 8 | @override 9 | State createState() { 10 | // TODO: implement createState 11 | return MusicListState(); 12 | } 13 | } 14 | 15 | class MusicListState extends State { 16 | @override 17 | void initState() { 18 | // TODO: implement initState 19 | super.initState(); 20 | } 21 | @override 22 | Widget build(BuildContext context) { 23 | // TODO: implement build 24 | return new Scaffold( 25 | backgroundColor: Colors.black87.withOpacity(0.75), 26 | body: new Column( 27 | children: [ 28 | new Container( 29 | height: 160, 30 | width: 100, 31 | child: new OpacityTapWidget( 32 | onTap: () { 33 | Navigator.of(context).pop(false); 34 | }, 35 | child: new Icon(Icons.close, color: Colors.white, size: 36,), 36 | ), 37 | ), 38 | new Expanded( 39 | child:_buildListView() 40 | ) 41 | ], 42 | ), 43 | ); 44 | } 45 | 46 | Widget _buildListView() { 47 | return new ListView.builder( 48 | padding: EdgeInsets.fromLTRB(0, MediaQuery.of(context).size.height * 0.3, 0, 0), 49 | itemCount: PlayerTools.instance.songArr.length, 50 | itemBuilder: (context, i) { 51 | if (PlayerTools.instance.songArr.length > 0) { 52 | return getRow(PlayerTools.instance.songArr[i], i); 53 | } 54 | }); 55 | } 56 | 57 | Widget getRow(Song song, int index) { 58 | return new Container( 59 | child: new OpacityTapWidget( 60 | onTap: () { 61 | PlayerTools.instance.setIndexPlay(index); 62 | Navigator.of(context).pop(true); 63 | }, 64 | child: new Column( 65 | crossAxisAlignment: CrossAxisAlignment.start, 66 | children: [ 67 | new Container( 68 | padding: EdgeInsets.fromLTRB(10, 15, 10, 15), 69 | width: MediaQuery.of(context).size.width, 70 | child: new Text(song.title, style: TextStyle(color: Colors.white),), 71 | ), 72 | new Divider(height: 1, color: AppConfig.divider,) 73 | ], 74 | ), 75 | ), 76 | ); 77 | } 78 | } -------------------------------------------------------------------------------- /lib/viewmodel/home/comment_provide.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter_flowermusic/base/base.dart'; 3 | import 'package:flutter_flowermusic/data/comment.dart'; 4 | import 'package:flutter_flowermusic/data/song.dart'; 5 | import 'package:flutter_flowermusic/model/home_repository.dart'; 6 | import 'package:rxdart/rxdart.dart'; 7 | 8 | class CommentProvide extends BaseProvide { 9 | Song _song; 10 | Song get song => _song; 11 | set song(Song song) { 12 | _song = song; 13 | } 14 | final HomeRepo _repo = HomeRepo(); 15 | 16 | // 页数 17 | int _page = 0; 18 | int get page => _page; 19 | set page(int page) { 20 | _page = page; 21 | } 22 | 23 | final subjectMore = new BehaviorSubject.seeded(false); 24 | 25 | bool _hasMore = false; 26 | bool get hasMore => _hasMore; 27 | set hasMore(bool hasMore) { 28 | _hasMore = hasMore; 29 | subjectMore.value = hasMore; 30 | } 31 | 32 | List _dataArr = []; 33 | List get dataArr => _dataArr; 34 | set dataArr(List arr) { 35 | _dataArr = arr; 36 | this.notify(); 37 | } 38 | 39 | bool _showEmpty = false; 40 | bool get showEmpty => _showEmpty; 41 | set showEmpty(bool showEmpty) { 42 | _showEmpty = showEmpty; 43 | } 44 | 45 | /// 评论列表 46 | Observable commentList(bool isRefrsh) { 47 | isRefrsh ? page = 0 : page++; 48 | return _repo 49 | .commentList(this.page, song.id) 50 | .doOnData((result) { 51 | if (isRefrsh) { 52 | this.dataArr.clear(); 53 | } 54 | var arr = result.data as List; 55 | this.dataArr.addAll(arr.map((map) => Comment.fromJson(map))); 56 | 57 | this.hasMore = result.total > this.dataArr.length; 58 | if (this.dataArr.length == 0) { 59 | this.showEmpty = true; 60 | } 61 | 62 | this.notify(); 63 | }) 64 | .doOnError((e, stacktrace) { 65 | }) 66 | .doOnListen(() { 67 | }) 68 | .doOnDone(() { 69 | }); 70 | } 71 | 72 | /// 发表评论 73 | Observable sendComment(String comment) { 74 | return _repo 75 | .sendComment(comment, song.id) 76 | .doOnData((result) { 77 | }) 78 | .doOnError((e, stacktrace) { 79 | }) 80 | .doOnListen(() { 81 | }) 82 | .doOnDone(() { 83 | }); 84 | } 85 | 86 | Observable niceComment(String commentId) { 87 | return _repo 88 | .niceComment(commentId) 89 | .doOnData((result) { 90 | 91 | int index = this.dataArr.indexWhere((song) { 92 | return song.id == commentId; 93 | }); 94 | this.dataArr[index].niceCount = this.dataArr[index].niceCount + 1; 95 | this.notify(); 96 | }) 97 | .doOnError((e, stacktrace) { 98 | }) 99 | .doOnListen(() { 100 | }) 101 | .doOnDone(() { 102 | }); 103 | } 104 | 105 | notify() { 106 | notifyListeners(); 107 | } 108 | } -------------------------------------------------------------------------------- /lib/viewmodel/home/home_provide.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter_flowermusic/base/app_config.dart'; 3 | import 'package:flutter_flowermusic/base/base.dart'; 4 | import 'package:flutter_flowermusic/data/song.dart'; 5 | import 'package:flutter_flowermusic/model/home_repository.dart'; 6 | import 'package:flutter_flowermusic/tools/player_tool.dart'; 7 | import 'package:fluttertoast/fluttertoast.dart'; 8 | import 'package:rxdart/rxdart.dart'; 9 | 10 | class HomeProvide extends BaseProvide { 11 | // 页数 12 | int _page = 0; 13 | int get page => _page; 14 | set page(int page) { 15 | _page = page; 16 | } 17 | 18 | final subjectMore = new BehaviorSubject(); 19 | 20 | bool _hasMore = false; 21 | bool get hasMore => _hasMore; 22 | set hasMore(bool hasMore) { 23 | _hasMore = hasMore; 24 | subjectMore.add(hasMore); 25 | } 26 | 27 | 28 | List _dataArr = []; 29 | List get dataArr => _dataArr; 30 | set dataArr(List arr) { 31 | _dataArr = arr; 32 | this.notify(); 33 | } 34 | 35 | int _count = 0; 36 | int get count => _count; 37 | set count(int count) { 38 | _count = count; 39 | notify(); 40 | } 41 | expand(int index) { 42 | this.count = index; 43 | this.dataArr[index].isExpaned = !this.dataArr[index].isExpaned; 44 | notify(); 45 | } 46 | 47 | final HomeRepo _repo = HomeRepo(); 48 | 49 | notify() { 50 | notifyListeners(); 51 | } 52 | 53 | setSongs(int index) { 54 | PlayerTools.instance.setSongs(this.dataArr, index); 55 | } 56 | 57 | Observable getSongs(bool isRefrsh) { 58 | isRefrsh ? page = 0 : page++; 59 | var query = { 60 | 'page': this.page, 61 | 'pageSize': 30, 62 | 'orderkey': 'imgUrl', 63 | 'sequence': true, 64 | 'searchKey': '', 65 | 'userId': AppConfig.userTools.getUserId() 66 | }; 67 | return _repo 68 | .getSongs(query) 69 | .doOnData((result) { 70 | if (isRefrsh) { 71 | this.dataArr.clear(); 72 | } 73 | var arr = result.data as List; 74 | this.dataArr.addAll(arr.map((map) => Song.fromJson(map))); 75 | 76 | this.hasMore = result.total > this.dataArr.length; 77 | 78 | this.notify(); 79 | }) 80 | .doOnError((e, stacktrace) { 81 | }) 82 | .doOnListen(() { 83 | }) 84 | .doOnDone(() { 85 | }); 86 | } 87 | 88 | /// 收藏 89 | Observable collectionSong(String songId) { 90 | return _repo 91 | .collectionSong(songId) 92 | .doOnData((result) { 93 | 94 | int index = this.dataArr.indexWhere((song) { 95 | return song.id == songId; 96 | }); 97 | this.dataArr[index].isFav = true; 98 | this.notify(); 99 | }) 100 | .doOnError((e, stacktrace) { 101 | }) 102 | .doOnListen(() { 103 | }) 104 | .doOnDone(() { 105 | }); 106 | } 107 | /// 取消收藏 108 | Observable uncollectionSong(String songId) { 109 | return _repo 110 | .uncollectionSong(songId) 111 | .doOnData((result) { 112 | 113 | int index = this.dataArr.indexWhere((song) { 114 | return song.id == songId; 115 | }); 116 | this.dataArr[index].isFav = false; 117 | this.notify(); 118 | 119 | Fluttertoast.showToast( 120 | msg: "取消收藏成功", 121 | gravity: ToastGravity.CENTER 122 | ); 123 | }) 124 | .doOnError((e, stacktrace) { 125 | }) 126 | .doOnListen(() { 127 | }) 128 | .doOnDone(() { 129 | }); 130 | } 131 | } -------------------------------------------------------------------------------- /lib/viewmodel/mine/advice_provide.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_flowermusic/base/app_config.dart'; 3 | import 'package:flutter_flowermusic/base/base.dart'; 4 | import 'package:flutter_flowermusic/data/song.dart'; 5 | import 'package:flutter_flowermusic/main/dialog/dialog.dart'; 6 | import 'package:flutter_flowermusic/model/mine_respository.dart'; 7 | import 'package:fluttertoast/fluttertoast.dart'; 8 | import 'package:rxdart/rxdart.dart'; 9 | 10 | class AdviceProvide extends BaseProvide { 11 | 12 | final MineRepo _repo = MineRepo(); 13 | 14 | String _advice = ''; 15 | String get advice => _advice; 16 | set advice(String advice) { 17 | _advice = advice; 18 | } 19 | 20 | String _contact = ''; 21 | String get contact => _contact; 22 | set contact(String contact) { 23 | _contact = contact; 24 | } 25 | 26 | List _imgArr = []; 27 | List get imgArr => _imgArr; 28 | set imgArr(List arr) { 29 | _imgArr = arr; 30 | print(_imgArr); 31 | notify(); 32 | } 33 | 34 | notify() { 35 | notifyListeners(); 36 | } 37 | 38 | Observable adviceSubmit() { 39 | var body = { 40 | 'content': this.advice, 41 | 'phone': this.contact, 42 | 'imgs': this.imgArr 43 | }; 44 | return _repo 45 | .adviceSubmit(body) 46 | .doOnData((result) { 47 | if (result.success) { 48 | Fluttertoast.showToast( 49 | msg: "提交成功", 50 | gravity: ToastGravity.CENTER 51 | ); 52 | } 53 | }) 54 | .doOnError((e, stacktrace) { 55 | }) 56 | .doOnListen(() { 57 | }) 58 | .doOnDone(() { 59 | }); 60 | } 61 | 62 | // 上传图片 63 | Observable uploadImages(body) { 64 | return _repo 65 | .uploadImg(body) 66 | .doOnData((result) { 67 | if (result.success) { 68 | this.imgArr.clear(); 69 | var arr = result.data as List; 70 | this.imgArr.addAll(arr.map((map) => AppConfig.baseUrl + map['imageUrl'])); 71 | this.imgArr = this.imgArr; 72 | } 73 | }) 74 | .doOnError((e, stacktrace) { 75 | Fluttertoast.showToast( 76 | msg: "上传失败", 77 | gravity: ToastGravity.CENTER 78 | ); 79 | }) 80 | .doOnListen(() { 81 | }) 82 | .doOnDone(() { 83 | }); 84 | } 85 | } -------------------------------------------------------------------------------- /lib/viewmodel/mine/collection_provide.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_flowermusic/base/base.dart'; 2 | import 'package:flutter_flowermusic/data/song.dart'; 3 | import 'package:flutter_flowermusic/model/mine_respository.dart'; 4 | import 'package:fluttertoast/fluttertoast.dart'; 5 | import 'package:rxdart/rxdart.dart'; 6 | 7 | class CollectionProvide extends BaseProvide { 8 | 9 | final MineRepo _repo = MineRepo(); 10 | 11 | List _dataArr = []; 12 | List get dataArr => _dataArr; 13 | set dataArr(List arr) { 14 | _dataArr = arr; 15 | this.notify(); 16 | } 17 | 18 | bool _showEmpty = false; 19 | bool get showEmpty => _showEmpty; 20 | set showEmpty(bool showEmpty) { 21 | _showEmpty = showEmpty; 22 | } 23 | 24 | notify() { 25 | notifyListeners(); 26 | } 27 | 28 | Observable favList() { 29 | return _repo 30 | .favList() 31 | .doOnData((result) { 32 | this.dataArr.clear(); 33 | var arr = result.data as List; 34 | this.dataArr.addAll(arr.map((map) => Song.fromJson(map))); 35 | this.notify(); 36 | }) 37 | .doOnError((e, stacktrace) { 38 | }) 39 | .doOnListen(() { 40 | }) 41 | .doOnDone(() { 42 | }); 43 | } 44 | 45 | Observable uncollectionSong(String songId) { 46 | return _repo 47 | .uncollectionSong(songId) 48 | .doOnData((result) { 49 | int index = this.dataArr.indexWhere((song) { 50 | return song.id == songId; 51 | }); 52 | this.dataArr.removeAt(index); 53 | if (this.dataArr.length == 0) { 54 | this.showEmpty = true; 55 | } 56 | 57 | this.notify(); 58 | 59 | Fluttertoast.showToast( 60 | msg: "取消收藏成功", 61 | gravity: ToastGravity.CENTER 62 | ); 63 | }) 64 | .doOnError((e, stacktrace) { 65 | }) 66 | .doOnListen(() { 67 | }) 68 | .doOnDone(() { 69 | }); 70 | } 71 | } -------------------------------------------------------------------------------- /lib/viewmodel/mine/login_provide.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_flowermusic/base/base.dart'; 2 | import 'package:flutter_flowermusic/data/user.dart'; 3 | import 'package:flutter_flowermusic/model/mine_respository.dart'; 4 | import 'package:flutter_flowermusic/tools/user_tool.dart'; 5 | import 'package:flutter_flowermusic/utils/common_util.dart'; 6 | import 'package:rxdart/rxdart.dart'; 7 | import 'package:fluttertoast/fluttertoast.dart'; 8 | 9 | class LoginProvide extends BaseProvide { 10 | 11 | List placeHoderText = ['请输入用户名(至少3位)', '请输入您的邮箱(可选)', '6-12位数字与字母的组合']; 12 | List titles = ['用户名', '邮箱', '密码']; 13 | 14 | String _userName = ''; 15 | String get userName => _userName; 16 | set userName(String userName) { 17 | _userName = userName; 18 | loginBtnCanClick(); 19 | } 20 | 21 | String _password = ''; 22 | String get password => _password; 23 | set password(String password) { 24 | _password = password; 25 | loginBtnCanClick(); 26 | } 27 | 28 | String _email = ''; 29 | String get email => _email; 30 | set email(String email) { 31 | _email = email; 32 | loginBtnCanClick(); 33 | } 34 | 35 | /// 按钮是否可以点击 36 | bool _loginEnable = false; 37 | bool get loginEnable => _loginEnable; 38 | set loginEnable(bool loginEnable) { 39 | _loginEnable = loginEnable; 40 | notify(); 41 | } 42 | 43 | /// 判断按钮是否可以点击 44 | loginBtnCanClick() { 45 | bool emailValue = true; 46 | if (this.email.length > 0) { 47 | emailValue = CommonUtil.isEmail(this.email); 48 | } 49 | if (this.userName.length >= 3 && CommonUtil.isPassword(this.password) && emailValue) { 50 | this.loginEnable = true; 51 | } else { 52 | this.loginEnable = false; 53 | } 54 | print('44444${this.loginEnable}'); 55 | notifyListeners(); 56 | } 57 | 58 | /// 密码是否是可见的 59 | bool _passwordVisiable = true; 60 | bool get passwordVisiable => _passwordVisiable; 61 | set passwordVisiable(bool passwordVisiable) { 62 | _passwordVisiable = passwordVisiable; 63 | notify(); 64 | } 65 | 66 | /// 是否同意注册协议 67 | bool _agreeProtocol = true; 68 | bool get agreeProtocol => _agreeProtocol; 69 | set agreeProtocol(bool agreeProtocol) { 70 | _agreeProtocol = agreeProtocol; 71 | notify(); 72 | } 73 | 74 | notify() { 75 | notifyListeners(); 76 | } 77 | 78 | 79 | final MineRepo _repo = MineRepo(); 80 | /// 登录 81 | Observable login() { 82 | var body = { 83 | 'userName': this.userName, 84 | 'email': this.email, 85 | 'passWord': this.password 86 | }; 87 | return _repo 88 | .login(body) 89 | .doOnData((result) { 90 | }) 91 | .doOnError((e, stacktrace) { 92 | }) 93 | .doOnListen(() { 94 | }) 95 | .doOnDone(() { 96 | }); 97 | } 98 | 99 | // 重置密码 100 | Observable resetPassword() { 101 | var body = { 102 | 'userName': this.userName, 103 | 'email': this.email, 104 | 'passWord': this.password 105 | }; 106 | return _repo 107 | .resetPassword(body) 108 | .doOnData((result) { 109 | }) 110 | .doOnError((e, stacktrace) { 111 | }) 112 | .doOnListen(() { 113 | }) 114 | .doOnDone(() { 115 | }); 116 | } 117 | } -------------------------------------------------------------------------------- /lib/viewmodel/mine/mine_provide.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_flowermusic/base/app_config.dart'; 6 | import 'package:flutter_flowermusic/base/base.dart'; 7 | import 'package:flutter_flowermusic/data/user.dart'; 8 | import 'package:flutter_flowermusic/main/dialog/dialog.dart'; 9 | import 'package:flutter_flowermusic/model/mine_respository.dart'; 10 | import 'package:flutter_flowermusic/view/mine/advice_page.dart'; 11 | import 'package:flutter_flowermusic/view/mine/author_page.dart'; 12 | import 'package:flutter_flowermusic/view/mine/collection_page.dart'; 13 | import 'package:flutter_flowermusic/view/mine/login_page.dart'; 14 | import 'package:flutter_flowermusic/view/mine/setting_page.dart'; 15 | import 'package:fluttertoast/fluttertoast.dart'; 16 | import 'package:rxdart/rxdart.dart'; 17 | import 'package:url_launcher/url_launcher.dart'; 18 | 19 | class MineProvide extends BaseProvide { 20 | List content = ['我的收藏', '五星好评', '意见反馈', '设置', '关于作者']; 21 | List icons = [Icons.favorite_border, Icons.star_border, Icons.email, Icons.settings, Icons.settings]; 22 | List colors = [Color(0xFF007aff), Color(0xFFFF7F00), Color(0xFFEEAD0E), Color(0xFFC0FF3E), Color(0xFFC0FF3E)]; 23 | 24 | User _userInfo; 25 | User get userInfo => _userInfo; 26 | set userInfo(User userInfo) { 27 | _userInfo = userInfo; 28 | notifyListeners(); 29 | } 30 | 31 | MineProvide() { 32 | this._loginedOrNot(); 33 | } 34 | 35 | final MineRepo _repo = MineRepo(); 36 | 37 | _loginedOrNot() { 38 | var user = AppConfig.userTools.getUserData(); 39 | if (user != null) { 40 | var userinfo = user as Map; 41 | this.userInfo = User.fromJson(userinfo); 42 | } 43 | } 44 | 45 | loginOut(BuildContext context) { 46 | showAlert(context, title: '确定要退出登录?', onlyPositive: false) 47 | .then((value) { 48 | if (value) { 49 | AppConfig.userTools.delectUserData().then((value) { 50 | if (value) { 51 | this.userInfo = null; 52 | } 53 | }); 54 | } 55 | }); 56 | } 57 | 58 | gotoLogin(BuildContext context) { 59 | if (this.userInfo != null) { 60 | return; 61 | } 62 | Navigator.push(context, MaterialPageRoute( 63 | builder: (_) => LoginPage())).then((value) { 64 | if (value) { 65 | this._loginedOrNot(); 66 | } 67 | }); 68 | } 69 | 70 | clickCell(int index, BuildContext context) { 71 | if (index == 0) { 72 | bool logined = _showLoginText(); 73 | if (logined) { 74 | Navigator.push(context, MaterialPageRoute( 75 | builder: (_) => CollectionPage())); 76 | } 77 | } 78 | if (index == 1) { 79 | try{ 80 | AppConfig.platform.invokeMethod('GoodComment'); 81 | } catch(e){ 82 | } 83 | } 84 | 85 | if (index == 2) { 86 | Navigator.push(context, MaterialPageRoute( 87 | builder: (_) => AdvicePage())); 88 | } 89 | 90 | if (index == 3) { 91 | Navigator.push(context, MaterialPageRoute( 92 | builder: (_) => SettingPage())); 93 | } 94 | if (index == 4) { 95 | Navigator.push(context, MaterialPageRoute( 96 | builder: (_) => AuthorPage())); 97 | } 98 | } 99 | 100 | bool _showLoginText() { 101 | if (this.userInfo == null) { 102 | Fluttertoast.showToast( 103 | msg: "请先登录", 104 | gravity: ToastGravity.CENTER 105 | ); 106 | return false; 107 | } else { 108 | return true; 109 | } 110 | } 111 | 112 | // 上传头像 113 | Observable uploadUserHeader(FormData body) { 114 | return _repo 115 | .uploadUserHeader(body) 116 | .doOnData((result) { 117 | if (result.success) { 118 | // 更新用户信息 119 | AppConfig.userTools.updateUserIcon(result.data['imageUrl']).then((user) { 120 | this.userInfo = User.fromJson(user); 121 | 122 | Fluttertoast.showToast( 123 | msg: "上传成功", 124 | gravity: ToastGravity.CENTER 125 | ); 126 | }); 127 | } 128 | }) 129 | .doOnError((e, stacktrace) { 130 | Fluttertoast.showToast( 131 | msg: "上传失败", 132 | gravity: ToastGravity.CENTER 133 | ); 134 | }) 135 | .doOnListen(() { 136 | }) 137 | .doOnDone(() { 138 | }); 139 | } 140 | } -------------------------------------------------------------------------------- /lib/viewmodel/mine/regiest_provide.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_flowermusic/base/base.dart'; 2 | import 'package:flutter_flowermusic/base/base2.dart'; 3 | import 'package:flutter_flowermusic/model/mine_respository.dart'; 4 | import 'package:rxdart/rxdart.dart'; 5 | 6 | class RegiestProvide extends BaseProvide2 { 7 | 8 | String _protocol = ''; 9 | String get protocol => _protocol; 10 | set protocol(String protocol) { 11 | _protocol = protocol; 12 | notifyListeners(); 13 | } 14 | 15 | final MineRepo _repo = MineRepo(); 16 | 17 | // 重置密码 18 | Observable getProtocol() { 19 | return _repo 20 | .getProtocol() 21 | .doOnData((result) { 22 | this.protocol = result.data[0]['content']; 23 | }) 24 | .doOnError((e, stacktrace) { 25 | }) 26 | .doOnListen(() { 27 | }) 28 | .doOnDone(() { 29 | }); 30 | } 31 | } -------------------------------------------------------------------------------- /lib/viewmodel/mine/reset_password_provide.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_flowermusic/base/base.dart'; 2 | import 'package:flutter_flowermusic/model/mine_respository.dart'; 3 | import 'package:flutter_flowermusic/utils/common_util.dart'; 4 | import 'package:rxdart/rxdart.dart'; 5 | 6 | class ResetPasswordProvide extends BaseProvide { 7 | List placeHoderText = ['请输入用户名(至少3位)', '请输入您注册时的邮箱', '6-12位数字与字母的组合']; 8 | List titles = ['用户名', '邮箱', '新密码']; 9 | 10 | String _userName = ''; 11 | String get userName => _userName; 12 | set userName(String userName) { 13 | _userName = userName; 14 | _loginBtnCanClick(); 15 | } 16 | 17 | String _password = ''; 18 | String get password => _password; 19 | set password(String password) { 20 | _password = password; 21 | _loginBtnCanClick(); 22 | } 23 | 24 | String _email = ''; 25 | String get email => _email; 26 | set email(String email) { 27 | _email = email; 28 | _loginBtnCanClick(); 29 | } 30 | 31 | /// 按钮是否可以点击 32 | bool _loginEnable = false; 33 | bool get loginEnable => _loginEnable; 34 | set loginEnable(bool loginEnable) { 35 | _loginEnable = loginEnable; 36 | notifyListeners(); 37 | } 38 | 39 | /// 判断按钮是否可以点击 40 | _loginBtnCanClick() { 41 | bool emailValue = true; 42 | emailValue = CommonUtil.isEmail(email); 43 | if (userName.length >= 3 && CommonUtil.isPassword(password) && emailValue) { 44 | this.loginEnable = true; 45 | } else { 46 | this.loginEnable = false; 47 | } 48 | notifyListeners(); 49 | } 50 | 51 | /// 密码是否是可见的 52 | bool _passwordVisiable = true; 53 | bool get passwordVisiable => _passwordVisiable; 54 | set passwordVisiable(bool passwordVisiable) { 55 | _passwordVisiable = passwordVisiable; 56 | notifyListeners(); 57 | } 58 | 59 | final MineRepo _repo = MineRepo(); 60 | 61 | // 重置密码 62 | Observable resetPassword() { 63 | var body = { 64 | 'userName': this.userName, 65 | 'email': this.email, 66 | 'passWord': this.password 67 | }; 68 | return _repo 69 | .resetPassword(body) 70 | .doOnData((result) { 71 | }) 72 | .doOnError((e, stacktrace) { 73 | }) 74 | .doOnListen(() { 75 | }) 76 | .doOnDone(() { 77 | }); 78 | } 79 | } -------------------------------------------------------------------------------- /lib/viewmodel/mine/setting_provide.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter_flowermusic/base/base.dart'; 3 | import 'package:flutter_flowermusic/tools/player_tool.dart'; 4 | 5 | 6 | class SettingProvide extends BaseProvide { 7 | 8 | SettingProvide() { 9 | if (PlayerTools.instance.countdownNum > 0) { 10 | this.openTimer = true; 11 | } 12 | } 13 | bool _openTimer = false; 14 | bool get openTimer => _openTimer; 15 | set openTimer(bool openTimer) { 16 | _openTimer = openTimer; 17 | if (openTimer) { 18 | if (PlayerTools.instance.countdownNum > 0) { 19 | this.sliderValue = (PlayerTools.instance.countdownNum / 60); 20 | } else { 21 | this.sliderValue = 10; 22 | } 23 | this.startTimer(); 24 | } else { 25 | this.stopTimer(); 26 | } 27 | notify(); 28 | } 29 | 30 | double _sliderValue = 10; 31 | double get sliderValue => _sliderValue; 32 | set sliderValue(double sliderValue) { 33 | _sliderValue = sliderValue; 34 | PlayerTools.instance.countdownNum = (sliderValue * 60).toInt(); 35 | this.startTimer(); 36 | } 37 | 38 | String _downText = '09:59后播放器关闭'; 39 | String get downText => _downText; 40 | set downText(String downText) { 41 | _downText = downText; 42 | notify(); 43 | } 44 | 45 | /// 开启定时器 46 | startTimer() { 47 | PlayerTools.instance.startTimer(); 48 | } 49 | /// 关闭定时器 50 | stopTimer() { 51 | PlayerTools.instance.countdownNum = 0; 52 | } 53 | 54 | notify() { 55 | notifyListeners(); 56 | } 57 | } -------------------------------------------------------------------------------- /lib/viewmodel/old/old_provide.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_flowermusic/base/app_config.dart'; 2 | import 'package:flutter_flowermusic/base/base.dart'; 3 | import 'package:flutter_flowermusic/data/song.dart'; 4 | import 'package:flutter_flowermusic/model/old_repository.dart'; 5 | import 'package:flutter_flowermusic/tools/player_tool.dart'; 6 | import 'package:fluttertoast/fluttertoast.dart'; 7 | import 'package:rxdart/rxdart.dart'; 8 | 9 | class OldProvide extends BaseProvide { 10 | // 页数 11 | int _page = 0; 12 | int get page => _page; 13 | set page(int page) { 14 | _page = page; 15 | } 16 | 17 | final subjectMore = new BehaviorSubject.seeded(false); 18 | 19 | bool _hasMore = false; 20 | bool get hasMore => _hasMore; 21 | set hasMore(bool hasMore) { 22 | _hasMore = hasMore; 23 | subjectMore.value = hasMore; 24 | } 25 | 26 | 27 | List _dataArr = []; 28 | List get dataArr => _dataArr; 29 | set dataArr(List arr) { 30 | _dataArr = arr; 31 | notifyListeners(); 32 | } 33 | 34 | setSongs(int index) { 35 | PlayerTools.instance.setSongs(this.dataArr, index); 36 | } 37 | 38 | final OldRepo _repo = OldRepo(); 39 | 40 | Observable getSongs(bool isRefrsh) { 41 | isRefrsh ? page = 0 : page++; 42 | var query = { 43 | 'page': this.page, 44 | 'pageSize': 10, 45 | 'orderkey': '', 46 | 'sequence': true, 47 | 'searchKey': '', 48 | 'userId': AppConfig.userTools.getUserId() 49 | }; 50 | return _repo 51 | .getSongs(query) 52 | .doOnData((result) { 53 | print("11111111111"+result.data.toString()+""); 54 | this.hasMore = result.totalPage >= this.page; 55 | if (isRefrsh) { 56 | this.dataArr.clear(); 57 | } 58 | var arr = result.data as List; 59 | this.dataArr.addAll(arr.map((map) => Song.fromJson(map))); 60 | notifyListeners(); 61 | }) 62 | .doOnError((e, stacktrace) { 63 | }) 64 | .doOnListen(() { 65 | }) 66 | .doOnDone(() { 67 | }); 68 | } 69 | 70 | /// 收藏 71 | Observable collectionSong(String songId) { 72 | return _repo 73 | .collectionSong(songId) 74 | .doOnData((result) { 75 | 76 | int index = this.dataArr.indexWhere((song) { 77 | return song.id == songId; 78 | }); 79 | this.dataArr[index].isFav = true; 80 | notifyListeners(); 81 | }) 82 | .doOnError((e, stacktrace) { 83 | }) 84 | .doOnListen(() { 85 | }) 86 | .doOnDone(() { 87 | }); 88 | } 89 | /// 取消收藏 90 | Observable uncollectionSong(String songId) { 91 | return _repo 92 | .uncollectionSong(songId) 93 | .doOnData((result) { 94 | 95 | int index = this.dataArr.indexWhere((song) { 96 | return song.id == songId; 97 | }); 98 | this.dataArr[index].isFav = false; 99 | notifyListeners(); 100 | 101 | Fluttertoast.showToast( 102 | msg: "取消收藏成功", 103 | gravity: ToastGravity.CENTER 104 | ); 105 | }) 106 | .doOnError((e, stacktrace) { 107 | }) 108 | .doOnListen(() { 109 | }) 110 | .doOnDone(() { 111 | }); 112 | } 113 | } -------------------------------------------------------------------------------- /lib/viewmodel/player/player_provide.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_flowermusic/base/base.dart'; 3 | import 'package:flutter_flowermusic/data/song.dart'; 4 | import 'package:flutter_flowermusic/tools/audio_tool.dart'; 5 | import 'package:flutter_flowermusic/tools/player_tool.dart'; 6 | import 'package:flutter_flowermusic/utils/common_util.dart'; 7 | import 'package:flutter_flowermusic/view/player/music_list_page.dart'; 8 | import 'package:flutter_svg/svg.dart'; 9 | 10 | class PlayerProvide extends BaseProvide { 11 | 12 | PlayerProvide() { 13 | PlayerTools.instance.stateSubject.listen((state) { 14 | setControlls(); 15 | if (state == AudioToolsState.isCacheing || state == AudioToolsState.beginPlay) { 16 | this.cacheing = true; 17 | } else { 18 | this.cacheing = false; 19 | } 20 | }); 21 | 22 | PlayerTools.instance.progressSubject.listen((progress) { 23 | var pro = '${PlayerTools.instance.currentProgress}'; 24 | this.songProgress = CommonUtil.dealDuration(pro); 25 | }); 26 | 27 | PlayerTools.instance.currentSongSubject.listen((song) { 28 | this.currentSong = song; 29 | this.notify(); 30 | }); 31 | 32 | setControlls(); 33 | } 34 | 35 | List _controls = []; 36 | List get controls => _controls; 37 | set controls(List controls) { 38 | _controls = controls; 39 | notify(); 40 | } 41 | bool _cacheing = false; 42 | bool get cacheing => _cacheing; 43 | set cacheing(bool cacheing) { 44 | _cacheing = cacheing; 45 | notify(); 46 | } 47 | 48 | double _offsetY = 0.0; 49 | double get offsetY => _offsetY; 50 | set offsetY(double offsetY) { 51 | _offsetY = offsetY; 52 | notify(); 53 | } 54 | 55 | Song _currentSong = Song(); 56 | Song get currentSong => _currentSong; 57 | set currentSong(Song currentSong) { 58 | _currentSong = currentSong; 59 | } 60 | /// 歌曲进度 61 | String _songProgress = ''; 62 | String get songProgress => _songProgress; 63 | set songProgress(String progress) { 64 | _songProgress = progress; 65 | notify(); 66 | } 67 | 68 | /// 歌曲时长 69 | String songDuration() { 70 | return CommonUtil.dealDuration('${PlayerTools.instance.duration}'); 71 | } 72 | 73 | /// slider 74 | double sliderValue() { 75 | if (PlayerTools.instance.duration == 0) { 76 | return 0.0; 77 | } 78 | var value = (PlayerTools.instance.currentProgress/PlayerTools.instance.duration) ?? 0.0; 79 | if (value > 1) { 80 | value = 1.0; 81 | } 82 | return value; 83 | } 84 | 85 | /// seek 86 | seek(double value) { 87 | int d = (value * PlayerTools.instance.duration).toInt(); 88 | PlayerTools.instance.seek(d); 89 | } 90 | 91 | pre() { 92 | PlayerTools.instance.preAction(); 93 | } 94 | play() { 95 | if (PlayerTools.instance.currentState == AudioToolsState.isPlaying) { 96 | PlayerTools.instance.pause(); 97 | } 98 | if (PlayerTools.instance.currentState == AudioToolsState.isPaued) { 99 | PlayerTools.instance.resume(); 100 | } 101 | } 102 | next() { 103 | PlayerTools.instance.nextAction(); 104 | } 105 | 106 | Widget _getModeWidget() { 107 | return 108 | PlayerTools.instance.mode == 0 ? 109 | new SvgPicture.asset("images/ic_spen.svg", width: 28, height: 28): 110 | PlayerTools.instance.mode == 1 ? 111 | new SvgPicture.asset("images/ic_rand.svg", width: 28, height: 28): 112 | new SvgPicture.asset("images/is_single.svg", width: 28, height: 28); 113 | } 114 | 115 | changeMode() { 116 | if (PlayerTools.instance.mode == 0) { 117 | PlayerTools.instance.mode = 1; 118 | setControlls(); 119 | return; 120 | } 121 | if (PlayerTools.instance.mode == 1) { 122 | PlayerTools.instance.mode = 2; 123 | setControlls(); 124 | return; 125 | } 126 | if (PlayerTools.instance.mode == 2) { 127 | PlayerTools.instance.mode = 0; 128 | setControlls(); 129 | return; 130 | } 131 | } 132 | 133 | setControlls() { 134 | this.controls = [ 135 | _getModeWidget(), 136 | new Icon(Icons.skip_previous, color: Colors.white,size: 27,), 137 | PlayerTools.instance.currentState == AudioToolsState.isPlaying ? new Icon(Icons.pause_circle_outline, color: Colors.white,size: 46,):new Icon(Icons.play_circle_outline, color: Colors.white,size: 46,), 138 | new Icon(Icons.skip_next, color: Colors.white,size: 27,), 139 | new Icon(Icons.menu, color: Colors.white,size: 27,) 140 | ]; 141 | } 142 | 143 | showMenu(BuildContext context) { 144 | // Navigator.push(context, new PageRouteBuilder( 145 | // opaque: false, 146 | // pageBuilder: (BuildContext context, _, __) { 147 | // return MusicListPage(); 148 | // }, 149 | // transitionsBuilder: (___, Animation animation, ____, Widget child) { 150 | // return new FadeTransition( 151 | // opacity: animation, 152 | // child: child, 153 | // ); 154 | // } 155 | // )); 156 | 157 | // Navigator.push(context, MaterialPageRoute( 158 | // builder: (_) => MusicListPage())).then((value) { 159 | // if (value) { 160 | // this.loginedOrNot(); 161 | // } 162 | // }); 163 | // pageBuilder: (BuildContext context, _, __) => MusicListPage())).then((value) { 164 | // if (value) { 165 | // } 166 | // }, 167 | Navigator.push( 168 | context, 169 | PageRouteBuilder( 170 | opaque: false, 171 | transitionsBuilder: (___, Animation animation, ____, Widget child) { 172 | return new FadeTransition( 173 | opacity: animation, 174 | child: child, 175 | ); 176 | }, 177 | pageBuilder: (BuildContext context, _, __) => MusicListPage(), 178 | ) 179 | ).then((value) { 180 | if (value) { 181 | this.notify(); 182 | } 183 | }); 184 | } 185 | 186 | notify() { 187 | notifyListeners(); 188 | } 189 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_flowermusic 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 | # Read more about versioning at semver.org. 10 | version: 1.0.0+1 11 | 12 | environment: 13 | sdk: ">=2.0.0-dev.68.0 <3.0.0" 14 | 15 | dependencies: 16 | flutter: 17 | sdk: flutter 18 | 19 | # The following adds the Cupertino Icons font to your application. 20 | # Use with the CupertinoIcons class for iOS style icons. 21 | cupertino_icons: ^0.1.2 22 | dio: ^2.0.7 23 | rxdart: ^0.21.0 24 | # provide: ^1.0.2 25 | cached_network_image: ^2.0.0-rc 26 | flutter_html: ^0.9.6 27 | shared_preferences: ^0.5.1+2 28 | fluttertoast: ^3.1.3 29 | multi_image_picker: ^4.6.1 30 | audioplayers: ^0.11.0 31 | flutter_svg: ^0.13.1 32 | provider: ^3.1.0 33 | webview_flutter: ^0.3.10+3 34 | flutter_webview_plugin: ^0.3.7 35 | url_launcher: ^5.1.0 36 | 37 | dev_dependencies: 38 | flutter_test: 39 | sdk: flutter 40 | 41 | build_runner: ^1.0.0 42 | json_serializable: ^2.0.0 43 | # For information on the generic Dart part of this file, see the 44 | # following page: https://www.dartlang.org/tools/pub/pubspec 45 | 46 | # The following section is specific to Flutter. 47 | flutter: 48 | 49 | # The following line ensures that the Material Icons font is 50 | # included with your application, so that you can use the icons in 51 | # the material Icons class. 52 | uses-material-design: true 53 | 54 | # To add assets to your application, add an assets section, like this: 55 | assets: 56 | - images/ 57 | 58 | # An image asset can refer to one or more resolution-specific "variants", see 59 | # https://flutter.io/assets-and-images/#resolution-aware. 60 | 61 | # For details regarding adding assets from package dependencies, see 62 | # https://flutter.io/assets-and-images/#from-packages 63 | 64 | # To add custom fonts to your application, add a fonts section here, 65 | # in this "flutter" section. Each entry in this list should have a 66 | # "family" key with the font family name, and a "fonts" key with a 67 | # list giving the asset and other descriptors for the font. For 68 | # example: 69 | # fonts: 70 | # - family: Schyler 71 | # fonts: 72 | # - asset: fonts/Schyler-Regular.ttf 73 | # - asset: fonts/Schyler-Italic.ttf 74 | # style: italic 75 | # - family: Trajan Pro 76 | # fonts: 77 | # - asset: fonts/TrajanPro.ttf 78 | # - asset: fonts/TrajanPro_Bold.ttf 79 | # weight: 700 80 | # 81 | # For details regarding fonts from package dependencies, 82 | # see https://flutter.io/custom-fonts/#from-packages 83 | -------------------------------------------------------------------------------- /screenShots/IMG_6994(20191205-090511).JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/screenShots/IMG_6994(20191205-090511).JPG -------------------------------------------------------------------------------- /screenShots/QRCode_258.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/screenShots/QRCode_258.png -------------------------------------------------------------------------------- /screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.151.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.151.png -------------------------------------------------------------------------------- /screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.18.png -------------------------------------------------------------------------------- /screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.21.png -------------------------------------------------------------------------------- /screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.25.png -------------------------------------------------------------------------------- /screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.28.png -------------------------------------------------------------------------------- /screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.35.png -------------------------------------------------------------------------------- /screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.38.png -------------------------------------------------------------------------------- /screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.44.png -------------------------------------------------------------------------------- /screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.53.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/screenShots/Simulator Screen Shot - iPhone Xʀ - 2019-05-06 at 18.23.53.png -------------------------------------------------------------------------------- /screenShots/屏幕快照 2019-05-06 下午8.29.23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darren-chenchen/flutter_flowermusic/b434a027fbdb8b56732b20339282b1ac25a892c7/screenShots/屏幕快照 2019-05-06 下午8.29.23.png -------------------------------------------------------------------------------- /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_flowermusic/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 | --------------------------------------------------------------------------------