├── 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 | ![](http://pic.d3collection.cn/2019-10-09-140344.png) 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 目前所能公开的信息: 60 | 61 | | ![](http://pic.d3collection.cn/2019-10-13-132011.png) | ![](http://pic.d3collection.cn/2019-10-13-132000.png) | ![](http://pic.d3collection.cn/2019-10-13-125812.png) | 62 | | ----------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------- | 63 | | ![](http://pic.d3collection.cn/2019-10-13-125844.png) | ![](http://pic.d3collection.cn/2019-10-13-130202.png) | ![](http://pic.d3collection.cn/2019-10-13-130248.png) | 64 | | ![](http://pic.d3collection.cn/2019-10-14-151915.png) | ![](http://pic.d3collection.cn/2019-10-16-062303.png) | ![](http://pic.d3collection.cn/2019-10-22-074251.png) | 65 | | ![](http://pic.d3collection.cn/2019-10-30-063952.png) | ![](http://pic.d3collection.cn/2019-10-30-091021.png) | ![](http://pic.d3collection.cn/2019-11-06-081431.png) | 66 | | ![](http://pic.d3collection.cn/2019-11-08-083202.png) | ![](http://pic.d3collection.cn/2019-11-15-031201.png) | ![](http://pic.d3collection.cn/2019-11-15-031134.png) | 67 | | ![](http://pic.d3collection.cn/2019-11-15-031215.png) | ![](http://pic.d3collection.cn/2019-11-15-031227.png) | ![](http://pic.d3collection.cn/2019-11-15-031246.png) | 68 | | ![](http://pic.d3collection.cn/2019-11-15-031353.png) | ![](http://pic.d3collection.cn/2019-11-15-031408.png) | ![](http://pic.d3collection.cn/2019-11-27-061505.png) | 69 | 70 | --- 71 | 72 | 播放页面(因模拟器虚化效果和真机不同,所以播放页面的UI由截图上传): 73 | 74 | | 播放 | 暂停 | 评论 | 75 | | :-----------------------------------------------------: | :----: | ------------------------------------------------------- | 76 | | ![](http://pic.d3collection.cn/2019-10-21-025741.jpg) | ![](http://pic.d3collection.cn/2019-10-21-025618.jpg) | ![](http://pic.d3collection.cn/2019-10-23-073549.gif) | 77 | | ![](http://pic.d3collection.cn/2019-10-28-112602.gif) | ![](http://pic.d3collection.cn/2019-11-08-072720.gif) | | 78 | 79 | 80 | 81 | [![Stargazers over time](https://starchart.cc/fluttercandies/NeteaseCloudMusic.svg)](https://starchart.cc/fluttercandies/NeteaseCloudMusic) 82 | 83 | 84 | 85 | 另我个人创建了一个「Flutter 交流群」,可以添加我个人微信 「17610912320」来入群。 86 | 87 | 88 | 89 | ![](http://pic.d3collection.cn/2019-10-09-140347.png) 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 | --------------------------------------------------------------------------------