├── netease_cloud_music
├── android
│ ├── settings_aar.gradle
│ ├── key.jks
│ ├── gradle.properties
│ ├── app
│ │ ├── src
│ │ │ ├── main
│ │ │ │ ├── res
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── xml
│ │ │ │ │ │ └── network_security_config.xml
│ │ │ │ │ ├── values
│ │ │ │ │ │ └── styles.xml
│ │ │ │ │ └── drawable
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── kotlin
│ │ │ │ │ └── com
│ │ │ │ │ │ └── example
│ │ │ │ │ │ └── netease_cloud_music
│ │ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ └── build.gradle
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ ├── settings.gradle
│ └── build.gradle
├── ios
│ ├── Runner
│ │ ├── Runner-Bridging-Header.h
│ │ ├── Assets.xcassets
│ │ │ ├── LaunchImage.imageset
│ │ │ │ ├── splash-3.png
│ │ │ │ ├── splash.png
│ │ │ │ ├── splash1.png
│ │ │ │ ├── splash@3x.png
│ │ │ │ ├── splash_xr.png
│ │ │ │ ├── LaunchImage.png
│ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ ├── LaunchImage@3x.png
│ │ │ │ ├── splash_640_1136.png
│ │ │ │ ├── splash_640_960.png
│ │ │ │ ├── README.md
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ │ └── Contents.json
│ │ ├── AppDelegate.swift
│ │ ├── Base.lproj
│ │ │ ├── Main.storyboard
│ │ │ └── LaunchScreen.storyboard
│ │ └── Info.plist
│ ├── Flutter
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── AppFrameworkInfo.plist
│ ├── Runner.xcodeproj
│ │ ├── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── Podfile.lock
├── images
│ ├── bet.png
│ ├── bfc.png
│ ├── bgm.png
│ ├── ck.9.png
│ ├── list.png
│ ├── play.png
│ ├── pause.png
│ ├── bg_daily.png
│ ├── icon_del.png
│ ├── icon_down.png
│ ├── icon_edit.png
│ ├── icon_logo.png
│ ├── icon_look.png
│ ├── icon_rank.png
│ ├── icon_up.png
│ ├── welcome.png
│ ├── icon_album.png
│ ├── icon_daily.png
│ ├── icon_liked.png
│ ├── icon_line_1.png
│ ├── icon_music.png
│ ├── icon_parise.png
│ ├── icon_radio.png
│ ├── icon_share.png
│ ├── icon_broadcast.png
│ ├── icon_collect.png
│ ├── icon_comment.png
│ ├── icon_dislike.png
│ ├── icon_download.png
│ ├── icon_late_play.png
│ ├── icon_playlist.png
│ ├── icon_song_left.png
│ ├── icon_song_more.png
│ ├── icon_song_play.png
│ ├── icon_triangle.png
│ ├── icon_event_play.png
│ ├── icon_event_share.png
│ ├── icon_play_songs.png
│ ├── icon_song_pause.png
│ ├── icon_song_right.png
│ ├── icon_download_black.png
│ ├── icon_event_commend.png
│ ├── icon_event_comment.png
│ ├── icon_multi_select.png
│ ├── icon_song_comment.png
│ ├── icon_song_download.png
│ ├── icon_songs_circle.png
│ ├── icon_songs_random.png
│ ├── icon_event_video_bar.png
│ ├── icon_event_video_play.png
│ ├── icon_song_play_type_1.png
│ ├── icon_event_video_b_play.png
│ └── icon_song_single_circle.png
├── lib
│ ├── pages
│ │ ├── comment
│ │ │ ├── comment_type.dart
│ │ │ └── comment_input_widget.dart
│ │ ├── home
│ │ │ ├── my
│ │ │ │ └── playlist_title.dart
│ │ │ └── home_page.dart
│ │ ├── splash_page.dart
│ │ ├── user
│ │ │ └── user_detail_page.dart
│ │ └── play_list
│ │ │ └── play_list_desc_dialog.dart
│ ├── model
│ │ ├── music.dart
│ │ ├── song.dart
│ │ ├── comment_head.dart
│ │ ├── banner.dart
│ │ └── hot_search.dart
│ ├── widgets
│ │ ├── h_empty_view.dart
│ │ ├── v_empty_view.dart
│ │ ├── widget_img_menu.dart
│ │ ├── widget_topic_text.dart
│ │ ├── widget_round_img.dart
│ │ ├── widget_footer_tab.dart
│ │ ├── common_button.dart
│ │ ├── rounded_net_image.dart
│ │ ├── widget_load_footer.dart
│ │ ├── widget_net_error.dart
│ │ ├── loading.dart
│ │ ├── widget_tag.dart
│ │ ├── widget_search_user.dart
│ │ ├── widget_play_bottom_menu.dart
│ │ ├── widget_artists.dart
│ │ ├── widget_at_text.dart
│ │ ├── widget_album.dart
│ │ ├── widget_search_play_list.dart
│ │ ├── widget_play_list.dart
│ │ ├── widget_play_list_cover.dart
│ │ ├── widget_music_list_header.dart
│ │ ├── widget_play_list_app_bar.dart
│ │ ├── widget_song_progress.dart
│ │ ├── widget_event_video.dart
│ │ ├── widget_search_video.dart
│ │ ├── widget_sliver_future_builder.dart
│ │ ├── widget_event_song.dart
│ │ ├── widget_edit_play_list.dart
│ │ ├── common_text_style.dart
│ │ ├── widget_music_list_item.dart
│ │ ├── widget_future_builder.dart
│ │ ├── widget_play.dart
│ │ ├── widget_create_play_list.dart
│ │ ├── flexible_detail_bar.dart
│ │ ├── widget_play_list_menu.dart
│ │ └── widget_banner.dart
│ ├── route
│ │ ├── navigate_service.dart
│ │ ├── transparent_route.dart
│ │ ├── routes.dart
│ │ └── route_handles.dart
│ ├── application.dart
│ ├── utils
│ │ ├── event_special_text_span_builder.dart
│ │ ├── custom_log_interceptor.dart
│ │ ├── log_util.dart
│ │ ├── number_utils.dart
│ │ ├── fluro_convert_utils.dart
│ │ ├── utils.dart
│ │ └── navigator_util.dart
│ ├── provider
│ │ ├── user_model.dart
│ │ ├── play_list_model.dart
│ │ └── play_songs_model.dart
│ └── main.dart
├── .metadata
├── README.md
├── test
│ └── widget_test.dart
├── .gitignore
├── .flutter-plugins-dependencies
└── pubspec.yaml
├── .github
└── workflows
│ └── feedback.yml
├── .gitignore
└── README.md
/netease_cloud_music/android/settings_aar.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
--------------------------------------------------------------------------------
/netease_cloud_music/android/key.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/android/key.jks
--------------------------------------------------------------------------------
/netease_cloud_music/images/bet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/bet.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/bfc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/bfc.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/bgm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/bgm.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/ck.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/ck.9.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/list.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/play.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/pause.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/bg_daily.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/bg_daily.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_del.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_del.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_down.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_edit.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_logo.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_look.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_look.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_rank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_rank.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_up.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/welcome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/welcome.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_album.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_album.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_daily.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_daily.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_liked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_liked.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_line_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_line_1.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_music.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_music.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_parise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_parise.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_radio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_radio.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_share.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_broadcast.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_broadcast.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_collect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_collect.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_comment.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_dislike.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_dislike.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_download.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_late_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_late_play.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_playlist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_playlist.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_song_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_song_left.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_song_more.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_song_more.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_song_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_song_play.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_triangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_triangle.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_event_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_event_play.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_event_share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_event_share.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_play_songs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_play_songs.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_song_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_song_pause.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_song_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_song_right.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_download_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_download_black.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_event_commend.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_event_commend.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_event_comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_event_comment.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_multi_select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_multi_select.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_song_comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_song_comment.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_song_download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_song_download.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_songs_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_songs_circle.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_songs_random.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_songs_random.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/netease_cloud_music/android/gradle.properties:
--------------------------------------------------------------------------------
1 | android.enableJetifier=true
2 | android.useAndroidX=true
3 | org.gradle.jvmargs=-Xmx1536M
4 |
5 | android.enableR8=true
6 |
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_event_video_bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_event_video_bar.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_event_video_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_event_video_play.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_song_play_type_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_song_play_type_1.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_event_video_b_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_event_video_b_play.png
--------------------------------------------------------------------------------
/netease_cloud_music/images/icon_song_single_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/images/icon_song_single_circle.png
--------------------------------------------------------------------------------
/netease_cloud_music/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/netease_cloud_music/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/netease_cloud_music/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/netease_cloud_music/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/netease_cloud_music/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/splash-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/splash-3.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/splash.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/splash1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/splash1.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/splash@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/splash@3x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/splash_xr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/splash_xr.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/splash_640_1136.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/splash_640_1136.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/splash_640_960.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/splash_640_960.png
--------------------------------------------------------------------------------
/netease_cloud_music/lib/pages/comment/comment_type.dart:
--------------------------------------------------------------------------------
1 | enum CommentType{
2 | song, // 歌曲
3 | mv, // mv
4 | playList, // 歌单
5 | album, // 专辑
6 | broadcast, // 电台
7 | video, // 视频
8 | thread, // 动态
9 | }
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/NeteaseCloudMusic/HEAD/netease_cloud_music/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/netease_cloud_music/android/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/model/music.dart:
--------------------------------------------------------------------------------
1 | class MusicData {
2 | num mvid;
3 | String picUrl;
4 | String songName;
5 | String artists;
6 | int index;
7 |
8 | MusicData({
9 | this.mvid,
10 | this.picUrl,
11 | this.songName,
12 | this.artists,
13 | this.index,
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/netease_cloud_music/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 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/h_empty_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class HEmptyView extends StatelessWidget {
4 | final double width;
5 |
6 | HEmptyView(this.width);
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return SizedBox(width: width,);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/netease_cloud_music/.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: cc949a8e8b9cf394b9290a8e80f87af3e207dce5
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/netease_cloud_music/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/netease_cloud_music/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/netease_cloud_music/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.
--------------------------------------------------------------------------------
/netease_cloud_music/lib/route/navigate_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class NavigateService {
4 | final GlobalKey key = GlobalKey(debugLabel: 'navigate_key');
5 |
6 | NavigatorState get navigator => key.currentState;
7 |
8 | get pushNamed => navigator.pushNamed;
9 | get push => navigator.push;
10 | get popAndPushNamed => navigator.popAndPushNamed;
11 | }
12 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/v_empty_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 |
4 | class VEmptyView extends StatelessWidget {
5 | final double height;
6 |
7 | VEmptyView(this.height);
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return SizedBox(
12 | height: ScreenUtil().setWidth(height),
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/netease_cloud_music/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/netease_cloud_music/android/app/src/main/kotlin/com/example/netease_cloud_music/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.netease_cloud_music
2 |
3 | import android.os.Bundle
4 |
5 | import io.flutter.app.FlutterActivity
6 | import io.flutter.plugins.GeneratedPluginRegistrant
7 |
8 | class MainActivity: FlutterActivity() {
9 | override fun onCreate(savedInstanceState: Bundle?) {
10 | super.onCreate(savedInstanceState)
11 | GeneratedPluginRegistrant.registerWith(this)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/netease_cloud_music/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/netease_cloud_music/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 |
--------------------------------------------------------------------------------
/.github/workflows/feedback.yml:
--------------------------------------------------------------------------------
1 | name: feedback
2 | on: [fork, watch, issues]
3 | jobs:
4 | feedback:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - name: feedback
8 | id: feedback
9 | uses: d2-projects/repo-email-feedback@v1.3
10 | with:
11 | username: ${{ secrets.FEEDBACK_USERNAME }}
12 | sign: ${{ secrets.FEEDBACK_SIGN }}
13 | wxpusher: ${{ secrets.FEEDBACK_WXPUSHER_UID }}
14 | template: ${{ github.repository }}
15 | repo: ${{ github.repository }}
16 | actor: ${{ github.actor }}
17 | token: ${{ secrets.GITHUB_TOKEN }}
18 |
--------------------------------------------------------------------------------
/netease_cloud_music/README.md:
--------------------------------------------------------------------------------
1 | # netease_cloud_music
2 |
3 | A new Flutter project.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
13 |
14 | For help getting started with Flutter, view our
15 | [online documentation](https://flutter.dev/docs), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_img_menu.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 |
4 | class ImageMenuWidget extends StatelessWidget {
5 | final String img;
6 | final double size;
7 | final VoidCallback onTap;
8 |
9 | ImageMenuWidget(this.img, this.size, {this.onTap});
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Expanded(
14 | child: GestureDetector(
15 | onTap: onTap,
16 | child: Image.asset(
17 | img,
18 | width: ScreenUtil().setWidth(size),
19 | height: ScreenUtil().setWidth(size),
20 | ),
21 | ),
22 | );
23 | }
24 | }
--------------------------------------------------------------------------------
/netease_cloud_music/lib/model/song.dart:
--------------------------------------------------------------------------------
1 | class Song {
2 | int id; // 歌曲id
3 | String name; // 歌曲名称
4 | String artists; // 演唱者
5 | String picUrl; // 歌曲图片
6 |
7 | Song(this.id, {this.name, this.artists, this.picUrl});
8 |
9 | Song.fromJson(Map json)
10 | : id = json['id'],
11 | name = json['name'],
12 | artists = json['artists'],
13 | picUrl = json['picUrl'];
14 |
15 | Map toJson() => {
16 | 'id': id,
17 | 'name': name,
18 | 'artists': artists,
19 | 'picUrl': picUrl,
20 | };
21 |
22 | @override
23 | String toString() {
24 | return 'Song{id: $id, name: $name, artists: $artists}';
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_topic_text.dart:
--------------------------------------------------------------------------------
1 | import 'package:extended_text/extended_text.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class TopicText extends SpecialText{
5 | static const String flag = "#";
6 | final int start;
7 |
8 | TopicText(TextStyle textStyle, this.start) : super(flag, flag, textStyle);
9 |
10 | @override
11 | InlineSpan finishText() {
12 | TextStyle textStyle =
13 | this.textStyle?.copyWith(color: Colors.blue, fontSize: 16.0);
14 |
15 | final String topicText = toString();
16 |
17 | return SpecialTextSpan(
18 | text: topicText,
19 | actualText: topicText,
20 | start: start,
21 | style: textStyle,);
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/netease_cloud_music/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.2.71'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.2.1'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | jcenter()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/application.dart:
--------------------------------------------------------------------------------
1 | import 'package:fluro/fluro.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:get_it/get_it.dart';
4 | import 'package:netease_cloud_music/route/navigate_service.dart';
5 | import 'package:shared_preferences/shared_preferences.dart';
6 |
7 | class Application{
8 | static Router router;
9 | static GlobalKey key = GlobalKey();
10 | static SharedPreferences sp;
11 | static double screenWidth;
12 | static double screenHeight;
13 | static double statusBarHeight;
14 | static double bottomBarHeight;
15 | static GetIt getIt = GetIt.instance;
16 |
17 | static initSp() async{
18 | sp = await SharedPreferences.getInstance();
19 | }
20 |
21 | static setupLocator(){
22 | getIt.registerSingleton(NavigateService());
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/netease_cloud_music/lib/utils/event_special_text_span_builder.dart:
--------------------------------------------------------------------------------
1 | import 'package:extended_text/extended_text.dart';
2 | import 'package:flutter/src/painting/text_style.dart';
3 | import 'package:netease_cloud_music/widgets/widget_at_text.dart';
4 | import 'package:netease_cloud_music/widgets/widget_topic_text.dart';
5 |
6 | class EventSpecialTextSpanBuilder extends SpecialTextSpanBuilder{
7 | @override
8 | SpecialText createSpecialText(String flag, {TextStyle textStyle, onTap, int index}) {
9 | if (flag == null || flag == "") return null;
10 | if(isStart(flag, TopicText.flag)){
11 | return TopicText(textStyle, index - (TopicText.flag.length - 1));
12 | }else if (isStart(flag, AtText.flag)){
13 | return AtText(textStyle, onTap, start: index - (AtText.flag.length - 1));
14 | }
15 | return null;
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_round_img.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:netease_cloud_music/utils/utils.dart';
4 |
5 | class RoundImgWidget extends StatelessWidget {
6 | final String img;
7 | final double width;
8 | final BoxFit fit;
9 |
10 | RoundImgWidget(this.img, this.width, {this.fit});
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | return ClipRRect(
15 | borderRadius: BorderRadius.circular(width / 2),
16 | child: img.startsWith('http')
17 | ? Utils.showNetImage(img,
18 | width: ScreenUtil().setWidth(width),
19 | height: ScreenUtil().setWidth(width),
20 | fit: fit)
21 | : Image.asset(img, fit: fit,),
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/utils/custom_log_interceptor.dart:
--------------------------------------------------------------------------------
1 |
2 | import 'package:dio/dio.dart';
3 |
4 | import 'log_util.dart';
5 |
6 | class CustomLogInterceptor extends LogInterceptor {
7 | CustomLogInterceptor({
8 | request = true,
9 | requestHeader = true,
10 | requestBody = false,
11 | responseHeader = true,
12 | responseBody = false,
13 | error = true,
14 | logSize = 9999999,
15 | }) : super(
16 | request: request,
17 | requestHeader: requestHeader,
18 | requestBody: requestBody,
19 | responseHeader: responseHeader,
20 | responseBody: responseBody,
21 | error: error,);
22 |
23 | @override
24 | printKV(String key, Object v) {
25 | LogUtil.e('$key: $v');
26 | }
27 | @override
28 | printAll(msg) {
29 | LogUtil.e('$msg');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_footer_tab.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:netease_cloud_music/widgets/common_text_style.dart';
4 | import 'package:netease_cloud_music/widgets/v_empty_view.dart';
5 |
6 | class FooterTabWidget extends StatelessWidget {
7 | final String img;
8 | final String text;
9 | final VoidCallback onTap;
10 |
11 |
12 | FooterTabWidget(this.img, this.text, this.onTap);
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return Expanded(
17 | child: GestureDetector(
18 | onTap: onTap,
19 | child: Column(
20 | children: [
21 | Image.asset(img, width: ScreenUtil().setWidth(50), height: ScreenUtil().setWidth(50),),
22 | VEmptyView(8),
23 | Text(text, style: common14White70TextStyle,)
24 | ],
25 | ),
26 | ),
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/common_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CommonButton extends StatelessWidget {
4 | final VoidCallback callback;
5 | final String content;
6 | final double width;
7 | final double height;
8 | final double fontSize;
9 |
10 | CommonButton({
11 | @required this.callback,
12 | @required this.content,
13 | this.width = 250,
14 | this.height = 50,
15 | this.fontSize = 18,
16 | });
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return Container(
21 | width: width,
22 | height: height,
23 | child: RaisedButton(
24 | onPressed: callback,
25 | color: Colors.red,
26 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(height / 2))),
27 | child: Text(
28 | content,
29 | style: TextStyle(color: Colors.white, fontSize: fontSize),
30 | ),
31 | ),
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/rounded_net_image.dart:
--------------------------------------------------------------------------------
1 | import 'package:extended_image/extended_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:netease_cloud_music/utils/utils.dart';
5 |
6 | class RoundedNetImage extends StatelessWidget {
7 | final String url;
8 | final double width;
9 | final double height;
10 | final double radius;
11 | final BoxFit fit;
12 |
13 | RoundedNetImage(this.url, {this.width, this.height, this.radius = 10, this.fit});
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return ClipRRect(
18 | borderRadius: BorderRadius.all(Radius.circular(ScreenUtil().setWidth(radius))),
19 | child: Utils.showNetImage(
20 | url,
21 | width: width == null ? null : ScreenUtil().setWidth(width),
22 | height: height == null ? null : ScreenUtil().setWidth(height),
23 | fit: fit
24 | ),
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/model/comment_head.dart:
--------------------------------------------------------------------------------
1 | import 'package:netease_cloud_music/pages/comment/comment_type.dart';
2 |
3 | /// 用于传给评论页面
4 | class CommentHead{
5 | String img; // 图片
6 | String title; // 标题
7 | String author; // 作者
8 | int count; // 评论数
9 | int id; // id
10 | int type; //类型(歌曲还是歌单之类的)
11 |
12 | CommentHead(this.img, this.title, this.author, this.count, this.id, this.type);
13 |
14 |
15 |
16 | CommentHead.fromJson(Map json) {
17 | img = json['img'];
18 | title = json['title'];
19 | author = json['author'];
20 | count = json['count'];
21 | id = json['id'];
22 | type = json['type'];
23 | }
24 |
25 | Map toJson() {
26 | final Map data = new Map();
27 | data['img'] = this.img;
28 | data['title'] = this.title;
29 | data['author'] = this.author;
30 | data['count'] = this.count;
31 | data['id'] = this.id;
32 | data['type'] = this.type;
33 | return data;
34 | }
35 | }
--------------------------------------------------------------------------------
/netease_cloud_music/lib/utils/log_util.dart:
--------------------------------------------------------------------------------
1 | class LogUtil {
2 | static const String _TAG_DEF = "###common_utils###";
3 |
4 | static bool debuggable = false; //是否是debug模式,true: log v 不输出.
5 | static String TAG = _TAG_DEF;
6 |
7 | static void init({bool isDebug = false, String tag = _TAG_DEF}) {
8 | debuggable = isDebug;
9 | TAG = tag;
10 | }
11 |
12 | static void e(Object object, {String tag}) {
13 | _printLog(tag, ':', object);
14 | }
15 |
16 | static void v(Object object, {String tag}) {
17 | if (debuggable) {
18 | _printLog(tag, ':', object);
19 | }
20 | }
21 |
22 | static void _printLog(String tag, String stag, Object object) {
23 | String da = object.toString();
24 | String _tag = (tag == null || tag.isEmpty) ? TAG : tag;
25 | while (da.isNotEmpty) {
26 | if (da.length > 1024) {
27 | print("$_tag $stag ${da.substring(0, 1024)}");
28 | da = da.substring(1024, da.length);
29 | } else {
30 | print("$_tag $stag $da");
31 | da = "";
32 | }
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/netease_cloud_music/lib/route/transparent_route.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class TransparentRoute extends PageRoute {
4 | TransparentRoute({
5 | @required this.builder,
6 | RouteSettings settings,
7 | }) : assert(builder != null),
8 | super(settings: settings, fullscreenDialog: false);
9 |
10 | final WidgetBuilder builder;
11 |
12 | @override
13 | bool get opaque => false;
14 |
15 | @override
16 | Color get barrierColor => null;
17 |
18 | @override
19 | String get barrierLabel => null;
20 |
21 | @override
22 | bool get maintainState => true;
23 |
24 | @override
25 | Duration get transitionDuration => Duration(milliseconds: 300);
26 |
27 | @override
28 | Widget buildPage(BuildContext context, Animation animation,
29 | Animation secondaryAnimation) {
30 | final result = builder(context);
31 | //FadeTransition渐隐渐现
32 | return FadeTransition(
33 | opacity: Tween(begin: 0.0, end: 1.0)
34 | .animate(CurvedAnimation(parent: animation, curve: Curves.linear)),
35 | child: result,
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_load_footer.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/src/painting/basic_types.dart';
3 | import 'package:flutter/src/widgets/framework.dart';
4 | import 'package:flutter_easyrefresh/easy_refresh.dart';
5 | import 'package:flutter_screenutil/flutter_screenutil.dart';
6 |
7 | class LoadFooter extends Footer {
8 | @override
9 | Widget contentBuilder(
10 | BuildContext context,
11 | LoadMode loadState,
12 | double pulledExtent,
13 | double loadTriggerPullDistance,
14 | double loadIndicatorExtent,
15 | AxisDirection axisDirection,
16 | bool float,
17 | Duration completeDuration,
18 | bool enableInfiniteLoad,
19 | bool success,
20 | bool noMore) {
21 | if (noMore)
22 | return Container(
23 | height: ScreenUtil().setWidth(100),
24 | alignment: Alignment.center,
25 | child: Text('暂无更多数据'),
26 | );
27 | else
28 | return Container(
29 | height: ScreenUtil().setWidth(100),
30 | alignment: Alignment.center,
31 | child: Text('正在加载...'),
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_net_error.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:netease_cloud_music/widgets/common_text_style.dart';
3 | import 'package:netease_cloud_music/widgets/v_empty_view.dart';
4 | import 'package:flutter_screenutil/flutter_screenutil.dart';
5 |
6 | class NetErrorWidget extends StatelessWidget {
7 | final VoidCallback callback;
8 |
9 | NetErrorWidget({@required this.callback});
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return GestureDetector(
14 | onTap: callback,
15 | child: Container(
16 | alignment: Alignment.center,
17 | height: ScreenUtil().setWidth(200),
18 | child: Column(
19 | mainAxisSize: MainAxisSize.min,
20 | children: [
21 | Icon(
22 | Icons.error_outline,
23 | size: ScreenUtil().setWidth(80),
24 | ),
25 | VEmptyView(ScreenUtil().setWidth(10)),
26 | Text(
27 | '点击重新请求',
28 | style: commonTextStyle,
29 | )
30 | ],
31 | ),
32 | ),
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/netease_cloud_music/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:netease_cloud_music/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/utils/number_utils.dart:
--------------------------------------------------------------------------------
1 | class NumberUtils {
2 | static const double MILLION = 10000.0;
3 | static const double MILLIONS = 1000000.0;
4 | static const double BILLION = 100000000.0;
5 | static const String MILLION_UNIT = "万";
6 | static const String BILLION_UNIT = "亿";
7 |
8 | static String amountConversion(num amount) {
9 | //最终返回的结果值
10 | String result = amount.toString();
11 |
12 | if(amount > MILLION * 10 && amount <= MILLIONS){
13 | result = '${(amount / MILLION).toStringAsFixed(1)}$MILLION_UNIT';
14 | }else if (amount > MILLIONS && amount <= BILLION) {
15 | //如果值刚好是10000万,则要变成1亿
16 | if (amount == BILLION) {
17 | result = '${amount ~/ BILLION}$BILLION_UNIT';
18 | } else {
19 | result = '${amount ~/ MILLION}$MILLION_UNIT';
20 | }
21 | }
22 | //金额大于1亿
23 | else if (amount > BILLION) {
24 |
25 | result = '${amount ~/ BILLION}$BILLION_UNIT';
26 | } else {
27 | result = amount.toString();
28 | }
29 | return result;
30 | }
31 |
32 | static String formatNum(num n){
33 | if(n >= MILLION){
34 | var r = n ~/ MILLION;
35 | return '${r >= 10 ? 10 : r}w+';
36 | }else{
37 | return '$n';
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/utils/fluro_convert_utils.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | /// fluro 参数编码解码工具类
4 | class FluroConvertUtils {
5 | /// fluro 传递中文参数前,先转换,fluro 不支持中文传递
6 | static String fluroCnParamsEncode(String originalCn) {
7 | return jsonEncode(Utf8Encoder().convert(originalCn));
8 | }
9 |
10 | /// fluro 传递后取出参数,解析
11 | static String fluroCnParamsDecode(String encodeCn) {
12 | var list = List();
13 |
14 | ///字符串解码
15 | jsonDecode(encodeCn).forEach(list.add);
16 | String value = Utf8Decoder().convert(list);
17 | return value;
18 | }
19 |
20 | /// string 转为 int
21 | static int string2int(String str) {
22 | return int.parse(str);
23 | }
24 |
25 | /// string 转为 double
26 | static double string2double(String str) {
27 | return double.parse(str);
28 | }
29 |
30 | /// string 转为 bool
31 | static bool string2bool(String str) {
32 | if (str == 'true') {
33 | return true;
34 | } else {
35 | return false;
36 | }
37 | }
38 |
39 | /// object 转为 string json
40 | static String object2string(T t) {
41 | return fluroCnParamsEncode(jsonEncode(t));
42 | }
43 |
44 | /// string json 转为 map
45 | static Map string2map(String str) {
46 | return json.decode(fluroCnParamsDecode(str));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/loading.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class Loading {
5 | static bool isLoading = false;
6 |
7 | static void showLoading(BuildContext context) {
8 | if (!isLoading) {
9 | isLoading = true;
10 | showGeneralDialog(
11 | context: context,
12 | barrierDismissible: false,
13 | barrierLabel:
14 | MaterialLocalizations.of(context).modalBarrierDismissLabel,
15 | transitionDuration: const Duration(milliseconds: 150),
16 | pageBuilder: (BuildContext context, Animation animation,
17 | Animation secondaryAnimation) {
18 | return Align(
19 | child: ClipRRect(
20 | borderRadius: BorderRadius.circular(10),
21 | child: Container(
22 | width: 100,
23 | height: 100,
24 | color: Colors.black54,
25 | child: CupertinoActivityIndicator(),
26 | ),
27 | ),
28 | );
29 | }).then((v) {
30 | isLoading = false;
31 | });
32 | }
33 | }
34 |
35 | static void hideLoading(BuildContext context) {
36 | if (isLoading) {
37 | Navigator.of(context).pop();
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/provider/user_model.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:netease_cloud_music/application.dart';
5 | import 'package:netease_cloud_music/model/user.dart';
6 | import 'package:netease_cloud_music/utils/navigator_util.dart';
7 | import 'package:netease_cloud_music/utils/net_utils.dart';
8 | import 'package:fluttertoast/fluttertoast.dart';
9 | import 'package:netease_cloud_music/utils/utils.dart';
10 |
11 | class UserModel with ChangeNotifier {
12 | User _user;
13 |
14 | User get user => _user;
15 |
16 | /// 初始化 User
17 | void initUser() {
18 | if (Application.sp.containsKey('user')) {
19 | String s = Application.sp.getString('user');
20 | _user = User.fromJson(json.decode(s));
21 | }
22 | }
23 |
24 | /// 登录
25 | Future login(BuildContext context, String phone, String pwd) async {
26 |
27 | User user = await NetUtils.login(context, phone, pwd);
28 | if (user.code > 299) {
29 | Utils.showToast(user.msg ?? '登录失败,请检查账号密码');
30 | return null;
31 | }
32 | Utils.showToast(user.msg ?? '登录成功');
33 | _saveUserInfo(user);
34 | return user;
35 | }
36 |
37 | /// 保存用户信息到 sp
38 | _saveUserInfo(User user) {
39 | _user = user;
40 | Application.sp.setString('user', json.encode(user.toJson()));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_tag.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_screenutil/flutter_screenutil.dart';
5 | import 'package:netease_cloud_music/widgets/common_text_style.dart';
6 |
7 | class TagWidget extends StatelessWidget {
8 |
9 | final String tag;
10 |
11 |
12 | TagWidget(this.tag);
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return Container(
17 | margin: EdgeInsets.only(left: ScreenUtil().setWidth(20)),
18 | child: ClipRRect(
19 | borderRadius: BorderRadius.horizontal(
20 | left: Radius.circular(ScreenUtil().setWidth(20)),
21 | right: Radius.circular(ScreenUtil().setWidth(20))),
22 | child: Container(
23 | height: ScreenUtil().setWidth(40),
24 | child: BackdropFilter(
25 | filter: ImageFilter.blur(
26 | sigmaY: 30,
27 | sigmaX: 30,
28 | ),
29 | child: Container(
30 | color: Colors.white10,
31 | padding: EdgeInsets.symmetric(horizontal: ScreenUtil().setWidth(25), vertical: ScreenUtil().setWidth(5)),
32 | alignment: Alignment.center,
33 | child: Text(tag, style: smallWhiteTextStyle,),
34 | ),
35 | ),
36 | ),
37 | ),
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/provider/play_list_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:netease_cloud_music/model/play_list.dart';
3 | import 'package:netease_cloud_music/model/user.dart';
4 | import 'package:netease_cloud_music/utils/net_utils.dart';
5 |
6 | class PlayListModel with ChangeNotifier {
7 | User user;
8 |
9 | List _selfCreatePlayList = []; // 我创建的歌单
10 | List _collectPlayList = []; // 收藏的歌单
11 | List _allPlayList = []; // 所有的歌单
12 |
13 | List get selfCreatePlayList => _selfCreatePlayList;
14 |
15 | List get collectPlayList => _collectPlayList;
16 |
17 | List get allPlayList => _allPlayList;
18 |
19 |
20 | void setPlayList(List value) {
21 | _allPlayList = value;
22 | _splitPlayList();
23 | }
24 |
25 | void _splitPlayList() {
26 | _selfCreatePlayList =
27 | _allPlayList.where((p) => p.creator.userId == user.account.id).toList();
28 | _collectPlayList =
29 | _allPlayList.where((p) => p.creator.userId != user.account.id).toList();
30 | notifyListeners();
31 | }
32 |
33 | void addPlayList(Playlist playlist){
34 | _allPlayList.add(playlist);
35 | _splitPlayList();
36 | }
37 |
38 | void delPlayList(Playlist playlist) {
39 | _allPlayList.remove(playlist);
40 | _splitPlayList();
41 | }
42 |
43 | void getSelfPlaylistData(BuildContext context) async{
44 | var result = await NetUtils.getSelfPlaylistData(context, params: {'uid': user.account.id});
45 | setPlayList(result.playlist);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_search_user.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:netease_cloud_music/widgets/v_empty_view.dart';
4 | import 'package:netease_cloud_music/widgets/widget_round_img.dart';
5 |
6 | import 'common_text_style.dart';
7 | import 'h_empty_view.dart';
8 |
9 | class SearchUserWidget extends StatelessWidget {
10 |
11 | final String url;
12 | final String name;
13 | final String description;
14 |
15 |
16 | SearchUserWidget({this.url, this.name, this.description});
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return Padding(
21 | padding:
22 | EdgeInsets.symmetric(vertical: ScreenUtil().setWidth(10)),
23 | child: Row(
24 | children: [
25 | RoundImgWidget(
26 | '$url?param=150y150',
27 | 140,
28 | ),
29 | HEmptyView(10),
30 | Expanded(
31 | child: Column(
32 | mainAxisSize: MainAxisSize.min,
33 | crossAxisAlignment: CrossAxisAlignment.start,
34 | children: [
35 | Text(
36 | name,
37 | style: common14TextStyle,
38 | ),
39 | description.isEmpty ? Container() : VEmptyView(10),
40 | Text(
41 | description,
42 | style: smallGrayTextStyle,
43 | ),
44 | ],
45 | ),
46 | ),
47 | ],
48 | ),
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/route/routes.dart:
--------------------------------------------------------------------------------
1 | import 'package:fluro/fluro.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:netease_cloud_music/pages/login_page.dart';
4 | import 'package:netease_cloud_music/route/route_handles.dart';
5 |
6 | class Routes {
7 | static String root = "/";
8 | static String home = "/home";
9 | static String login = "/login";
10 | static String dailySongs = "/daily_songs";
11 | static String playList = "/play_list";
12 | static String topList = "/top_list";
13 | static String playSongs = "/play_songs";
14 | static String comment = "/comment";
15 | static String search = "/search";
16 | static String lookImg = "/look_img";
17 | static String userDetail = "/user_detail";
18 |
19 | static void configureRoutes(Router router) {
20 | router.notFoundHandler = new Handler(
21 | handlerFunc: (BuildContext context, Map> params) {
22 | print("ROUTE WAS NOT FOUND !!!");
23 | return LoginPage();
24 | });
25 | router.define(root, handler: splashHandler);
26 | router.define(login, handler: loginHandler);
27 | router.define(home, handler: homeHandler);
28 | router.define(dailySongs, handler: dailySongsHandler);
29 | router.define(playList, handler: playListHandler);
30 | router.define(topList, handler: topListHandler);
31 | router.define(playSongs, handler: playSongsHandler);
32 | router.define(comment, handler: commentHandler);
33 | router.define(search, handler: searchHandler);
34 | router.define(lookImg, handler: lookImgHandler);
35 | router.define(userDetail, handler: userDetailHandler);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_play_bottom_menu.dart:
--------------------------------------------------------------------------------
1 | import 'package:audioplayers/audioplayers.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:netease_cloud_music/provider/play_songs_model.dart';
5 | import 'package:netease_cloud_music/widgets/widget_img_menu.dart';
6 |
7 | class PlayBottomMenuWidget extends StatelessWidget {
8 |
9 | final PlaySongsModel model;
10 |
11 | PlayBottomMenuWidget(this.model);
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Container(
16 | height: ScreenUtil().setWidth(150),
17 | alignment: Alignment.topCenter,
18 | child: Row(
19 | children: [
20 | ImageMenuWidget('images/icon_song_play_type_1.png', 80),
21 | ImageMenuWidget(
22 | 'images/icon_song_left.png',
23 | 80,
24 | onTap: () {
25 | model.prePlay();
26 | },
27 | ),
28 | ImageMenuWidget(
29 | model.curState != AudioPlayerState.PAUSED
30 | ? 'images/icon_song_pause.png'
31 | : 'images/icon_song_play.png',
32 | 150,
33 | onTap: () {
34 | model.togglePlay();
35 | },
36 | ),
37 | ImageMenuWidget(
38 | 'images/icon_song_right.png',
39 | 80,
40 | onTap: () {
41 | model.nextPlay();
42 | },
43 | ),
44 | ImageMenuWidget('images/icon_play_songs.png', 80),
45 | ],
46 | ),
47 | );
48 | }
49 | }
50 |
51 |
52 |
--------------------------------------------------------------------------------
/netease_cloud_music/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 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_artists.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:netease_cloud_music/utils/utils.dart';
4 | import 'package:netease_cloud_music/widgets/widget_round_img.dart';
5 |
6 | import 'common_text_style.dart';
7 | import 'h_empty_view.dart';
8 |
9 | class ArtistsWidget extends StatelessWidget {
10 | final String picUrl;
11 | final String name;
12 | final int accountId;
13 |
14 | const ArtistsWidget({Key key, this.picUrl, this.name, this.accountId})
15 | : super(key: key);
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Padding(
20 | padding: EdgeInsets.symmetric(vertical: ScreenUtil().setWidth(15)),
21 | child: Row(
22 | children: [
23 | RoundImgWidget(
24 | '$picUrl?param=150y150',
25 | 120,
26 | fit: BoxFit.cover,
27 | ),
28 | HEmptyView(10),
29 | Text(name),
30 | Spacer(),
31 | accountId == null || accountId == 0
32 | ? Container()
33 | : Row(
34 | mainAxisSize: MainAxisSize.min,
35 | children: [
36 | Icon(
37 | Icons.account_circle,
38 | color: Colors.red,
39 | size: ScreenUtil().setWidth(30),
40 | ),
41 | HEmptyView(5),
42 | Text(
43 | '已入驻',
44 | style: smallGrayTextStyle,
45 | ),
46 | ],
47 | )
48 | ],
49 | ),
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_at_text.dart:
--------------------------------------------------------------------------------
1 | import 'package:extended_text_library/extended_text_library.dart';
2 | import 'package:flutter/gestures.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class AtText extends SpecialText {
6 | static const String flag = "@";
7 | final int start;
8 |
9 | /// whether show background for @somebody
10 | final bool showAtBackground;
11 |
12 | AtText(TextStyle textStyle, SpecialTextGestureTapCallback onTap,
13 | {this.showAtBackground: false, this.start})
14 | : super(flag, " ", textStyle, onTap: onTap);
15 |
16 | @override
17 | InlineSpan finishText() {
18 | TextStyle textStyle =
19 | this.textStyle?.copyWith(color: Colors.blue, fontSize: 16.0);
20 |
21 | final String atText = toString();
22 |
23 | return showAtBackground
24 | ? BackgroundTextSpan(
25 | background: Paint()..color = Colors.blue.withOpacity(0.15),
26 | text: atText,
27 | actualText: atText,
28 | start: start,
29 |
30 | ///caret can move into special text
31 | deleteAll: true,
32 | style: textStyle,
33 | recognizer: (TapGestureRecognizer()
34 | ..onTap = () {
35 | if (onTap != null) onTap(atText);
36 | }))
37 | : SpecialTextSpan(
38 | text: atText,
39 | actualText: atText,
40 | start: start,
41 | style: textStyle,
42 | recognizer: (TapGestureRecognizer()
43 | ..onTap = () {
44 | if (onTap != null) onTap(atText);
45 | }));
46 | }
47 | }
48 |
49 | List atList = [
50 | "@Nevermore ",
51 | "@Dota2 ",
52 | "@Biglao ",
53 | "@艾莉亚·史塔克 ",
54 | "@丹妮莉丝 ",
55 | "@HandPulledNoodles ",
56 | "@Zmtzawqlp ",
57 | "@FaDeKongJian ",
58 | "@CaiJingLongDaLao ",
59 | ];
60 |
--------------------------------------------------------------------------------
/netease_cloud_music/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .packages
28 | .pub-cache/
29 | .pub/
30 | /build/
31 |
32 | # Android related
33 | **/android/**/gradle-wrapper.jar
34 | **/android/.gradle
35 | **/android/captures/
36 | **/android/gradlew
37 | **/android/gradlew.bat
38 | **/android/local.properties
39 | **/android/**/GeneratedPluginRegistrant.java
40 |
41 | # iOS/XCode related
42 | **/ios/**/*.mode1v3
43 | **/ios/**/*.mode2v3
44 | **/ios/**/*.moved-aside
45 | **/ios/**/*.pbxuser
46 | **/ios/**/*.perspectivev3
47 | **/ios/**/*sync/
48 | **/ios/**/.sconsign.dblite
49 | **/ios/**/.tags*
50 | **/ios/**/.vagrant/
51 | **/ios/**/DerivedData/
52 | **/ios/**/Icon?
53 | **/ios/**/Pods/
54 | **/ios/**/.symlinks/
55 | **/ios/**/profile
56 | **/ios/**/xcuserdata
57 | **/ios/.generated/
58 | **/ios/Flutter/App.framework
59 | **/ios/Flutter/Flutter.framework
60 | **/ios/Flutter/Generated.xcconfig
61 | **/ios/Flutter/app.flx
62 | **/ios/Flutter/app.zip
63 | **/ios/Flutter/flutter_assets/
64 | **/ios/Flutter/flutter_export_environment.sh
65 | **/ios/ServiceDefinitions.json
66 | **/ios/Runner/GeneratedPluginRegistrant.*
67 |
68 | # Exceptions to above rules.
69 | !**/ios/**/default.mode1v3
70 | !**/ios/**/default.mode2v3
71 | !**/ios/**/default.pbxuser
72 | !**/ios/**/default.perspectivev3
73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
74 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_album.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:netease_cloud_music/widgets/rounded_net_image.dart';
4 | import 'package:netease_cloud_music/widgets/v_empty_view.dart';
5 |
6 | import 'common_text_style.dart';
7 | import 'h_empty_view.dart';
8 |
9 | class AlbumWidget extends StatelessWidget {
10 | final String albumCoverUrl;
11 | final String albumName;
12 | final String albumInfo;
13 |
14 | AlbumWidget(this.albumCoverUrl, this.albumName, this.albumInfo);
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return Padding(
19 | padding: EdgeInsets.symmetric(vertical: ScreenUtil().setWidth(10)),
20 | child: Row(
21 | children: [
22 | Row(
23 | mainAxisSize: MainAxisSize.min,
24 | children: [
25 | RoundedNetImage(
26 | '$albumCoverUrl?param=150y150',
27 | width: 140,
28 | height: 140,
29 | radius: 8,
30 | ),
31 | Image.asset(
32 | 'images/icon_album.png',
33 | height: ScreenUtil().setWidth(140),
34 | )
35 | ],
36 | ),
37 | HEmptyView(10),
38 | Expanded(
39 | child: Column(
40 | mainAxisSize: MainAxisSize.min,
41 | crossAxisAlignment: CrossAxisAlignment.start,
42 | children: [
43 | Text(
44 | albumName,
45 | style: common14TextStyle,
46 | ),
47 | VEmptyView(10),
48 | Text(
49 | albumInfo,
50 | style: smallGrayTextStyle,
51 | ),
52 | ],
53 | ),
54 | ),
55 | ],
56 | ),
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | netease_cloud_music
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | NSAppTransportSecurity
26 |
27 | NSAllowsArbitraryLoads
28 |
29 |
30 | UIBackgroundModes
31 |
32 | audio
33 |
34 | UILaunchStoryboardName
35 | LaunchScreen
36 | UIMainStoryboardFile
37 | Main
38 | UISupportedInterfaceOrientations
39 |
40 | UIInterfaceOrientationPortrait
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UISupportedInterfaceOrientations~ipad
45 |
46 | UIInterfaceOrientationPortrait
47 | UIInterfaceOrientationPortraitUpsideDown
48 | UIInterfaceOrientationLandscapeLeft
49 | UIInterfaceOrientationLandscapeRight
50 |
51 | UIViewControllerBasedStatusBarAppearance
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/model/banner.dart:
--------------------------------------------------------------------------------
1 | class Banner {
2 | List _banners;
3 | int _code;
4 |
5 | Banner({List banners, int code}) {
6 | this._banners = banners;
7 | this._code = code;
8 | }
9 |
10 | List get banners => _banners;
11 | set banners(List banners) => _banners = banners;
12 | int get code => _code;
13 | set code(int code) => _code = code;
14 |
15 | Banner.fromJson(Map json) {
16 | if (json['banners'] != null) {
17 | _banners = new List();
18 | json['banners'].forEach((v) {
19 | _banners.add(new Banners.fromJson(v));
20 | });
21 | }
22 | _code = json['code'];
23 | }
24 |
25 | Map toJson() {
26 | final Map data = new Map();
27 | if (this._banners != null) {
28 | data['banners'] = this._banners.map((v) => v.toJson()).toList();
29 | }
30 | data['code'] = this._code;
31 | return data;
32 | }
33 | }
34 |
35 | class Banners {
36 | String _pic;
37 | String _typeTitle;
38 | int _targetId;
39 |
40 | Banners({String pic, String typeTitle, int targetId}) {
41 | this._pic = pic;
42 | this._typeTitle = typeTitle;
43 | this._targetId = targetId;
44 | }
45 |
46 | String get pic => _pic;
47 | set pic(String pic) => _pic = pic;
48 | String get typeTitle => _typeTitle;
49 | set typeTitle(String typeTitle) => _typeTitle = typeTitle;
50 | int get targetId => _targetId;
51 | set targetId(int targetId) => _targetId = targetId;
52 |
53 | Banners.fromJson(Map json) {
54 | _pic = json['pic'];
55 | _typeTitle = json['typeTitle'];
56 | _targetId = json['targetId'];
57 | }
58 |
59 | Map toJson() {
60 | final Map data = new Map();
61 | data['pic'] = this._pic;
62 | data['typeTitle'] = this._typeTitle;
63 | data['targetId'] = this._targetId;
64 | return data;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "extent" : "full-screen",
5 | "idiom" : "iphone",
6 | "subtype" : "2688h",
7 | "filename" : "splash@3x.png",
8 | "minimum-system-version" : "12.0",
9 | "orientation" : "portrait",
10 | "scale" : "3x"
11 | },
12 | {
13 | "extent" : "full-screen",
14 | "idiom" : "iphone",
15 | "subtype" : "1792h",
16 | "filename" : "splash_xr.png",
17 | "minimum-system-version" : "12.0",
18 | "orientation" : "portrait",
19 | "scale" : "2x"
20 | },
21 | {
22 | "extent" : "full-screen",
23 | "idiom" : "iphone",
24 | "subtype" : "2436h",
25 | "filename" : "splash.png",
26 | "minimum-system-version" : "11.0",
27 | "orientation" : "portrait",
28 | "scale" : "3x"
29 | },
30 | {
31 | "extent" : "full-screen",
32 | "idiom" : "iphone",
33 | "subtype" : "736h",
34 | "filename" : "splash1.png",
35 | "minimum-system-version" : "8.0",
36 | "orientation" : "portrait",
37 | "scale" : "3x"
38 | },
39 | {
40 | "extent" : "full-screen",
41 | "idiom" : "iphone",
42 | "subtype" : "667h",
43 | "filename" : "splash-3.png",
44 | "minimum-system-version" : "8.0",
45 | "orientation" : "portrait",
46 | "scale" : "2x"
47 | },
48 | {
49 | "orientation" : "portrait",
50 | "idiom" : "iphone",
51 | "filename" : "splash_640_960.png",
52 | "extent" : "full-screen",
53 | "minimum-system-version" : "7.0",
54 | "scale" : "2x"
55 | },
56 | {
57 | "extent" : "full-screen",
58 | "idiom" : "iphone",
59 | "subtype" : "retina4",
60 | "filename" : "splash_640_1136.png",
61 | "minimum-system-version" : "7.0",
62 | "orientation" : "portrait",
63 | "scale" : "2x"
64 | }
65 | ],
66 | "info" : {
67 | "version" : 1,
68 | "author" : "xcode"
69 | }
70 | }
--------------------------------------------------------------------------------
/netease_cloud_music/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:fluro/fluro.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:netease_cloud_music/pages/splash_page.dart';
4 | import 'package:netease_cloud_music/provider/play_list_model.dart';
5 | import 'package:netease_cloud_music/provider/play_songs_model.dart';
6 | import 'package:netease_cloud_music/provider/user_model.dart';
7 | import 'package:netease_cloud_music/route/navigate_service.dart';
8 | import 'package:netease_cloud_music/route/routes.dart';
9 | import 'package:netease_cloud_music/utils/net_utils.dart';
10 | import 'package:provider/provider.dart';
11 |
12 | import 'application.dart';
13 | import 'utils/log_util.dart';
14 |
15 | void main() {
16 | Router router = Router();
17 | Routes.configureRoutes(router);
18 | Application.router = router;
19 | Application.setupLocator();
20 | LogUtil.init(tag: 'NETEASE_MUSIC');
21 | // AudioPlayer.logEnabled = true;
22 | Provider.debugCheckInvalidValueType = null;
23 | runApp(MultiProvider(
24 | providers: [
25 | ChangeNotifierProvider(
26 | create: (_) => UserModel(),
27 | ),
28 | ChangeNotifierProvider(
29 | create: (_) => PlaySongsModel()..init(),
30 | ),
31 | ChangeNotifierProvider(
32 | create: (_) => PlayListModel(),
33 | ),
34 | ],
35 | child: MyApp(),
36 | ));
37 | }
38 |
39 | class MyApp extends StatelessWidget {
40 | @override
41 | Widget build(BuildContext context) {
42 | return MaterialApp(
43 | debugShowCheckedModeBanner: false,
44 | title: 'Flutter Demo',
45 | navigatorKey: Application.getIt().key,
46 | theme: ThemeData(
47 | brightness: Brightness.light,
48 | primaryColor: Colors.white,
49 | splashColor: Colors.transparent,
50 | tooltipTheme: TooltipThemeData(verticalOffset: -100000)),
51 | home: SplashPage(),
52 | onGenerateRoute: Application.router.generator,
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/utils/utils.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:extended_image/extended_image.dart';
3 | import 'package:flutter/cupertino.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:fluttertoast/fluttertoast.dart';
6 | import 'package:netease_cloud_music/model/lyric.dart';
7 |
8 | class Utils {
9 | static void showToast(String msg) {
10 | Fluttertoast.showToast(msg: msg, gravity: ToastGravity.CENTER);
11 | }
12 |
13 | static Widget showNetImage(String url,
14 | {double width, double height, BoxFit fit}) {
15 | return Image(
16 | image: ExtendedNetworkImageProvider(url, cache: true),
17 | width: width,
18 | height: height,
19 | fit: fit,
20 | );
21 | }
22 |
23 | /// 格式化歌词
24 | static List formatLyric(String lyricStr) {
25 | RegExp reg = RegExp(r"^\[\d{2}");
26 |
27 | List result =
28 | lyricStr.split("\n").where((r) => reg.hasMatch(r)).map((s) {
29 | String time = s.substring(0, s.indexOf(']'));
30 | String lyric = s.substring(s.indexOf(']') + 1);
31 | time = s.substring(1, time.length - 1);
32 | int hourSeparatorIndex = time.indexOf(":");
33 | int minuteSeparatorIndex = time.indexOf(".");
34 | return Lyric(
35 | lyric,
36 | startTime: Duration(
37 | minutes: int.parse(
38 | time.substring(0, hourSeparatorIndex),
39 | ),
40 | seconds: int.parse(
41 | time.substring(hourSeparatorIndex + 1, minuteSeparatorIndex)),
42 | milliseconds: int.parse(time.substring(minuteSeparatorIndex + 1)),
43 | ),
44 | );
45 | }).toList();
46 |
47 | for (int i = 0; i < result.length - 1; i++) {
48 | result[i].endTime = result[i + 1].startTime;
49 | }
50 | result[result.length - 1].endTime = Duration(hours: 1);
51 | return result;
52 | }
53 |
54 | /// 查找歌词
55 | static int findLyricIndex(double curDuration, List lyrics) {
56 | for (int i = 0; i < lyrics.length; i++) {
57 | if (curDuration >= lyrics[i].startTime.inMilliseconds &&
58 | curDuration <= lyrics[i].endTime.inMilliseconds) {
59 | return i;
60 | }
61 | }
62 | return 0;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://www.dartlang.org/guides/libraries/private-files
2 |
3 | # Files and directories created by pub
4 | .dart_tool/
5 | .packages
6 | .pub/
7 | build/
8 | # If you're building an application, you may want to check-in your pubspec.lock
9 | pubspec.lock
10 |
11 | # Directory created by dartdoc
12 | # If you don't generate documentation locally you can remove this line.
13 | doc/api/
14 | # Miscellaneous
15 | *.class
16 | *.log
17 | *.pyc
18 | *.swp
19 | .DS_Store
20 | .atom/
21 | .buildlog/
22 | .history
23 | .svn/
24 |
25 | # IntelliJ related
26 | *.iml
27 | *.ipr
28 | *.iws
29 | .idea/
30 |
31 | # The .vscode folder contains launch configuration and tasks you configure in
32 | # VS Code which you may wish to be included in version control, so this line
33 | # is commented out by default.
34 | #.vscode/
35 |
36 | # Flutter/Dart/Pub related
37 | **/doc/api/
38 | .dart_tool/
39 | .flutter-plugins
40 | .packages
41 | .pub-cache/
42 | .pub/
43 | /build/
44 |
45 | # Android related
46 | **/android/**/gradle-wrapper.jar
47 | **/android/.gradle
48 | **/android/captures/
49 | **/android/gradlew
50 | **/android/gradlew.bat
51 | **/android/local.properties
52 | **/android/**/GeneratedPluginRegistrant.java
53 |
54 | # iOS/XCode related
55 | **/ios/**/*.mode1v3
56 | **/ios/**/*.mode2v3
57 | **/ios/**/*.moved-aside
58 | **/ios/**/*.pbxuser
59 | **/ios/**/*.perspectivev3
60 | **/ios/**/*sync/
61 | **/ios/**/.sconsign.dblite
62 | **/ios/**/.tags*
63 | **/ios/**/.vagrant/
64 | **/ios/**/DerivedData/
65 | **/ios/**/Icon?
66 | **/ios/**/Pods/
67 | **/ios/**/.symlinks/
68 | **/ios/**/profile
69 | **/ios/**/xcuserdata
70 | **/ios/.generated/
71 | **/ios/Flutter/App.framework
72 | **/ios/Flutter/Flutter.framework
73 | **/ios/Flutter/Generated.xcconfig
74 | **/ios/Flutter/app.flx
75 | **/ios/Flutter/app.zip
76 | **/ios/Flutter/flutter_assets/
77 | **/ios/Flutter/flutter_export_environment.sh
78 | **/ios/ServiceDefinitions.json
79 | **/ios/Runner/GeneratedPluginRegistrant.*
80 |
81 | # Exceptions to above rules.
82 | !**/ios/**/default.mode1v3
83 | !**/ios/**/default.mode2v3
84 | !**/ios/**/default.pbxuser
85 | !**/ios/**/default.perspectivev3
86 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
87 | netease_cloud_music/android/app/release/output.json
88 | netease_cloud_music/android/app/release/网易云音乐.apk
89 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_search_play_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:netease_cloud_music/model/recommend.dart';
4 | import 'package:netease_cloud_music/utils/navigator_util.dart';
5 | import 'package:netease_cloud_music/utils/number_utils.dart';
6 | import 'package:netease_cloud_music/widgets/rounded_net_image.dart';
7 | import 'package:netease_cloud_music/widgets/v_empty_view.dart';
8 |
9 | import 'common_text_style.dart';
10 | import 'h_empty_view.dart';
11 |
12 | class SearchPlayListWidget extends StatelessWidget {
13 |
14 | final String url;
15 | final String name;
16 | final String info;
17 | final double width;
18 | final int id;
19 | final int playCount;
20 |
21 |
22 | SearchPlayListWidget({this.id, this.url, this.name, this.info, this.width = 140, this.playCount});
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | return GestureDetector(
27 | onTap: (){
28 | NavigatorUtil.goPlayListPage(context, data: Recommend(
29 | id: id,
30 | name: name,
31 | picUrl: url,
32 | playcount: playCount,
33 | ));
34 | },
35 | child: Padding(
36 | padding:
37 | EdgeInsets.symmetric(vertical: ScreenUtil().setWidth(10)),
38 | child: Row(
39 | children: [
40 | RoundedNetImage(
41 | '$url?param=150y150',
42 | width: width,
43 | height: width,
44 | radius: 8,
45 | ),
46 | HEmptyView(10),
47 | Expanded(
48 | child: Column(
49 | mainAxisSize: MainAxisSize.min,
50 | crossAxisAlignment: CrossAxisAlignment.start,
51 | children: [
52 | Text(
53 | name,
54 | maxLines: 1,
55 | overflow: TextOverflow.ellipsis,
56 | style: common14TextStyle,
57 | ),
58 | VEmptyView(10),
59 | Text(
60 | info,
61 | style: smallGrayTextStyle,
62 | ),
63 | ],
64 | ),
65 | ),
66 | ],
67 | ),
68 | ),
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_play_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:netease_cloud_music/widgets/common_text_style.dart';
4 | import 'package:netease_cloud_music/utils/number_utils.dart';
5 | import 'package:netease_cloud_music/widgets/v_empty_view.dart';
6 | import 'package:flutter_screenutil/flutter_screenutil.dart';
7 | import 'package:netease_cloud_music/widgets/widget_play_list_cover.dart';
8 |
9 | /// 歌单、新碟上架等封装的组件
10 | class PlayListWidget extends StatelessWidget {
11 | final String picUrl;
12 | final String text;
13 | final String subText;
14 | final num playCount;
15 | final int maxLines;
16 | final VoidCallback onTap;
17 | final int index;
18 |
19 | PlayListWidget({
20 | this.picUrl,
21 | @required this.text,
22 | this.playCount,
23 | this.subText,
24 | this.onTap,
25 | this.maxLines = -1,
26 | this.index,
27 | });
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | return GestureDetector(
32 | behavior: HitTestBehavior.translucent,
33 | onTap: onTap,
34 | child: Container(
35 | width: ScreenUtil().setWidth(200),
36 | child: Column(
37 | crossAxisAlignment: CrossAxisAlignment.start,
38 | mainAxisSize: MainAxisSize.min,
39 | children: [
40 | picUrl == null ? Container() : PlayListCoverWidget(
41 | picUrl,
42 | playCount: playCount,
43 | ),
44 | index == null ? Container() : Text(index.toString(), style: commonGrayTextStyle,),
45 | VEmptyView(5),
46 | Text(
47 | text,
48 | style: smallCommonTextStyle,
49 | maxLines: maxLines != -1 ? maxLines : null,
50 | overflow: maxLines != -1 ? TextOverflow.ellipsis : null,
51 | ),
52 | subText == null ? Container() : VEmptyView(2),
53 | subText == null
54 | ? Container()
55 | : Text(
56 | subText,
57 | style: TextStyle(fontSize: 10, color: Colors.grey),
58 | maxLines: maxLines != -1 ? maxLines : null,
59 | overflow: maxLines != -1 ? TextOverflow.ellipsis : null,
60 | ),
61 | ],
62 | ),
63 | ),
64 | );
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_play_list_cover.dart:
--------------------------------------------------------------------------------
1 | import 'package:extended_image/extended_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:netease_cloud_music/utils/number_utils.dart';
5 | import 'package:netease_cloud_music/utils/utils.dart';
6 |
7 | /// 歌单、新碟上架等封面组件
8 | class PlayListCoverWidget extends StatelessWidget {
9 | final String url;
10 | final int playCount;
11 | final double width;
12 | final double height;
13 | final double radius;
14 |
15 | PlayListCoverWidget(this.url,
16 | {this.playCount, this.width = 200, this.height, this.radius = 16});
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return ClipRRect(
21 | borderRadius: BorderRadius.all(Radius.circular(ScreenUtil().setWidth(radius))),
22 | child: Container(
23 | width: ScreenUtil().setWidth(width),
24 | height: ScreenUtil().setWidth(height ?? width),
25 | child: Stack(
26 | alignment: Alignment.topRight,
27 | children: [
28 | Utils.showNetImage('$url?param=200y200', width: ScreenUtil().setWidth(width), height: ScreenUtil().setWidth(height ?? width), fit: BoxFit.cover),
29 | playCount == null
30 | ? Container()
31 | : Padding(
32 | padding: EdgeInsets.only(
33 | top: ScreenUtil().setWidth(2),
34 | right: ScreenUtil().setWidth(5)),
35 | child: Row(
36 | mainAxisSize: MainAxisSize.min,
37 | children: [
38 | Image.asset(
39 | 'images/icon_triangle.png',
40 | width: ScreenUtil().setWidth(30),
41 | height: ScreenUtil().setWidth(30),
42 | ),
43 | Text(
44 | '${NumberUtils.amountConversion(playCount)}',
45 | style: TextStyle(
46 | color: Colors.white,
47 | fontSize: 12,
48 | fontWeight: FontWeight.w500,
49 | ),
50 | )
51 | ],
52 | ),
53 | )
54 | ],
55 | ),
56 | ),
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_music_list_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:netease_cloud_music/provider/play_songs_model.dart';
4 | import 'package:provider/provider.dart';
5 |
6 | import 'common_text_style.dart';
7 | import 'h_empty_view.dart';
8 |
9 | typedef PlayModelCallback = void Function(PlaySongsModel model);
10 |
11 | class MusicListHeader extends StatelessWidget implements PreferredSizeWidget {
12 | MusicListHeader({this.count, this.tail, this.onTap});
13 |
14 | final int count;
15 | final Widget tail;
16 | final PlayModelCallback onTap;
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return ClipRRect(
21 | borderRadius: BorderRadius.vertical(
22 | top: Radius.circular(ScreenUtil().setWidth(30))),
23 | child: Container(
24 | color: Colors.white,
25 | child: Consumer(builder: (context, model, child) {
26 | return InkWell(
27 | onTap: (){
28 | onTap(model);
29 | },
30 | child: SizedBox.fromSize(
31 | size: preferredSize,
32 | child: Row(
33 | children: [
34 | HEmptyView(20),
35 | Icon(
36 | Icons.play_circle_outline,
37 | size: ScreenUtil().setWidth(50),
38 | ),
39 | HEmptyView(10),
40 | Padding(
41 | padding: const EdgeInsets.only(top: 3.0),
42 | child: Text(
43 | "播放全部",
44 | style: mCommonTextStyle,
45 | ),
46 | ),
47 | HEmptyView(5),
48 | Padding(
49 | padding: const EdgeInsets.only(top: 3.0),
50 | child: count == null
51 | ? Container()
52 | : Text(
53 | "(共$count首)",
54 | style: smallGrayTextStyle,
55 | ),
56 | ),
57 | Spacer(),
58 | tail ?? Container(),
59 | ],
60 | ),
61 | ),
62 | );
63 | }),
64 | ),
65 | );
66 | }
67 |
68 | @override
69 | Size get preferredSize => Size.fromHeight(ScreenUtil().setWidth(100));
70 | }
71 |
--------------------------------------------------------------------------------
/netease_cloud_music/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 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_play_list_app_bar.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:extended_image/extended_image.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:netease_cloud_music/utils/utils.dart';
6 | import 'package:netease_cloud_music/widgets/widget_music_list_header.dart';
7 |
8 | import 'flexible_detail_bar.dart';
9 |
10 | class PlayListAppBarWidget extends StatelessWidget {
11 | final double expandedHeight;
12 | final Widget content;
13 | final String backgroundImg;
14 | final String title;
15 | final double sigma;
16 | final PlayModelCallback playOnTap;
17 | final int count;
18 |
19 | PlayListAppBarWidget({
20 | @required this.expandedHeight,
21 | @required this.content,
22 | @required this.title,
23 | @required this.backgroundImg,
24 | this.sigma = 5,
25 | this.playOnTap,
26 | this.count,
27 | });
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | return SliverAppBar(
32 | centerTitle: true,
33 | expandedHeight: expandedHeight,
34 | pinned: true,
35 | elevation: 0,
36 | brightness: Brightness.dark,
37 | iconTheme: IconThemeData(color: Colors.white),
38 | title: Text(
39 | title,
40 | style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
41 | ),
42 | bottom: MusicListHeader(
43 | onTap: playOnTap,
44 | count: count,
45 | ),
46 | flexibleSpace: FlexibleDetailBar(
47 | content: content,
48 | background: Stack(
49 | children: [
50 | backgroundImg.startsWith('http')
51 | ? Utils.showNetImage(
52 | '$backgroundImg?param=200y200',
53 | width: double.infinity,
54 | height: double.infinity,
55 | fit: BoxFit.cover,
56 | )
57 | : Image.asset(
58 | backgroundImg,
59 | width: double.infinity,
60 | height: double.infinity,
61 | fit: BoxFit.cover,
62 | ),
63 | BackdropFilter(
64 | filter: ImageFilter.blur(
65 | sigmaY: sigma,
66 | sigmaX: sigma,
67 | ),
68 | child: Container(
69 | color: Colors.black38,
70 | width: double.infinity,
71 | height: double.infinity,
72 | ),
73 | ),
74 | ],
75 | ),
76 | ),
77 | );
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_song_progress.dart:
--------------------------------------------------------------------------------
1 | import 'package:common_utils/common_utils.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:netease_cloud_music/provider/play_songs_model.dart';
5 | import 'package:netease_cloud_music/widgets/common_text_style.dart';
6 |
7 | class SongProgressWidget extends StatelessWidget {
8 | final PlaySongsModel model;
9 |
10 | SongProgressWidget(this.model);
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | return StreamBuilder(
15 | stream: model.curPositionStream,
16 | builder: (context, snapshot) {
17 | if (snapshot.hasData) {
18 | var totalTime =
19 | snapshot.data.substring(snapshot.data.indexOf('-') + 1);
20 | var curTime = double.parse(snapshot.data.substring(0, snapshot.data.indexOf('-')));
21 | var curTimeStr = DateUtil.formatDateMs(curTime.toInt(), format: "mm:ss");
22 | return buildProgress(curTime, totalTime, curTimeStr);
23 | } else {
24 | return buildProgress(0, "0", "00:00");
25 | }
26 | },
27 | );
28 | }
29 |
30 | Widget buildProgress(double curTime, String totalTime, String curTimeStr){
31 | return Container(
32 | height: ScreenUtil().setWidth(50),
33 | child: Row(
34 | children: [
35 | Text(
36 | curTimeStr,
37 | style: smallWhiteTextStyle,
38 | ),
39 | Expanded(
40 | child: SliderTheme(
41 | data: SliderThemeData(
42 | trackHeight: ScreenUtil().setWidth(2),
43 | thumbShape: RoundSliderThumbShape(
44 | enabledThumbRadius: ScreenUtil().setWidth(10),
45 | ),
46 | ),
47 | child: Slider(
48 | value: curTime,
49 | onChanged: (data) {
50 | model.sinkProgress(data.toInt());
51 | },
52 | onChangeStart: (data) {
53 | model.pausePlay();
54 | },
55 | onChangeEnd: (data) {
56 | model.seekPlay(data.toInt());
57 | },
58 | activeColor: Colors.white,
59 | inactiveColor: Colors.white30,
60 | min: 0,
61 | max: double.parse(totalTime),
62 | ),
63 | ),
64 | ),
65 | Text(
66 | DateUtil.formatDateMs(int.parse(totalTime), format: "mm:ss"),
67 | style: smallWhite30TextStyle,
68 | ),
69 | ],
70 | ),
71 | );
72 | }
73 | }
74 |
75 |
--------------------------------------------------------------------------------
/netease_cloud_music/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
26 |
33 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/pages/comment/comment_input_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:fluttertoast/fluttertoast.dart';
4 | import 'package:netease_cloud_music/utils/utils.dart';
5 | import 'package:netease_cloud_music/widgets/common_text_style.dart';
6 |
7 | typedef CommentCallback = void Function(String content);
8 |
9 | class CommentInputWidget extends StatelessWidget {
10 | final TextEditingController _editingController = TextEditingController();
11 |
12 | final CommentCallback onTapComment;
13 |
14 |
15 | CommentInputWidget(this.onTapComment);
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Container(
20 | height: ScreenUtil().setWidth(100),
21 | color: Colors.white,
22 | child: Column(
23 | mainAxisSize: MainAxisSize.min,
24 | children: [
25 | Container(
26 | height: ScreenUtil().setWidth(1.5),
27 | color: Colors.black12,
28 | ),
29 | Expanded(
30 | child: Row(
31 | children: [
32 | Expanded(
33 | child: Container(
34 | padding: EdgeInsets.symmetric(
35 | horizontal: ScreenUtil().setWidth(20)),
36 | color: Colors.white,
37 | child: TextField(
38 | controller: _editingController,
39 | style: common14TextStyle,
40 | textInputAction: TextInputAction.send,
41 | onEditingComplete: sendComment,
42 | decoration: InputDecoration(
43 | border: InputBorder.none,
44 | hintText: "这一次也许就是你上热评了",
45 | hintStyle: common14GrayTextStyle,
46 | ),
47 | ),
48 | ),
49 | ),
50 | GestureDetector(
51 | onTap: sendComment,
52 | child: Container(
53 | alignment: Alignment.center,
54 | color: Colors.white,
55 | width: ScreenUtil().setWidth(100),
56 | height: ScreenUtil().setWidth(100),
57 | child: Text('发送'),
58 | ),
59 | )
60 | ],
61 | ),
62 | ),
63 | ],
64 | ),
65 | );
66 | }
67 |
68 | void sendComment(){
69 | String text = _editingController.text;
70 | if(text.isEmpty){
71 | Utils.showToast( '评论内容不能为空!');
72 | return;
73 | }
74 | onTapComment(_editingController.text);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/pages/home/my/playlist_title.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:netease_cloud_music/widgets/common_text_style.dart';
4 | import 'package:netease_cloud_music/widgets/h_empty_view.dart';
5 |
6 | /// 构建歌单标题
7 | class PlaylistTitle extends StatefulWidget {
8 | final String title;
9 | final int count;
10 | final VoidCallback onSwitchTap;
11 | final VoidCallback onMoreTap;
12 | final Widget trailing;
13 |
14 | PlaylistTitle(this.title, this.count, this.onSwitchTap, this.onMoreTap,
15 | {this.trailing});
16 |
17 | @override
18 | _PlaylistTitleState createState() => _PlaylistTitleState();
19 | }
20 |
21 | class _PlaylistTitleState extends State {
22 | List arrows = [
23 | 'images/icon_down.png',
24 | 'images/icon_up.png',
25 | ];
26 | String arrow;
27 |
28 |
29 | @override
30 | void initState() {
31 | super.initState();
32 | arrow = arrows[0];
33 | }
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | return Container(
38 | height: ScreenUtil().setWidth(80),
39 | child: GestureDetector(
40 | behavior: HitTestBehavior.translucent,
41 | onTap: () {
42 | setState(() {
43 | if (arrow == arrows[0])
44 | arrow = arrows[1];
45 | else
46 | arrow = arrows[0];
47 | widget.onSwitchTap();
48 | });
49 | },
50 | child: Row(
51 | children: [
52 | AnimatedSwitcher(
53 | transitionBuilder: (child, anim) {
54 | return ScaleTransition(child: child, scale: anim);
55 | },
56 | duration: Duration(milliseconds: 300),
57 | child: Image.asset(
58 | arrow,
59 | key: ValueKey(arrow),
60 | width: ScreenUtil().setWidth(30),
61 | ),
62 | ),
63 | HEmptyView(10),
64 | Text(
65 | widget.title,
66 | style: bold18TextStyle,
67 | ),
68 | HEmptyView(5),
69 | Text(
70 | '(${widget.count})',
71 | style: common14GrayTextStyle,
72 | ),
73 | Spacer(),
74 | widget.trailing ?? Container(),
75 | SizedBox(
76 | height: ScreenUtil().setWidth(50),
77 | width: ScreenUtil().setWidth(70),
78 | child: IconButton(
79 | icon: Icon(
80 | Icons.more_vert,
81 | color: Colors.black87,
82 | ),
83 | onPressed: widget.onMoreTap,
84 | padding: EdgeInsets.zero,
85 | ),
86 | ),
87 | ],
88 | ),
89 | ),
90 | );
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_event_video.dart:
--------------------------------------------------------------------------------
1 | import 'package:common_utils/common_utils.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:netease_cloud_music/model/event_content.dart';
5 | import 'package:netease_cloud_music/utils/number_utils.dart';
6 | import 'package:netease_cloud_music/widgets/common_text_style.dart';
7 | import 'package:netease_cloud_music/widgets/h_empty_view.dart';
8 | import 'package:netease_cloud_music/widgets/rounded_net_image.dart';
9 |
10 | class EventVideoWidget extends StatefulWidget {
11 | final Video video;
12 |
13 | EventVideoWidget(this.video);
14 |
15 | @override
16 | _EventVideoWidgetState createState() => _EventVideoWidgetState();
17 | }
18 |
19 | class _EventVideoWidgetState extends State {
20 | @override
21 | Widget build(BuildContext context) {
22 | return Stack(
23 | children: [
24 | RoundedNetImage(
25 | widget.video.coverUrl,
26 | ),
27 | Positioned.fill(
28 | child: Container(
29 | decoration: BoxDecoration(
30 | borderRadius: BorderRadius.circular(ScreenUtil().setWidth(10)),
31 | color: Colors.black26),
32 | child: Stack(
33 | children: [
34 | Align(
35 | alignment: Alignment.center,
36 | child: Image.asset(
37 | 'images/icon_event_video_play.png',
38 | width: ScreenUtil().setWidth(100),
39 | ),
40 | ),
41 | Align(
42 | alignment: Alignment.bottomCenter,
43 | child: Row(
44 | children: [
45 | Image.asset('images/icon_event_video_b_play.png',
46 | width: ScreenUtil().setWidth(50)),
47 | Text(
48 | NumberUtils.amountConversion(widget.video.playTime),
49 | style: common14WhiteTextStyle,
50 | ),
51 | Spacer(),
52 | Image.asset(
53 | 'images/icon_event_video_bar.png',
54 | width: ScreenUtil().setWidth(25),
55 | fit: BoxFit.cover,
56 | ),
57 | HEmptyView(5),
58 | Text(
59 | DateUtil.formatDateMs(
60 | Duration(seconds: widget.video.duration)
61 | .inMilliseconds,
62 | format: 'mm:ss'),
63 | style: common14WhiteTextStyle,
64 | ),
65 | HEmptyView(10),
66 | ],
67 | ),
68 | )
69 | ],
70 | ),
71 | ),
72 | ),
73 | ],
74 | );
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - audioplayers (0.0.1):
3 | - Flutter
4 | - Flutter (1.0.0)
5 | - fluttertoast (0.0.2):
6 | - Flutter
7 | - FMDB (2.7.5):
8 | - FMDB/standard (= 2.7.5)
9 | - FMDB/standard (2.7.5)
10 | - path_provider (0.0.1):
11 | - Flutter
12 | - path_provider_macos (0.0.1):
13 | - Flutter
14 | - "permission_handler (4.4.0+hotfix.4)":
15 | - Flutter
16 | - shared_preferences (0.0.1):
17 | - Flutter
18 | - shared_preferences_macos (0.0.1):
19 | - Flutter
20 | - shared_preferences_web (0.0.1):
21 | - Flutter
22 | - sqflite (0.0.1):
23 | - Flutter
24 | - FMDB (~> 2.7.2)
25 |
26 | DEPENDENCIES:
27 | - audioplayers (from `.symlinks/plugins/audioplayers/ios`)
28 | - Flutter (from `.symlinks/flutter/ios`)
29 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
30 | - path_provider (from `.symlinks/plugins/path_provider/ios`)
31 | - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`)
32 | - permission_handler (from `.symlinks/plugins/permission_handler/ios`)
33 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
34 | - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`)
35 | - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`)
36 | - sqflite (from `.symlinks/plugins/sqflite/ios`)
37 |
38 | SPEC REPOS:
39 | trunk:
40 | - FMDB
41 |
42 | EXTERNAL SOURCES:
43 | audioplayers:
44 | :path: ".symlinks/plugins/audioplayers/ios"
45 | Flutter:
46 | :path: ".symlinks/flutter/ios"
47 | fluttertoast:
48 | :path: ".symlinks/plugins/fluttertoast/ios"
49 | path_provider:
50 | :path: ".symlinks/plugins/path_provider/ios"
51 | path_provider_macos:
52 | :path: ".symlinks/plugins/path_provider_macos/ios"
53 | permission_handler:
54 | :path: ".symlinks/plugins/permission_handler/ios"
55 | shared_preferences:
56 | :path: ".symlinks/plugins/shared_preferences/ios"
57 | shared_preferences_macos:
58 | :path: ".symlinks/plugins/shared_preferences_macos/ios"
59 | shared_preferences_web:
60 | :path: ".symlinks/plugins/shared_preferences_web/ios"
61 | sqflite:
62 | :path: ".symlinks/plugins/sqflite/ios"
63 |
64 | SPEC CHECKSUMS:
65 | audioplayers: 84f968cea3f2deab00ec4f8ff53358b3c0b3992c
66 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
67 | fluttertoast: b644586ef3b16f67fae9a1f8754cef6b2d6b634b
68 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
69 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
70 | path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0
71 | permission_handler: 8278954f2382902f63f00dd8828769c0bd6d511b
72 | shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
73 | shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087
74 | shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9
75 | sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0
76 |
77 | PODFILE CHECKSUM: 10ae9c18d12c9ffc2275c9a159a3b1e281990db0
78 |
79 | COCOAPODS: 1.9.1
80 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_search_video.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:netease_cloud_music/widgets/v_empty_view.dart';
4 | import 'package:netease_cloud_music/widgets/widget_play_list_cover.dart';
5 |
6 | import 'common_text_style.dart';
7 | import 'h_empty_view.dart';
8 |
9 | class SearchVideoWidget extends StatelessWidget {
10 | final String url;
11 | final int playCount;
12 | final int type;
13 | final String title;
14 | final String creatorName;
15 |
16 | SearchVideoWidget(
17 | {this.url, this.playCount, this.type, this.title, this.creatorName});
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | return Padding(
22 | padding: EdgeInsets.symmetric(vertical: ScreenUtil().setWidth(10)),
23 | child: Row(
24 | children: [
25 | PlayListCoverWidget(
26 | '$url?param=200y150',
27 | playCount: playCount,
28 | width: 250,
29 | height: 150,
30 | radius: 8,
31 | ),
32 | HEmptyView(10),
33 | Expanded(
34 | child: Column(
35 | mainAxisSize: MainAxisSize.min,
36 | crossAxisAlignment: CrossAxisAlignment.start,
37 | children: [
38 | RichText(
39 | text: TextSpan(
40 | children: [
41 | WidgetSpan(
42 | child: type == 0
43 | ? Container(
44 | child: Text(
45 | 'MV',
46 | style: common10RedTextStyle,
47 | ),
48 | padding: EdgeInsets.symmetric(
49 | horizontal: ScreenUtil().setWidth(8),
50 | vertical: ScreenUtil().setWidth(2)),
51 | decoration: BoxDecoration(
52 | border: Border.all(color: Colors.red),
53 | borderRadius: BorderRadius.all(
54 | Radius.circular(
55 | ScreenUtil().setWidth(6)))),
56 | )
57 | : Container(),
58 | ),
59 | WidgetSpan(child: type == 0 ? HEmptyView(6) : Container()),
60 | TextSpan(
61 | text: title,
62 | style: common14TextStyle,
63 | ),
64 | ],
65 | ),
66 | ),
67 | VEmptyView(10),
68 | Text(
69 | creatorName,
70 | style: smallGrayTextStyle,
71 | ),
72 | ],
73 | ),
74 | ),
75 | ],
76 | ),
77 | );
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_sliver_future_builder.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:netease_cloud_music/widgets/widget_net_error.dart';
4 | import 'package:flutter_screenutil/flutter_screenutil.dart';
5 |
6 | typedef ValueWidgetBuilder = Widget Function(
7 | BuildContext context,
8 | T value,
9 | );
10 |
11 | /// FutureBuilder 简单封装,除正确返回和错误外,其他返回 小菊花
12 | /// 错误时返回定义好的错误 Widget,例如点击重新请求
13 | class CustomSliverFutureBuilder extends StatefulWidget {
14 | final ValueWidgetBuilder builder;
15 | final Function futureFunc;
16 | final Map params;
17 |
18 | CustomSliverFutureBuilder({
19 | @required this.futureFunc,
20 | @required this.builder,
21 | this.params,
22 | });
23 |
24 | @override
25 | _CustomFutureBuilderState createState() => _CustomFutureBuilderState();
26 | }
27 |
28 | class _CustomFutureBuilderState extends State> {
29 | Future _future;
30 |
31 | @override
32 | void initState() {
33 | super.initState();
34 | WidgetsBinding.instance.addPostFrameCallback((call) {
35 | _request();
36 | });
37 | }
38 |
39 | void _request() {
40 | setState(() {
41 | if (widget.params == null)
42 | _future = widget.futureFunc(context);
43 | else
44 | _future = widget.futureFunc(context, params: widget.params);
45 | });
46 | }
47 |
48 | @override
49 | Widget build(BuildContext context) {
50 | return _future == null
51 | ? SliverToBoxAdapter(
52 | child: Container(
53 | alignment: Alignment.center,
54 | height: ScreenUtil().setWidth(200),
55 | child: CupertinoActivityIndicator(),
56 | ),
57 | )
58 | : FutureBuilder(
59 | future: _future,
60 | builder: (context, snapshot) {
61 | switch (snapshot.connectionState) {
62 | case ConnectionState.none:
63 | case ConnectionState.waiting:
64 | case ConnectionState.active:
65 | return SliverToBoxAdapter(
66 | child: Container(
67 | alignment: Alignment.center,
68 | height: ScreenUtil().setWidth(200),
69 | child: CupertinoActivityIndicator(),
70 | ),
71 | );
72 | case ConnectionState.done:
73 | if (snapshot.hasData) {
74 | return widget.builder(context, snapshot.data);
75 | } else if (snapshot.hasError) {
76 | return SliverToBoxAdapter(
77 | child: NetErrorWidget(
78 | callback: () {
79 | _request();
80 | },
81 | ),
82 | );
83 | }
84 | }
85 | return Container();
86 | },
87 | );
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/netease_cloud_music/.flutter-plugins-dependencies:
--------------------------------------------------------------------------------
1 | {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"audioplayers","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/audioplayers-0.15.1/","dependencies":["path_provider"]},{"name":"fluttertoast","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/fluttertoast-3.1.3/","dependencies":[]},{"name":"path_provider","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider-1.6.8/","dependencies":[]},{"name":"permission_handler","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/permission_handler-4.4.0+hotfix.4/","dependencies":[]},{"name":"shared_preferences","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.5.7+2/","dependencies":[]},{"name":"sqflite","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/sqflite-1.3.0+1/","dependencies":[]}],"android":[{"name":"audioplayers","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/audioplayers-0.15.1/","dependencies":["path_provider"]},{"name":"fluttertoast","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/fluttertoast-3.1.3/","dependencies":[]},{"name":"path_provider","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider-1.6.8/","dependencies":[]},{"name":"permission_handler","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/permission_handler-4.4.0+hotfix.4/","dependencies":[]},{"name":"shared_preferences","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.5.7+2/","dependencies":[]},{"name":"sqflite","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/sqflite-1.3.0+1/","dependencies":[]}],"macos":[{"name":"audioplayers","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/audioplayers-0.15.1/","dependencies":[]},{"name":"path_provider_macos","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider_macos-0.0.4+3/","dependencies":[]},{"name":"shared_preferences_macos","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_macos-0.0.1+9/","dependencies":[]},{"name":"sqflite","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/sqflite-1.3.0+1/","dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"audioplayers","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/audioplayers-0.15.1/","dependencies":[]},{"name":"shared_preferences_web","path":"/Users/wanglu/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_web-0.1.2+7/","dependencies":[]}]},"dependencyGraph":[{"name":"audioplayers","dependencies":["path_provider"]},{"name":"fluttertoast","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_macos","shared_preferences_web"]},{"name":"shared_preferences_macos","dependencies":[]},{"name":"shared_preferences_web","dependencies":[]},{"name":"sqflite","dependencies":[]}],"date_created":"2020-05-21 21:09:09.555615","version":"1.17.0"}
--------------------------------------------------------------------------------
/netease_cloud_music/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion 28
30 |
31 | sourceSets {
32 | main.java.srcDirs += 'src/main/kotlin'
33 | }
34 |
35 | lintOptions {
36 | disable 'InvalidPackage'
37 | }
38 |
39 | defaultConfig {
40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
41 | applicationId "com.wanglu.netease_cloud_music"
42 | minSdkVersion 16
43 | targetSdkVersion 28
44 | versionCode flutterVersionCode.toInteger()
45 | versionName flutterVersionName
46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
47 | }
48 |
49 | buildTypes {
50 | release {
51 | // TODO: Add your own signing config for the release build.
52 | // Signing with the debug keys for now, so `flutter run --release` works.
53 | signingConfig signingConfigs.debug
54 | }
55 | }
56 | }
57 |
58 | flutter {
59 | source '../..'
60 | }
61 |
62 | dependencies {
63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
64 | testImplementation 'junit:junit:4.12'
65 | androidTestImplementation 'androidx.test:runner:1.1.0'
66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
67 | }
68 |
69 | configurations.all {
70 | resolutionStrategy {
71 | resolutionStrategy.eachDependency { details ->
72 | if (details.requested.group == 'androidx.core') {
73 | details.useVersion "1.0.1"
74 | }
75 | if (details.requested.group == 'androidx.lifecycle') {
76 | details.useVersion "2.0.0"
77 | }
78 | if (details.requested.group == 'androidx.versionedparcelable') {
79 | details.useVersion "1.0.0"
80 | }
81 | if (details.requested.group == 'androidx.fragment') {
82 | details.useVersion "1.0.0"
83 | }
84 | if (details.requested.group == 'androidx.appcompat') {
85 | details.useVersion "1.0.1"
86 | }
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/netease_cloud_music/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 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/utils/navigator_util.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:extended_image/extended_image.dart';
4 | import 'package:fluro/fluro.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:netease_cloud_music/model/comment_head.dart';
7 | import 'package:netease_cloud_music/model/recommend.dart';
8 | import 'package:netease_cloud_music/pages/look_img_page.dart';
9 | import 'package:netease_cloud_music/route/routes.dart';
10 | import 'package:netease_cloud_music/route/transparent_route.dart';
11 | import 'package:netease_cloud_music/utils/fluro_convert_utils.dart';
12 |
13 | import '../application.dart';
14 |
15 | class NavigatorUtil {
16 | static _navigateTo(BuildContext context, String path,
17 | {bool replace = false,
18 | bool clearStack = false,
19 | Duration transitionDuration = const Duration(milliseconds: 250),
20 | RouteTransitionsBuilder transitionBuilder}) {
21 | Application.router.navigateTo(context, path,
22 | replace: replace,
23 | clearStack: clearStack,
24 | transitionDuration: transitionDuration,
25 | transitionBuilder: transitionBuilder,
26 | transition: TransitionType.material);
27 | }
28 |
29 | /// 登录页
30 | static void goLoginPage(BuildContext context) {
31 | _navigateTo(context, Routes.login, clearStack: true);
32 | }
33 |
34 | /// 首页
35 | static void goHomePage(BuildContext context) {
36 | _navigateTo(context, Routes.home, clearStack: true);
37 | }
38 |
39 | /// 每日推荐歌曲
40 | static void goDailySongsPage(BuildContext context) {
41 | _navigateTo(context, Routes.dailySongs);
42 | }
43 |
44 | /// 歌单详情
45 | static void goPlayListPage(BuildContext context, {@required Recommend data}) {
46 | _navigateTo(context,
47 | "${Routes.playList}?data=${FluroConvertUtils.object2string(data)}");
48 | }
49 |
50 | /// 排行榜首页
51 | static void goTopListPage(BuildContext context) {
52 | _navigateTo(context, Routes.topList);
53 | }
54 |
55 | /// 播放歌曲页面
56 | static void goPlaySongsPage(BuildContext context) {
57 | _navigateTo(context, Routes.playSongs);
58 | }
59 |
60 | /// 评论页面
61 | static void goCommentPage(BuildContext context,
62 | {@required CommentHead data}) {
63 | _navigateTo(context,
64 | "${Routes.comment}?data=${FluroConvertUtils.object2string(data)}");
65 | }
66 |
67 | /// 搜索页面
68 | static void goSearchPage(BuildContext context) {
69 | _navigateTo(context, Routes.search);
70 | }
71 |
72 | /// 查看图片页面
73 | static void goLookImgPage(
74 | BuildContext context, List imgs, int index) {
75 | // Application.router.navigateTo(context, '${Routes.lookImg}?imgs=${FluroConvertUtils.object2string(imgs.join(','))}&index=$index', transitionBuilder: (){});
76 | // _navigateTo(context, '${Routes.lookImg}?imgs=${FluroConvertUtils.object2string(imgs.join(','))}&index=$index');
77 | // _navigateTo(context, '${Routes.lookImg}');
78 | Navigator.push(
79 | context,
80 | TransparentRoute(builder: (_) => LookImgPage(imgs, index),),
81 | );
82 | }
83 |
84 | /// 用户详情页面
85 | static void goUserDetailPage(BuildContext context, int userId) {
86 | _navigateTo(context, "${Routes.userDetail}?id=$userId");
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_event_song.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:netease_cloud_music/model/song.dart';
4 | import 'package:netease_cloud_music/provider/play_songs_model.dart';
5 | import 'package:netease_cloud_music/utils/navigator_util.dart';
6 | import 'package:netease_cloud_music/widgets/common_text_style.dart';
7 | import 'package:netease_cloud_music/widgets/h_empty_view.dart';
8 | import 'package:netease_cloud_music/widgets/rounded_net_image.dart';
9 | import 'package:netease_cloud_music/widgets/v_empty_view.dart';
10 | import 'package:provider/provider.dart';
11 |
12 | class EventSongWidget extends StatelessWidget {
13 | final Song song;
14 |
15 | EventSongWidget(this.song);
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Consumer(
20 | builder: (context, model, _) {
21 | return GestureDetector(
22 | onTap: () {
23 | model.playSong(song);
24 | NavigatorUtil.goPlaySongsPage(context);
25 | },
26 | child: Container(
27 | decoration: BoxDecoration(
28 | borderRadius: BorderRadius.circular(ScreenUtil().setWidth(10)),
29 | color: Color(0xFFf3f3f3),
30 | ),
31 | padding: EdgeInsets.all(ScreenUtil().setWidth(13)),
32 | child: Row(
33 | children: [
34 | Stack(
35 | children: [
36 | RoundedNetImage(
37 | song.picUrl,
38 | width: 100,
39 | height: 100,
40 | fit: BoxFit.cover,
41 | radius: 5,
42 | ),
43 | Container(
44 | width: ScreenUtil().setWidth(100),
45 | height: ScreenUtil().setWidth(100),
46 | alignment: Alignment.center,
47 | child: Image.asset(
48 | 'images/icon_event_play.png',
49 | width: ScreenUtil().setWidth(70),
50 | height: ScreenUtil().setWidth(70),
51 | ),
52 | ),
53 | ],
54 | ),
55 | HEmptyView(10),
56 | Expanded(
57 | child: Column(
58 | crossAxisAlignment: CrossAxisAlignment.start,
59 | mainAxisSize: MainAxisSize.min,
60 | children: [
61 | Text(
62 | song.name,
63 | style: commonTextStyle,
64 | maxLines: 1,
65 | overflow: TextOverflow.ellipsis,
66 | ),
67 | VEmptyView(5),
68 | Text(
69 | song.artists,
70 | style: common13GrayTextStyle,
71 | ),
72 | ],
73 | ),
74 | ),
75 | ],
76 | ),
77 | ),
78 | );
79 | },
80 | );
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_edit_play_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:netease_cloud_music/model/play_list.dart';
4 | import 'package:netease_cloud_music/widgets/h_empty_view.dart';
5 |
6 | import 'common_text_style.dart';
7 |
8 | typedef SubmitCallback = Function(String name, String desc);
9 |
10 | class EditPlayListWidget extends StatefulWidget {
11 |
12 |
13 | final SubmitCallback submitCallback;
14 | final Playlist playlist;
15 |
16 | EditPlayListWidget({@required this.submitCallback, @required this.playlist});
17 |
18 | @override
19 | _EditPlayListWidgetState createState() => _EditPlayListWidgetState();
20 |
21 | }
22 |
23 | class _EditPlayListWidgetState extends State {
24 | bool isPrivatePlayList = false;
25 | TextEditingController _editingController;
26 | TextEditingController _descTextController;
27 | SubmitCallback submitCallback;
28 |
29 | @override
30 | void initState() {
31 | super.initState();
32 | _editingController = TextEditingController(text: widget.playlist.name);
33 | _descTextController = TextEditingController(text: widget.playlist.description ?? "");
34 | _editingController.addListener((){
35 | if(_editingController.text.isEmpty){
36 | setState(() {
37 | submitCallback = null;
38 | });
39 | }else{
40 | setState(() {
41 | if(submitCallback == null){
42 | submitCallback = widget.submitCallback;
43 | }
44 | });
45 | }
46 | });
47 | }
48 |
49 | @override
50 | Widget build(BuildContext context) {
51 | return AlertDialog(
52 | title: Text(
53 | '更改歌单信息',
54 | style: bold16TextStyle,
55 | ),
56 | shape: RoundedRectangleBorder(
57 | borderRadius:
58 | BorderRadius.all(Radius.circular(ScreenUtil().setWidth(20)))),
59 | content: Theme(
60 | data: ThemeData(primaryColor: Colors.red),
61 | child: Column(
62 | mainAxisSize: MainAxisSize.min,
63 | crossAxisAlignment: CrossAxisAlignment.start,
64 | children: [
65 | TextField(
66 | controller: _editingController,
67 | decoration: InputDecoration(
68 | hintText: "请输入歌单标题",
69 | hintStyle: common14GrayTextStyle,
70 | ),
71 | style: common14TextStyle,
72 | maxLength: 40,
73 | ),
74 | TextField(
75 | controller: _descTextController,
76 | decoration: InputDecoration(
77 | hintText: "输入歌单描述",
78 | hintStyle: common14GrayTextStyle,
79 | ),
80 | style: common14TextStyle,
81 | ),
82 | ],
83 | ),
84 | ),
85 | actions: [
86 | FlatButton(
87 | onPressed: () => Navigator.of(context).pop(),
88 | child: Text('取消'),
89 | textColor: Colors.red,
90 | ),
91 | FlatButton(
92 | onPressed: () {
93 | submitCallback(_editingController.text, _descTextController.text);
94 | },
95 | child: Text('提交'),
96 | textColor: Colors.red,
97 | ),
98 | ],
99 | );
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/netease_cloud_music/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: netease_cloud_music
2 | description: A new Flutter project.
3 |
4 | # The following defines the version and build number for your application.
5 | # A version number is three numbers separated by dots, like 1.2.43
6 | # followed by an optional build number separated by a +.
7 | # Both the version and the builder number may be overridden in flutter
8 | # build by specifying --build-name and --build-number, respectively.
9 | # In Android, build-name is used as versionName while build-number used as versionCode.
10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
12 | # Read more about iOS versioning at
13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
14 | version: 1.0.0+1
15 |
16 | environment:
17 | sdk: ">=2.2.2 <3.0.0"
18 |
19 | dependencies:
20 | flutter:
21 | sdk: flutter
22 |
23 | # The following adds the Cupertino Icons font to your application.
24 | # Use with the CupertinoIcons class for iOS style icons.
25 | cupertino_icons: ^0.1.3
26 | provider: ^3.2.0
27 | shared_preferences: ^0.5.6
28 | dio: ^3.0.9
29 | flutter_screenutil: ^1.0.2
30 | fluro: ^1.5.1
31 | fluttertoast: ^3.1.3
32 | common_utils: ^1.1.3
33 | dio_cookie_manager: ^1.0.0
34 | cookie_jar: ^1.0.1
35 | permission_handler: ^4.0.0
36 | path_provider: ^1.5.1
37 | extended_image: ^0.7.1
38 | get_it: ^3.0.3
39 | audioplayers: ^0.15.1
40 | flutter_easyrefresh: ^2.0.8
41 | cached_network_image: ^2.0.0
42 | extended_text: ^0.6.6
43 | loading_more_list: ^1.0.4
44 |
45 | dev_dependencies:
46 | flutter_test:
47 | sdk: flutter
48 |
49 |
50 | # For information on the generic Dart part of this file, see the
51 | # following page: https://dart.dev/tools/pub/pubspec
52 |
53 | # The following section is specific to Flutter.
54 | flutter:
55 |
56 | # The following line ensures that the Material Icons font is
57 | # included with your application, so that you can use the icons in
58 | # the material Icons class.
59 | uses-material-design: true
60 |
61 | # To add assets to your application, add an assets section, like this:
62 | assets:
63 | - images/
64 | # - images/a_dot_ham.jpeg
65 |
66 | # An image asset can refer to one or more resolution-specific "variants", see
67 | # https://flutter.dev/assets-and-images/#resolution-aware.
68 |
69 | # For details regarding adding assets from package dependencies, see
70 | # https://flutter.dev/assets-and-images/#from-packages
71 |
72 | # To add custom fonts to your application, add a fonts section here,
73 | # in this "flutter" section. Each entry in this list should have a
74 | # "family" key with the font family name, and a "fonts" key with a
75 | # list giving the asset and other descriptors for the font. For
76 | # example:
77 | # fonts:
78 | # - family: Schyler
79 | # fonts:
80 | # - asset: fonts/Schyler-Regular.ttf
81 | # - asset: fonts/Schyler-Italic.ttf
82 | # style: italic
83 | # - family: Trajan Pro
84 | # fonts:
85 | # - asset: fonts/TrajanPro.ttf
86 | # - asset: fonts/TrajanPro_Bold.ttf
87 | # weight: 700
88 | #
89 | # For details regarding fonts from package dependencies,
90 | # see https://flutter.dev/custom-fonts/#from-packages
91 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flutter实战 | 从 0 搭建「网易云音乐」APP
2 |
3 | **@所有人:**
4 |
5 | **请仔细看README!**
6 |
7 | **请仔细看README!**
8 |
9 | **请仔细看README!**
10 |
11 | **该APP还在开发当中,具体开发完成哪些功能可以看下面的图片,没传图的就是还没开发完成!**
12 |
13 | **另外,本人也只是下班时间才能开发该APP,所以项目进度不会很快!请悉知!**
14 |
15 | **该APP所使用的账号密码就是你本人网易云的账号密码!请不要再尝试登录我的账号了!我自己都登录不上去了!**
16 |
17 |
18 |
19 | **最新版本兼容到了1.17**
20 |
21 | **请及时更新代码,因为不一定什么时候我就会修复一些以前的bug。**
22 |
23 |
24 |
25 |
26 |
27 | 下载体验:[点击下载,也可以扫描二维码,由于蒲公英的关系,必须要输入密码,密码为 1](https://www.pgyer.com/qNNy)
28 |
29 |
30 |
31 |
32 |
33 | 接口用的是[Binaryify大佬的 - NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
34 |
35 | 接口用的是[Binaryify大佬的 - NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
36 |
37 | 接口用的是[Binaryify大佬的 - NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
38 |
39 |
40 |
41 | 请本地跑本项目时,先按照上面项目的步骤跑起本地服务!!!
42 |
43 | 请本地跑本项目时,先按照上面项目的步骤跑起本地服务!!!
44 |
45 | 请本地跑本项目时,先按照上面项目的步骤跑起本地服务!!!
46 |
47 |
48 |
49 | 该APP 功能的思维导图:
50 |
51 | 
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | 目前所能公开的信息:
60 |
61 | |  |  |  |
62 | | ----------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------- |
63 | |  |  |  |
64 | |  |  |  |
65 | |  |  |  |
66 | |  |  |  |
67 | |  |  |  |
68 | |  |  |  |
69 |
70 | ---
71 |
72 | 播放页面(因模拟器虚化效果和真机不同,所以播放页面的UI由截图上传):
73 |
74 | | 播放 | 暂停 | 评论 |
75 | | :-----------------------------------------------------: | :----: | ------------------------------------------------------- |
76 | |  |  |  |
77 | |  |  | |
78 |
79 |
80 |
81 | [](https://starchart.cc/fluttercandies/NeteaseCloudMusic)
82 |
83 |
84 |
85 | 另我个人创建了一个「Flutter 交流群」,可以添加我个人微信 「17610912320」来入群。
86 |
87 |
88 |
89 | 
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/netease_cloud_music/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/route/route_handles.dart:
--------------------------------------------------------------------------------
1 | import 'package:fluro/fluro.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:netease_cloud_music/model/comment_head.dart';
4 | import 'package:netease_cloud_music/model/recommend.dart';
5 | import 'package:netease_cloud_music/pages/comment/comment_page.dart';
6 | import 'package:netease_cloud_music/pages/daily_songs/daily_songs_page.dart';
7 | import 'package:netease_cloud_music/pages/home/home_page.dart';
8 | import 'package:netease_cloud_music/pages/login_page.dart';
9 | import 'package:netease_cloud_music/pages/look_img_page.dart';
10 | import 'package:netease_cloud_music/pages/play_list/play_list_page.dart';
11 | import 'package:netease_cloud_music/pages/play_songs/play_songs_page.dart';
12 | import 'package:netease_cloud_music/pages/search/search_page.dart';
13 | import 'package:netease_cloud_music/pages/splash_page.dart';
14 | import 'package:netease_cloud_music/pages/top_list/top_list_page.dart';
15 | import 'package:netease_cloud_music/pages/user/user_detail_page.dart';
16 | import 'package:netease_cloud_music/utils/fluro_convert_utils.dart';
17 |
18 | // splash 页面
19 | var splashHandler = new Handler(
20 | handlerFunc: (BuildContext context, Map> params) {
21 | return SplashPage();
22 | });
23 |
24 | // 登录页
25 | var loginHandler = new Handler(
26 | handlerFunc: (BuildContext context, Map> params) {
27 | return LoginPage();
28 | });
29 |
30 | // 跳转到主页
31 | var homeHandler = new Handler(
32 | handlerFunc: (BuildContext context, Map> params) {
33 | return HomePage();
34 | });
35 |
36 | // 跳转到每日推荐歌曲
37 | var dailySongsHandler = new Handler(
38 | handlerFunc: (BuildContext context, Map> params) {
39 | return DailySongsPage();
40 | });
41 |
42 | // 跳转到歌单
43 | var playListHandler = new Handler(
44 | handlerFunc: (BuildContext context, Map> params) {
45 | String data = params['data'].first;
46 | return PlayListPage(Recommend.fromJson(FluroConvertUtils.string2map(data)));
47 | });
48 |
49 | // 跳转到每日推荐歌曲
50 | var topListHandler = new Handler(
51 | handlerFunc: (BuildContext context, Map> params) {
52 | return TopListPage();
53 | });
54 |
55 |
56 | // 跳转到播放歌曲
57 | var playSongsHandler = new Handler(
58 | handlerFunc: (BuildContext context, Map> params) {
59 | return PlaySongsPage();
60 | });
61 |
62 | // 跳转到评论
63 | var commentHandler = new Handler(
64 | handlerFunc: (BuildContext context, Map> params) {
65 | String data = params['data'].first;
66 | return CommentPage(CommentHead.fromJson(FluroConvertUtils.string2map(data)));
67 | });
68 |
69 | // 跳转到搜索页面
70 | var searchHandler = new Handler(
71 | handlerFunc: (BuildContext context, Map> params) {
72 | return SearchPage();
73 | });
74 |
75 | // 跳转到查看图片页面
76 | var lookImgHandler = new Handler(
77 | handlerFunc: (BuildContext context, Map> params) {
78 | List imgs = FluroConvertUtils.fluroCnParamsDecode(params['imgs'].first).split(',');
79 | String index = params['index'].first;
80 | print(imgs);
81 | print(index);
82 | return LookImgPage(imgs, int.parse(index));
83 | });
84 |
85 | // 跳转到搜索页面
86 | var userDetailHandler = new Handler(
87 | handlerFunc: (BuildContext context, Map> params) {
88 | var id = int.parse(params['id'].first);
89 | return UserDetailPage(userId: id,);
90 | });
91 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/common_text_style.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | final commonTextStyle = TextStyle(fontSize: 16, color: Colors.black87);
4 | final commonWhiteTextStyle = TextStyle(fontSize: 16, color: Colors.white);
5 | final commonGrayTextStyle = TextStyle(fontSize: 16, color: Colors.grey);
6 | final commonWhite70TextStyle = TextStyle(fontSize: 16, color: Colors.white70);
7 | final smallWhite70TextStyle = TextStyle(fontSize: 12, color: Colors.white70);
8 | final smallWhite30TextStyle = TextStyle(fontSize: 12, color: Colors.white30);
9 | final smallWhiteTextStyle = TextStyle(fontSize: 12, color: Colors.white);
10 | final smallRedTextStyle = TextStyle(fontSize: 12, color: Colors.red);
11 | final mWhiteTextStyle = TextStyle(fontSize: 18, color: Colors.white);
12 | final mCommonTextStyle = TextStyle(fontSize: 18, color: Colors.black87);
13 | final mGrayTextStyle = TextStyle(fontSize: 18, color: Colors.grey);
14 | final mWhiteBoldTextStyle = TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold);
15 | final mBlackBoldTextStyle = TextStyle(fontSize: 18, color: Colors.black87, fontWeight: FontWeight.bold);
16 | final mGrayBoldTextStyle = TextStyle(fontSize: 18, color: Colors.grey, fontWeight: FontWeight.bold);
17 | final smallCommonTextStyle = TextStyle(fontSize: 12, color: Colors.black87);
18 | final smallGrayTextStyle = TextStyle(fontSize: 12, color: Colors.grey);
19 | final common14TextStyle = TextStyle(fontSize: 14, color: Colors.black87);
20 | final common13TextStyle = TextStyle(fontSize: 13, color: Colors.black87);
21 | final bold20TextStyle = TextStyle(fontSize: 20, color: Colors.black87, fontWeight: FontWeight.bold);
22 | final bold18TextStyle = TextStyle(fontSize: 18, color: Colors.black87, fontWeight: FontWeight.bold);
23 | final w500_18TextStyle = TextStyle(fontSize: 18, color: Colors.black87, fontWeight: FontWeight.w500);
24 | final w500_16TextStyle = TextStyle(fontSize: 16, color: Colors.black87, fontWeight: FontWeight.w500);
25 | final bold17TextStyle = TextStyle(fontSize: 17, color: Colors.black87, fontWeight: FontWeight.bold);
26 | final bold16TextStyle = TextStyle(fontSize: 16, color: Colors.black87, fontWeight: FontWeight.bold);
27 | final bold16RedTextStyle = TextStyle(fontSize: 16, color: Colors.red, fontWeight: FontWeight.bold);
28 | final bold18RedTextStyle = TextStyle(fontSize: 18, color: Colors.red, fontWeight: FontWeight.bold);
29 | final bold16GrayTextStyle = TextStyle(fontSize: 16, color: Colors.grey, fontWeight: FontWeight.bold);
30 | final bold18GrayTextStyle = TextStyle(fontSize: 18, color: Colors.grey, fontWeight: FontWeight.bold);
31 | final common14WhiteTextStyle = TextStyle(fontSize: 14, color: Colors.white);
32 | final common10White70TextStyle = TextStyle(fontSize: 10, color: Colors.white70);
33 | final common14GrayTextStyle = TextStyle(fontSize: 14, color: Colors.grey);
34 | final common13GrayTextStyle = TextStyle(fontSize: 13, color: Colors.grey);
35 | final common15GrayTextStyle = TextStyle(fontSize: 15, color: Colors.grey);
36 | final common15TextStyle = TextStyle(fontSize: 15, color: Colors.black87, height: 1.2);
37 | final common16GrayTextStyle = TextStyle(fontSize: 16, color: Colors.grey);
38 | final common16TextStyle = TextStyle(fontSize: 16, color: Colors.black87);
39 | final common18TextStyle = TextStyle(fontSize: 18, color: Colors.black87);
40 | final common10RedTextStyle = TextStyle(fontSize: 10, color: Colors.red);
41 | final common13BlueTextStyle = TextStyle(fontSize: 13, color: Colors.blue);
42 | final common14BlueTextStyle = TextStyle(fontSize: 14, color: Colors.blue);
43 | final common14White70TextStyle = TextStyle(fontSize: 14, color: Colors.white70);
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_music_list_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:netease_cloud_music/model/music.dart';
4 | import 'package:netease_cloud_music/widgets/rounded_net_image.dart';
5 | import 'package:netease_cloud_music/widgets/v_empty_view.dart';
6 |
7 | import '../application.dart';
8 | import 'common_text_style.dart';
9 | import 'h_empty_view.dart';
10 |
11 | class WidgetMusicListItem extends StatelessWidget {
12 | final MusicData _data;
13 | final VoidCallback onTap;
14 |
15 | WidgetMusicListItem(this._data, {this.onTap});
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return GestureDetector(
20 | behavior: HitTestBehavior.translucent,
21 | onTap: onTap,
22 | child: Container(
23 | width: Application.screenWidth,
24 | height: ScreenUtil().setWidth(120),
25 | child: Row(
26 | mainAxisSize: MainAxisSize.min,
27 | crossAxisAlignment: CrossAxisAlignment.center,
28 | children: [
29 | _data.index == null && _data.picUrl == null
30 | ? Container()
31 | : HEmptyView(15),
32 | _data.picUrl == null
33 | ? Container()
34 | : RoundedNetImage(
35 | '${_data.picUrl}?param=150y150',
36 | width: 100,
37 | height: 100,
38 | radius: 5,
39 | ),
40 | _data.index == null
41 | ? Container()
42 | : Container(
43 | alignment: Alignment.center,
44 | width: ScreenUtil().setWidth(60),
45 | height: ScreenUtil().setWidth(50),
46 | child: Text(
47 | _data.index.toString(),
48 | style: mGrayTextStyle,
49 | ),
50 | ),
51 | _data.index == null && _data.picUrl == null
52 | ? Container()
53 | : HEmptyView(10),
54 | Expanded(
55 | child: Column(
56 | mainAxisSize: MainAxisSize.min,
57 | crossAxisAlignment: CrossAxisAlignment.start,
58 | children: [
59 | Text(
60 | _data.songName,
61 | maxLines: 1,
62 | overflow: TextOverflow.ellipsis,
63 | style: commonTextStyle,
64 | ),
65 | VEmptyView(10),
66 | Text(
67 | _data.artists,
68 | style: smallGrayTextStyle,
69 | maxLines: 1,
70 | overflow: TextOverflow.ellipsis,
71 | ),
72 | ],
73 | ),
74 | ),
75 | Align(
76 | alignment: Alignment.center,
77 | child: _data.mvid == 0
78 | ? Container()
79 | : IconButton(
80 | icon: Icon(Icons.play_circle_outline),
81 | onPressed: () {},
82 | color: Colors.grey,
83 | ),
84 | ),
85 | Align(
86 | alignment: Alignment.center,
87 | child: IconButton(
88 | icon: Icon(Icons.more_vert),
89 | onPressed: () {},
90 | color: Colors.grey,
91 | ),
92 | ),
93 | ],
94 | ),
95 | ),
96 | );
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_future_builder.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:netease_cloud_music/widgets/widget_net_error.dart';
4 | import 'package:flutter_screenutil/flutter_screenutil.dart';
5 |
6 | typedef ValueWidgetBuilder = Widget Function(
7 | BuildContext context,
8 | T value,
9 | );
10 |
11 | /// FutureBuilder 简单封装,除正确返回和错误外,其他返回 小菊花
12 | /// 错误时返回定义好的错误 Widget,例如点击重新请求
13 | class CustomFutureBuilder extends StatefulWidget {
14 | final ValueWidgetBuilder builder;
15 | final Function futureFunc;
16 | final Map params;
17 | final Widget loadingWidget;
18 |
19 | CustomFutureBuilder({
20 | @required this.futureFunc,
21 | @required this.builder,
22 | this.params,
23 | Widget loadingWidget,
24 | }) : loadingWidget = loadingWidget ??
25 | Container(
26 | alignment: Alignment.center,
27 | height: ScreenUtil().setWidth(200),
28 | child: CupertinoActivityIndicator(),
29 | );
30 |
31 | @override
32 | _CustomFutureBuilderState createState() => _CustomFutureBuilderState();
33 | }
34 |
35 | class _CustomFutureBuilderState extends State> {
36 | Future _future;
37 | String oldParams = '';
38 |
39 | @override
40 | void initState() {
41 | super.initState();
42 | WidgetsBinding.instance.addPostFrameCallback((call) {
43 | _request();
44 | });
45 | }
46 |
47 | void _request() {
48 | setState(() {
49 | if (widget.params == null)
50 | _future = widget.futureFunc(context);
51 | else {
52 | _future = widget.futureFunc(context, params: widget.params);
53 | oldParams = widget.params.values.join();
54 | }
55 | });
56 | }
57 |
58 | @override
59 | void didUpdateWidget(CustomFutureBuilder oldWidget) {
60 | // 如果方法不一样了,那么则重新请求
61 | if (oldWidget.futureFunc != widget.futureFunc) {
62 | print('func not');
63 | WidgetsBinding.instance.addPostFrameCallback((call) {
64 | _request();
65 | });
66 | }
67 |
68 | // 如果方法还一样,但是参数不一样了,则重新请求
69 | if ((oldWidget.futureFunc == widget.futureFunc) &&
70 | oldWidget.params != null &&
71 | widget.params != null) {
72 | if (oldParams != widget.params.values.join()) {
73 | print('params not');
74 | oldParams = widget.params.values.join();
75 | WidgetsBinding.instance.addPostFrameCallback((call) {
76 | _request();
77 | });
78 | }
79 | }
80 | super.didUpdateWidget(oldWidget);
81 | }
82 |
83 | @override
84 | Widget build(BuildContext context) {
85 | return _future == null
86 | ? widget.loadingWidget
87 | : FutureBuilder(
88 | future: _future,
89 | builder: (context, snapshot) {
90 | switch (snapshot.connectionState) {
91 | case ConnectionState.none:
92 | case ConnectionState.waiting:
93 | case ConnectionState.active:
94 | return widget.loadingWidget;
95 | case ConnectionState.done:
96 | if (snapshot.hasData) {
97 | return widget.builder(context, snapshot.data);
98 | } else if (snapshot.hasError) {
99 | return NetErrorWidget(
100 | callback: () {
101 | _request();
102 | },
103 | );
104 | }
105 | }
106 | return Container();
107 | },
108 | );
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_play.dart:
--------------------------------------------------------------------------------
1 | import 'package:audioplayers/audioplayers.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:netease_cloud_music/application.dart';
5 | import 'package:netease_cloud_music/model/song.dart';
6 | import 'package:netease_cloud_music/provider/play_songs_model.dart';
7 | import 'package:netease_cloud_music/utils/navigator_util.dart';
8 | import 'package:netease_cloud_music/widgets/common_text_style.dart';
9 | import 'package:netease_cloud_music/widgets/h_empty_view.dart';
10 | import 'package:netease_cloud_music/widgets/widget_round_img.dart';
11 | import 'package:provider/provider.dart';
12 |
13 | /// 所有页面下面的播放条
14 | class PlayWidget extends StatelessWidget {
15 | @override
16 | Widget build(BuildContext context) {
17 | return Align(
18 | alignment: Alignment.bottomCenter,
19 | child: Consumer(builder: (context, model, child) {
20 | Widget child;
21 |
22 | if (model.allSongs.isEmpty)
23 | child = Text('暂无正在播放的歌曲');
24 | else {
25 | Song curSong = model.curSong;
26 | child = GestureDetector(
27 | behavior: HitTestBehavior.translucent,
28 | onTap: () {
29 | NavigatorUtil.goPlaySongsPage(context);
30 | },
31 | child: Row(
32 | children: [
33 | RoundImgWidget(curSong.picUrl, 80),
34 | HEmptyView(10),
35 | Expanded(
36 | child: Column(
37 | crossAxisAlignment: CrossAxisAlignment.start,
38 | mainAxisSize: MainAxisSize.min,
39 | children: [
40 | Text(curSong.name, style: commonTextStyle, maxLines: 1, overflow: TextOverflow.ellipsis,),
41 | Text(curSong.artists, style: common13TextStyle,),
42 | ],
43 | ),
44 | ),
45 |
46 | GestureDetector(
47 | onTap: (){
48 | if(model.curState == null){
49 | model.play();
50 | }else {
51 | model.togglePlay();
52 | }
53 | },
54 | child: Image.asset(
55 | model.curState == AudioPlayerState.PLAYING
56 | ? 'images/pause.png'
57 | : 'images/play.png',
58 | width: ScreenUtil().setWidth(50),
59 | ),
60 | ),
61 | HEmptyView(15),
62 | GestureDetector(
63 | onTap: (){},
64 | child: Image.asset(
65 | 'images/list.png',
66 | width: ScreenUtil().setWidth(50),
67 | ),
68 | ),
69 | ],
70 | ),
71 | );
72 | }
73 |
74 | return Container(
75 | width: Application.screenWidth,
76 | height: ScreenUtil().setWidth(110) + Application.bottomBarHeight,
77 | decoration: BoxDecoration(
78 | border: Border(top: BorderSide(color: Colors.grey[200])),
79 | color: Colors.white),
80 | alignment: Alignment.topCenter,
81 | padding: EdgeInsets.symmetric(vertical: ScreenUtil().setWidth(10)),
82 | child: Container(
83 | width: Application.screenWidth,
84 | height: ScreenUtil().setWidth(110),
85 | padding: EdgeInsets.symmetric(horizontal: ScreenUtil().setWidth(30)),
86 | alignment: Alignment.center,
87 | child: child,
88 | ),
89 | );
90 | }),
91 | );
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/pages/splash_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:netease_cloud_music/application.dart';
3 | import 'package:netease_cloud_music/model/song.dart';
4 | import 'package:netease_cloud_music/model/user.dart';
5 | import 'package:netease_cloud_music/provider/play_list_model.dart';
6 | import 'package:netease_cloud_music/provider/play_songs_model.dart';
7 | import 'package:netease_cloud_music/provider/user_model.dart';
8 | import 'package:netease_cloud_music/utils/fluro_convert_utils.dart';
9 | import 'package:netease_cloud_music/utils/navigator_util.dart';
10 | import 'package:flutter_screenutil/flutter_screenutil.dart';
11 | import 'package:fluttertoast/fluttertoast.dart';
12 | import 'package:netease_cloud_music/utils/net_utils.dart';
13 | import 'package:provider/provider.dart';
14 |
15 | class SplashPage extends StatefulWidget {
16 | @override
17 | _SplashPageState createState() => _SplashPageState();
18 | }
19 |
20 | class _SplashPageState extends State with TickerProviderStateMixin {
21 | AnimationController _logoController;
22 | Tween _scaleTween;
23 | CurvedAnimation _logoAnimation;
24 |
25 | @override
26 | void initState() {
27 | super.initState();
28 | _scaleTween = Tween(begin: 0, end: 1);
29 | _logoController =
30 | AnimationController(vsync: this, duration: Duration(milliseconds: 500))
31 | ..drive(_scaleTween);
32 | Future.delayed(Duration(milliseconds: 500), () {
33 | _logoController.forward();
34 | });
35 | _logoAnimation =
36 | CurvedAnimation(parent: _logoController, curve: Curves.easeOutQuart);
37 |
38 | _logoController.addStatusListener((status) {
39 | if (status == AnimationStatus.completed) {
40 | Future.delayed(Duration(milliseconds: 500), () {
41 | goPage();
42 | });
43 | }
44 | });
45 | }
46 |
47 | void goPage() async{
48 | await Application.initSp();
49 | UserModel userModel = Provider.of(context);
50 | userModel.initUser();
51 | PlaySongsModel playSongsModel = Provider.of(context);
52 | // 判断是否有保存的歌曲列表
53 | if(Application.sp.containsKey('playing_songs')){
54 | List songs = Application.sp.getStringList('playing_songs');
55 | playSongsModel.addSongs(songs.map((s) => Song.fromJson(FluroConvertUtils.string2map(s))).toList());
56 | int index = Application.sp.getInt('playing_index');
57 | playSongsModel.curIndex = index;
58 | }
59 | if (userModel.user != null) {
60 | await NetUtils.refreshLogin(context).then((value){
61 | if(value.data != -1){
62 | NavigatorUtil.goHomePage(context);
63 | }
64 | });
65 | Provider.of(context).user = userModel.user;
66 | } else
67 | NavigatorUtil.goLoginPage(context);
68 | }
69 |
70 | @override
71 | Widget build(BuildContext context) {
72 | NetUtils.init();
73 | ScreenUtil.init(context, width: 750, height: 1334);
74 | final size = MediaQuery.of(context).size;
75 | Application.screenWidth = size.width;
76 | Application.screenHeight = size.height;
77 | Application.statusBarHeight = MediaQuery.of(context).padding.top;
78 | Application.bottomBarHeight = MediaQuery.of(context).padding.bottom;
79 | return Scaffold(
80 | backgroundColor: Colors.white,
81 | body: Container(
82 | height: double.infinity,
83 | width: double.infinity,
84 | child: ScaleTransition(
85 | scale: _logoAnimation,
86 | child: Hero(
87 | tag: 'logo',
88 | child: Image.asset('images/icon_logo.png'),
89 | ),
90 | ),
91 | ),
92 | );
93 | }
94 |
95 | @override
96 | void dispose() {
97 | super.dispose();
98 | _logoController.dispose();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/model/hot_search.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert' show json;
2 | import 'package:flutter/foundation.dart';
3 |
4 | dynamic convertValueByType(value, Type type, {String stack: ""}) {
5 | if (value == null) {
6 | if (type == String) {
7 | return "";
8 | } else if (type == int) {
9 | return 0;
10 | } else if (type == double) {
11 | return 0.0;
12 | } else if (type == bool) {
13 | return false;
14 | }
15 | return null;
16 | }
17 |
18 | if (value.runtimeType == type) {
19 | return value;
20 | }
21 | var valueS = value.toString();
22 | if (type == String) {
23 | return valueS;
24 | } else if (type == int) {
25 | return int.tryParse(valueS);
26 | } else if (type == double) {
27 | return double.tryParse(valueS);
28 | } else if (type == bool) {
29 | valueS = valueS.toLowerCase();
30 | var intValue = int.tryParse(valueS);
31 | if (intValue != null) {
32 | return intValue == 1;
33 | }
34 | return valueS == "true";
35 | }
36 | }
37 |
38 | class HotSearchData {
39 | int code;
40 | List data;
41 | String message;
42 |
43 | HotSearchData({
44 | this.code,
45 | this.data,
46 | this.message,
47 | });
48 |
49 | factory HotSearchData.fromJson(jsonRes) {
50 | if (jsonRes == null) return null;
51 |
52 | List data = jsonRes['data'] is List ? [] : null;
53 | if (data != null) {
54 | for (var item in jsonRes['data']) {
55 | if (item != null) {
56 | data.add(Data.fromJson(item));
57 | }
58 | }
59 | }
60 | return HotSearchData(
61 | code:
62 | convertValueByType(jsonRes['code'], int, stack: "HotSearchData-code"),
63 | data: data,
64 | message: convertValueByType(jsonRes['message'], String,
65 | stack: "HotSearchData-message"),
66 | );
67 | }
68 |
69 | Map toJson() => {
70 | 'code': code,
71 | 'data': data,
72 | 'message': message,
73 | };
74 |
75 | @override
76 | String toString() {
77 | return json.encode(this);
78 | }
79 | }
80 |
81 | class Data {
82 | String searchWord;
83 | int score;
84 | String content;
85 | int source;
86 | int iconType;
87 | String iconUrl;
88 | String url;
89 | String alg;
90 |
91 | Data({
92 | this.searchWord,
93 | this.score,
94 | this.content,
95 | this.source,
96 | this.iconType,
97 | this.iconUrl,
98 | this.url,
99 | this.alg,
100 | });
101 |
102 | factory Data.fromJson(jsonRes) => jsonRes == null
103 | ? null
104 | : Data(
105 | searchWord: convertValueByType(jsonRes['searchWord'], String,
106 | stack: "Data-searchWord"),
107 | score: convertValueByType(jsonRes['score'], int, stack: "Data-score"),
108 | content: convertValueByType(jsonRes['content'], String,
109 | stack: "Data-content"),
110 | source:
111 | convertValueByType(jsonRes['source'], int, stack: "Data-source"),
112 | iconType: convertValueByType(jsonRes['iconType'], int,
113 | stack: "Data-iconType"),
114 | iconUrl: convertValueByType(jsonRes['iconUrl'], String,
115 | stack: "Data-iconUrl"),
116 | url: convertValueByType(jsonRes['url'], String, stack: "Data-url"),
117 | alg: convertValueByType(jsonRes['alg'], String, stack: "Data-alg"),
118 | );
119 |
120 | Map toJson() => {
121 | 'searchWord': searchWord,
122 | 'score': score,
123 | 'content': content,
124 | 'source': source,
125 | 'iconType': iconType,
126 | 'iconUrl': iconUrl,
127 | 'url': url,
128 | 'alg': alg,
129 | };
130 |
131 | @override
132 | String toString() {
133 | return json.encode(this);
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_create_play_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:netease_cloud_music/widgets/h_empty_view.dart';
4 |
5 | import 'common_text_style.dart';
6 |
7 | typedef SubmitCallback = Function(String name, bool isPrivate);
8 |
9 | class CreatePlayListWidget extends StatefulWidget {
10 | final SubmitCallback submitCallback;
11 |
12 | CreatePlayListWidget({@required this.submitCallback});
13 |
14 | @override
15 | _CreatePlayListWidgetState createState() => _CreatePlayListWidgetState();
16 | }
17 |
18 | class _CreatePlayListWidgetState extends State {
19 | bool isPrivatePlayList = false;
20 | TextEditingController _editingController;
21 | SubmitCallback submitCallback;
22 |
23 | @override
24 | void initState() {
25 | super.initState();
26 | _editingController = TextEditingController();
27 | _editingController.addListener(() {
28 | if (_editingController.text.isEmpty) {
29 | setState(() {
30 | submitCallback = null;
31 | });
32 | } else {
33 | setState(() {
34 | if (submitCallback == null) {
35 | submitCallback = widget.submitCallback;
36 | }
37 | });
38 | }
39 | });
40 | }
41 |
42 | @override
43 | Widget build(BuildContext context) {
44 | return AlertDialog(
45 | title: Text(
46 | '新建歌单',
47 | style: bold16TextStyle,
48 | ),
49 | shape: RoundedRectangleBorder(
50 | borderRadius:
51 | BorderRadius.all(Radius.circular(ScreenUtil().setWidth(20)))),
52 | content: Theme(
53 | data: ThemeData(primaryColor: Colors.red),
54 | child: Column(
55 | mainAxisSize: MainAxisSize.min,
56 | crossAxisAlignment: CrossAxisAlignment.start,
57 | children: [
58 | TextField(
59 | controller: _editingController,
60 | decoration: InputDecoration(
61 | hintText: "请输入歌单标题",
62 | hintStyle: common14GrayTextStyle,
63 | ),
64 | style: common14TextStyle,
65 | maxLength: 40,
66 | ),
67 | GestureDetector(
68 | onTap: () {
69 | setState(() {
70 | isPrivatePlayList = !isPrivatePlayList;
71 | });
72 | },
73 | child: Row(
74 | children: [
75 | SizedBox(
76 | width: ScreenUtil().setWidth(40),
77 | height: ScreenUtil().setWidth(40),
78 | child: Checkbox(
79 | activeColor: Colors.red,
80 | value: isPrivatePlayList,
81 | onChanged: (v) {
82 | setState(() {
83 | isPrivatePlayList = v;
84 | });
85 | },
86 | materialTapTargetSize:
87 | MaterialTapTargetSize.shrinkWrap),
88 | ),
89 | HEmptyView(4),
90 | Text(
91 | '设置为隐私歌单',
92 | style: common15GrayTextStyle,
93 | )
94 | ],
95 | ),
96 | )
97 | ],
98 | ),
99 | ),
100 | actions: [
101 | FlatButton(
102 | onPressed: () => Navigator.of(context).pop(),
103 | child: Text('取消'),
104 | textColor: Colors.red,
105 | ),
106 | FlatButton(
107 | onPressed: submitCallback == null
108 | ? null
109 | : () {
110 | submitCallback(_editingController.text, isPrivatePlayList);
111 | },
112 | child: Text('提交'),
113 | textColor: Colors.red,
114 | ),
115 | ],
116 | );
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/flexible_detail_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | ///the same as [FlexibleSpaceBar]
4 | class FlexibleDetailBar extends StatelessWidget {
5 | ///the content of bar
6 | ///scroll with the parent ScrollView
7 | final Widget content;
8 |
9 | ///the background of bar
10 | ///scroll in parallax
11 | final Widget background;
12 |
13 | ///custom content interaction with t
14 | ///[t] 0.0 -> Expanded 1.0 -> Collapsed to toolbar
15 | final Widget Function(BuildContext context, double t) builder;
16 |
17 | static double percentage(BuildContext context) {
18 | _FlexibleDetail value =
19 | context.inheritFromWidgetOfExactType(_FlexibleDetail);
20 | assert(value != null, 'ooh , can not find');
21 | return value.t;
22 | }
23 |
24 | const FlexibleDetailBar({
25 | Key key,
26 | @required this.content,
27 | this.builder,
28 | @required this.background,
29 | }) : assert(content != null),
30 | assert(background != null),
31 | super(key: key);
32 |
33 | @override
34 | Widget build(BuildContext context) {
35 | final FlexibleSpaceBarSettings settings =
36 | context.inheritFromWidgetOfExactType(FlexibleSpaceBarSettings);
37 |
38 | final List children = [];
39 |
40 | final double deltaExtent = settings.maxExtent - settings.minExtent;
41 | // 0.0 -> Expanded
42 | // 1.0 -> Collapsed to toolbar
43 | final double t =
44 | (1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent)
45 | .clamp(0.0, 1.0);
46 |
47 | //背景添加视差滚动效果
48 | children.add(Positioned(
49 | top: -Tween(begin: 0.0, end: deltaExtent / 4.0).transform(t),
50 | left: 0,
51 | right: 0,
52 | height: settings.maxExtent,
53 | child: background,
54 | ));
55 |
56 | //为content 添加 底部的 padding
57 | double bottomPadding = 0;
58 | SliverAppBar sliverBar = context.ancestorWidgetOfExactType(SliverAppBar);
59 | if (sliverBar != null && sliverBar.bottom != null) {
60 | bottomPadding = sliverBar.bottom.preferredSize.height;
61 | }
62 | children.add(Positioned(
63 | top: settings.currentExtent - settings.maxExtent,
64 | left: 0,
65 | right: 0,
66 | height: settings.maxExtent,
67 | child: Opacity(
68 | opacity: 1 - t,
69 | child: Padding(
70 | padding: EdgeInsets.only(bottom: bottomPadding),
71 | child: Material(
72 | child: DefaultTextStyle(
73 | style: Theme.of(context).primaryTextTheme.body1,
74 | child: content),
75 | elevation: 0,
76 | color: Colors.transparent),
77 | ),
78 | ),
79 | ));
80 |
81 | if (builder != null) {
82 | children.add(Column(children: [builder(context, t)]));
83 | }
84 |
85 | return _FlexibleDetail(t,
86 | child: ClipRect(
87 | child: DefaultTextStyle(
88 | style: Theme.of(context).primaryTextTheme.body1,
89 | child: Stack(children: children, fit: StackFit.expand))));
90 | }
91 | }
92 |
93 | class _FlexibleDetail extends InheritedWidget {
94 | ///0 : Expanded
95 | ///1 : Collapsed
96 | final double t;
97 |
98 | _FlexibleDetail(this.t, {Widget child}) : super(child: child);
99 |
100 | @override
101 | bool updateShouldNotify(_FlexibleDetail oldWidget) {
102 | return t != oldWidget.t;
103 | }
104 | }
105 |
106 | ///
107 | /// 用在 [FlexibleDetailBar.background]
108 | /// child上下滑动的时候会覆盖上黑色阴影
109 | ///
110 | class FlexShadowBackground extends StatelessWidget {
111 | final Widget child;
112 |
113 | const FlexShadowBackground({Key key, this.child}) : super(key: key);
114 |
115 | @override
116 | Widget build(BuildContext context) {
117 | var t = FlexibleDetailBar.percentage(context);
118 | t = Curves.ease.transform(t) / 2 + 0.2;
119 | return Container(
120 | foregroundDecoration: BoxDecoration(color: Colors.black.withOpacity(t)),
121 | child: child,
122 | );
123 | }
124 | }
--------------------------------------------------------------------------------
/netease_cloud_music/lib/pages/user/user_detail_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:common_utils/common_utils.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:netease_cloud_music/model/user_detail.dart';
5 | import 'package:netease_cloud_music/provider/user_model.dart';
6 | import 'package:netease_cloud_music/utils/net_utils.dart';
7 | import 'package:netease_cloud_music/widgets/widget_future_builder.dart';
8 | import 'package:netease_cloud_music/widgets/widget_play.dart';
9 | import 'package:netease_cloud_music/widgets/widget_play_list_app_bar.dart';
10 | import 'package:netease_cloud_music/widgets/widget_sliver_future_builder.dart';
11 | import 'package:provider/provider.dart';
12 |
13 | import '../../application.dart';
14 |
15 | class UserDetailPage extends StatefulWidget {
16 | final int userId;
17 | UserDetailPage({Key key, @required this.userId}) : super(key: key);
18 |
19 | @override
20 | _UserDetailPageState createState() => _UserDetailPageState();
21 | }
22 |
23 | class _UserDetailPageState extends State {
24 | double _expandedHeight = 500.w;
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | return Scaffold(
29 | backgroundColor: Colors.white,
30 | body: Stack(
31 | children: [
32 | Padding(
33 | padding:
34 | EdgeInsets.only(bottom: 80.w + Application.bottomBarHeight),
35 | child: CustomFutureBuilder(
36 | futureFunc: NetUtils.getUserInfo,
37 | params: {'uid': widget.userId},
38 | builder: (context, data) {
39 | return CustomScrollView(
40 | slivers: [
41 | PlayListAppBarWidget(
42 | backgroundImg: 'images/bg_daily.png',
43 | content: Column(
44 | crossAxisAlignment: CrossAxisAlignment.start,
45 | children: [
46 | Spacer(),
47 | Container(
48 | padding: EdgeInsets.only(
49 | left: ScreenUtil().setWidth(40)),
50 | margin: EdgeInsets.only(
51 | bottom: ScreenUtil().setWidth(5)),
52 | child: RichText(
53 | text: TextSpan(
54 | children: [
55 | TextSpan(
56 | text:
57 | '${DateUtil.formatDate(DateTime.now(), format: 'dd')} ',
58 | style: TextStyle(fontSize: 30)),
59 | TextSpan(
60 | text:
61 | '/ ${DateUtil.formatDate(DateTime.now(), format: 'MM')}',
62 | style: TextStyle(fontSize: 16)),
63 | ],
64 | ),
65 | ),
66 | ),
67 | Container(
68 | padding: EdgeInsets.only(
69 | left: ScreenUtil().setWidth(40)),
70 | margin: EdgeInsets.only(
71 | bottom: ScreenUtil().setWidth(20)),
72 | child: Text(
73 | '根据你的音乐口味,为你推荐好音乐。',
74 | style: TextStyle(
75 | fontSize: 14, color: Colors.white70),
76 | ),
77 | ),
78 | ],
79 | ),
80 | expandedHeight: _expandedHeight,
81 | title: '每日推荐',
82 | )
83 | ],
84 | );
85 | },
86 | ),
87 | ),
88 | PlayWidget(),
89 | ],
90 | ),
91 | );
92 | ;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/provider/play_songs_model.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 |
4 | import 'package:audioplayers/audioplayers.dart';
5 | import 'package:common_utils/common_utils.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:netease_cloud_music/application.dart';
8 | import 'package:netease_cloud_music/model/song.dart';
9 | import 'package:netease_cloud_music/model/user.dart';
10 | import 'package:netease_cloud_music/utils/fluro_convert_utils.dart';
11 | import 'package:netease_cloud_music/utils/navigator_util.dart';
12 | import 'package:netease_cloud_music/utils/net_utils.dart';
13 | import 'package:fluttertoast/fluttertoast.dart';
14 | import 'package:netease_cloud_music/utils/utils.dart';
15 |
16 | class PlaySongsModel with ChangeNotifier{
17 | AudioPlayer _audioPlayer = AudioPlayer();
18 | StreamController _curPositionController = StreamController.broadcast();
19 |
20 | List _songs = [];
21 | int curIndex = 0;
22 | Duration curSongDuration;
23 | AudioPlayerState _curState;
24 |
25 | List get allSongs => _songs;
26 | Song get curSong => _songs[curIndex];
27 | Stream get curPositionStream => _curPositionController.stream;
28 | AudioPlayerState get curState => _curState;
29 |
30 |
31 | void init() {
32 | _audioPlayer.setReleaseMode(ReleaseMode.STOP);
33 | // 播放状态监听
34 | _audioPlayer.onPlayerStateChanged.listen((state) {
35 | _curState = state;
36 | /// 先做顺序播放
37 | if(state == AudioPlayerState.COMPLETED){
38 | nextPlay();
39 | }
40 | // 其实也只有在播放状态更新时才需要通知。
41 | notifyListeners();
42 | });
43 | _audioPlayer.onDurationChanged.listen((d) {
44 | curSongDuration = d;
45 | });
46 | // 当前播放进度监听
47 | _audioPlayer.onAudioPositionChanged.listen((Duration p) {
48 | sinkProgress(p.inMilliseconds > curSongDuration.inMilliseconds ? curSongDuration.inMilliseconds : p.inMilliseconds);
49 | });
50 | }
51 |
52 | // 歌曲进度
53 | void sinkProgress(int m){
54 | _curPositionController.sink.add('$m-${curSongDuration.inMilliseconds}');
55 | }
56 |
57 | // 播放一首歌
58 | void playSong(Song song) {
59 | _songs.insert(curIndex, song);
60 | play();
61 | }
62 |
63 | // 播放很多歌
64 | void playSongs(List songs, {int index}) {
65 | this._songs = songs;
66 | if (index != null) curIndex = index;
67 | play();
68 | }
69 |
70 | // 添加歌曲
71 | void addSongs(List songs) {
72 | this._songs.addAll(songs);
73 | }
74 |
75 | /// 播放
76 | void play() async {
77 | var songId = this._songs[curIndex].id;
78 | var url = await NetUtils.getMusicURL(null, songId);
79 |
80 | _audioPlayer.play(url);
81 | saveCurSong();
82 | }
83 |
84 | /// 暂停、恢复
85 | void togglePlay(){
86 | if (_audioPlayer.state == AudioPlayerState.PAUSED) {
87 | resumePlay();
88 | } else {
89 | pausePlay();
90 | }
91 | }
92 |
93 | // 暂停
94 | void pausePlay() {
95 | _audioPlayer.pause();
96 | }
97 |
98 | /// 跳转到固定时间
99 | void seekPlay(int milliseconds){
100 | _audioPlayer.seek(Duration(milliseconds: milliseconds));
101 | resumePlay();
102 | }
103 |
104 | /// 恢复播放
105 | void resumePlay() {
106 | _audioPlayer.resume();
107 | }
108 |
109 | /// 下一首
110 | void nextPlay(){
111 | if(curIndex >= _songs.length){
112 | curIndex = 0;
113 | }else{
114 | curIndex++;
115 | }
116 | play();
117 | }
118 |
119 | void prePlay(){
120 | if(curIndex <= 0){
121 | curIndex = _songs.length - 1;
122 | }else{
123 | curIndex--;
124 | }
125 | play();
126 | }
127 |
128 | // 保存当前歌曲到本地
129 | void saveCurSong(){
130 | Application.sp.remove('playing_songs');
131 | Application.sp.setStringList('playing_songs', _songs.map((s) => FluroConvertUtils.object2string(s)).toList());
132 | Application.sp.setInt('playing_index', curIndex);
133 | }
134 |
135 | @override
136 | void dispose() {
137 | super.dispose();
138 | _curPositionController.close();
139 | _audioPlayer.dispose();
140 | }
141 |
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_play_list_menu.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:netease_cloud_music/model/play_list.dart';
4 | import 'package:netease_cloud_music/provider/play_list_model.dart';
5 | import 'package:netease_cloud_music/provider/user_model.dart';
6 | import 'package:netease_cloud_music/utils/net_utils.dart';
7 | import 'package:netease_cloud_music/utils/utils.dart';
8 | import 'package:netease_cloud_music/widgets/common_text_style.dart';
9 | import 'package:netease_cloud_music/widgets/widget_edit_play_list.dart';
10 |
11 | class PlayListMenuWidget extends StatefulWidget {
12 | final Playlist _playlist;
13 | final PlayListModel _model;
14 |
15 | PlayListMenuWidget(this._playlist, this._model);
16 |
17 | @override
18 | _PlayListMenuWidgetState createState() => _PlayListMenuWidgetState();
19 | }
20 |
21 | class _PlayListMenuWidgetState extends State {
22 | Widget _buildMenuItem(String img, String text, VoidCallback onTap) {
23 | return InkWell(
24 | onTap: onTap,
25 | child: Container(
26 | height: ScreenUtil().setWidth(110),
27 | alignment: Alignment.center,
28 | child: Row(
29 | children: [
30 | Container(
31 | width: ScreenUtil().setWidth(140),
32 | child: Align(
33 | child: Image.asset(
34 | img,
35 | width: ScreenUtil().setWidth(80),
36 | fit: BoxFit.fitWidth,
37 | ),
38 | ),
39 | ),
40 | Expanded(
41 | child: Text(
42 | text,
43 | style: common14TextStyle,
44 | ),
45 | )
46 | ],
47 | ),
48 | ),
49 | );
50 | }
51 |
52 | @override
53 | Widget build(BuildContext context) {
54 | return Container(
55 | decoration: BoxDecoration(
56 | borderRadius: BorderRadius.only(
57 | topLeft: Radius.circular(ScreenUtil().setWidth(40)),
58 | topRight: Radius.circular(ScreenUtil().setWidth(40))),
59 | color: Colors.white),
60 | child: Column(
61 | mainAxisSize: MainAxisSize.min,
62 | crossAxisAlignment: CrossAxisAlignment.start,
63 | children: [
64 | Container(
65 | height: ScreenUtil().setWidth(100),
66 | padding: EdgeInsets.only(left: ScreenUtil().setWidth(40)),
67 | alignment: Alignment.centerLeft,
68 | child: Text(
69 | '歌单:${widget._playlist.name}',
70 | style: common14GrayTextStyle,
71 | ),
72 | ),
73 | Container(
74 | height: ScreenUtil().setWidth(0.5),
75 | color: Colors.black26,
76 | ),
77 | Offstage(
78 | offstage: widget._playlist.creator.userId != widget._model.user.account.id,
79 | child: _buildMenuItem('images/icon_edit.png', '编辑歌单信息', () {
80 |
81 | showDialog(context: context, builder: (context){
82 | return EditPlayListWidget(submitCallback: (String name, String desc) {
83 |
84 | }, playlist: widget._playlist,);
85 | });
86 | }),
87 | ),
88 | Offstage(
89 | offstage: widget._playlist.creator.userId != widget._model.user.account.id,
90 | child: Container(
91 | color: Colors.grey,
92 | margin: EdgeInsets.only(left: ScreenUtil().setWidth(140)),
93 | height: ScreenUtil().setWidth(0.3),
94 | ),
95 | ),
96 | _buildMenuItem('images/icon_del.png', '删除', () async {
97 | NetUtils.deletePlaylist(context, params: {'id': widget._playlist.id}).then((v){
98 | if(v.code == 200) Navigator.pop(context, widget._playlist..type = 1);
99 | else Utils.showToast('删除失败,请重试');
100 | });
101 | }),
102 | Container(
103 | color: Colors.grey,
104 | margin: EdgeInsets.only(left: ScreenUtil().setWidth(140)),
105 | height: ScreenUtil().setWidth(0.3),
106 | ),
107 | ],
108 | ),
109 | );
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/widgets/widget_banner.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:extended_image/extended_image.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_screenutil/flutter_screenutil.dart';
6 | import 'package:netease_cloud_music/utils/utils.dart';
7 |
8 | class CustomBanner extends StatefulWidget {
9 | final List _images;
10 | final double height;
11 | final ValueChanged onTap;
12 | final Curve curve;
13 |
14 | CustomBanner(
15 | this._images, {
16 | this.height = 200,
17 | this.onTap,
18 | this.curve = Curves.linear,
19 | }) : assert(_images != null);
20 |
21 | @override
22 | _CustomBannerState createState() => _CustomBannerState();
23 | }
24 |
25 | class _CustomBannerState extends State {
26 | PageController _pageController;
27 | int _curIndex;
28 | Timer _timer;
29 |
30 | @override
31 | void initState() {
32 | super.initState();
33 | _curIndex = widget._images.length * 5;
34 | _pageController = PageController(initialPage: _curIndex);
35 | _initTimer();
36 | }
37 |
38 | @override
39 | Widget build(BuildContext context) {
40 | return Stack(
41 | alignment: Alignment.bottomCenter,
42 | children: [
43 | _buildPageView(),
44 | _buildIndicator(),
45 | ],
46 | );
47 | }
48 |
49 | Widget _buildIndicator() {
50 | var length = widget._images.length;
51 | return Positioned(
52 | bottom: 10,
53 | child: Row(
54 | children: widget._images.map((s) {
55 | return Padding(
56 | padding: const EdgeInsets.symmetric(horizontal: 3.0),
57 | child: ClipOval(
58 | child: Container(
59 | width: ScreenUtil().setWidth(12),
60 | height: ScreenUtil().setWidth(12),
61 | color: s == widget._images[_curIndex % length]
62 | ? Colors.red
63 | : Colors.grey,
64 | ),
65 | ),
66 | );
67 | }).toList(),
68 | ),
69 | );
70 | }
71 |
72 | Widget _buildPageView() {
73 | var length = widget._images.length;
74 | return AspectRatio(
75 | aspectRatio: (1080 + (ScreenUtil().setWidth(15) * 2)) /
76 | (400 - (ScreenUtil().setWidth(15))),
77 | child: PageView.builder(
78 | controller: _pageController,
79 | onPageChanged: (index) {
80 | setState(() {
81 | _curIndex = index;
82 | if (index == 0) {
83 | _curIndex = length;
84 | _changePage();
85 | }
86 | });
87 | },
88 | itemBuilder: (context, index) {
89 | return GestureDetector(
90 | onPanDown: (details) {
91 | _cancelTimer();
92 | },
93 | onTap: () {
94 | Scaffold.of(context).showSnackBar(
95 | SnackBar(
96 | content: Text('当前 page 为 ${index % length}'),
97 | duration: Duration(milliseconds: 500),
98 | ),
99 | );
100 | },
101 | child: Padding(
102 | padding: EdgeInsets.symmetric(
103 | horizontal: ScreenUtil().setWidth(15),
104 | ),
105 | child: ClipRRect(
106 | borderRadius: BorderRadius.all(Radius.circular(ScreenUtil().setWidth(10))),
107 | child: Utils.showNetImage(
108 | widget._images[index % length],
109 | fit: BoxFit.fitWidth,
110 | ),
111 | ),
112 | ));
113 | },
114 | ),
115 | );
116 | }
117 |
118 | /// 点击到图片的时候取消定时任务
119 | _cancelTimer() {
120 | if (_timer != null) {
121 | _timer.cancel();
122 | _timer = null;
123 | _initTimer();
124 | }
125 | }
126 |
127 | @override
128 | void dispose() {
129 | _timer.cancel();
130 | _timer = null;
131 | super.dispose();
132 | }
133 |
134 | /// 初始化定时任务
135 | _initTimer() {
136 | if (_timer == null) {
137 | if (mounted) {
138 | _timer = Timer.periodic(Duration(seconds: 3), (t) {
139 | _curIndex++;
140 | _pageController.animateToPage(
141 | _curIndex,
142 | duration: Duration(milliseconds: 300),
143 | curve: Curves.linear,
144 | );
145 | });
146 | }
147 | }
148 | }
149 |
150 | /// 切换页面,并刷新小圆点
151 | _changePage() {
152 | Timer(Duration(milliseconds: 350), () {
153 | _pageController.jumpToPage(_curIndex);
154 | });
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/pages/home/home_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:netease_cloud_music/provider/user_model.dart';
3 | import 'package:netease_cloud_music/utils/navigator_util.dart';
4 | import 'package:netease_cloud_music/widgets/v_empty_view.dart';
5 | import 'package:flutter_screenutil/flutter_screenutil.dart';
6 | import 'package:netease_cloud_music/widgets/widget_play.dart';
7 | import 'package:netease_cloud_music/widgets/widget_round_img.dart';
8 | import 'package:provider/provider.dart';
9 | import '../../application.dart';
10 | import 'discover/discover_page.dart';
11 | import 'event/event_page.dart';
12 | import 'my/my_page.dart';
13 |
14 | class HomePage extends StatefulWidget {
15 | @override
16 | _HomePageState createState() => _HomePageState();
17 | }
18 |
19 | class _HomePageState extends State with TickerProviderStateMixin {
20 | TabController _tabController;
21 |
22 | @override
23 | void initState() {
24 | super.initState();
25 | _tabController = TabController(vsync: this, length: 3);
26 | }
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | return Scaffold(
31 | // 设置没有高度的 appbar,目的是为了设置状态栏的颜色
32 | appBar: PreferredSize(
33 | child: AppBar(
34 | elevation: 0,
35 | ),
36 | preferredSize: Size.zero,
37 | ),
38 | backgroundColor: Colors.white,
39 | body: SafeArea(
40 | bottom: false,
41 | child: Stack(
42 | children: [
43 | Padding(
44 | child: Column(
45 | children: [
46 | Stack(
47 | children: [
48 | Positioned(
49 | left: 20.w,
50 | child: Consumer(
51 | builder: (_, model, __) {
52 | var user = model.user;
53 | return GestureDetector(
54 | onTap: () => NavigatorUtil.goUserDetailPage(context, user.account.id),
55 | child:
56 | RoundImgWidget(user.profile.avatarUrl, 140.w),
57 | );
58 | },
59 | ),
60 | ),
61 | Padding(
62 | padding: EdgeInsets.symmetric(
63 | horizontal: ScreenUtil().setWidth(150)),
64 | child: TabBar(
65 | labelStyle: TextStyle(
66 | fontSize: 20, fontWeight: FontWeight.bold),
67 | unselectedLabelStyle: TextStyle(fontSize: 14),
68 | indicator: UnderlineTabIndicator(),
69 | controller: _tabController,
70 | tabs: [
71 | Tab(
72 | text: '发现',
73 | ),
74 | Tab(
75 | text: '我的',
76 | ),
77 | Tab(
78 | text: '动态',
79 | ),
80 | ],
81 | ),
82 | ),
83 | Positioned(
84 | right: 20.w,
85 | child: IconButton(
86 | icon: Icon(
87 | Icons.search,
88 | size: 50.w,
89 | color: Colors.black87,
90 | ),
91 | onPressed: () {
92 | NavigatorUtil.goSearchPage(context);
93 | },
94 | ),
95 | ),
96 | ],
97 | ),
98 | VEmptyView(20),
99 | Expanded(
100 | child: TabBarView(
101 | controller: _tabController,
102 | children: [
103 | DiscoverPage(),
104 | MyPage(),
105 | EventPage(),
106 | ],
107 | ),
108 | ),
109 | ],
110 | ),
111 | padding: EdgeInsets.only(
112 | bottom:
113 | ScreenUtil().setWidth(80) + Application.bottomBarHeight),
114 | ),
115 | PlayWidget(),
116 | ],
117 | ),
118 | ),
119 | );
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/netease_cloud_music/lib/pages/play_list/play_list_desc_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 | import 'package:extended_image/extended_image.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_screenutil/flutter_screenutil.dart';
5 | import 'package:netease_cloud_music/application.dart';
6 | import 'package:netease_cloud_music/model/play_list.dart';
7 | import 'package:netease_cloud_music/utils/utils.dart';
8 | import 'package:netease_cloud_music/widgets/common_text_style.dart';
9 | import 'package:netease_cloud_music/widgets/rounded_net_image.dart';
10 | import 'package:netease_cloud_music/widgets/v_empty_view.dart';
11 | import 'package:netease_cloud_music/widgets/widget_tag.dart';
12 |
13 | class PlayListDescDialog extends StatelessWidget {
14 | final Playlist _data;
15 |
16 | PlayListDescDialog(this._data);
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return Material(
21 | child: GestureDetector(
22 | onTap: () {
23 | Navigator.of(context).pop();
24 | },
25 | child: Stack(
26 | children: [
27 | Utils.showNetImage(
28 | '${_data.coverImgUrl}?param=200y200',
29 | width: double.infinity,
30 | height: double.infinity,
31 | fit: BoxFit.cover,
32 | ),
33 | BackdropFilter(
34 | filter: ImageFilter.blur(
35 | sigmaY: 30,
36 | sigmaX: 30,
37 | ),
38 | child: Container(
39 | color: Colors.black38,
40 | ),
41 | ),
42 | SafeArea(
43 | bottom: false,
44 | child: Stack(
45 | children: [
46 | Positioned(
47 | right: ScreenUtil().setWidth(40),
48 | top: ScreenUtil().setWidth(10),
49 | child: Icon(
50 | Icons.close,
51 | color: Colors.white,
52 | size: ScreenUtil().setWidth(60),
53 | ),
54 | ),
55 | SingleChildScrollView(
56 | child: Padding(
57 | padding: EdgeInsets.symmetric(
58 | horizontal: ScreenUtil().setWidth(100)),
59 | child: Column(
60 | children: [
61 | VEmptyView(150),
62 | Align(
63 | alignment: Alignment.topCenter,
64 | child: RoundedNetImage(
65 | '${_data.coverImgUrl}?param=200y200',
66 | width: 400,
67 | height: 400,
68 | ),
69 | ),
70 | VEmptyView(40),
71 | Text(
72 | _data.name,
73 | textAlign: TextAlign.center,
74 | style: mWhiteBoldTextStyle,
75 | softWrap: true,
76 | ),
77 | VEmptyView(40),
78 | Image.asset(
79 | 'images/icon_line_1.png',
80 | width: Application.screenWidth * 3 / 4,
81 | ),
82 | VEmptyView(20),
83 | _data.tags.isEmpty
84 | ? Container()
85 | : Row(
86 | crossAxisAlignment: CrossAxisAlignment.start,
87 | children: [
88 | Text(
89 | '标签:',
90 | style: common14WhiteTextStyle,
91 | ),
92 | ..._data.tags
93 | .map((t) => TagWidget(t))
94 | .toList()
95 | ],
96 | ),
97 | _data.tags.isEmpty ? Container() : VEmptyView(40),
98 | Text(
99 | _data.description,
100 | style: common14WhiteTextStyle,
101 | softWrap: true,
102 | ),
103 | ],
104 | ),
105 | ),
106 | )
107 | ],
108 | ),
109 | ),
110 | ],
111 | ),
112 | ),
113 | );
114 | }
115 | }
116 |
--------------------------------------------------------------------------------