├── lib ├── redux │ ├── middlewares │ ├── reducers │ │ ├── comment.dart │ │ ├── rank.dart │ │ ├── profile.dart │ │ ├── search.dart │ │ ├── home.dart │ │ ├── lib.dart │ │ ├── dynamic.dart │ │ ├── main.dart │ │ └── video.dart │ ├── actions │ │ ├── rank.dart │ │ ├── main.dart │ │ ├── profile.dart │ │ ├── comment.dart │ │ ├── search.dart │ │ ├── home.dart │ │ ├── lib.dart │ │ └── dynamic.dart │ ├── view_models │ │ ├── profile.dart │ │ ├── rank.dart │ │ ├── search.dart │ │ ├── dynamic.dart │ │ ├── home.dart │ │ ├── main.dart │ │ └── lib.dart │ └── states │ │ ├── profile.dart │ │ ├── rank.dart │ │ ├── search.dart │ │ ├── main.dart │ │ ├── dynamic.dart │ │ ├── home.dart │ │ └── lib.dart ├── models │ ├── main.dart │ ├── enums.dart │ ├── keyword.dart │ ├── menu.dart │ ├── ad.dart │ ├── resource_category.dart │ ├── profile.dart │ ├── lib_resource.dart │ ├── help.dart │ ├── tv_schedule.dart │ ├── comment.dart │ ├── video_comment.dart │ ├── article_comment.dart │ ├── resource.dart │ ├── article.dart │ └── video_info.dart ├── pages │ ├── main.dart │ ├── photo_browser.dart │ ├── payer.dart │ ├── dynamic.dart │ ├── tab_page.dart │ ├── rank.dart │ ├── settings.dart │ ├── lib.dart │ ├── home.dart │ ├── reply.dart │ ├── search.dart │ └── comment.dart ├── widgets │ ├── section_divider.dart │ ├── section_title.dart │ ├── rate_view.dart │ ├── gradient_tag.dart │ ├── resource_tile.dart │ ├── icon_label.dart │ ├── search_bar.dart │ ├── rr_rate.dart │ ├── slider_indicator.dart │ ├── help_list_view.dart │ ├── tabbar_item.dart │ ├── video_grid_view.dart │ ├── empty_view.dart │ ├── grid_menu_item.dart │ ├── sliver_tabview_wrap.dart │ ├── tag.dart │ ├── comment_listtile.dart │ ├── rate_stars.dart │ ├── comment_bar.dart │ ├── comment_list.dart │ ├── main.dart │ ├── help_list_tile.dart │ ├── sort_bar.dart │ ├── hot_comment_list.dart │ ├── grid_menu_view.dart │ ├── user_info_tile.dart │ ├── action_bar.dart │ ├── video_player_view.dart │ ├── video_grid_item.dart │ ├── series_list_view.dart │ ├── hot_comment_listtile.dart │ ├── home_banner.dart │ ├── todays_boardcast.dart │ ├── app_drawer.dart │ ├── video_cover.dart │ └── video_listtile.dart ├── common │ └── utils.dart └── main.dart ├── android ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.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 │ │ │ ├── values │ │ │ │ └── styles.xml │ │ │ └── drawable │ │ │ │ └── launch_background.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── popeye │ │ │ │ └── yyets │ │ │ │ └── MainActivity.java │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── .gitignore ├── settings.gradle ├── build.gradle ├── gradlew.bat └── gradlew ├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── WorkspaceSettings.xcsettings ├── .gitignore ├── Podfile.lock └── Podfile ├── assets └── images │ ├── 公开课.png │ ├── 其他语种.png │ ├── 日剧.png │ ├── 片库.png │ ├── 电影.png │ ├── 纪录片.png │ ├── 美剧.png │ ├── 英剧.png │ ├── 韩剧.png │ ├── cover.jpg │ ├── cover.png │ ├── play_btn.png │ ├── drawer_bg.jpeg │ ├── placeholder.png │ ├── profile_bg.jpeg │ └── 排序_12x13_@3x.png ├── .gitignore ├── .metadata ├── yyets.iml ├── README.md ├── test └── widget_test.dart ├── yyets_android.iml ├── pubspec.yaml └── pubspec.lock /lib/redux/middlewares: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/redux/reducers/comment.dart: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /lib/models/main.dart: -------------------------------------------------------------------------------- 1 | export 'package:yyets/models/menu.dart'; 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lib/models/enums.dart: -------------------------------------------------------------------------------- 1 | enum UserLevel { member, vip, superVip, admin } 2 | -------------------------------------------------------------------------------- /assets/images/公开课.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/公开课.png -------------------------------------------------------------------------------- /assets/images/其他语种.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/其他语种.png -------------------------------------------------------------------------------- /assets/images/日剧.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/日剧.png -------------------------------------------------------------------------------- /assets/images/片库.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/片库.png -------------------------------------------------------------------------------- /assets/images/电影.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/电影.png -------------------------------------------------------------------------------- /assets/images/纪录片.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/纪录片.png -------------------------------------------------------------------------------- /assets/images/美剧.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/美剧.png -------------------------------------------------------------------------------- /assets/images/英剧.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/英剧.png -------------------------------------------------------------------------------- /assets/images/韩剧.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/韩剧.png -------------------------------------------------------------------------------- /assets/images/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/cover.jpg -------------------------------------------------------------------------------- /assets/images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/cover.png -------------------------------------------------------------------------------- /assets/images/play_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/play_btn.png -------------------------------------------------------------------------------- /assets/images/drawer_bg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/drawer_bg.jpeg -------------------------------------------------------------------------------- /assets/images/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/placeholder.png -------------------------------------------------------------------------------- /assets/images/profile_bg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/profile_bg.jpeg -------------------------------------------------------------------------------- /assets/images/排序_12x13_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/assets/images/排序_12x13_@3x.png -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | .idea 4 | .vscode 5 | 6 | .packages 7 | .pub/ 8 | 9 | build/ 10 | 11 | .flutter-plugins 12 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /lib/pages/main.dart: -------------------------------------------------------------------------------- 1 | export 'package:yyets/pages/home.dart'; 2 | export 'package:yyets/pages/lib.dart'; 3 | export 'package:yyets/pages/rank.dart'; 4 | export 'package:yyets/pages/dynamic.dart'; 5 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popeyelau/Flutter_YYeTs/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /lib/models/keyword.dart: -------------------------------------------------------------------------------- 1 | class Keyword { 2 | String keyword; 3 | 4 | Keyword({this.keyword}); 5 | 6 | Keyword.fromJson(Map json) { 7 | keyword = json['keyword']; 8 | } 9 | } -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | GeneratedPluginRegistrant.java 11 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/redux/actions/rank.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/models/resource.dart'; 2 | import 'package:yyets/redux/actions/main.dart'; 3 | 4 | class UpdateTopRanks extends ActionType { 5 | final Ranks payload; 6 | UpdateTopRanks({this.payload}) : super(payload: payload); 7 | } 8 | -------------------------------------------------------------------------------- /lib/models/menu.dart: -------------------------------------------------------------------------------- 1 | 2 | class Menu { 3 | final String title; 4 | final String icon; 5 | final String channel; 6 | final String area; 7 | 8 | Menu({ 9 | this.title, 10 | this.icon, 11 | this.channel = "", 12 | this.area = "" 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /lib/redux/actions/main.dart: -------------------------------------------------------------------------------- 1 | abstract class ActionType { 2 | final T payload; 3 | 4 | ActionType({this.payload}); 5 | 6 | @override 7 | String toString() => '$runtimeType(${payload?.runtimeType})'; 8 | } 9 | 10 | class VoidAction extends ActionType {} 11 | -------------------------------------------------------------------------------- /lib/redux/actions/profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/models/profile.dart'; 2 | import 'package:yyets/redux/actions/main.dart'; 3 | 4 | class UpdateUserProfile extends ActionType { 5 | final Profile payload; 6 | UpdateUserProfile({this.payload}) : super(payload: payload); 7 | } 8 | -------------------------------------------------------------------------------- /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.4-all.zip 7 | -------------------------------------------------------------------------------- /lib/widgets/section_divider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SectionDivider extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Container( 7 | height: 8.0, 8 | color: Colors.grey[100], 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/redux/actions/comment.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/models/article_comment.dart'; 2 | import 'package:yyets/redux/actions/main.dart'; 3 | 4 | class UpdateArticleComments extends ActionType { 5 | final List payload; 6 | UpdateArticleComments({this.payload}) : super(payload: payload); 7 | } 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.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: 472bbccf756e7954af2a81d2c8abc46d65a570af 8 | channel: dev 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/redux/reducers/rank.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/redux/actions/main.dart'; 2 | import 'package:yyets/redux/actions/rank.dart'; 3 | import 'package:yyets/redux/states/rank.dart'; 4 | 5 | RankState reducer(RankState state, ActionType action) { 6 | if (action is UpdateTopRanks) { 7 | return state.copyWith(ranks: action.payload, isLoading: false); 8 | } 9 | return state; 10 | } 11 | -------------------------------------------------------------------------------- /lib/redux/reducers/profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/redux/actions/main.dart'; 2 | import 'package:yyets/redux/actions/profile.dart'; 3 | import 'package:yyets/redux/states/profile.dart'; 4 | 5 | ProfileState reducer(ProfileState state, ActionType action) { 6 | if (action is UpdateUserProfile) { 7 | return state.copyWith(profile: action.payload); 8 | } 9 | return state; 10 | } 11 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /lib/redux/view_models/profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:yyets/models/profile.dart'; 3 | import 'package:yyets/redux/states/main.dart'; 4 | import 'package:yyets/redux/view_models/main.dart'; 5 | 6 | class ProfileViewModel extends ViewModel { 7 | ProfileViewModel(Store store) : super(store); 8 | 9 | Profile get profile => this.store.state.profile.profile; 10 | } 11 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /lib/redux/states/profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:yyets/models/profile.dart'; 3 | 4 | @immutable 5 | class ProfileState { 6 | final Profile profile; 7 | 8 | ProfileState({this.profile}); 9 | 10 | ProfileState copyWith({Profile profile}) { 11 | return ProfileState(profile: profile ?? this.profile); 12 | } 13 | 14 | ProfileState.initialState() : profile = Profile.initialState(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/redux/view_models/rank.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:yyets/models/resource.dart'; 3 | import 'package:yyets/redux/states/main.dart'; 4 | import 'package:yyets/redux/view_models/main.dart'; 5 | 6 | class RankViewModel extends ViewModel { 7 | RankViewModel(Store store) : super(store); 8 | 9 | Ranks get ranks => this.store.state.rank.ranks; 10 | bool get isLoading => this.store.state.rank.isLoading; 11 | } 12 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/popeye/yyets/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.popeye.yyets; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/models/ad.dart: -------------------------------------------------------------------------------- 1 | class Ad { 2 | int adId; 3 | int adType; 4 | String pic; 5 | String click; 6 | String wh; 7 | int status; 8 | 9 | Ad({this.adId, this.adType, this.pic, this.click, this.wh, this.status}); 10 | 11 | Ad.fromJson(Map json) { 12 | adId = json['adId']; 13 | adType = json['adType']; 14 | pic = json['pic']; 15 | click = json['click']; 16 | wh = json['wh']; 17 | status = json['status']; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /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: [UIApplicationLaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/redux/reducers/search.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/redux/actions/main.dart'; 2 | import 'package:yyets/redux/actions/search.dart'; 3 | import 'package:yyets/redux/states/search.dart'; 4 | 5 | SearchState reducer(SearchState state, ActionType action) { 6 | if (action is UpdateHotKeywords) { 7 | return state.copyWith(keywords: action.payload); 8 | } 9 | if (action is UpdateSearchResults) { 10 | return state.copyWith(results: action.payload); 11 | } 12 | return state; 13 | } 14 | -------------------------------------------------------------------------------- /lib/redux/actions/search.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/models/keyword.dart'; 2 | import 'package:yyets/models/lib_resource.dart'; 3 | import 'package:yyets/redux/actions/main.dart'; 4 | 5 | class UpdateHotKeywords extends ActionType { 6 | final List payload; 7 | UpdateHotKeywords({this.payload}) : super(payload: payload); 8 | } 9 | 10 | class UpdateSearchResults extends ActionType { 11 | final List payload; 12 | UpdateSearchResults({this.payload}) : super(payload: payload); 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/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 | -------------------------------------------------------------------------------- /lib/redux/states/rank.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:yyets/models/resource.dart'; 3 | 4 | @immutable 5 | class RankState { 6 | final Ranks ranks; 7 | final bool isLoading; 8 | 9 | RankState({this.ranks, this.isLoading}); 10 | 11 | RankState copyWith({Ranks ranks, bool isLoading}) { 12 | return RankState( 13 | ranks: ranks ?? this.ranks, isLoading: isLoading ?? this.isLoading); 14 | } 15 | 16 | RankState.initialState() 17 | : ranks = Ranks.initialState(), 18 | isLoading = true; 19 | } 20 | -------------------------------------------------------------------------------- /lib/widgets/section_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SectionTitle extends StatelessWidget { 4 | final String title; 5 | 6 | const SectionTitle({Key key, this.title}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Container( 11 | child: Padding( 12 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), 13 | child: Text( 14 | title, 15 | style: TextStyle(fontWeight: FontWeight.bold), 16 | ), 17 | ), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/redux/reducers/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/redux/actions/home.dart'; 2 | import 'package:yyets/redux/actions/main.dart'; 3 | import 'package:yyets/redux/states/home.dart'; 4 | 5 | HomeState reducer(HomeState state, ActionType action) { 6 | if (action is UpdateTVSchedule) { 7 | return state.copyWith(schedules: action.payload); 8 | } 9 | 10 | if (action is UpdateArticles) { 11 | return state.copyWith(articles: action.payload, isLoading: false); 12 | } 13 | 14 | if (action is UpdateAds) { 15 | return state.copyWith(ads: action.payload); 16 | } 17 | return state; 18 | } 19 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.1.2' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /lib/models/resource_category.dart: -------------------------------------------------------------------------------- 1 | class ResourceCategory { 2 | List category; 3 | List year; 4 | List tv; 5 | 6 | ResourceCategory({this.category, this.year, this.tv}); 7 | 8 | ResourceCategory.initialState() { 9 | category = []; 10 | year = []; 11 | tv = []; 12 | } 13 | 14 | ResourceCategory.fromJson(Map json) { 15 | category = (json['category'] as List).map((i) => i.toString()).toList(); 16 | year = (json['year'] as List).map((i) => i.toString()).toList(); 17 | tv = (json['tv'] as List).map((i) => i.toString()).toList(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/redux/reducers/lib.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/redux/actions/lib.dart'; 2 | import 'package:yyets/redux/actions/main.dart'; 3 | import 'package:yyets/redux/states/lib.dart'; 4 | 5 | LibState reducer(LibState state, ActionType action) { 6 | if (action is UpdateLibResources) { 7 | return state.copyWith(resources: action.payload, isLoading: false); 8 | } 9 | if (action is UpdateFiltedResources) { 10 | return state.copyWith(filtedResources: action.payload); 11 | } 12 | if (action is UpdateResourceCategory) { 13 | return state.copyWith(category: action.payload); 14 | } 15 | return state; 16 | } 17 | -------------------------------------------------------------------------------- /lib/redux/states/search.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:yyets/models/keyword.dart'; 3 | import 'package:yyets/models/lib_resource.dart'; 4 | 5 | @immutable 6 | class SearchState { 7 | final List keywords; 8 | final List results; 9 | 10 | SearchState({this.keywords, this.results}); 11 | 12 | SearchState copyWith({List keywords, List results}) { 13 | return SearchState( 14 | keywords: keywords ?? this.keywords, results: results ?? this.results); 15 | } 16 | 17 | SearchState.initialState() 18 | : keywords = [], 19 | results = []; 20 | } 21 | -------------------------------------------------------------------------------- /lib/redux/actions/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/models/ad.dart'; 2 | import 'package:yyets/models/article.dart'; 3 | import 'package:yyets/models/tv_schedule.dart'; 4 | import 'package:yyets/redux/actions/main.dart'; 5 | 6 | class UpdateTVSchedule extends ActionType { 7 | final List payload; 8 | UpdateTVSchedule({this.payload}) : super(payload: payload); 9 | } 10 | 11 | class UpdateArticles extends ActionType { 12 | final List
payload; 13 | UpdateArticles({this.payload}) : super(payload: payload); 14 | } 15 | 16 | class UpdateAds extends ActionType { 17 | final List payload; 18 | UpdateAds({this.payload}) : super(payload: payload); 19 | } 20 | -------------------------------------------------------------------------------- /lib/redux/view_models/search.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:yyets/api/api.dart'; 3 | import 'package:yyets/models/lib_resource.dart'; 4 | import 'package:yyets/redux/states/main.dart'; 5 | import 'package:yyets/redux/view_models/main.dart'; 6 | 7 | class SearchViewModel extends ViewModel { 8 | SearchViewModel(Store store) : super(store); 9 | 10 | List get keywords => 11 | this.store.state.search.keywords.map((v) => v.keyword).toList(); 12 | 13 | List get results => this.store.state.search.results; 14 | 15 | searchResrouces({String keyword}) { 16 | Networking.searchResrouces(keyword: keyword); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/redux/actions/lib.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/models/lib_resource.dart'; 2 | import 'package:yyets/models/resource_category.dart'; 3 | import 'package:yyets/redux/actions/main.dart'; 4 | 5 | class UpdateLibResources extends ActionType { 6 | final List payload; 7 | UpdateLibResources({this.payload}) : super(payload: payload); 8 | } 9 | 10 | class UpdateFiltedResources extends ActionType { 11 | final List payload; 12 | UpdateFiltedResources({this.payload}) : super(payload: payload); 13 | } 14 | 15 | class UpdateResourceCategory extends ActionType { 16 | final ResourceCategory payload; 17 | UpdateResourceCategory({this.payload}) : super(payload: payload); 18 | } 19 | -------------------------------------------------------------------------------- /lib/widgets/rate_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/widgets/rate_stars.dart'; 3 | import 'package:yyets/widgets/rr_rate.dart'; 4 | 5 | class RateView extends StatelessWidget { 6 | final String score; 7 | 8 | const RateView({Key key, this.score}) : super(key: key); 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | decoration: BoxDecoration( 13 | borderRadius: BorderRadius.circular(5.0), 14 | color: Colors.white.withAlpha(100), 15 | ), 16 | child: Row( 17 | children: [RRRate(score: score,), RateStars(rate: double.parse(score),)], 18 | ), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/redux/view_models/dynamic.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:yyets/models/comment.dart'; 3 | import 'package:yyets/models/help.dart'; 4 | import 'package:yyets/redux/states/main.dart'; 5 | import 'package:yyets/redux/view_models/main.dart'; 6 | 7 | class DynamicViewModel extends ViewModel { 8 | DynamicViewModel(Store store) : super(store); 9 | 10 | List get comments => this 11 | .store 12 | .state 13 | .dynamics 14 | .comments 15 | .where((v) => v.resourceInfo != null) 16 | .toList(); 17 | 18 | List get helps => this.store.state.dynamics.helps; 19 | bool get isLoading => this.store.state.dynamics.isLoading; 20 | } 21 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/app.flx 37 | /Flutter/app.zip 38 | /Flutter/flutter_assets/ 39 | /Flutter/App.framework 40 | /Flutter/Flutter.framework 41 | /Flutter/Generated.xcconfig 42 | /ServiceDefinitions.json 43 | 44 | Pods/ 45 | .symlinks/ 46 | -------------------------------------------------------------------------------- /lib/redux/reducers/dynamic.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/redux/actions/dynamic.dart'; 2 | import 'package:yyets/redux/actions/main.dart'; 3 | import 'package:yyets/redux/states/dynamic.dart'; 4 | 5 | DynamicState reducer(DynamicState state, ActionType action) { 6 | if (action is UpdateComments) { 7 | return state.copyWith(comments: action.payload); 8 | } 9 | if (action is UpdateHelps) { 10 | return state.copyWith(helps: action.payload, isLoading: false); 11 | } 12 | if (action is UpdateVideoInfo) { 13 | return state.copyWith(videoInfo: action.payload, isLoading: false); 14 | } 15 | if (action is UpdateVideoComments) { 16 | return state.copyWith(videoComments: action.payload); 17 | } 18 | return state; 19 | } 20 | -------------------------------------------------------------------------------- /lib/redux/view_models/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:yyets/models/ad.dart'; 3 | import 'package:yyets/models/article.dart'; 4 | import 'package:yyets/models/tv_schedule.dart'; 5 | import 'package:yyets/redux/states/main.dart'; 6 | import 'package:yyets/redux/view_models/main.dart'; 7 | 8 | class HomeViewModel extends ViewModel { 9 | HomeViewModel(Store store) : super(store); 10 | 11 | List get schedules => this.store.state.home.schedules; 12 | List
get articles => this.store.state.home.articles; 13 | List get ads => 14 | this.store.state.home.ads.where((i) => i.adType == 6).toList(); 15 | bool get isLoading => this.store.state.home.isLoading; 16 | } 17 | -------------------------------------------------------------------------------- /lib/widgets/gradient_tag.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class GradientTag extends StatelessWidget { 4 | final Color color; 5 | final String title; 6 | final Gradient gradient; 7 | 8 | const GradientTag({Key key, this.color, this.title, this.gradient}) 9 | : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Container( 14 | padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0), 15 | decoration: BoxDecoration( 16 | borderRadius: BorderRadius.circular(20.0), gradient: gradient), 17 | child: Text( 18 | title, 19 | style: TextStyle(color: color, fontSize: 10.0), 20 | ), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/redux/states/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:yyets/redux/states/dynamic.dart'; 3 | import 'package:yyets/redux/states/home.dart'; 4 | import 'package:yyets/redux/states/lib.dart'; 5 | import 'package:yyets/redux/states/profile.dart'; 6 | import 'package:yyets/redux/states/rank.dart'; 7 | import 'package:yyets/redux/states/search.dart'; 8 | 9 | @immutable 10 | class ReduxState { 11 | final HomeState home; 12 | final RankState rank; 13 | final LibState lib; 14 | final SearchState search; 15 | final DynamicState dynamics; 16 | final ProfileState profile; 17 | 18 | const ReduxState( 19 | {this.home, 20 | this.rank, 21 | this.lib, 22 | this.search, 23 | this.dynamics, 24 | this.profile}); 25 | } 26 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - screen (0.0.1): 4 | - Flutter 5 | - video_player (0.0.1): 6 | - Flutter 7 | 8 | DEPENDENCIES: 9 | - Flutter (from `.symlinks/flutter/ios`) 10 | - screen (from `.symlinks/plugins/screen/ios`) 11 | - video_player (from `.symlinks/plugins/video_player/ios`) 12 | 13 | EXTERNAL SOURCES: 14 | Flutter: 15 | :path: ".symlinks/flutter/ios" 16 | screen: 17 | :path: ".symlinks/plugins/screen/ios" 18 | video_player: 19 | :path: ".symlinks/plugins/video_player/ios" 20 | 21 | SPEC CHECKSUMS: 22 | Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a 23 | screen: abd91ca7bf3426e1cc3646d27e9b2358d6bf07b0 24 | video_player: 3964090a33353060ed7f58aa6427c7b4b208ec21 25 | 26 | PODFILE CHECKSUM: 7765ea4305eaab0b3dfd384c7de11902aa3195fd 27 | 28 | COCOAPODS: 1.7.1 29 | -------------------------------------------------------------------------------- /lib/redux/reducers/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/redux/states/main.dart'; 2 | import 'package:yyets/redux/reducers/home.dart' as home; 3 | import 'package:yyets/redux/reducers/rank.dart' as rank; 4 | import 'package:yyets/redux/reducers/lib.dart' as lib; 5 | import 'package:yyets/redux/reducers/search.dart' as search; 6 | import 'package:yyets/redux/reducers/dynamic.dart' as dynamics; 7 | import 'package:yyets/redux/reducers/profile.dart' as profile; 8 | 9 | ReduxState reduxReducer(ReduxState state, action) => ReduxState( 10 | home: home.reducer(state.home, action), 11 | rank: rank.reducer(state.rank, action), 12 | lib: lib.reducer(state.lib, action), 13 | search: search.reducer(state.search, action), 14 | dynamics: dynamics.reducer(state.dynamics, action), 15 | profile: profile.reducer(state.profile, action)); 16 | -------------------------------------------------------------------------------- /lib/widgets/resource_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/models/comment.dart'; 3 | 4 | class ResourceTile extends StatelessWidget { 5 | final ResourceInfo resource; 6 | 7 | const ResourceTile({Key key, this.resource}) : super(key: key); 8 | @override 9 | Widget build(BuildContext context) { 10 | return Container( 11 | color: Colors.grey[200], 12 | child: ListTile( 13 | leading: Hero( 14 | tag: resource.poster, 15 | child: Image( 16 | image: NetworkImage(resource.posterS), 17 | fit: BoxFit.cover, 18 | width: 50.0, 19 | height: 50.0, 20 | ), 21 | ), 22 | title: Text(resource.cnname), 23 | subtitle: Text(resource.itemupdate), 24 | ), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/widgets/icon_label.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class IconLabel extends StatelessWidget { 4 | final IconData icon; 5 | final Color color; 6 | final String title; 7 | 8 | const IconLabel({Key key, this.icon, this.color = Colors.grey, this.title}) 9 | : super(key: key); 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | child: Row( 14 | crossAxisAlignment: CrossAxisAlignment.center, 15 | children: [ 16 | Icon( 17 | icon, 18 | color: color, 19 | ), 20 | SizedBox( 21 | width: 8.0, 22 | ), 23 | Text( 24 | title, 25 | style: TextStyle(color: color), 26 | ) 27 | ], 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/redux/actions/dynamic.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/models/comment.dart'; 2 | import 'package:yyets/models/help.dart'; 3 | import 'package:yyets/models/video_comment.dart'; 4 | import 'package:yyets/models/video_info.dart'; 5 | import 'package:yyets/redux/actions/main.dart'; 6 | 7 | class UpdateComments extends ActionType { 8 | final List payload; 9 | UpdateComments({this.payload}) : super(payload: payload); 10 | } 11 | 12 | class UpdateHelps extends ActionType { 13 | final List payload; 14 | UpdateHelps({this.payload}) : super(payload: payload); 15 | } 16 | 17 | class UpdateVideoInfo extends ActionType { 18 | final VideoInfo payload; 19 | UpdateVideoInfo({this.payload}) : super(payload: payload); 20 | } 21 | 22 | class UpdateVideoComments extends ActionType { 23 | final List payload; 24 | UpdateVideoComments({this.payload}) : super(payload: payload); 25 | } 26 | -------------------------------------------------------------------------------- /lib/widgets/search_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SearchBar extends StatelessWidget { 4 | final String placeholder; 5 | final bool enabled; 6 | final Function onSubmitted; 7 | 8 | const SearchBar( 9 | {Key key, this.placeholder = "搜索美剧/日剧/电影", this.enabled = true, this.onSubmitted}) 10 | : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Container( 15 | height: 40.0, 16 | alignment: Alignment.center, 17 | decoration: BoxDecoration( 18 | color: Colors.white, borderRadius: BorderRadius.circular(5.0)), 19 | child: TextField( 20 | enabled: enabled, 21 | decoration: InputDecoration( 22 | prefixIcon: Icon(Icons.search), 23 | hintText: placeholder, 24 | border: InputBorder.none, 25 | ), 26 | onSubmitted: onSubmitted, 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/widgets/rr_rate.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class RRRate extends StatelessWidget { 4 | final String score; 5 | 6 | const RRRate({Key key, this.score}) : super(key: key); 7 | @override 8 | Widget build(BuildContext context) { 9 | return Container( 10 | padding: EdgeInsets.all(8.0), 11 | decoration: BoxDecoration( 12 | color: Colors.white, 13 | borderRadius: BorderRadius.only( 14 | bottomLeft: Radius.circular(5.0), topLeft: Radius.circular(5.0))), 15 | child: Column( 16 | mainAxisAlignment: MainAxisAlignment.center, 17 | children: [ 18 | Text( 19 | "人人评分", 20 | style: TextStyle(fontSize: 12.0), 21 | ), 22 | Text( 23 | score, 24 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18.0), 25 | ) 26 | ], 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /yyets.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /lib/common/utils.dart: -------------------------------------------------------------------------------- 1 | class Utils { 2 | 3 | static String readTimestamp(int timestamp) { 4 | String temp = ""; 5 | if (timestamp == null || timestamp == 0) { 6 | return temp; 7 | } 8 | 9 | try { 10 | int now = (new DateTime.now().millisecondsSinceEpoch ~/ 1000); 11 | int diff = now - timestamp; 12 | int months = (diff ~/ (60 * 60 * 24 * 30)); 13 | int days = (diff ~/ (60 * 60 * 24)); 14 | int hours = ((diff - days * (60 * 60 * 24)) ~/ (60 * 60)); 15 | int minutes = ((diff - days * (60 * 60 * 24) - hours * (60 * 60)) ~/ 60); 16 | if (months > 0) { 17 | temp = months.toString() + "月前"; 18 | } else if (days > 0) { 19 | temp = days.toString() + "天前"; 20 | } else if (hours > 0) { 21 | temp = hours.toString() + "小时前"; 22 | } else { 23 | temp = minutes.toString() + "分钟前"; 24 | } 25 | } catch (e) { 26 | e.toString(); 27 | } 28 | return temp; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/widgets/slider_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SliderIndicator extends StatelessWidget { 4 | final int count; 5 | final int index; 6 | 7 | const SliderIndicator({Key key, this.count, this.index = 0}) 8 | : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Row( 13 | mainAxisAlignment: MainAxisAlignment.center, 14 | children: dots, 15 | ); 16 | } 17 | 18 | List get dots { 19 | return List.generate(count, (i) { 20 | final double size = i == index ? 12.0 : 10.0; 21 | return Container( 22 | margin: EdgeInsets.symmetric(horizontal: 4.0), 23 | width: size, 24 | height: size, 25 | decoration: BoxDecoration( 26 | border: Border.all(color: Colors.white, width: 2.0), 27 | shape: BoxShape.circle, 28 | color: i == index ? Colors.white : null, 29 | ), 30 | ); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/redux/states/dynamic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:yyets/models/comment.dart'; 3 | import 'package:yyets/models/help.dart'; 4 | import 'package:yyets/models/video_comment.dart'; 5 | import 'package:yyets/models/video_info.dart'; 6 | 7 | @immutable 8 | class DynamicState { 9 | final List comments; 10 | final List helps; 11 | final bool isLoading; 12 | 13 | DynamicState({this.comments, this.helps, this.isLoading = true}); 14 | 15 | DynamicState copyWith( 16 | {List comments, 17 | List helps, 18 | VideoInfo videoInfo, 19 | List videoComments, 20 | bool isLoading}) { 21 | return DynamicState( 22 | comments: comments ?? this.comments, 23 | helps: helps ?? this.helps, 24 | isLoading: isLoading ?? this.isLoading); 25 | } 26 | 27 | DynamicState.initialState() 28 | : comments = [], 29 | helps = [], 30 | isLoading = true; 31 | } 32 | -------------------------------------------------------------------------------- /lib/redux/states/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:yyets/models/ad.dart'; 3 | import 'package:yyets/models/article.dart'; 4 | import 'package:yyets/models/tv_schedule.dart'; 5 | 6 | @immutable 7 | class HomeState { 8 | final List schedules; 9 | final List
articles; 10 | final List ads; 11 | final bool isLoading; 12 | 13 | HomeState({this.schedules, this.articles, this.ads, this.isLoading}); 14 | 15 | HomeState copyWith( 16 | {List schedules, 17 | List
articles, 18 | List ads, 19 | bool isLoading}) { 20 | return HomeState( 21 | schedules: schedules ?? this.schedules, 22 | articles: articles ?? this.articles, 23 | ads: ads ?? this.ads, 24 | isLoading: isLoading ?? this.isLoading); 25 | } 26 | 27 | HomeState.initialState() 28 | : schedules = [], 29 | articles = [], 30 | ads = [], 31 | isLoading = true; 32 | } 33 | -------------------------------------------------------------------------------- /lib/widgets/help_list_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/models/help.dart'; 3 | import 'package:yyets/widgets/help_list_tile.dart'; 4 | 5 | class HelpListView extends StatelessWidget { 6 | final List helps; 7 | 8 | const HelpListView({Key key, this.helps}) : super(key: key); 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | child: ListView.separated( 13 | shrinkWrap: true, 14 | physics: ClampingScrollPhysics(), 15 | separatorBuilder: (context, index) => Divider( 16 | height: 1.0, 17 | indent: 0.0, 18 | ), 19 | itemCount: helps.length, 20 | itemBuilder: (context, index) { 21 | return Padding( 22 | padding: const EdgeInsets.all(8.0), 23 | child: HelpListTile( 24 | help: helps[index], 25 | ), 26 | ); 27 | }, 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/widgets/tabbar_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TabBarItem extends StatelessWidget { 4 | final IconData icon; 5 | final String text; 6 | final bool selected; 7 | 8 | TabBarItem({this.text, this.icon, this.selected = false}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final Color selectedColor = Theme.of(context).primaryColor; 13 | final Color normalColor = Colors.grey[400]; 14 | 15 | return Container( 16 | height: 48.0, 17 | child: Column( 18 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 19 | children: [ 20 | Icon( 21 | icon, 22 | color: selected ? selectedColor : normalColor, 23 | ), 24 | Text( 25 | text, 26 | style: TextStyle( 27 | fontSize: 11.0, 28 | color: selected ? selectedColor : normalColor, 29 | ), 30 | ) 31 | ], 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/widgets/video_grid_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/models/resource.dart'; 3 | import 'package:yyets/widgets/video_grid_item.dart'; 4 | 5 | class VideoGridView extends StatelessWidget { 6 | final List resources; 7 | 8 | const VideoGridView({Key key, this.resources}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | 13 | return Container( 14 | padding: EdgeInsets.all(8.0), 15 | child: GridView.builder( 16 | physics: ClampingScrollPhysics(), 17 | shrinkWrap: true, 18 | itemCount: resources.length, 19 | itemBuilder: (context, index) { 20 | return VideoGridItem(resource: resources[index],); 21 | }, 22 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 23 | crossAxisCount: 3, 24 | childAspectRatio: 9 / 18, 25 | mainAxisSpacing: 8.0, 26 | crossAxisSpacing: 8.0), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YYeTS 2 | 3 | Flutter 人人影视 4 | 5 | > 项目在 Flutter dev channel 编译运行 6 | 7 | $ `flutter channel dev` 8 | 9 | ## Preview 10 | 11 | | ![Demo](https://ae01.alicdn.com/kf/HTB1IPYsboGF3KVjSZFm762qPXXaX.png) | ![Demo](https://ae01.alicdn.com/kf/HTB16cnvbmWD3KVjSZSg763CxVXaF.png) | ![Demo](https://ae01.alicdn.com/kf/HTB1X2_gX7xz61VjSZFt761DSVXaM.png) | 12 | | :-: | :-: | :-: | 13 | | ![Demo](https://ae01.alicdn.com/kf/HTB1atrgX7xz61VjSZFr760eLFXaa.png) | ![Demo](https://ae01.alicdn.com/kf/HTB1iKvtbk9E3KVjSZFG76319XXae.png) | ![Demo](https://ae01.alicdn.com/kf/HTB1vKDtbk9E3KVjSZFG76319XXam.png) | 14 | | ![Demo](https://ae01.alicdn.com/kf/HTB1mJ_sbfWG3KVjSZFg762TspXar.png) | | | 15 | 16 | 17 | ## 参考资料 18 | [官方文档](https://flutter.io/) 19 | 20 | [Flutter中文网](https://flutterchina.club/) 21 | 22 | [Flutter 跨平台移动应用开发实战](https://flutter-app-in-action.netlify.com/#/get-start) 23 | 24 | [![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](https://dartnode.com "Powered by DartNode - Free VPS for Open Source") 25 | 26 | -------------------------------------------------------------------------------- /lib/widgets/empty_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class EmptyView extends StatelessWidget { 4 | final String title; 5 | 6 | const EmptyView({Key key, this.title = "No data."}) : super(key: key); 7 | @override 8 | Widget build(BuildContext context) { 9 | return ConstrainedBox( 10 | constraints: BoxConstraints(minHeight: 240.0), 11 | child: Center( 12 | child: Column( 13 | mainAxisSize: MainAxisSize.max, 14 | mainAxisAlignment: MainAxisAlignment.center, 15 | children: [ 16 | Icon( 17 | Icons.local_movies, 18 | color: Colors.grey[300], 19 | size: 64.0, 20 | ), 21 | SizedBox( 22 | height: 16.0, 23 | ), 24 | Text( 25 | title, 26 | textAlign: TextAlign.center, 27 | style: TextStyle(color: Colors.grey[400], fontSize: 16.0), 28 | ), 29 | ], 30 | ), 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/redux/view_models/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:yyets/redux/states/dynamic.dart'; 3 | import 'package:yyets/redux/states/home.dart'; 4 | import 'package:yyets/redux/states/lib.dart'; 5 | import 'package:yyets/redux/states/main.dart'; 6 | import 'package:yyets/redux/reducers/main.dart'; 7 | import 'package:yyets/redux/states/profile.dart'; 8 | import 'package:yyets/redux/states/rank.dart'; 9 | import 'package:yyets/redux/states/search.dart'; 10 | 11 | abstract class ViewModel { 12 | final Store store; 13 | ViewModel(this.store); 14 | } 15 | 16 | class StoreContainer { 17 | static final Store global = reduxStore(); 18 | } 19 | 20 | Store reduxStore() => Store(reduxReducer, 21 | initialState: ReduxState( 22 | home: HomeState.initialState(), 23 | rank: RankState.initialState(), 24 | lib: LibState.initialState(), 25 | search: SearchState.initialState(), 26 | dynamics: DynamicState.initialState(), 27 | profile: ProfileState.initialState())); 28 | -------------------------------------------------------------------------------- /lib/redux/states/lib.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:yyets/models/lib_resource.dart'; 3 | import 'package:yyets/models/resource_category.dart'; 4 | 5 | @immutable 6 | class LibState { 7 | final List resources; 8 | final List filtedResources; 9 | final ResourceCategory category; 10 | final bool isLoading; 11 | 12 | LibState( 13 | {this.resources, this.filtedResources, this.category, this.isLoading}); 14 | 15 | LibState copyWith( 16 | {List resources, 17 | List filtedResources, 18 | ResourceCategory category, 19 | bool isLoading}) { 20 | return LibState( 21 | resources: resources ?? this.resources, 22 | filtedResources: filtedResources ?? this.filtedResources, 23 | category: category ?? this.category, 24 | isLoading: isLoading ?? this.isLoading); 25 | } 26 | 27 | LibState.initialState() 28 | : resources = [], 29 | filtedResources = [], 30 | category = ResourceCategory.initialState(), 31 | isLoading = true; 32 | } 33 | -------------------------------------------------------------------------------- /lib/widgets/grid_menu_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/models/main.dart'; 3 | 4 | class GridMenuItem extends StatelessWidget { 5 | final Menu menu; 6 | final Function callback; 7 | const GridMenuItem({Key key, this.menu, this.callback}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | child: GestureDetector( 13 | onTap: () { 14 | callback(menu); 15 | }, 16 | child: Column( 17 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 18 | children: [ 19 | Expanded( 20 | flex: 2, 21 | child: Image.asset("assets/images/${menu.icon}.png",width: 30.0, height: 30.0,), 22 | ), 23 | Expanded( 24 | flex: 1, 25 | child: Text( 26 | menu.title, 27 | textAlign: TextAlign.center, 28 | style: Theme.of(context).textTheme.body2, 29 | ), 30 | ) 31 | ], 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/widgets/sliver_tabview_wrap.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SliverTabViewWrap extends StatelessWidget { 4 | final List children; 5 | 6 | const SliverTabViewWrap({Key key, this.children}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return SafeArea( 11 | top: false, 12 | bottom: false, 13 | child: Builder( 14 | builder: (BuildContext context) { 15 | return CustomScrollView( 16 | slivers: [ 17 | SliverOverlapInjector( 18 | handle: 19 | NestedScrollView.sliverOverlapAbsorberHandleFor(context), 20 | ), 21 | SliverPadding( 22 | padding: const EdgeInsets.symmetric( 23 | vertical: 0.0, 24 | horizontal: 8.0, 25 | ), 26 | sliver: SliverList( 27 | delegate: SliverChildListDelegate(children), 28 | ), 29 | ), 30 | ], 31 | ); 32 | }, 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/widgets/tag.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Tag extends StatelessWidget { 4 | final String text; 5 | final Color backgroudColor; 6 | final Color color; 7 | final bool border; 8 | 9 | const Tag( 10 | {Key key, 11 | this.text, 12 | this.color = Colors.black, 13 | this.backgroudColor, 14 | this.border = false}) 15 | : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return ConstrainedBox( 20 | constraints: BoxConstraints(maxWidth: 120.0), 21 | child: Container( 22 | padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), 23 | decoration: BoxDecoration( 24 | color: backgroudColor, 25 | borderRadius: BorderRadius.circular(20.0), 26 | border: border ? Border.all(color: color) : null), 27 | child: Text( 28 | text, 29 | maxLines: 1, 30 | overflow: TextOverflow.ellipsis, 31 | textAlign: TextAlign.center, 32 | style: TextStyle(fontSize: 10.0, color: color), 33 | ), 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'package:yyets/main.dart'; 11 | 12 | void main() { 13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(new MyApp()); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /lib/widgets/comment_listtile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/models/enums.dart'; 3 | import 'package:yyets/widgets/user_info_tile.dart'; 4 | 5 | class CommentListTile extends StatelessWidget { 6 | 7 | final String avatar; 8 | final String nickName; 9 | final String dateline; 10 | final String groupName; 11 | final String content; 12 | 13 | const CommentListTile({Key key, this.avatar, this.nickName, this.dateline, this.groupName, this.content}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | child: Column( 19 | crossAxisAlignment: CrossAxisAlignment.start, 20 | children: [ 21 | SizedBox(height: 5.0,), 22 | UserInfoTile( 23 | level: UserLevel.superVip, 24 | avatar: avatar, 25 | nickName: nickName, 26 | time: dateline, 27 | groupName: groupName, 28 | ), 29 | Padding( 30 | padding: const EdgeInsets.symmetric(vertical: 8.0), 31 | child: Text(content), 32 | ) 33 | ], 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/pages/photo_browser.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:photo_view/photo_view.dart'; 3 | import 'package:photo_view/photo_view_gallery.dart'; 4 | 5 | class PhotoBrowser extends StatelessWidget { 6 | final List imageUrls; 7 | final int index; 8 | 9 | const PhotoBrowser({Key key, this.imageUrls, this.index = 0}) 10 | : super(key: key); 11 | @override 12 | Widget build(BuildContext context) { 13 | PageController controller = PageController(initialPage: index); 14 | return Container( 15 | color: Colors.black12, 16 | child: Dismissible( 17 | direction: DismissDirection.down, 18 | onDismissed: (_) => Navigator.of(context).pop(), 19 | child: PhotoViewGallery( 20 | // backgroundColor: Colors.black26, 21 | pageController: controller, 22 | pageOptions: imageUrls 23 | .map((url) => PhotoViewGalleryPageOptions( 24 | imageProvider: NetworkImage(url), 25 | initialScale: PhotoViewComputedScale.contained, 26 | )) 27 | .toList(), 28 | ), 29 | key: Key("gallery"), 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/widgets/rate_stars.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class RateStars extends StatelessWidget { 4 | final double rate; 5 | 6 | const RateStars({Key key, this.rate}) : super(key: key); 7 | @override 8 | Widget build(BuildContext context) { 9 | return Container( 10 | padding: EdgeInsets.symmetric(horizontal: 8.0), 11 | child: Row( 12 | children: _buildStart(), 13 | ), 14 | ); 15 | } 16 | 17 | List _buildStart() { 18 | final fullstars = (rate / 2).floor(); 19 | final left = rate % 2; 20 | 21 | List starts = []; 22 | for (var i = 0; i < fullstars; i++) { 23 | starts.add(Icon( 24 | Icons.star, 25 | color: Colors.orangeAccent, 26 | )); 27 | } 28 | if (left > 1.0) { 29 | starts.add(Icon( 30 | Icons.star_half, 31 | color: Colors.orangeAccent, 32 | )); 33 | } 34 | final append = 5 - starts.length; 35 | if (append > 0) { 36 | for (var i = 0; i < append; i++) { 37 | starts.add(Icon( 38 | Icons.star_border, 39 | color: Colors.orangeAccent, 40 | )); 41 | } 42 | } 43 | return starts; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/widgets/comment_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CommentBar extends StatelessWidget { 4 | final String replayName; 5 | 6 | const CommentBar({Key key, this.replayName}) : super(key: key); 7 | @override 8 | Widget build(BuildContext context) { 9 | return Container( 10 | color: Colors.white, 11 | padding: EdgeInsets.symmetric(vertical: 2.0), 12 | child: Row( 13 | children: [ 14 | Expanded( 15 | child: TextField( 16 | style: TextStyle(height: 0.8, color: Colors.black), 17 | decoration: InputDecoration( 18 | hintText: "回复: $replayName", 19 | border: InputBorder.none, 20 | filled: true, 21 | fillColor: Colors.grey[200], 22 | ), 23 | ), 24 | ), 25 | SizedBox( 26 | width: 8.0, 27 | ), 28 | RaisedButton( 29 | color: Colors.blue, 30 | child: Text( 31 | "回复", 32 | style: TextStyle(color: Colors.white), 33 | ), 34 | onPressed: () {}, 35 | ) 36 | ], 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/widgets/comment_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/models/video_comment.dart'; 3 | import 'package:yyets/widgets/comment_listtile.dart'; 4 | 5 | class CommentList extends StatelessWidget { 6 | final List comments; 7 | 8 | const CommentList({Key key, this.comments}) : super(key: key); 9 | @override 10 | Widget build(BuildContext context) { 11 | return ListView.separated( 12 | physics: ClampingScrollPhysics(), 13 | shrinkWrap: true, 14 | separatorBuilder: (context, index) => Divider( 15 | height: 1.0, 16 | indent: 0.0, 17 | ), 18 | itemCount: comments.length, 19 | itemBuilder: (context, index) { 20 | final item = comments[index]; 21 | return Padding( 22 | padding: const EdgeInsets.all(8.0), 23 | child: GestureDetector( 24 | child: CommentListTile( 25 | avatar: item.userpic, 26 | nickName: item.nickname, 27 | dateline: item.dateline, 28 | groupName: item.groupName, 29 | content: item.content, 30 | ), 31 | onTap: () { 32 | 33 | }, 34 | ), 35 | ); 36 | }, 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/redux/view_models/lib.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:yyets/api/api.dart'; 3 | import 'package:yyets/models/lib_resource.dart'; 4 | import 'package:yyets/redux/states/main.dart'; 5 | import 'package:yyets/redux/view_models/main.dart'; 6 | 7 | class LibViewModel extends ViewModel { 8 | LibViewModel(Store store) : super(store); 9 | 10 | List get resources => this.store.state.lib.resources; 11 | List get filtedResources => this.store.state.lib.filtedResources; 12 | List get years => ["ALL"]..addAll(this.store.state.lib.category.year); 13 | List get categories => 14 | ["ALL"]..addAll(store.state.lib.category.category); 15 | List get tvs => ["ALL"]..addAll(this.store.state.lib.category.tv); 16 | bool get isLoading => this.store.state.lib.isLoading; 17 | 18 | String selectedCategory = ""; 19 | String selectedYear = ""; 20 | String selectedTv = ""; 21 | String sort = "itemupdate"; 22 | String area = ""; 23 | 24 | void refresResources() { 25 | final category = selectedCategory == "ALL" ? "" : selectedCategory; 26 | final year = selectedYear == "ALL" ? "" : selectedYear; 27 | final tv = selectedTv == "ALL" ? "" : selectedTv; 28 | Networking.fetchLibResources( 29 | category: category, year: year, tv: tv, sort: sort, area: area); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:redux/redux.dart'; 3 | import 'package:yyets/pages/tab_page.dart'; 4 | import 'package:yyets/redux/states/main.dart'; 5 | import 'package:yyets/redux/view_models/main.dart'; 6 | import 'package:flutter_redux/flutter_redux.dart'; 7 | 8 | void main() { 9 | final Store store = StoreContainer.global; 10 | runApp(MyApp(store: store)); 11 | } 12 | 13 | class MyApp extends StatelessWidget { 14 | final Store store; 15 | MyApp({this.store}); 16 | 17 | // This widget is the root of your application. 18 | @override 19 | Widget build(BuildContext context) { 20 | return StoreProvider( 21 | store: store, 22 | child: MaterialApp( 23 | debugShowCheckedModeBanner: false, 24 | theme: ThemeData( 25 | primaryColor: Colors.black, 26 | textTheme: TextTheme( 27 | body1: TextStyle(fontSize: 15.0, color: Colors.black87), 28 | body2: TextStyle(fontSize: 13.0, color: Colors.grey), 29 | caption: TextStyle(fontSize: 10.0, color: Colors.grey), 30 | display1: TextStyle( 31 | fontSize: 14.0, 32 | color: Colors.black87, 33 | fontWeight: FontWeight.bold))), 34 | home: TabPage(), 35 | )); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/models/profile.dart: -------------------------------------------------------------------------------- 1 | class Profile { 2 | String upgrade; 3 | String uid; 4 | String sex; 5 | String phoneNumber; 6 | int didChanged; 7 | String phoneArea; 8 | String userpic; 9 | String groupName; 10 | int isInternalGroup; 11 | String token; 12 | String point; 13 | String nickname; 14 | String email; 15 | 16 | Profile( 17 | {this.upgrade, 18 | this.uid, 19 | this.sex, 20 | this.phoneNumber, 21 | this.didChanged, 22 | this.phoneArea, 23 | this.userpic, 24 | this.groupName, 25 | this.isInternalGroup, 26 | this.token, 27 | this.point, 28 | this.nickname, 29 | this.email}); 30 | 31 | Profile.initialState() { 32 | nickname = "Login"; 33 | userpic = 34 | "http://files.zmzjstu.com/ftp/avatar/2018/0721/9810ce1e3d9b7bc442495c334b35427a_b.jpg"; 35 | groupName = "初级会员"; 36 | } 37 | 38 | Profile.fromJson(Map json) { 39 | upgrade = json['upgrade']; 40 | uid = json['uid']; 41 | sex = json['sex']; 42 | phoneNumber = json['phone_number']; 43 | didChanged = json['did_changed']; 44 | phoneArea = json['phone_area']; 45 | userpic = json['userpic']; 46 | groupName = json['group_name']; 47 | isInternalGroup = json['is_internal_group']; 48 | token = json['token']; 49 | point = json['point']; 50 | nickname = json['nickname']; 51 | email = json['email']; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/models/lib_resource.dart: -------------------------------------------------------------------------------- 1 | class LibResource { 2 | String id; 3 | String cnname; 4 | String enname; 5 | String channel; 6 | String area; 7 | String category; 8 | String tvstation; 9 | String playStatus; 10 | String rank; 11 | String views; 12 | String score; 13 | String publishYear; 14 | String itemupdate; 15 | String poster; 16 | String posterB; 17 | String posterM; 18 | String posterS; 19 | 20 | LibResource( 21 | {this.id, 22 | this.cnname, 23 | this.enname, 24 | this.channel, 25 | this.area, 26 | this.category, 27 | this.tvstation, 28 | this.playStatus, 29 | this.rank, 30 | this.views, 31 | this.score, 32 | this.publishYear, 33 | this.itemupdate, 34 | this.poster, 35 | this.posterB, 36 | this.posterM, 37 | this.posterS}); 38 | 39 | LibResource.fromJson(Map json) { 40 | id = json['id']; 41 | cnname = json['cnname']; 42 | enname = json['enname']; 43 | channel = json['channel']; 44 | area = json['area']; 45 | category = json['category']; 46 | tvstation = json['tvstation']; 47 | playStatus = json['play_status']; 48 | rank = json['rank']; 49 | views = json['views']; 50 | score = json['score']; 51 | publishYear = json['publish_year']; 52 | itemupdate = json['itemupdate']; 53 | poster = json['poster']; 54 | posterB = json['poster_b']; 55 | posterM = json['poster_m']; 56 | posterS = json['poster_s']; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/widgets/main.dart: -------------------------------------------------------------------------------- 1 | export 'package:yyets/widgets/tabbar_item.dart'; 2 | export 'package:yyets/widgets/search_bar.dart'; 3 | export 'package:yyets/widgets/slider_indicator.dart'; 4 | export 'package:yyets/widgets/home_banner.dart'; 5 | export 'package:yyets/widgets/section_title.dart'; 6 | export 'package:yyets/widgets/todays_boardcast.dart'; 7 | export 'package:yyets/widgets/article_view.dart'; 8 | export 'package:yyets/widgets/section_divider.dart'; 9 | export 'package:yyets/widgets/tag.dart'; 10 | export 'package:yyets/widgets/video_grid_view.dart'; 11 | export 'package:yyets/widgets/grid_menu_view.dart'; 12 | export 'package:yyets/widgets/video_listtile.dart'; 13 | export 'package:yyets/widgets/user_info_tile.dart'; 14 | export 'package:yyets/widgets/gradient_tag.dart'; 15 | export 'package:yyets/widgets/series_list_view.dart'; 16 | export 'package:yyets/widgets/help_list_view.dart'; 17 | export 'package:yyets/widgets/rate_view.dart'; 18 | export 'package:yyets/widgets/series_list_view.dart'; 19 | export 'package:yyets/widgets/sliver_tabview_wrap.dart'; 20 | export 'package:yyets/widgets/video_grid_view.dart'; 21 | export 'package:yyets/widgets/hot_comment_list.dart'; 22 | export 'package:yyets/widgets/video_cover.dart'; 23 | export 'package:yyets/widgets/comment_list.dart'; 24 | export 'package:yyets/widgets/empty_view.dart'; 25 | export 'package:yyets/widgets/sort_bar.dart'; 26 | export 'package:yyets/widgets/video_player_view.dart'; 27 | export 'package:yyets/widgets/app_drawer.dart'; 28 | export 'package:yyets/widgets/action_bar.dart'; 29 | -------------------------------------------------------------------------------- /lib/models/help.dart: -------------------------------------------------------------------------------- 1 | class Help { 2 | String detail; 3 | String uid; 4 | String typeCn; 5 | String resourceCnname; 6 | String userpic; 7 | String type; 8 | String title; 9 | String resourceid; 10 | String dateline; 11 | String lastOpenTime; 12 | String statusCn; 13 | String groupName; 14 | String hid; 15 | String nickname; 16 | String commentsCount; 17 | String status; 18 | String views; 19 | 20 | Help( 21 | {this.detail, 22 | this.uid, 23 | this.typeCn, 24 | this.resourceCnname, 25 | this.userpic, 26 | this.type, 27 | this.title, 28 | this.resourceid, 29 | this.dateline, 30 | this.lastOpenTime, 31 | this.statusCn, 32 | this.groupName, 33 | this.hid, 34 | this.nickname, 35 | this.commentsCount, 36 | this.status, 37 | this.views}); 38 | 39 | Help.fromJson(Map json) { 40 | detail = json['detail']; 41 | uid = json['uid']; 42 | typeCn = json['type_cn']; 43 | resourceCnname = json['resource_cnname']; 44 | userpic = json['userpic']; 45 | type = json['type']; 46 | title = json['title']; 47 | resourceid = json['resourceid']; 48 | dateline = json['dateline']; 49 | lastOpenTime = json['last_open_time']; 50 | statusCn = json['status_cn']; 51 | groupName = json['group_name']; 52 | hid = json['hid']; 53 | nickname = json['nickname']; 54 | commentsCount = json['comments_count']; 55 | status = json['status']; 56 | views = json['views']; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /yyets_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /lib/pages/payer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/widgets/video_player_view.dart'; 3 | 4 | class PlayerPage extends StatefulWidget { 5 | final String title; 6 | final String cover; 7 | final String videoUrl; 8 | 9 | const PlayerPage( 10 | {Key key, 11 | this.title, 12 | this.cover, 13 | this.videoUrl = 14 | "http://f.us.sinaimg.cn/003whHFKlx07msm6jWwM01040200EQjd0k010.mp4?label=mp4_720p&template=1280x720.28&Expires=1533011716&ssig=LIpeUQDoHe&KID=unistore,video"}) 15 | : super(key: key); 16 | 17 | @override 18 | _PlayerPageState createState() => _PlayerPageState(); 19 | } 20 | 21 | class _PlayerPageState extends State { 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | appBar: AppBar( 26 | title: Text(widget.title), 27 | ), 28 | body: Container( 29 | child: OrientationBuilder( 30 | builder: (context, orientation) { 31 | final height = orientation == Orientation.portrait 32 | ? MediaQuery.of(context).size.height * 0.35 33 | : double.infinity; 34 | return Container( 35 | padding: EdgeInsets.only(top: 8.0), 36 | width: double.infinity, 37 | height: height, 38 | color: Colors.black, 39 | child: VideoPlayerView( 40 | placeholder: widget.cover, 41 | url: widget.videoUrl, 42 | ), 43 | ); 44 | }, 45 | ), 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/widgets/help_list_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/models/enums.dart'; 3 | import 'package:yyets/models/help.dart'; 4 | import 'package:yyets/widgets/main.dart'; 5 | 6 | class HelpListTile extends StatelessWidget { 7 | final Help help; 8 | 9 | const HelpListTile({Key key, this.help}) : super(key: key); 10 | @override 11 | Widget build(BuildContext context) { 12 | List trailing; 13 | if (help.resourceCnname != null) { 14 | trailing = [ 15 | Tag( 16 | text: help.resourceCnname, 17 | color: Colors.blue, 18 | border: true, 19 | ) 20 | ]; 21 | } 22 | 23 | return Container( 24 | child: Column( 25 | crossAxisAlignment: CrossAxisAlignment.start, 26 | children: [ 27 | UserInfoTile( 28 | level: UserLevel.superVip, 29 | nickName: help.nickname, 30 | avatar: help.userpic, 31 | groupName: help.groupName, 32 | time: help.dateline, 33 | trailing: trailing, 34 | ), 35 | SizedBox( 36 | height: 8.0, 37 | ), 38 | Text( 39 | help.title, 40 | style: TextStyle(fontWeight: FontWeight.bold), 41 | ), 42 | Padding( 43 | padding: const EdgeInsets.symmetric(vertical: 8.0), 44 | child: Text( 45 | help.detail, 46 | style: TextStyle(height: 1.2, color: Colors.black54), 47 | ), 48 | ), 49 | ], 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/models/tv_schedule.dart: -------------------------------------------------------------------------------- 1 | class TVSchedule { 2 | String id; 3 | String enname; 4 | String episode; 5 | String season; 6 | String cnname; 7 | String statusCn; 8 | String poster; 9 | String posterM; 10 | String posterB; 11 | String posterS; 12 | String playTime; 13 | String status; 14 | 15 | TVSchedule( 16 | {this.id, 17 | this.enname, 18 | this.episode, 19 | this.season, 20 | this.cnname, 21 | this.statusCn, 22 | this.poster, 23 | this.posterM, 24 | this.posterB, 25 | this.posterS, 26 | this.playTime, 27 | this.status}); 28 | 29 | TVSchedule.fromJson(Map json) { 30 | id = json['id']; 31 | enname = json['enname']; 32 | episode = json['episode']; 33 | season = json['season']; 34 | cnname = json['cnname']; 35 | statusCn = json['status_cn']; 36 | poster = json['poster']; 37 | posterM = json['poster_m']; 38 | posterB = json['poster_b']; 39 | posterS = json['poster_s']; 40 | playTime = json['play_time']; 41 | status = json['status']; 42 | } 43 | 44 | Map toJson() { 45 | final Map data = new Map(); 46 | data['id'] = this.id; 47 | data['enname'] = this.enname; 48 | data['episode'] = this.episode; 49 | data['season'] = this.season; 50 | data['cnname'] = this.cnname; 51 | data['status_cn'] = this.statusCn; 52 | data['poster'] = this.poster; 53 | data['poster_m'] = this.posterM; 54 | data['poster_b'] = this.posterB; 55 | data['poster_s'] = this.posterS; 56 | data['play_time'] = this.playTime; 57 | data['status'] = this.status; 58 | return data; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/widgets/sort_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/widgets/section_title.dart'; 3 | 4 | class Sort { 5 | final String key; 6 | final String text; 7 | 8 | Sort({this.key, this.text}); 9 | 10 | static List defaults = [ 11 | Sort(key: "itemupdate", text: "按更新时间"), 12 | Sort(key: "views", text: "按发布日期"), 13 | Sort(key: "rank", text: "按排名"), 14 | Sort(key: "score", text: "按评分"), 15 | Sort(key: "dateline", text: "按点击率"), 16 | ]; 17 | } 18 | 19 | class SortBar extends StatefulWidget { 20 | final Function onUpdate; 21 | 22 | const SortBar({Key key, this.onUpdate}) : super(key: key); 23 | @override 24 | SortBarState createState() { 25 | return new SortBarState(); 26 | } 27 | } 28 | 29 | class SortBarState extends State { 30 | Sort sort = Sort.defaults.first; 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Container( 35 | child: PopupMenuButton( 36 | child: 37 | Row(mainAxisAlignment: MainAxisAlignment.start, children: [ 38 | SectionTitle( 39 | title: sort.text, 40 | ), 41 | Icon(Icons.sort_by_alpha) 42 | ]), 43 | onSelected: (Sort v) { 44 | setState(() { 45 | sort = v; 46 | }); 47 | widget.onUpdate(v); 48 | }, 49 | itemBuilder: (BuildContext context) { 50 | return Sort.defaults 51 | .map((v) => PopupMenuItem( 52 | key: Key(v.key), 53 | value: v, 54 | child: Text(v.text), 55 | )) 56 | .toList(); 57 | }, 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/redux/reducers/video.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:yyets/models/help.dart'; 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:yyets/models/video_comment.dart'; 5 | import 'package:yyets/models/video_info.dart'; 6 | import 'package:yyets/redux/actions/dynamic.dart'; 7 | 8 | VideoState videoReducer(VideoState state, action) { 9 | if (action is UpdateVideoInfo) { 10 | return state.copyWith(videoInfo: action.payload); 11 | } 12 | if (action is UpdateVideoComments) { 13 | return state.copyWith(videoComments: action.payload); 14 | } 15 | return state; 16 | } 17 | 18 | @immutable 19 | class VideoState { 20 | final List videoComments; 21 | final VideoInfo videoInfo; 22 | 23 | VideoState({ 24 | this.videoInfo, 25 | this.videoComments, 26 | }); 27 | 28 | VideoState copyWith( 29 | {VideoInfo videoInfo, 30 | List helps, 31 | List videoComments}) { 32 | return VideoState( 33 | videoInfo: videoInfo ?? this.videoInfo, 34 | videoComments: videoComments ?? this.videoComments); 35 | } 36 | 37 | VideoState.initialState() 38 | : videoInfo = VideoInfo.initialState(), 39 | videoComments = []; 40 | } 41 | 42 | class VideoViewModel { 43 | final Store store; 44 | 45 | VideoViewModel(this.store); 46 | List get helps => this.store.state.videoInfo.helpList; 47 | List get videoComments => this.store.state.videoComments; 48 | 49 | VideoInfo get videoInfo => this.store.state.videoInfo; 50 | List get seasons => this.store.state.videoInfo.seasonList; 51 | String get title => videoInfo.resourceInfo?.cnname; 52 | String get cover => videoInfo.resourceInfo?.poster; 53 | } 54 | -------------------------------------------------------------------------------- /lib/widgets/hot_comment_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:yyets/pages/reply.dart'; 4 | import 'package:yyets/redux/states/main.dart'; 5 | import 'package:yyets/redux/view_models/dynamic.dart'; 6 | import 'package:yyets/widgets/hot_comment_listtile.dart'; 7 | 8 | class HotCommentList extends StatelessWidget { 9 | @override 10 | Widget build(BuildContext context) { 11 | return StoreConnector( 12 | converter: (store) => DynamicViewModel(store), 13 | builder: (context, vm) { 14 | return vm.isLoading 15 | ? Center(child: CircularProgressIndicator()) 16 | : ListView.separated( 17 | physics: ClampingScrollPhysics(), 18 | shrinkWrap: true, 19 | separatorBuilder: (context, index) => Divider( 20 | height: 1.0, 21 | indent: 0.0, 22 | ), 23 | itemCount: vm.comments.length, 24 | itemBuilder: (context, index) { 25 | return Padding( 26 | padding: const EdgeInsets.all(8.0), 27 | child: GestureDetector( 28 | child: HotCommentListTile(comment: vm.comments[index]), 29 | onTap: () { 30 | Navigator.of(context).push(MaterialPageRoute( 31 | builder: (context) => ReplyPage( 32 | comment: vm.comments[index], 33 | ))); 34 | }, 35 | ), 36 | ); 37 | }, 38 | ); 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | 人人影视 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | yyets 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIMainStoryboardFile 35 | Main 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UIViewControllerBasedStatusBarAppearance 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /lib/pages/dynamic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:yyets/api/api.dart'; 4 | import 'package:yyets/redux/states/main.dart'; 5 | import 'package:yyets/redux/view_models/dynamic.dart'; 6 | import 'package:yyets/widgets/main.dart'; 7 | 8 | class DynamicPage extends StatefulWidget { 9 | @override 10 | _DynamicPageState createState() => _DynamicPageState(); 11 | } 12 | 13 | class _DynamicPageState extends State 14 | with SingleTickerProviderStateMixin { 15 | TabController controller; 16 | @override 17 | void initState() { 18 | controller = TabController(length: 2, vsync: this); 19 | super.initState(); 20 | Networking.fetchHotComments(); 21 | Networking.fetchHotHelps(); 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return Scaffold( 27 | appBar: AppBar( 28 | elevation: 0.0, 29 | title: Text("动态"), 30 | bottom: TabBar( 31 | indicatorSize: TabBarIndicatorSize.label, 32 | indicatorColor: Colors.white, 33 | controller: controller, 34 | tabs: [ 35 | Tab( 36 | text: "热门评论", 37 | ), 38 | Tab( 39 | text: "会员求助", 40 | ), 41 | ], 42 | ), 43 | ), 44 | body: Container( 45 | child: TabBarView( 46 | controller: controller, 47 | children: [ 48 | HotCommentList(), 49 | StoreConnector( 50 | converter: (store) => DynamicViewModel(store), 51 | builder: (context, vm) { 52 | return HelpListView( 53 | helps: vm.helps, 54 | ); 55 | }) 56 | ], 57 | ), 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/widgets/grid_menu_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/models/main.dart'; 3 | import 'package:yyets/widgets/grid_menu_item.dart'; 4 | 5 | class GridMenuView extends StatelessWidget { 6 | final Function callback; 7 | 8 | GridMenuView({Key key, this.callback}) : super(key: key); 9 | 10 | final List menus = [ 11 | Menu(title: "电影", icon: "电影", channel: "movie"), 12 | Menu(title: "美剧", icon: "美剧", channel: "tv", area: "美国"), 13 | Menu(title: "英剧", icon: "英剧", channel: "tv", area: "英国"), 14 | Menu(title: "日剧", icon: "日剧", channel: "tv", area: "日本"), 15 | Menu(title: "韩剧", icon: "韩剧", channel: "tv", area: "韩国"), 16 | Menu(title: "其他语种", icon: "其他语种", channel: "tv"), 17 | Menu(title: "公开课", icon: "公开课", channel: "openclass"), 18 | Menu(title: "纪录片", icon: "纪录片", channel: "documentary"), 19 | ]; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Container( 24 | child: Column( 25 | children: [ 26 | GridView.builder( 27 | itemCount: menus.length, 28 | shrinkWrap: true, 29 | physics: ClampingScrollPhysics(), 30 | itemBuilder: (context, index) { 31 | final border = BorderSide(color: Colors.grey[200]); 32 | return Container( 33 | decoration: BoxDecoration( 34 | border: BorderDirectional( 35 | top: border, 36 | start: border, 37 | end: (index % 4) == 3 ? border : BorderSide.none)), 38 | child: GridMenuItem( 39 | menu: menus[index], 40 | callback: callback, 41 | )); 42 | }, 43 | gridDelegate: 44 | SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4), 45 | ), 46 | Container( 47 | height: 1.0, 48 | color: Colors.grey[200], 49 | ) 50 | ], 51 | )); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/widgets/user_info_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/models/enums.dart'; 3 | import 'package:yyets/widgets/gradient_tag.dart'; 4 | 5 | class UserInfoTile extends StatelessWidget { 6 | final UserLevel level; 7 | final List trailing; 8 | final String avatar; 9 | final String nickName; 10 | final String groupName; 11 | final String time; 12 | 13 | const UserInfoTile( 14 | {Key key, 15 | this.level, 16 | this.avatar, 17 | this.time = "未知", 18 | this.nickName = "匿名", 19 | this.trailing, 20 | this.groupName = "至尊会员"}) 21 | : super(key: key); 22 | @override 23 | Widget build(BuildContext context) { 24 | final isVip = level == UserLevel.superVip; 25 | final List colors = isVip 26 | ? [Colors.orange, Colors.red] 27 | : [Colors.blue, Colors.lightBlue.withAlpha(100)]; 28 | List children = [ 29 | CircleAvatar( 30 | backgroundImage: NetworkImage(avatar), 31 | ), 32 | SizedBox( 33 | width: 8.0, 34 | ), 35 | Column( 36 | crossAxisAlignment: CrossAxisAlignment.start, 37 | children: [ 38 | Text(nickName), 39 | Text( 40 | time, 41 | style: Theme.of(context).textTheme.body2, 42 | ) 43 | ], 44 | ), 45 | SizedBox( 46 | width: 8.0, 47 | ), 48 | GradientTag( 49 | title: groupName, 50 | color: Colors.white, 51 | gradient: LinearGradient( 52 | begin: Alignment.centerLeft, 53 | end: Alignment.centerRight, 54 | colors: colors)) 55 | ]; 56 | 57 | if (trailing != null) { 58 | children.add(Expanded( 59 | child: Row( 60 | mainAxisAlignment: MainAxisAlignment.end, 61 | children: trailing, 62 | ), 63 | )); 64 | } 65 | 66 | return Container( 67 | child: Row( 68 | children: children, 69 | ), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/pages/tab_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/pages/main.dart'; 3 | import 'package:yyets/widgets/main.dart'; 4 | 5 | class TabPage extends StatefulWidget { 6 | @override 7 | TabPageState createState() { 8 | return new TabPageState(); 9 | } 10 | } 11 | 12 | class TabPageState extends State with TickerProviderStateMixin { 13 | TabController controller; 14 | int selectedIndex = 0; 15 | @override 16 | void initState() { 17 | controller = TabController(vsync: this, length: 4); 18 | controller.addListener(() { 19 | setState(() { 20 | selectedIndex = controller.index; 21 | }); 22 | }); 23 | super.initState(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | body: TabBarView( 30 | controller: controller, 31 | physics: NeverScrollableScrollPhysics(), 32 | children: [ 33 | HomePage(), 34 | RankPage(), 35 | LibPage(), 36 | DynamicPage(), 37 | ], 38 | ), 39 | bottomNavigationBar: Material( 40 | color: Colors.grey[100], 41 | child: TabBar( 42 | isScrollable: false, 43 | indicatorColor: Colors.transparent, 44 | controller: controller, 45 | tabs: [ 46 | TabBarItem( 47 | text: "首页", 48 | icon: Icons.home, 49 | selected: selectedIndex == 0, 50 | ), 51 | TabBarItem( 52 | text: "排行", 53 | icon: Icons.local_play, 54 | selected: selectedIndex == 1, 55 | ), 56 | TabBarItem( 57 | text: "片库", 58 | icon: Icons.ondemand_video, 59 | selected: selectedIndex == 2, 60 | ), 61 | TabBarItem( 62 | text: "动态", 63 | icon: Icons.camera, 64 | selected: selectedIndex == 3, 65 | ) 66 | ], 67 | ), 68 | ), 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /lib/widgets/action_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ActionBar extends StatelessWidget { 4 | final String likes; 5 | final String comments; 6 | final Function onComment; 7 | final Function onLike; 8 | final bool liked; 9 | 10 | const ActionBar( 11 | {Key key, 12 | this.likes, 13 | this.comments, 14 | this.onComment, 15 | this.onLike, 16 | this.liked = false}) 17 | : super(key: key); 18 | @override 19 | Widget build(BuildContext context) { 20 | return Container( 21 | child: Row( 22 | mainAxisAlignment: MainAxisAlignment.spaceAround, 23 | children: [ 24 | GestureDetector( 25 | onTap: onComment, 26 | child: Row( 27 | crossAxisAlignment: CrossAxisAlignment.center, 28 | children: [ 29 | Icon( 30 | Icons.comment, 31 | color: Colors.grey[400], 32 | ), 33 | SizedBox( 34 | width: 8.0, 35 | ), 36 | Text(comments, 37 | style: TextStyle( 38 | color: Colors.grey[400], 39 | )), 40 | ], 41 | ), 42 | ), 43 | Container( 44 | width: 1.0, 45 | height: 30.0, 46 | padding: EdgeInsets.symmetric(vertical: 4.0), 47 | color: Colors.grey[200], 48 | ), 49 | Row( 50 | crossAxisAlignment: CrossAxisAlignment.center, 51 | children: [ 52 | Icon( 53 | Icons.thumb_up, 54 | color: liked ? Colors.red : Colors.grey[400], 55 | ), 56 | SizedBox( 57 | width: 8.0, 58 | ), 59 | Text(likes, 60 | style: TextStyle( 61 | color: Colors.grey[400], 62 | )), 63 | ], 64 | ), 65 | ]), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/widgets/video_player_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:chewie/chewie.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:video_player/video_player.dart'; 4 | 5 | class VideoPlayerView extends StatefulWidget { 6 | final String url; 7 | final String placeholder; 8 | 9 | const VideoPlayerView({Key key, this.url, this.placeholder}) 10 | : super(key: key); 11 | 12 | @override 13 | _VideoPlayerViewState createState() => _VideoPlayerViewState(); 14 | } 15 | 16 | class _VideoPlayerViewState extends State { 17 | bool playing = false; 18 | VideoPlayerController controller; 19 | 20 | @override 21 | void initState() { 22 | controller = VideoPlayerController.network(widget.url); 23 | super.initState(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Container( 29 | child: playing 30 | ? Chewie( 31 | controller: ChewieController( 32 | videoPlayerController: controller, 33 | showControls: playing, 34 | aspectRatio: 16 / 9, 35 | autoPlay: playing, 36 | looping: false, 37 | placeholder: Container( 38 | decoration: BoxDecoration( 39 | image: DecorationImage( 40 | fit: BoxFit.cover, 41 | image: NetworkImage(widget.placeholder))), 42 | ), 43 | )) 44 | : GestureDetector( 45 | onTap: () { 46 | setState(() { 47 | playing = true; 48 | controller.play(); 49 | }); 50 | }, 51 | child: Stack(alignment: Alignment.center, children: [ 52 | Image.network( 53 | widget.placeholder, 54 | fit: BoxFit.cover, 55 | ), 56 | Image( 57 | fit: BoxFit.cover, 58 | width: 60.0, 59 | height: 60.0, 60 | image: AssetImage("assets/images/play_btn.png"), 61 | ), 62 | ]), 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/models/comment.dart: -------------------------------------------------------------------------------- 1 | class Comment { 2 | String id; 3 | String channel; 4 | String author; 5 | ResourceInfo resourceInfo; 6 | String userpic; 7 | String source; 8 | String groupName; 9 | String good; 10 | String dateline; 11 | int goodStatus; 12 | String channelCn; 13 | String itemid; 14 | String replyNum; 15 | String nickname; 16 | String content; 17 | 18 | Comment( 19 | {this.id, 20 | this.channel, 21 | this.author, 22 | this.resourceInfo, 23 | this.userpic, 24 | this.source, 25 | this.groupName, 26 | this.good, 27 | this.dateline, 28 | this.goodStatus, 29 | this.channelCn, 30 | this.itemid, 31 | this.replyNum, 32 | this.nickname, 33 | this.content}); 34 | 35 | Comment.fromJson(Map json) { 36 | id = json['id']; 37 | channel = json['channel']; 38 | author = json['author']; 39 | resourceInfo = json['resource_info'] != null 40 | ? new ResourceInfo.fromJson(json['resource_info']) 41 | : null; 42 | userpic = json['userpic']; 43 | source = json['source']; 44 | groupName = json['group_name']; 45 | good = json['good']; 46 | dateline = json['dateline']; 47 | goodStatus = json['good_status']; 48 | channelCn = json['channel_cn']; 49 | itemid = json['itemid']; 50 | replyNum = json['reply_num']; 51 | nickname = json['nickname']; 52 | content = json['content']; 53 | } 54 | } 55 | 56 | class ResourceInfo { 57 | String cnname; 58 | String enname; 59 | String itemupdate; 60 | String id; 61 | String poster; 62 | String posterM; 63 | String posterB; 64 | String posterS; 65 | 66 | ResourceInfo( 67 | {this.cnname, 68 | this.enname, 69 | this.itemupdate, 70 | this.id, 71 | this.poster, 72 | this.posterM, 73 | this.posterB, 74 | this.posterS}); 75 | 76 | ResourceInfo.fromJson(Map json) { 77 | cnname = json['cnname']; 78 | enname = json['enname']; 79 | itemupdate = json['itemupdate']; 80 | id = json['id']; 81 | poster = json['poster']; 82 | posterM = json['poster_m']; 83 | posterB = json['poster_b']; 84 | posterS = json['poster_s']; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /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 | throw new GradleException("versionCode not found. Define flutter.versionCode in the local.properties file.") 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | throw new GradleException("versionName not found. Define flutter.versionName in the local.properties file.") 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 27 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.popeye.yyets" 37 | minSdkVersion 16 38 | targetSdkVersion 27 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 61 | } 62 | -------------------------------------------------------------------------------- /lib/models/video_comment.dart: -------------------------------------------------------------------------------- 1 | class VideoComment { 2 | String id; 3 | String author; 4 | String userpic; 5 | String source; 6 | String groupName; 7 | String good; 8 | String dateline; 9 | int goodStatus; 10 | List replyList; 11 | String replyNum; 12 | String nickname; 13 | String content; 14 | 15 | VideoComment( 16 | {this.id, 17 | this.author, 18 | this.userpic, 19 | this.source, 20 | this.groupName, 21 | this.good, 22 | this.dateline, 23 | this.goodStatus, 24 | this.replyList, 25 | this.replyNum, 26 | this.nickname, 27 | this.content}); 28 | 29 | VideoComment.fromJson(Map json) { 30 | id = json['id']; 31 | author = json['author']; 32 | userpic = json['userpic']; 33 | source = json['source']; 34 | groupName = json['group_name']; 35 | good = json['good']; 36 | dateline = json['dateline']; 37 | goodStatus = json['good_status']; 38 | if (json['reply_list'] != null) { 39 | replyList = new List(); 40 | json['reply_list'].forEach((v) { 41 | replyList.add(new ReplyList.fromJson(v)); 42 | }); 43 | } 44 | replyNum = json['reply_num']; 45 | nickname = json['nickname']; 46 | content = json['content']; 47 | } 48 | } 49 | 50 | class ReplyList { 51 | String author; 52 | String toUid; 53 | String toNickname; 54 | String id; 55 | String authorNickname; 56 | String content; 57 | String replyid; 58 | String dateline; 59 | String authorUserpic; 60 | String authorGroupName; 61 | 62 | ReplyList( 63 | {this.author, 64 | this.toUid, 65 | this.toNickname, 66 | this.id, 67 | this.authorNickname, 68 | this.content, 69 | this.replyid, 70 | this.dateline, 71 | this.authorUserpic, 72 | this.authorGroupName}); 73 | 74 | ReplyList.fromJson(Map json) { 75 | author = json['author']; 76 | toUid = json['to_uid']; 77 | toNickname = json['to_nickname']; 78 | id = json['id']; 79 | authorNickname = json['author_nickname']; 80 | content = json['content']; 81 | replyid = json['replyid']; 82 | dateline = json['dateline']; 83 | authorUserpic = json['author_userpic']; 84 | authorGroupName = json['author_group_name']; 85 | } 86 | } -------------------------------------------------------------------------------- /lib/models/article_comment.dart: -------------------------------------------------------------------------------- 1 | class ArticleComment { 2 | String id; 3 | String author; 4 | String userpic; 5 | String source; 6 | String groupName; 7 | String good; 8 | String dateline; 9 | int goodStatus; 10 | List replys; 11 | String replyNum; 12 | String nickname; 13 | String content; 14 | 15 | ArticleComment( 16 | {this.id, 17 | this.author, 18 | this.userpic, 19 | this.source, 20 | this.groupName, 21 | this.good, 22 | this.dateline, 23 | this.goodStatus, 24 | this.replys, 25 | this.replyNum, 26 | this.nickname, 27 | this.content}); 28 | 29 | ArticleComment.fromJson(Map json) { 30 | id = json['id']; 31 | author = json['author']; 32 | userpic = json['userpic']; 33 | source = json['source']; 34 | groupName = json['group_name']; 35 | good = json['good']; 36 | dateline = json['dateline']; 37 | goodStatus = json['good_status']; 38 | if (json['reply_list'] != null) { 39 | replys = new List(); 40 | json['reply_list'].forEach((v) { 41 | replys.add(new ArticleReply.fromJson(v)); 42 | }); 43 | } 44 | replyNum = json['reply_num']; 45 | nickname = json['nickname']; 46 | content = json['content']; 47 | } 48 | } 49 | 50 | class ArticleReply { 51 | String author; 52 | String toUid; 53 | String toNickname; 54 | String id; 55 | String authorNickname; 56 | String content; 57 | String replyid; 58 | String dateline; 59 | String authorUserpic; 60 | String authorGroupName; 61 | 62 | ArticleReply( 63 | {this.author, 64 | this.toUid, 65 | this.toNickname, 66 | this.id, 67 | this.authorNickname, 68 | this.content, 69 | this.replyid, 70 | this.dateline, 71 | this.authorUserpic, 72 | this.authorGroupName}); 73 | 74 | ArticleReply.fromJson(Map json) { 75 | author = json['author']; 76 | toUid = json['to_uid']; 77 | toNickname = json['to_nickname']; 78 | id = json['id']; 79 | authorNickname = json['author_nickname']; 80 | content = json['content']; 81 | replyid = json['replyid']; 82 | dateline = json['dateline']; 83 | authorUserpic = json['author_userpic']; 84 | authorGroupName = json['author_group_name']; 85 | } 86 | } -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | def parse_KV_file(file, separator='=') 8 | file_abs_path = File.expand_path(file) 9 | if !File.exists? file_abs_path 10 | return []; 11 | end 12 | pods_ary = [] 13 | skip_line_start_symbols = ["#", "/"] 14 | File.foreach(file_abs_path) { |line| 15 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 16 | plugin = line.split(pattern=separator) 17 | if plugin.length == 2 18 | podname = plugin[0].strip() 19 | path = plugin[1].strip() 20 | podpath = File.expand_path("#{path}", file_abs_path) 21 | pods_ary.push({:name => podname, :path => podpath}); 22 | else 23 | puts "Invalid plugin specification: #{line}" 24 | end 25 | } 26 | return pods_ary 27 | end 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | 32 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 33 | # referring to absolute paths on developers' machines. 34 | system('rm -rf .symlinks') 35 | system('mkdir -p .symlinks/plugins') 36 | 37 | # Flutter Pods 38 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 39 | if generated_xcode_build_settings.empty? 40 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 41 | end 42 | generated_xcode_build_settings.map { |p| 43 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 44 | symlink = File.join('.symlinks', 'flutter') 45 | File.symlink(File.dirname(p[:path]), symlink) 46 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 47 | end 48 | } 49 | 50 | # Plugin Pods 51 | plugin_pods = parse_KV_file('../.flutter-plugins') 52 | plugin_pods.map { |p| 53 | symlink = File.join('.symlinks', 'plugins', p[:name]) 54 | File.symlink(p[:path], symlink) 55 | pod p[:name], :path => File.join(symlink, 'ios') 56 | } 57 | end 58 | 59 | post_install do |installer| 60 | installer.pods_project.targets.each do |target| 61 | target.build_configurations.each do |config| 62 | config.build_settings['ENABLE_BITCODE'] = 'NO' 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/widgets/video_grid_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/models/resource.dart'; 3 | import 'package:yyets/pages/video.dart'; 4 | import 'package:yyets/widgets/tag.dart'; 5 | 6 | class VideoGridItem extends StatelessWidget { 7 | final Resource resource; 8 | 9 | const VideoGridItem({Key key, this.resource}) : super(key: key); 10 | @override 11 | Widget build(BuildContext context) { 12 | return GestureDetector( 13 | onTap: () { 14 | Navigator.of(context).push(MaterialPageRoute( 15 | builder: (conext) => VideoPage( 16 | id: resource.id, 17 | poster: resource.poster, 18 | title: resource.cnname, 19 | ))); 20 | }, 21 | child: Container( 22 | child: Column( 23 | children: [ 24 | Expanded( 25 | child: Card( 26 | elevation: 8.0, 27 | child: AspectRatio( 28 | aspectRatio: 4 / 3, 29 | child: Hero( 30 | tag: resource.poster, 31 | child: FadeInImage( 32 | fit: BoxFit.cover, 33 | image: NetworkImage(resource.poster), 34 | placeholder: AssetImage( 35 | "assets/images/placeholder.png", 36 | ), 37 | ), 38 | )), 39 | ), 40 | ), 41 | Padding( 42 | padding: const EdgeInsets.all(8.0), 43 | child: Text( 44 | resource.cnname, 45 | maxLines: 1, 46 | overflow: TextOverflow.ellipsis, 47 | ), 48 | ), 49 | Text( 50 | resource.playStatus, 51 | style: Theme.of(context).textTheme.body2, 52 | ), 53 | resource.channelCn != null 54 | ? Padding( 55 | padding: const EdgeInsets.symmetric(vertical: 5.0), 56 | child: Tag( 57 | text: resource.channelCn, 58 | color: Theme.of(context).primaryColor, 59 | border: true, 60 | ), 61 | ) 62 | : Container() 63 | ], 64 | ), 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/models/resource.dart: -------------------------------------------------------------------------------- 1 | class Ranks { 2 | List month; 3 | List japan; 4 | List movie; 5 | List total; 6 | List news; 7 | List today; 8 | 9 | 10 | Ranks.initialState() { 11 | month = []; 12 | japan = []; 13 | movie = []; 14 | total = []; 15 | news = []; 16 | today = []; 17 | } 18 | 19 | Ranks.fromJson(Map json) { 20 | if (json['month_list'] != null) { 21 | month = List(); 22 | json['month_list'].forEach((v) { 23 | month.add(Resource.fromJson(v)); 24 | }); 25 | } 26 | if (json['japan_list'] != null) { 27 | japan = List(); 28 | json['japan_list'].forEach((v) { 29 | japan.add(Resource.fromJson(v)); 30 | }); 31 | } 32 | 33 | if (json['movie_list'] != null) { 34 | movie = List(); 35 | json['movie_list'].forEach((v) { 36 | movie.add(Resource.fromJson(v)); 37 | }); 38 | } 39 | if (json['total_list'] != null) { 40 | total = List(); 41 | json['total_list'].forEach((v) { 42 | total.add(Resource.fromJson(v)); 43 | }); 44 | } 45 | if (json['new_list'] != null) { 46 | news = List(); 47 | json['new_list'].forEach((v) { 48 | news.add(Resource.fromJson(v)); 49 | }); 50 | } 51 | if (json['today_list'] != null) { 52 | today = List(); 53 | json['today_list'].forEach((v) { 54 | today.add(Resource.fromJson(v)); 55 | }); 56 | } 57 | } 58 | } 59 | 60 | class Resource { 61 | String id; 62 | String cnname; 63 | String enname; 64 | String playStatus; 65 | String channel; 66 | String channelCn; 67 | String category; 68 | String area; 69 | String poster; 70 | String posterM; 71 | 72 | Resource( 73 | {this.id, 74 | this.cnname, 75 | this.enname, 76 | this.playStatus, 77 | this.channel, 78 | this.channelCn, 79 | this.category, 80 | this.area, 81 | this.poster}); 82 | 83 | Resource.fromJson(Map json) { 84 | id = json['id']; 85 | cnname = json['cnname']; 86 | enname = json['enname']; 87 | playStatus = json['play_status']; 88 | channel = json['channel']; 89 | channelCn = json['channel_cn']; 90 | category = json['category']; 91 | area = json['area']; 92 | poster = json['poster']; 93 | posterM = json['poster_m']; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/pages/rank.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:yyets/api/api.dart'; 4 | import 'package:yyets/pages/search.dart'; 5 | import 'package:yyets/redux/states/main.dart'; 6 | import 'package:yyets/redux/view_models/rank.dart'; 7 | import 'package:yyets/widgets/main.dart'; 8 | 9 | class RankPage extends StatefulWidget { 10 | @override 11 | _RankPageState createState() => _RankPageState(); 12 | } 13 | 14 | class _RankPageState extends State 15 | with SingleTickerProviderStateMixin { 16 | TabController controller; 17 | final List tabs = ["今日", "本月", "日剧", "电影", "新剧", "总榜"]; 18 | @override 19 | void initState() { 20 | super.initState(); 21 | controller = TabController(length: tabs.length, vsync: this); 22 | Networking.fetchTopRanks(); 23 | } 24 | 25 | @override 26 | void dispose() { 27 | super.dispose(); 28 | controller.dispose(); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Scaffold( 34 | appBar: AppBar( 35 | title: GestureDetector( 36 | child: SearchBar( 37 | enabled: false, 38 | ), 39 | onTap: () { 40 | Navigator 41 | .of(context) 42 | .push(MaterialPageRoute(builder: (context) => SearchPage())); 43 | }, 44 | ), 45 | bottom: TabBar( 46 | controller: controller, 47 | isScrollable: true, 48 | indicatorSize: TabBarIndicatorSize.label, 49 | indicatorColor: Colors.white, 50 | tabs: tabs.map((title) => Tab(text: title)).toList(), 51 | ), 52 | ), 53 | body: Container( 54 | child: StoreConnector( 55 | converter: (store) => RankViewModel(store), 56 | builder: (context, vm) { 57 | return TabBarView( 58 | controller: controller, 59 | children: [ 60 | vm.isLoading 61 | ? Center(child: CircularProgressIndicator()) 62 | : VideoGridView(resources: vm.ranks.today), 63 | VideoGridView(resources: vm.ranks?.month), 64 | VideoGridView(resources: vm.ranks?.japan), 65 | VideoGridView(resources: vm.ranks?.movie), 66 | VideoGridView(resources: vm.ranks?.news), 67 | VideoGridView(resources: vm.ranks?.total) 68 | ], 69 | ); 70 | }), 71 | ), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/widgets/series_list_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/models/video_info.dart'; 3 | import 'package:yyets/widgets/tag.dart'; 4 | 5 | class SeriesListView extends StatelessWidget { 6 | final SeasonList season; 7 | final Function callback; 8 | 9 | const SeriesListView({Key key, this.season, this.callback}) : super(key: key); 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | color: Colors.white, 14 | child: Column( 15 | crossAxisAlignment: CrossAxisAlignment.start, 16 | children: [ 17 | SizedBox( 18 | height: 16.0, 19 | ), 20 | Tag( 21 | text: season.seasonCn, 22 | color: Colors.white, 23 | border: true, 24 | backgroudColor: Colors.blue, 25 | ), 26 | GridView.builder( 27 | itemCount: season.episodeList.length, 28 | physics: ClampingScrollPhysics(), 29 | shrinkWrap: true, 30 | itemBuilder: (context, index) { 31 | final item = season.episodeList[index]; 32 | return GestureDetector( 33 | onTap: () { 34 | callback(item); 35 | }, 36 | child: Container( 37 | child: Card( 38 | elevation: 2.0, 39 | child: Padding( 40 | padding: const EdgeInsets.all(8.0), 41 | child: Column( 42 | mainAxisAlignment: MainAxisAlignment.center, 43 | crossAxisAlignment: CrossAxisAlignment.center, 44 | children: [ 45 | Text( 46 | "${item.episode.padLeft(2, '0')}", 47 | style: TextStyle( 48 | fontWeight: FontWeight.bold, fontSize: 18.0), 49 | ), 50 | Text( 51 | item.playTime, 52 | maxLines: 1, 53 | style: TextStyle(fontSize: 10.0), 54 | ) 55 | ], 56 | ), 57 | ), 58 | )), 59 | ); 60 | }, 61 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 62 | childAspectRatio: 4 / 3, 63 | crossAxisCount: 4, 64 | crossAxisSpacing: 8.0), 65 | ), 66 | ], 67 | ), 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/widgets/hot_comment_listtile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/models/comment.dart'; 3 | import 'package:yyets/models/enums.dart'; 4 | import 'package:yyets/pages/video.dart'; 5 | import 'package:yyets/widgets/icon_label.dart'; 6 | import 'package:yyets/widgets/resource_tile.dart'; 7 | import 'package:yyets/widgets/tag.dart'; 8 | import 'package:yyets/widgets/user_info_tile.dart'; 9 | 10 | class HotCommentListTile extends StatelessWidget { 11 | final Comment comment; 12 | const HotCommentListTile({Key key, this.comment}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | child: Column( 18 | crossAxisAlignment: CrossAxisAlignment.start, 19 | children: [ 20 | UserInfoTile( 21 | level: UserLevel.superVip, 22 | nickName: comment.nickname, 23 | avatar: comment.userpic, 24 | groupName: comment.groupName, 25 | time: comment.dateline, 26 | trailing: [ 27 | Tag( 28 | text: comment.channelCn, 29 | color: Theme.of(context).primaryColor, 30 | border: true, 31 | ) 32 | ], 33 | ), 34 | Padding( 35 | padding: const EdgeInsets.symmetric(vertical: 8.0), 36 | child: Text(comment.content), 37 | ), 38 | SizedBox( 39 | height: 8.0, 40 | ), 41 | Row( 42 | crossAxisAlignment: CrossAxisAlignment.center, 43 | mainAxisAlignment: MainAxisAlignment.end, 44 | children: [ 45 | IconLabel( 46 | icon: Icons.comment, 47 | title: comment.replyNum, 48 | ), 49 | SizedBox( 50 | width: 50.0, 51 | ), 52 | IconLabel( 53 | icon: Icons.thumb_up, 54 | title: comment.good, 55 | ), 56 | ], 57 | ), 58 | SizedBox( 59 | height: 8.0, 60 | ), 61 | GestureDetector( 62 | child: ResourceTile( 63 | resource: comment.resourceInfo, 64 | ), 65 | onTap: () { 66 | Navigator.of(context).push(MaterialPageRoute( 67 | builder: (context) => VideoPage( 68 | id: comment.resourceInfo.id, 69 | poster: comment.resourceInfo.poster, 70 | title: comment.resourceInfo.cnname, 71 | ))); 72 | }, 73 | ) 74 | ], 75 | ), 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /lib/widgets/home_banner.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:yyets/models/ad.dart'; 4 | import 'package:yyets/pages/video.dart'; 5 | import 'package:yyets/widgets/slider_indicator.dart'; 6 | 7 | class HomeBanner extends StatefulWidget { 8 | final List banners; 9 | 10 | const HomeBanner({Key key, this.banners}) : super(key: key); 11 | 12 | @override 13 | _HomeBannerState createState() => _HomeBannerState(); 14 | } 15 | 16 | class _HomeBannerState extends State { 17 | int sliderIndex = 0; 18 | bool runing = false; 19 | PageController controller; 20 | 21 | @override 22 | void initState() { 23 | controller = new PageController(); 24 | super.initState(); 25 | run(); 26 | } 27 | 28 | void run() async { 29 | this.runing = true; 30 | while (this.runing) { 31 | await Future.delayed(new Duration(seconds: 5)); 32 | if (mounted) { 33 | this.setState(() { 34 | this.sliderIndex = this.sliderIndex == widget.banners.length - 1 35 | ? 0 36 | : this.sliderIndex + 1; 37 | this.controller.animateToPage( 38 | this.sliderIndex, 39 | duration: new Duration(milliseconds: 600), 40 | curve: Curves.easeInOut, 41 | ); 42 | }); 43 | } 44 | } 45 | } 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | return Stack( 50 | alignment: Alignment.bottomCenter, 51 | children: [ 52 | Card( 53 | margin: EdgeInsets.zero, 54 | elevation: 0.0, 55 | child: AspectRatio( 56 | aspectRatio: 9.0 / 5.0, 57 | child: PageView( 58 | controller: controller, 59 | onPageChanged: (index) { 60 | setState(() { 61 | sliderIndex = index; 62 | }); 63 | }, 64 | children: widget.banners 65 | .map((ad) => GestureDetector( 66 | onTap: () { 67 | final id = 68 | Uri.parse(ad.click).queryParameters["id"]; 69 | Navigator.of(context).push(MaterialPageRoute( 70 | builder: (context) => VideoPage( 71 | id: id, 72 | poster: ad.pic, 73 | ))); 74 | }, 75 | child: Image( 76 | image: NetworkImage(ad.pic), 77 | ), 78 | )) 79 | .toList()), 80 | ), 81 | ), 82 | Padding( 83 | padding: const EdgeInsets.only(bottom: 8.0), 84 | child: 85 | SliderIndicator(count: widget.banners.length, index: sliderIndex), 86 | ) 87 | ], 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/widgets/todays_boardcast.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/models/tv_schedule.dart'; 3 | import 'package:yyets/pages/video.dart'; 4 | import 'package:yyets/widgets/tag.dart'; 5 | 6 | class TodaysBoardcast extends StatelessWidget { 7 | final List schedules; 8 | 9 | const TodaysBoardcast({Key key, this.schedules}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Container( 14 | height: 150.0, 15 | child: GridView.builder( 16 | shrinkWrap: true, 17 | scrollDirection: Axis.horizontal, 18 | itemCount: schedules.length, 19 | itemBuilder: (context, index) { 20 | final item = schedules[index]; 21 | return GestureDetector( 22 | onTap: () { 23 | Navigator.of(context).push(MaterialPageRoute( 24 | builder: (conext) => VideoPage( 25 | id: item.id, 26 | poster: item.poster, 27 | title: item.cnname, 28 | ))); 29 | }, 30 | child: Card( 31 | margin: EdgeInsets.zero, 32 | child: GridTile( 33 | child: Hero( 34 | tag: item.poster, 35 | child: FadeInImage( 36 | fit: BoxFit.cover, 37 | image: NetworkImage(item.poster), 38 | placeholder: AssetImage( 39 | "assets/images/placeholder.png", 40 | ), 41 | ), 42 | ), 43 | header: GridTileBar( 44 | leading: item.statusCn.isNotEmpty 45 | ? Tag( 46 | text: item.statusCn, 47 | color: Colors.white, 48 | backgroudColor: 49 | Theme.of(context).primaryColorDark.withAlpha(200), 50 | ) 51 | : Container(), 52 | ), 53 | footer: Container( 54 | height: 40.0, 55 | child: GridTileBar( 56 | title: Text( 57 | item.cnname, 58 | style: TextStyle(fontSize: 12.0), 59 | ), 60 | backgroundColor: Colors.black.withAlpha(120), 61 | subtitle: Text( 62 | "S${item.season.padLeft(2, '0')}E${item.episode.padLeft(2, '0')}", 63 | style: TextStyle(color: Colors.white, fontSize: 12.0)), 64 | ), 65 | ), 66 | ), 67 | ), 68 | ); 69 | }, 70 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 71 | childAspectRatio: 1.2, 72 | crossAxisCount: 1, 73 | crossAxisSpacing: 5.0, 74 | mainAxisSpacing: 8.0), 75 | ), 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: yyets 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 | # Read more about versioning at semver.org. 10 | version: 1.0.0+1 11 | 12 | dependencies: 13 | flutter: 14 | sdk: flutter 15 | 16 | # The following adds the Cupertino Icons font to your application. 17 | # Use with the CupertinoIcons class for iOS style icons. 18 | cupertino_icons: ^0.1.2 19 | http: ^0.12.0+2 20 | chewie: "^0.9.7" 21 | rxdart: "^0.22.0" 22 | redux: "^3.0.0" 23 | flutter_redux: "^0.5.3" 24 | # cached_network_image: "^0.4.1" 25 | intl: ^0.15.8 26 | photo_view: ^0.4.0 27 | # flutter_cache_manager: ^0.1.1 28 | 29 | 30 | 31 | 32 | 33 | 34 | dev_dependencies: 35 | flutter_test: 36 | sdk: flutter 37 | 38 | 39 | # For information on the generic Dart part of this file, see the 40 | # following page: https://www.dartlang.org/tools/pub/pubspec 41 | 42 | # The following section is specific to Flutter. 43 | flutter: 44 | 45 | # The following line ensures that the Material Icons font is 46 | # included with your application, so that you can use the icons in 47 | # the material Icons class. 48 | uses-material-design: true 49 | 50 | # To add assets to your application, add an assets section, like this: 51 | assets: 52 | - assets/images/profile_bg.jpeg 53 | - assets/images/日剧.png 54 | - assets/images/片库.png 55 | - assets/images/电影.png 56 | - assets/images/美剧.png 57 | - assets/images/英剧.png 58 | - assets/images/韩剧.png 59 | - assets/images/公开课.png 60 | - assets/images/纪录片.png 61 | - assets/images/其他语种.png 62 | - assets/images/cover.jpg 63 | - assets/images/play_btn.png 64 | - assets/images/placeholder.png 65 | - assets/images/drawer_bg.jpeg 66 | # - images/a_dot_ham.jpeg 67 | 68 | # An image asset can refer to one or more resolution-specific "variants", see 69 | # https://flutter.io/assets-and-images/#resolution-aware. 70 | 71 | # For details regarding adding assets from package dependencies, see 72 | # https://flutter.io/assets-and-images/#from-packages 73 | 74 | # To add custom fonts to your application, add a fonts section here, 75 | # in this "flutter" section. Each entry in this list should have a 76 | # "family" key with the font family name, and a "fonts" key with a 77 | # list giving the asset and other descriptors for the font. For 78 | # example: 79 | # fonts: 80 | # - family: Schyler 81 | # fonts: 82 | # - asset: fonts/Schyler-Regular.ttf 83 | # - asset: fonts/Schyler-Italic.ttf 84 | # style: italic 85 | # - family: Trajan Pro 86 | # fonts: 87 | # - asset: fonts/TrajanPro.ttf 88 | # - asset: fonts/TrajanPro_Bold.ttf 89 | # weight: 700 90 | # 91 | # For details regarding fonts from package dependencies, 92 | # see https://flutter.io/custom-fonts/#from-packages 93 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/pages/settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SettingRow { 4 | final String title; 5 | final String subtitle; 6 | final bool arrow; 7 | 8 | SettingRow({this.title, this.subtitle, this.arrow = false}); 9 | } 10 | 11 | class SettingsPage extends StatelessWidget { 12 | final List items = [ 13 | SettingRow(title: "昵称", subtitle: "Popeye Lau", arrow: true), 14 | SettingRow(title: "更改密码", subtitle: null, arrow: true), 15 | SettingRow(title: "意见反馈", subtitle: null, arrow: true), 16 | SettingRow(title: "版本信息", subtitle: "V2.5.2", arrow: false), 17 | SettingRow(title: "加入人人影视字幕组", subtitle: null, arrow: true), 18 | SettingRow(title: "官方微博", subtitle: null, arrow: true), 19 | SettingRow(title: "官方微信", subtitle: "美剧炸了/USTVBOOM", arrow: false), 20 | SettingRow(title: "官方邮箱", subtitle: "fanyi@yyets.com", arrow: false), 21 | SettingRow(title: "官方网站", subtitle: null, arrow: true), 22 | ]; 23 | @override 24 | Widget build(BuildContext context) { 25 | return Scaffold( 26 | appBar: AppBar( 27 | title: Text( 28 | "设置", 29 | )), 30 | body: Container( 31 | padding: EdgeInsets.all(8.0), 32 | child: Column( 33 | children: [ 34 | ListView.separated( 35 | shrinkWrap: true, 36 | itemBuilder: (BuildContext context, int index) { 37 | final item = items[index]; 38 | return Padding( 39 | padding: EdgeInsets.all(8.0), 40 | child: Row( 41 | crossAxisAlignment: CrossAxisAlignment.center, 42 | children: [ 43 | Expanded( 44 | child: Text(item.title), 45 | ), 46 | Row( 47 | children: [ 48 | item.subtitle == null 49 | ? Container() 50 | : Text(item.subtitle), 51 | item.arrow 52 | ? Icon( 53 | Icons.keyboard_arrow_right, 54 | color: Colors.grey, 55 | ) 56 | : Container() 57 | ], 58 | ) 59 | ], 60 | ), 61 | ); 62 | }, 63 | itemCount: items.length, 64 | separatorBuilder: (BuildContext context, int index) { 65 | return Divider(); 66 | }, 67 | ), 68 | SizedBox( 69 | height: 16.0, 70 | ), 71 | RaisedButton( 72 | color: Theme.of(context).primaryColor, 73 | child: Padding( 74 | padding: const EdgeInsets.symmetric(horizontal: 80.0), 75 | child: Text( 76 | "退出", 77 | style: TextStyle(color: Colors.white), 78 | ), 79 | ), 80 | onPressed: () {}, 81 | ) 82 | ], 83 | ), 84 | ), 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/widgets/app_drawer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:yyets/pages/settings.dart'; 4 | import 'package:yyets/redux/states/main.dart'; 5 | import 'package:yyets/redux/view_models/profile.dart'; 6 | import 'package:yyets/widgets/gradient_tag.dart'; 7 | 8 | class AppDrawer extends StatelessWidget { 9 | @override 10 | Widget build(BuildContext context) { 11 | return StoreConnector( 12 | converter: (store) => ProfileViewModel(store), 13 | builder: (context, vm) { 14 | return Drawer( 15 | child: Column( 16 | children: [ 17 | UserAccountsDrawerHeader( 18 | decoration: BoxDecoration( 19 | image: DecorationImage( 20 | fit: BoxFit.cover, 21 | image: AssetImage("assets/images/drawer_bg.jpeg"), 22 | )), 23 | currentAccountPicture: CircleAvatar( 24 | backgroundImage: NetworkImage(vm.profile.userpic ?? 25 | "http://popeyelau.qiniudn.com/popeye.png"), 26 | ), 27 | accountEmail: Text(vm.profile.email ?? "popeyelau@gmail.com"), 28 | accountName: Row( 29 | children: [ 30 | Text(vm.profile.nickname ?? "Popeye"), 31 | SizedBox( 32 | width: 8.0, 33 | ), 34 | GradientTag( 35 | title: vm.profile.groupName ?? "至尊会员", 36 | gradient: 37 | LinearGradient(colors: [Colors.orange, Colors.red]), 38 | ) 39 | ], 40 | ), 41 | ), 42 | ListTile( 43 | onTap: () => Navigator.of(context).pop(), 44 | leading: Icon( 45 | Icons.comment, 46 | color: Colors.orangeAccent, 47 | ), 48 | title: Text("我的评论"), 49 | ), 50 | ListTile( 51 | onTap: () => Navigator.of(context).pop(), 52 | leading: Icon( 53 | Icons.star, 54 | color: Colors.redAccent, 55 | ), 56 | title: Text("我的收藏"), 57 | ), 58 | ListTile( 59 | onTap: () { 60 | Navigator.of(context).push(MaterialPageRoute( 61 | builder: (context) => SettingsPage())); 62 | }, 63 | leading: Icon( 64 | Icons.settings, 65 | color: Colors.blue, 66 | ), 67 | title: Text("设置"), 68 | ), 69 | ListTile( 70 | onTap: () => Navigator.of(context).pop(), 71 | leading: Icon( 72 | Icons.help, 73 | color: Colors.purple, 74 | ), 75 | title: Text("帮助"), 76 | ), 77 | ], 78 | ), 79 | ); 80 | }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/widgets/video_cover.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui' as ui; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:yyets/models/video_info.dart'; 5 | import 'package:yyets/widgets/rate_view.dart'; 6 | 7 | class VideoCover extends StatelessWidget { 8 | final ResourceInfo resource; 9 | final String poster; 10 | 11 | const VideoCover({Key key, this.resource, this.poster = ""}) 12 | : super(key: key); 13 | 14 | @override 15 | Widget build(context) { 16 | return Container( 17 | child: BackdropFilter( 18 | filter: ui.ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), 19 | child: Container( 20 | child: Container( 21 | alignment: Alignment.center, 22 | child: Stack( 23 | children: [ 24 | Positioned( 25 | top: 70.0, 26 | left: 8.0, 27 | right: 8.0, 28 | bottom: 80.0, 29 | child: Container( 30 | child: Row( 31 | children: [ 32 | Hero( 33 | tag: poster, 34 | child: Card( 35 | color: Colors.white.withAlpha(100), 36 | elevation: 5.0, 37 | child: AspectRatio( 38 | aspectRatio: 3 / 4, 39 | child: Image.network( 40 | poster == "" ? resource.poster : poster, 41 | fit: BoxFit.cover, 42 | )), 43 | ), 44 | ), 45 | SizedBox( 46 | width: 16.0, 47 | ), 48 | Column( 49 | crossAxisAlignment: CrossAxisAlignment.start, 50 | mainAxisAlignment: MainAxisAlignment.start, 51 | children: [ 52 | Text( 53 | resource.cnname ?? "", 54 | style: TextStyle( 55 | fontSize: 16.0, color: Colors.white), 56 | ), 57 | SizedBox( 58 | height: 8.0, 59 | ), 60 | Expanded( 61 | child: Column( 62 | mainAxisAlignment: MainAxisAlignment.center, 63 | crossAxisAlignment: CrossAxisAlignment.start, 64 | children: [ 65 | Text(resource.category ?? ""), 66 | Text(resource.playStatus ?? ""), 67 | Text(resource.premiere ?? ""), 68 | ], 69 | ), 70 | ), 71 | RateView( 72 | score: resource.score ?? "0.0", 73 | ), 74 | ], 75 | ), 76 | ], 77 | ), 78 | )) 79 | ], 80 | ), 81 | ), 82 | decoration: BoxDecoration(color: Colors.white.withOpacity(0.25)), 83 | ), 84 | ), 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/pages/lib.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:yyets/api/api.dart'; 4 | import 'package:yyets/models/menu.dart'; 5 | import 'package:yyets/pages/lib_filt.dart'; 6 | import 'package:yyets/pages/search.dart'; 7 | import 'package:yyets/pages/video.dart'; 8 | import 'package:yyets/redux/states/main.dart'; 9 | import 'package:yyets/redux/view_models/lib.dart'; 10 | import 'package:yyets/widgets/main.dart'; 11 | 12 | class LibPage extends StatefulWidget { 13 | @override 14 | _LibPageState createState() => _LibPageState(); 15 | } 16 | 17 | class _LibPageState extends State { 18 | @override 19 | void initState() { 20 | Networking.fetchLibIndexResources(); 21 | super.initState(); 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return Scaffold( 27 | backgroundColor: Colors.white, 28 | appBar: AppBar( 29 | title: GestureDetector( 30 | child: SearchBar( 31 | enabled: false, 32 | ), 33 | onTap: () { 34 | Navigator 35 | .of(context) 36 | .push(MaterialPageRoute(builder: (context) => SearchPage())); 37 | }, 38 | ), 39 | ), 40 | body: Container( 41 | child: ListView( 42 | children: [ 43 | GridMenuView( 44 | callback: (Menu menu) { 45 | Networking.fetchLibResources( 46 | channel: menu.channel, area: menu.area); 47 | Navigator.of(context).push(MaterialPageRoute( 48 | builder: (context) => LibFiltPage( 49 | menu: menu, 50 | ))); 51 | }, 52 | ), 53 | SortBar( 54 | onUpdate: (Sort v) { 55 | Networking.fetchLibIndexResources(sort: v.key); 56 | }, 57 | ), 58 | StoreConnector( 59 | converter: (store) => LibViewModel(store), 60 | builder: (context, vm) { 61 | return vm.isLoading 62 | ? Center(child: CircularProgressIndicator()) 63 | : ListView.builder( 64 | shrinkWrap: true, 65 | physics: ClampingScrollPhysics(), 66 | itemCount: vm.resources.length, 67 | itemBuilder: (context, index) { 68 | return GestureDetector( 69 | onTap: () { 70 | Navigator.of(context).push(MaterialPageRoute( 71 | builder: (conext) => VideoPage( 72 | id: vm.resources[index].id, 73 | poster: vm.resources[index].poster ?? 74 | "https://images.unsplash.com/photo-1503249023995-51b0f3778ccf?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=9df0dd21ec52afa496c963a48da2d287&auto=format&fit=crop&w=800&q=60", 75 | title: vm.resources[index].cnname, 76 | ))); 77 | }, 78 | child: VideoListTile( 79 | resource: vm.resources[index], 80 | ), 81 | ); 82 | }, 83 | ); 84 | }), 85 | ], 86 | ), 87 | ), 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /lib/widgets/video_listtile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/common/utils.dart'; 3 | import 'package:yyets/models/lib_resource.dart'; 4 | import 'package:yyets/widgets/tag.dart'; 5 | 6 | class VideoListTile extends StatelessWidget { 7 | final LibResource resource; 8 | 9 | const VideoListTile({Key key, this.resource}) : super(key: key); 10 | @override 11 | Widget build(BuildContext context) { 12 | String date = ""; 13 | 14 | int timestap = int.tryParse(resource.itemupdate); 15 | date = 16 | timestap == null ? resource.itemupdate : Utils.readTimestamp(timestap); 17 | 18 | return Container( 19 | margin: EdgeInsets.all(8.0), 20 | child: Column( 21 | children: [ 22 | Row( 23 | mainAxisAlignment: MainAxisAlignment.start, 24 | children: [ 25 | Hero( 26 | tag: resource.poster ?? resource.cnname, 27 | child: Container( 28 | height: 100.0, 29 | width: 100.0, 30 | child: Container( 31 | decoration: BoxDecoration( 32 | image: DecorationImage( 33 | fit: BoxFit.contain, 34 | image: resource.posterM == null 35 | ? AssetImage("assets/images/placeholder.png") 36 | : NetworkImage(resource.posterM), 37 | )), 38 | ), 39 | ), 40 | ), 41 | SizedBox( 42 | width: 8.0, 43 | ), 44 | Expanded( 45 | child: Container( 46 | child: Column( 47 | crossAxisAlignment: CrossAxisAlignment.start, 48 | children: [ 49 | Padding( 50 | padding: EdgeInsets.symmetric(vertical: 4.0), 51 | child: Text( 52 | resource.cnname, 53 | maxLines: 2, 54 | overflow: TextOverflow.ellipsis, 55 | style: TextStyle(fontSize: 16.0), 56 | ), 57 | ), 58 | resource.area.isNotEmpty 59 | ? Tag( 60 | text: resource.area, 61 | color: Theme.of(context).primaryColor, 62 | border: true, 63 | ) 64 | : Container(), 65 | SizedBox( 66 | height: 8.0, 67 | ), 68 | Text( 69 | resource.category, 70 | style: Theme.of(context).textTheme.body2, 71 | ), 72 | Text( 73 | resource.playStatus, 74 | style: Theme.of(context).textTheme.body2, 75 | ), 76 | Text( 77 | date, 78 | style: Theme.of(context).textTheme.body2, 79 | maxLines: 1, 80 | ), 81 | ], 82 | ), 83 | ), 84 | ), 85 | Card( 86 | elevation: 2.0, 87 | child: Container( 88 | width: 60.0, 89 | height: 60.0, 90 | child: Column( 91 | mainAxisAlignment: MainAxisAlignment.center, 92 | children: [ 93 | Text("Score"), 94 | Text( 95 | resource.score, 96 | style: TextStyle(fontSize: 24.0), 97 | ), 98 | ], 99 | ), 100 | ), 101 | ), 102 | ], 103 | ), 104 | Divider() 105 | ], 106 | ), 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/pages/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:yyets/api/api.dart'; 4 | import 'package:yyets/models/article.dart'; 5 | import 'package:yyets/pages/search.dart'; 6 | import 'package:yyets/redux/states/main.dart'; 7 | import 'package:yyets/redux/view_models/home.dart'; 8 | import 'package:yyets/widgets/main.dart'; 9 | 10 | class HomePage extends StatefulWidget { 11 | @override 12 | _HomePageState createState() => _HomePageState(); 13 | } 14 | 15 | class _HomePageState extends State { 16 | int sliderIndex = 0; 17 | @override 18 | void initState() { 19 | super.initState(); 20 | Networking.fetchtSchedule(); 21 | Networking.fetchArticles(); 22 | Networking.fetchBanners(); 23 | Networking.fetchUserProfile(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | backgroundColor: Colors.white, 30 | drawer: AppDrawer(), 31 | appBar: AppBar( 32 | elevation: 0.0, 33 | title: GestureDetector( 34 | child: SearchBar( 35 | enabled: false, 36 | ), 37 | onTap: () { 38 | Navigator 39 | .of(context) 40 | .push(MaterialPageRoute(builder: (context) => SearchPage())); 41 | }, 42 | ), 43 | actions: [ 44 | Container( 45 | margin: EdgeInsets.only(right: 8.0), 46 | child: CircleAvatar( 47 | child: Text(DateTime.now().day.toString()), 48 | ), 49 | ), 50 | ], 51 | ), 52 | body: StoreConnector( 53 | converter: (store) => HomeViewModel(store), 54 | builder: (context, vm) { 55 | return Container( 56 | child: vm.isLoading 57 | ? Center( 58 | child: CircularProgressIndicator(), 59 | ) 60 | : Container( 61 | child: ListView( 62 | primary: true, 63 | children: [ 64 | Padding( 65 | padding: const EdgeInsets.all(8.0), 66 | child: vm.ads.isNotEmpty 67 | ? HomeBanner( 68 | banners: vm.ads, 69 | ) 70 | : Container(), 71 | ), 72 | SectionTitle( 73 | title: "今日播出", 74 | ), 75 | Padding( 76 | padding: 77 | const EdgeInsets.symmetric(horizontal: 8.0), 78 | child: vm.schedules.isNotEmpty 79 | ? TodaysBoardcast( 80 | schedules: vm.schedules, 81 | ) 82 | : Container(), 83 | ), 84 | SectionDivider(), 85 | vm.articles.isNotEmpty 86 | ? ListView.builder( 87 | shrinkWrap: true, 88 | physics: ClampingScrollPhysics(), 89 | itemCount: vm.articles.length, 90 | itemBuilder: (context, index) { 91 | return ArticleView( 92 | vm: ArticleViewModel 93 | .fromArticle(vm.articles[index]), 94 | ); 95 | }, 96 | ) 97 | : Container(), 98 | ], 99 | ), 100 | )); 101 | }), 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/pages/reply.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/api/api.dart'; 3 | import 'package:yyets/models/article_comment.dart'; 4 | import 'package:yyets/models/comment.dart'; 5 | import 'package:yyets/widgets/comment_bar.dart'; 6 | import 'package:yyets/widgets/comment_listtile.dart'; 7 | import 'package:yyets/widgets/empty_view.dart'; 8 | import 'package:yyets/widgets/hot_comment_listtile.dart'; 9 | 10 | class ReplyPage extends StatefulWidget { 11 | final Comment comment; 12 | 13 | const ReplyPage({Key key, this.comment}) : super(key: key); 14 | @override 15 | _ReplyPageState createState() => _ReplyPageState(); 16 | } 17 | 18 | class _ReplyPageState extends State { 19 | String replayName = ""; 20 | @override 21 | Widget build(BuildContext context) { 22 | return Scaffold( 23 | appBar: AppBar( 24 | title: Text("评论详情"), 25 | ), 26 | body: Container( 27 | color: Colors.white, 28 | padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), 29 | child: Stack( 30 | children: [ 31 | Container( 32 | margin: EdgeInsets.only(bottom: 50.0), 33 | child: ListView( 34 | children: [ 35 | HotCommentListTile(comment: widget.comment), 36 | SizedBox( 37 | height: 8.0, 38 | ), 39 | FutureBuilder>( 40 | future: Networking.fetchReplyList(id: widget.comment.id), 41 | builder: (BuildContext context, 42 | AsyncSnapshot> snapshot) { 43 | if (snapshot.hasData) { 44 | return ListView.separated( 45 | shrinkWrap: true, 46 | physics: ClampingScrollPhysics(), 47 | itemCount: snapshot.data.length, 48 | itemBuilder: (context, index) { 49 | final item = snapshot.data[index]; 50 | List children = [ 51 | CommentListTile( 52 | avatar: item.authorUserpic, 53 | nickName: item.authorNickname, 54 | dateline: item.dateline, 55 | groupName: item.authorGroupName, 56 | content: item.content, 57 | ), 58 | ]; 59 | 60 | return GestureDetector( 61 | onTap: () { 62 | setState(() { 63 | replayName = item.authorNickname; 64 | }); 65 | }, 66 | child: Column(children: children), 67 | ); 68 | }, 69 | separatorBuilder: (context, index) { 70 | return Divider( 71 | height: 5.0, 72 | color: Colors.grey[200], 73 | ); 74 | }, 75 | ); 76 | } else if (snapshot.hasError) { 77 | return FractionallySizedBox( 78 | heightFactor: 1.0, 79 | child: EmptyView( 80 | title: "出错啦!\n${snapshot.error.toString()}", 81 | )); 82 | } 83 | return Center(child: CircularProgressIndicator()); 84 | }) 85 | ], 86 | ), 87 | ), 88 | Positioned( 89 | bottom: 0.0, 90 | right: 0.0, 91 | left: 0.0, 92 | child: CommentBar( 93 | replayName: replayName, 94 | )), 95 | ], 96 | ), 97 | ), 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/pages/search.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:yyets/api/api.dart'; 4 | import 'package:yyets/pages/video.dart'; 5 | import 'package:yyets/redux/actions/search.dart'; 6 | import 'package:yyets/redux/states/main.dart'; 7 | import 'package:yyets/redux/view_models/main.dart'; 8 | import 'package:yyets/redux/view_models/search.dart'; 9 | import 'package:yyets/widgets/search_bar.dart'; 10 | import 'package:yyets/widgets/video_listtile.dart'; 11 | 12 | class SearchPage extends StatefulWidget { 13 | @override 14 | _SearchPageState createState() => _SearchPageState(); 15 | } 16 | 17 | class _SearchPageState extends State { 18 | @override 19 | void initState() { 20 | Networking.fetchHotSearchKeywords(); 21 | super.initState(); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | StoreContainer.global.dispatch(UpdateSearchResults(payload: [])); 27 | super.dispose(); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return Scaffold( 33 | appBar: AppBar( 34 | title: SearchBar( 35 | onSubmitted: (String keyword) { 36 | if (keyword.isEmpty) { 37 | return; 38 | } 39 | Networking.searchResrouces(keyword: keyword); 40 | }, 41 | ), 42 | ), 43 | body: StoreConnector( 44 | converter: (store) => SearchViewModel(store), 45 | builder: (context, vm) { 46 | return ListView( 47 | children: [ 48 | vm.results.isEmpty 49 | ? Container( 50 | child: Column( 51 | crossAxisAlignment: CrossAxisAlignment.start, 52 | children: [ 53 | Padding( 54 | padding: 55 | const EdgeInsets.symmetric(horizontal: 8.0), 56 | child: Column( 57 | crossAxisAlignment: CrossAxisAlignment.start, 58 | children: [ 59 | SizedBox( 60 | height: 8.0, 61 | ), 62 | Text("热门搜索"), 63 | Divider(), 64 | Wrap( 65 | spacing: 5.0, 66 | runSpacing: 5.0, 67 | children: vm.keywords 68 | .map((v) => GestureDetector( 69 | child: Chip( 70 | label: Text(v), 71 | ), 72 | onTap: () => 73 | vm.searchResrouces(keyword: v))) 74 | .toList(), 75 | ), 76 | ], 77 | ), 78 | ), 79 | ], 80 | ), 81 | ) 82 | : Container( 83 | child: ListView.builder( 84 | shrinkWrap: true, 85 | physics: ClampingScrollPhysics(), 86 | itemCount: vm.results.length, 87 | itemBuilder: (context, index) { 88 | return GestureDetector( 89 | onTap: () { 90 | Navigator.of(context).push(MaterialPageRoute( 91 | builder: (context) => VideoPage( 92 | id: vm.results[index].id, 93 | poster: vm.results[index].poster, 94 | title: vm.results[index].cnname, 95 | ))); 96 | }, 97 | child: VideoListTile( 98 | resource: vm.results[index], 99 | ), 100 | ); 101 | }, 102 | ), 103 | ) 104 | ], 105 | ); 106 | }), 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/pages/comment.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yyets/api/api.dart'; 3 | import 'package:yyets/models/article_comment.dart'; 4 | import 'package:yyets/widgets/comment_bar.dart'; 5 | import 'package:yyets/widgets/comment_listtile.dart'; 6 | import 'package:yyets/widgets/empty_view.dart'; 7 | 8 | class CommentPage extends StatefulWidget { 9 | final String id; 10 | final String channel; 11 | 12 | const CommentPage({Key key, this.id, this.channel = "article"}) 13 | : super(key: key); 14 | @override 15 | _CommentPageState createState() => _CommentPageState(); 16 | } 17 | 18 | class _CommentPageState extends State { 19 | String replayName = ""; 20 | @override 21 | void initState() { 22 | super.initState(); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | appBar: AppBar( 29 | title: Text("全部评论"), 30 | ), 31 | body: Container( 32 | color: Colors.white, 33 | padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), 34 | child: Stack( 35 | children: [ 36 | Container( 37 | margin: EdgeInsets.only(bottom: 50.0), 38 | child: FutureBuilder>( 39 | future: Networking.fetchArticleComments( 40 | id: widget.id, channel: widget.channel), 41 | builder: (BuildContext context, 42 | AsyncSnapshot> snapshot) { 43 | if (snapshot.hasData) { 44 | bool hasComment = snapshot.data.length > 0; 45 | return hasComment 46 | ? ListView.separated( 47 | itemCount: snapshot.data.length, 48 | itemBuilder: (context, index) { 49 | final item = snapshot.data[index]; 50 | final hasReplys = item.replys.length > 0; 51 | List children = [ 52 | CommentListTile( 53 | avatar: item.userpic, 54 | nickName: item.nickname, 55 | dateline: item.dateline, 56 | groupName: item.groupName, 57 | content: item.content, 58 | ), 59 | ]; 60 | if (hasReplys) { 61 | children.addAll(_buildReplys(item.replys)); 62 | } 63 | return GestureDetector( 64 | onTap: () { 65 | setState(() { 66 | replayName = item.nickname; 67 | }); 68 | }, 69 | child: Column(children: children), 70 | ); 71 | }, 72 | separatorBuilder: (context, index) { 73 | return Divider( 74 | height: 5.0, 75 | color: Colors.grey[200], 76 | ); 77 | }, 78 | ) 79 | : FractionallySizedBox( 80 | heightFactor: 1.0, 81 | child: EmptyView( 82 | title: "还没有评论哦.", 83 | )); 84 | } else if (snapshot.hasError) { 85 | return FractionallySizedBox( 86 | heightFactor: 1.0, 87 | child: EmptyView( 88 | title: "出错啦!\n${snapshot.error.toString()}", 89 | )); 90 | } 91 | return Center(child: CircularProgressIndicator()); 92 | }, 93 | ), 94 | ), 95 | Positioned( 96 | bottom: 0.0, 97 | left: 0.0, 98 | right: 0.0, 99 | child: CommentBar( 100 | replayName: replayName, 101 | ), 102 | ) 103 | ], 104 | ), 105 | ), 106 | ); 107 | } 108 | 109 | List _buildReplys(List replys) { 110 | return replys 111 | .map((v) => GestureDetector( 112 | onTap: () { 113 | setState(() { 114 | replayName = v.authorNickname; 115 | }); 116 | }, 117 | child: Container( 118 | margin: EdgeInsets.only(bottom: 5.0), 119 | child: Row( 120 | crossAxisAlignment: CrossAxisAlignment.start, 121 | children: [ 122 | Text( 123 | "${v.authorNickname}:", 124 | style: TextStyle(color: Colors.blue), 125 | ), 126 | SizedBox( 127 | width: 8.0, 128 | ), 129 | Expanded(child: Text("${v.content}")), 130 | ], 131 | ), 132 | ), 133 | )) 134 | .toList(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /lib/models/article.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/common/utils.dart'; 2 | import 'package:yyets/models/video_info.dart'; 3 | 4 | class Article { 5 | String id; 6 | String mid; 7 | String typeCn; 8 | String video480; 9 | String time; 10 | String url; 11 | String type; 12 | String homepage; 13 | String video720; 14 | String oid; 15 | String good; 16 | String cover; 17 | String username; 18 | String userLogo; 19 | int goodStatus; 20 | String nodeType; 21 | String commentsCount; 22 | List imgs; 23 | String content; 24 | String intro; 25 | String poster; 26 | String posterB; 27 | String posterM; 28 | String posterS; 29 | String userpic; 30 | String groupName; 31 | String authorName; 32 | String title; 33 | String dateline; 34 | 35 | Article( 36 | {this.id, 37 | this.mid, 38 | this.typeCn, 39 | this.video480, 40 | this.time, 41 | this.url, 42 | this.type, 43 | this.homepage, 44 | this.video720, 45 | this.oid, 46 | this.good, 47 | this.cover, 48 | this.username, 49 | this.userLogo, 50 | this.goodStatus, 51 | this.nodeType, 52 | this.commentsCount, 53 | this.imgs, 54 | this.content, 55 | this.intro, 56 | this.poster, 57 | this.posterB, 58 | this.posterM, 59 | this.posterS, 60 | this.userpic, 61 | this.groupName, 62 | this.authorName, 63 | this.title, 64 | this.dateline}); 65 | 66 | Article.fromJson(Map json) { 67 | id = json['id']; 68 | mid = json['mid']; 69 | typeCn = json['type_cn']; 70 | video480 = json['video_480']; 71 | time = json['time']; 72 | url = json['url']; 73 | type = json['type']; 74 | homepage = json['homepage']; 75 | video720 = json['video_720']; 76 | oid = json['oid']; 77 | good = json['good']; 78 | cover = json['cover']; 79 | username = json['username']; 80 | userLogo = json['user_logo']; 81 | goodStatus = json['good_status']; 82 | nodeType = json['node_type']; 83 | commentsCount = json['comments_count']; 84 | if (json['imgs'] != null) { 85 | imgs = new List(); 86 | json['imgs'].forEach((v) { 87 | imgs.add(new Imgs.fromJson(v)); 88 | }); 89 | } 90 | content = json['content']; 91 | intro = json['intro']; 92 | poster = json['poster']; 93 | posterB = json['poster_b']; 94 | posterM = json['poster_m']; 95 | posterS = json['poster_s']; 96 | userpic = json['userpic']; 97 | groupName = json['group_name']; 98 | authorName = json["author_name"]; 99 | title = json["title"]; 100 | dateline = json["dateline"]; 101 | } 102 | } 103 | 104 | class ArticleViewModel { 105 | String id; 106 | String author; 107 | String goodCount; 108 | String commentsCount; 109 | String content; 110 | String title; 111 | String avatar; 112 | List images; 113 | String video; 114 | String videoCover; 115 | String tag; 116 | bool liked; 117 | String time; 118 | String cid; 119 | 120 | ArticleViewModel.fromArticle(Article article) { 121 | id = article.id; 122 | author = article.username ?? article.authorName; 123 | goodCount = article.good; 124 | commentsCount = article.commentsCount; 125 | content = article.content ?? article.intro; 126 | title = article.title ?? ""; 127 | avatar = article.userLogo ?? article.userpic; 128 | video = article.video720 ?? ""; 129 | videoCover = article.cover ?? ""; 130 | tag = article.typeCn ?? ""; 131 | liked = article.goodStatus == 1; 132 | if (article.dateline != null) { 133 | time = Utils.readTimestamp(int.parse(article.dateline)); 134 | } else { 135 | time = Utils.readTimestamp(int.parse(article.time)); 136 | } 137 | cid = article.mid; 138 | 139 | if (isVideo) { 140 | images = []; 141 | } else if (article.type == "news" || 142 | article.type == "t_review" || 143 | article.type == "m_review" || 144 | article.type == "new_review" || 145 | article.type == "recom") { 146 | images = [article.posterB]; 147 | } else { 148 | images = article.imgs.map((i) => i.url).toList() ?? []; 149 | } 150 | } 151 | 152 | ArticleViewModel.fromVideoArticle(ArticleList article) { 153 | id = article.id; 154 | author = article.authorName; 155 | goodCount = article.good; 156 | commentsCount = article.commentsCount; 157 | content = article.intro; 158 | title = article.title ?? ""; 159 | avatar = article.userpic; 160 | video = ""; 161 | videoCover = ""; 162 | tag = article.typeCn ?? ""; 163 | liked = article.goodStatus == 1; 164 | images = []; 165 | 166 | time = article.dateline ?? ""; 167 | if (article.poster.isNotEmpty) { 168 | images = [article.poster]; 169 | } 170 | } 171 | 172 | bool get isVideo { 173 | return video.isNotEmpty; 174 | } 175 | } 176 | 177 | class Imgs { 178 | String bigImgUrl; 179 | int height; 180 | String url; 181 | int width; 182 | 183 | Imgs({this.bigImgUrl, this.height, this.url, this.width}); 184 | 185 | Imgs.fromJson(Map json) { 186 | bigImgUrl = json['bigImgUrl']; 187 | height = json['height']; 188 | url = json['url']; 189 | width = json['width']; 190 | } 191 | 192 | Map toJson() { 193 | final Map data = new Map(); 194 | data['bigImgUrl'] = this.bigImgUrl; 195 | data['height'] = this.height; 196 | data['url'] = this.url; 197 | data['width'] = this.width; 198 | return data; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | after_layout: 5 | dependency: transitive 6 | description: 7 | name: after_layout 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "1.0.7+1" 11 | async: 12 | dependency: transitive 13 | description: 14 | name: async 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.2.0" 18 | boolean_selector: 19 | dependency: transitive 20 | description: 21 | name: boolean_selector 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.0.4" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.1.2" 32 | chewie: 33 | dependency: "direct main" 34 | description: 35 | name: chewie 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "0.9.7" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.11" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "0.1.2" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_redux: 59 | dependency: "direct main" 60 | description: 61 | name: flutter_redux 62 | url: "https://pub.dartlang.org" 63 | source: hosted 64 | version: "0.5.3" 65 | flutter_test: 66 | dependency: "direct dev" 67 | description: flutter 68 | source: sdk 69 | version: "0.0.0" 70 | http: 71 | dependency: "direct main" 72 | description: 73 | name: http 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "0.12.0+2" 77 | http_parser: 78 | dependency: transitive 79 | description: 80 | name: http_parser 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "3.1.3" 84 | intl: 85 | dependency: "direct main" 86 | description: 87 | name: intl 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "0.15.8" 91 | matcher: 92 | dependency: transitive 93 | description: 94 | name: matcher 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "0.12.5" 98 | meta: 99 | dependency: transitive 100 | description: 101 | name: meta 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "1.1.6" 105 | open_iconic_flutter: 106 | dependency: transitive 107 | description: 108 | name: open_iconic_flutter 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "0.3.0" 112 | path: 113 | dependency: transitive 114 | description: 115 | name: path 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "1.6.2" 119 | pedantic: 120 | dependency: transitive 121 | description: 122 | name: pedantic 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "1.7.0" 126 | photo_view: 127 | dependency: "direct main" 128 | description: 129 | name: photo_view 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "0.4.0" 133 | quiver: 134 | dependency: transitive 135 | description: 136 | name: quiver 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "2.0.3" 140 | redux: 141 | dependency: "direct main" 142 | description: 143 | name: redux 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "3.0.0" 147 | rxdart: 148 | dependency: "direct main" 149 | description: 150 | name: rxdart 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "0.22.0" 154 | screen: 155 | dependency: transitive 156 | description: 157 | name: screen 158 | url: "https://pub.dartlang.org" 159 | source: hosted 160 | version: "0.0.5" 161 | sky_engine: 162 | dependency: transitive 163 | description: flutter 164 | source: sdk 165 | version: "0.0.99" 166 | source_span: 167 | dependency: transitive 168 | description: 169 | name: source_span 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "1.5.5" 173 | stack_trace: 174 | dependency: transitive 175 | description: 176 | name: stack_trace 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "1.9.3" 180 | stream_channel: 181 | dependency: transitive 182 | description: 183 | name: stream_channel 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "2.0.0" 187 | string_scanner: 188 | dependency: transitive 189 | description: 190 | name: string_scanner 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "1.0.4" 194 | term_glyph: 195 | dependency: transitive 196 | description: 197 | name: term_glyph 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "1.1.0" 201 | test_api: 202 | dependency: transitive 203 | description: 204 | name: test_api 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "0.2.5" 208 | typed_data: 209 | dependency: transitive 210 | description: 211 | name: typed_data 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "1.1.6" 215 | vector_math: 216 | dependency: transitive 217 | description: 218 | name: vector_math 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "2.0.8" 222 | video_player: 223 | dependency: transitive 224 | description: 225 | name: video_player 226 | url: "https://pub.dartlang.org" 227 | source: hosted 228 | version: "0.10.1+2" 229 | sdks: 230 | dart: ">=2.2.0 <3.0.0" 231 | flutter: ">=1.5.9-pre.94 <2.0.0" 232 | -------------------------------------------------------------------------------- /lib/models/video_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:yyets/models/help.dart'; 2 | import 'package:yyets/models/resource.dart'; 3 | 4 | class VideoInfo { 5 | List similarList; 6 | List articleList; 7 | List helpList; 8 | List seasonList; 9 | ResourceInfo resourceInfo; 10 | 11 | VideoInfo( 12 | {this.similarList, 13 | this.articleList, 14 | this.helpList, 15 | this.seasonList, 16 | this.resourceInfo}); 17 | 18 | VideoInfo.initialState() { 19 | similarList = []; 20 | articleList = []; 21 | helpList = []; 22 | seasonList = []; 23 | resourceInfo = ResourceInfo.initialState(); 24 | } 25 | 26 | VideoInfo.fromJson(Map json) { 27 | if (json['similar_list'] != null) { 28 | similarList = new List(); 29 | json['similar_list'].forEach((v) { 30 | similarList.add(new Resource.fromJson(v)); 31 | }); 32 | } 33 | if (json['article_list'] != null) { 34 | articleList = new List(); 35 | json['article_list'].forEach((v) { 36 | articleList.add(new ArticleList.fromJson(v)); 37 | }); 38 | } 39 | if (json['help_list'] != null) { 40 | helpList = new List(); 41 | json['help_list'].forEach((v) { 42 | helpList.add(new Help.fromJson(v)); 43 | }); 44 | } 45 | if (json['season_list'] != null) { 46 | seasonList = new List(); 47 | json['season_list'].forEach((v) { 48 | seasonList.add(new SeasonList.fromJson(v)); 49 | }); 50 | } 51 | resourceInfo = json['resource_info'] != null 52 | ? new ResourceInfo.fromJson(json['resource_info']) 53 | : null; 54 | } 55 | } 56 | 57 | class ArticleList { 58 | String id; 59 | String intro; 60 | String commentsCount; 61 | String typeCn; 62 | String author; 63 | String userpic; 64 | String type; 65 | String title; 66 | String good; 67 | String dateline; 68 | String groupName; 69 | int goodStatus; 70 | String poster; 71 | String authorName; 72 | String posterB; 73 | String posterM; 74 | String posterS; 75 | String views; 76 | 77 | ArticleList( 78 | {this.id, 79 | this.intro, 80 | this.commentsCount, 81 | this.typeCn, 82 | this.author, 83 | this.userpic, 84 | this.type, 85 | this.title, 86 | this.good, 87 | this.dateline, 88 | this.groupName, 89 | this.goodStatus, 90 | this.poster, 91 | this.authorName, 92 | this.posterB, 93 | this.posterM, 94 | this.posterS, 95 | this.views}); 96 | 97 | ArticleList.fromJson(Map json) { 98 | id = json['id']; 99 | intro = json['intro']; 100 | commentsCount = json['comments_count'].toString(); 101 | typeCn = json['type_cn']; 102 | author = json['author']; 103 | userpic = json['userpic']; 104 | type = json['type']; 105 | title = json['title']; 106 | good = json['good']; 107 | dateline = json['dateline']; 108 | groupName = json['group_name']; 109 | goodStatus = json['good_status']; 110 | poster = json['poster']; 111 | authorName = json['author_name']; 112 | posterB = json['poster_b']; 113 | posterM = json['poster_m']; 114 | posterS = json['poster_s']; 115 | views = json['views']; 116 | } 117 | } 118 | 119 | class SeasonList { 120 | String season; 121 | String seasonCn; 122 | List episodeList; 123 | 124 | SeasonList({this.season, this.seasonCn, this.episodeList}); 125 | 126 | SeasonList.fromJson(Map json) { 127 | season = json['season']; 128 | seasonCn = json['season_cn']; 129 | if (json['episode_list'] != null) { 130 | episodeList = new List(); 131 | json['episode_list'].forEach((v) { 132 | episodeList.add(new EpisodeList.fromJson(v)); 133 | }); 134 | } 135 | } 136 | } 137 | 138 | class EpisodeList { 139 | int playStatus; 140 | String playTime; 141 | String playStatusCn; 142 | String episode; 143 | 144 | EpisodeList( 145 | {this.playStatus, this.playTime, this.playStatusCn, this.episode}); 146 | 147 | EpisodeList.fromJson(Map json) { 148 | playStatus = json['play_status']; 149 | playTime = json['play_time']; 150 | playStatusCn = json['play_status_cn']; 151 | episode = json['episode']; 152 | } 153 | } 154 | 155 | class ResourceInfo { 156 | String rank; 157 | String cnname; 158 | String lang; 159 | String playStatus; 160 | String favorites; 161 | String aliasname; 162 | String channel; 163 | String score; 164 | String views; 165 | String category; 166 | String posterS; 167 | String id; 168 | String enname; 169 | String format; 170 | String poster; 171 | String itemupdate; 172 | String premiere; 173 | String area; 174 | String scoreCounts; 175 | int favoriteStatus; 176 | String tvstation; 177 | String posterM; 178 | String posterB; 179 | String content; 180 | 181 | ResourceInfo( 182 | {this.rank, 183 | this.cnname, 184 | this.lang, 185 | this.playStatus, 186 | this.favorites, 187 | this.aliasname, 188 | this.channel, 189 | this.score, 190 | this.views, 191 | this.category, 192 | this.posterS, 193 | this.id, 194 | this.enname, 195 | this.format, 196 | this.poster, 197 | this.itemupdate, 198 | this.premiere, 199 | this.area, 200 | this.scoreCounts, 201 | this.favoriteStatus, 202 | this.tvstation, 203 | this.posterM, 204 | this.posterB, 205 | this.content}); 206 | 207 | ResourceInfo.initialState() { 208 | cnname = ""; 209 | channel = "tv"; 210 | posterM = ""; 211 | poster = ""; 212 | } 213 | 214 | ResourceInfo.fromJson(Map json) { 215 | rank = json['rank']; 216 | cnname = json['cnname']; 217 | lang = json['lang']; 218 | playStatus = json['play_status']; 219 | favorites = json['favorites']; 220 | aliasname = json['aliasname']; 221 | channel = json['channel']; 222 | score = json['score']; 223 | views = json['views']; 224 | category = json['category']; 225 | posterS = json['poster_s']; 226 | id = json['id']; 227 | enname = json['enname']; 228 | format = json['format']; 229 | poster = json['poster']; 230 | itemupdate = json['itemupdate']; 231 | premiere = json['premiere']; 232 | area = json['area']; 233 | scoreCounts = json['score_counts']; 234 | favoriteStatus = json['favorite_status']; 235 | tvstation = json['tvstation']; 236 | posterM = json['poster_m']; 237 | posterB = json['poster_b']; 238 | content = json['content']; 239 | } 240 | } 241 | --------------------------------------------------------------------------------