├── .gitignore
├── .metadata
├── 48.jpg
├── DoubanAPI.md
├── README.md
├── android
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── doubanapp
│ │ │ │ └── MainActivity.java
│ │ └── res
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── ic_launcher.png
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-ldpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_bg.png
│ │ │ └── ic_launcher_fg.png
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
└── images
│ ├── bg_books_stack_default.png
│ ├── bg_music_stack_default.png
│ ├── bg_person_center_default.webp
│ ├── bg_videos_stack_default.png
│ ├── douban_film_list.png
│ ├── douban_guess.png
│ ├── douban_top.png
│ ├── find_movie.png
│ ├── home.png
│ ├── ic_action_playable_video_s.png
│ ├── ic_arrow_back.png
│ ├── ic_circle_yellow.png
│ ├── ic_default_img_subject_movie.9.png
│ ├── ic_group_check_anonymous.png
│ ├── ic_group_checked_anonymous.png
│ ├── ic_group_top.png
│ ├── ic_info_done.png
│ ├── ic_info_wish.png
│ ├── ic_launcher.png
│ ├── ic_me_doulist.png
│ ├── ic_me_follows.png
│ ├── ic_me_journal.png
│ ├── ic_me_photo_album.png
│ ├── ic_me_wallet.png
│ ├── ic_more.png
│ ├── ic_new_empty_view_default.png
│ ├── ic_notification_tv_calendar_comments.png
│ ├── ic_notify.png
│ ├── ic_pause.png
│ ├── ic_playing.png
│ ├── ic_status_detail_reshare_icon.png
│ ├── ic_subject_mark_added.png
│ ├── ic_subject_rating_mark_wish.png
│ ├── ic_tab_group_active.png
│ ├── ic_tab_group_normal.png
│ ├── ic_tab_home_active.png
│ ├── ic_tab_home_normal.png
│ ├── ic_tab_profile_active.png
│ ├── ic_tab_profile_normal.png
│ ├── ic_tab_shiji_active.png
│ ├── ic_tab_shiji_normal.png
│ ├── ic_tab_subject_active.png
│ ├── ic_tab_subject_normal.png
│ ├── ic_vote.png
│ ├── ic_vote_normal_large.png
│ ├── landscape.png
│ ├── person_top_bg.jpg
│ └── sofa.png
├── douya-qr.png
├── douya-release.png
├── ios
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── Runner
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── icon-1024.png
│ │ ├── icon-20-ipad.png
│ │ ├── icon-20@2x-ipad.png
│ │ ├── icon-20@2x.png
│ │ ├── icon-20@3x.png
│ │ ├── icon-29-ipad.png
│ │ ├── icon-29.png
│ │ ├── icon-29@2x-ipad.png
│ │ ├── icon-29@2x.png
│ │ ├── icon-29@3x.png
│ │ ├── icon-40.png
│ │ ├── icon-40@2x.png
│ │ ├── icon-40@3x.png
│ │ ├── icon-50.png
│ │ ├── icon-50@2x.png
│ │ ├── icon-57.png
│ │ ├── icon-57@2x.png
│ │ ├── icon-60@2x.png
│ │ ├── icon-60@3x.png
│ │ ├── icon-72.png
│ │ ├── icon-72@2x.png
│ │ ├── icon-76.png
│ │ ├── icon-76@2x.png
│ │ └── icon-83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── main.m
├── lib
├── bean
│ ├── celebrity_entity.dart
│ ├── celebrity_work_entity.dart
│ ├── comments_entity.dart
│ ├── movie_detail_bean.dart
│ ├── movie_long_comments_entity.dart
│ ├── search_result_entity.dart
│ ├── subject_entity.dart
│ └── top_item_bean.dart
├── constant
│ ├── cache_key.dart
│ ├── color_constant.dart
│ ├── constant.dart
│ └── text_size_constant.dart
├── demo
│ ├── image_colors.dart
│ ├── my_scroll_view.dart
│ ├── my_scrollable.dart
│ └── slide_container.dart
├── generated
│ └── i18n.dart
├── http
│ ├── API.dart
│ ├── http_request.dart
│ └── mock_request.dart
├── main.dart
├── pages
│ ├── container_page.dart
│ ├── detail
│ │ ├── detail_page.dart
│ │ ├── detail_title_widget.dart
│ │ ├── long_comment_widget.dart
│ │ ├── look_confirm_button.dart
│ │ ├── score_start.dart
│ │ ├── summary_widget.dart
│ │ └── tags_widget.dart
│ ├── douya_top_250_list_widget.dart
│ ├── group
│ │ └── group_page.dart
│ ├── home
│ │ ├── home_app_bar.dart
│ │ ├── home_page.dart
│ │ └── my_home_tab_bar.dart
│ ├── movie
│ │ ├── book_audio_video_page.dart
│ │ ├── hot_soon_movie_widget.dart
│ │ ├── hot_soon_tab_bar.dart
│ │ ├── movie_app_bar.dart
│ │ ├── movie_list_page.dart
│ │ ├── movie_page.dart
│ │ ├── movie_page_new.dart
│ │ ├── title_widget.dart
│ │ ├── today_play_movie_widget.dart
│ │ ├── top_item_widget.dart
│ │ └── tv_page.dart
│ ├── person
│ │ └── person_center_page.dart
│ ├── person_detail_page.dart
│ ├── photo_hero_page.dart
│ ├── search
│ │ └── search_page.dart
│ ├── shop_page.dart
│ ├── splash
│ │ └── splash_widget.dart
│ ├── videos_play_page.dart
│ └── web_view_page.dart
├── repository
│ ├── movie_repository.dart
│ └── person_detail_repository.dart
├── router.dart
├── util
│ ├── pick_img_main_color.dart
│ └── screen_utils.dart
└── widgets
│ ├── animal_photo.dart
│ ├── bottom_drag_widget.dart
│ ├── image
│ ├── LaminatedImage.dart
│ ├── cache_img_radius.dart
│ ├── heart_img_widget.dart
│ ├── network_img_widget.dart
│ └── radius_img.dart
│ ├── item_count_title.dart
│ ├── loading_widget.dart
│ ├── my_tab_bar_widget.dart
│ ├── rating_bar.dart
│ ├── search_text_field_widget.dart
│ ├── subject_mark_image_widget.dart
│ ├── title_bar.dart
│ ├── video_progress_bar.dart
│ └── video_widget.dart
├── logo.png
├── mock
├── celebrity.json
├── coming_soon.json
├── comments.json
├── group.json
├── in_theaters.json
├── reviews.json
├── subject_26266893.json
├── top250.json
├── weekly.json
└── works.json
├── pubspec.yaml
└── res
└── values
└── strings_en.arb
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.lock
4 | *.log
5 | *.pyc
6 | *.swp
7 | .DS_Store
8 | .atom/
9 | .buildlog/
10 | .history
11 | .svn/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # Visual Studio Code related
20 | .vscode/
21 |
22 | # Flutter/Dart/Pub related
23 | **/doc/api/
24 | .dart_tool/
25 | .flutter-plugins
26 | .packages
27 | .pub-cache/
28 | .pub/
29 | build/
30 |
31 | # Android related
32 | **/android/**/gradle-wrapper.jar
33 | **/android/.gradle
34 | **/android/captures/
35 | **/android/gradlew
36 | **/android/gradlew.bat
37 | **/android/local.properties
38 | **/android/**/GeneratedPluginRegistrant.java
39 |
40 | # iOS/XCode related
41 | **/ios/**/*.mode1v3
42 | **/ios/**/*.mode2v3
43 | **/ios/**/*.moved-aside
44 | **/ios/**/*.pbxuser
45 | **/ios/**/*.perspectivev3
46 | **/ios/**/*sync/
47 | **/ios/**/.sconsign.dblite
48 | **/ios/**/.tags*
49 | **/ios/**/.vagrant/
50 | **/ios/**/DerivedData/
51 | **/ios/**/Icon?
52 | **/ios/**/Pods/
53 | **/ios/**/.symlinks/
54 | **/ios/**/profile
55 | **/ios/**/xcuserdata
56 | **/ios/.generated/
57 | **/ios/Flutter/App.framework
58 | **/ios/Flutter/Flutter.framework
59 | **/ios/Flutter/Generated.xcconfig
60 | **/ios/Flutter/app.flx
61 | **/ios/Flutter/app.zip
62 | **/ios/Flutter/flutter_assets/
63 | **/ios/ServiceDefinitions.json
64 | **/ios/Runner/GeneratedPluginRegistrant.*
65 |
66 | # Exceptions to above rules.
67 | !**/ios/**/default.mode1v3
68 | !**/ios/**/default.mode2v3
69 | !**/ios/**/default.pbxuser
70 | !**/ios/**/default.perspectivev3
71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
72 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 7a88fbc5fd987dce78e468ec45e9e841a49f422d
8 | channel: dev
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/48.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/48.jpg
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 | [](https://github.com/kaina404/FlutterDouBan/stargazers)
5 | [](https://github.com/kaina404/FlutterDouBan/network)
6 | [](https://github.com/kaina404/FlutterDouBan/issues)
7 |
8 | > SDK Version
9 | ```java
10 | kaina404 ~ % flutter --version
11 | Flutter 2.5.3 • channel stable • https://github.com/flutter/flutter.git
12 | Framework • revision 18116933e7 (4 months ago) • 2021-10-15 10:46:35 -0700
13 | Engine • revision d3ea636dc5
14 | Tools • Dart 2.14.4
15 | kaina404 ~ % flutter doctor
16 | Doctor summary (to see all details, run flutter doctor -v):
17 | [✓] Flutter (Channel stable, 2.5.3, on macOS 11.4 20F71 darwin-arm, locale zh-Hans-CN)
18 | [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
19 | [✓] Xcode - develop for iOS and macOS
20 | [✓] Chrome - develop for the web
21 | [✓] Android Studio (version 2020.3)
22 | [✓] VS Code (version 1.62.3)
23 | [✓] Connected device (3 available)
24 | ```
25 |
26 | > 如果产生其他依赖无法编译的问题,可以尝试将`pubspec.yaml`中的`dependencies`中的所有依赖的"^"去掉,重新编译尝试。
27 | ## [博客地址](https://www.jianshu.com/u/1c09737416aa)
28 | 真正的豆瓣客户端,90%还原豆瓣。首页、书影音、小组、市集及个人中心,一个不拉。项目持续更新中...
29 | # [演示预览(右键,新标签页面打开)](https://img.xuvip.top/douyademo.mp4)
30 |
31 | **如果您觉得还可以的话,给个Star白~**
32 |
33 | # 使用Flutter开发一个豆瓣App
34 |
35 | * 此项目,90%还原某瓣APP,所有UI均按照某瓣来实现。
36 | * 项目中的数据均来自豆瓣api真实有效数据
37 | * 项目中用到了几乎所有的Flutter widget
38 | * 还有两个比较大的自定义魔改源码实现特效
39 | * 大年初一也在维护的项目
40 |
41 | > APP中所有数据均为真实数据。但是默认,对于"书影音单个电影tab"的数据,使用模拟数据。因为,频繁的打开关闭APP,会频繁调用
42 | 这个接口。接口是有调用限制的,次数过于频繁,会被锁IP。如果想看真实数据,则可以进入
43 | "我的",然后打开"书影音数据来自网络"开关后,重启APP即可。
44 |
45 | # 下载地址
46 | ## [打开新页面扫码下载](https://upload-images.jianshu.io/upload_images/3884536-d9adbda0e5f61c84.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
47 | #### [Release版本下载地址](https://img.xuvip.top/douya_release.apk)
48 |
49 |
50 | 
51 |
52 |
53 |
54 |
55 |
56 | #### Demo(刷不出gif图的,耐心等待一会,或者多刷几次。)
57 | 
58 | 
59 | 
60 | 
61 | 
62 | 
63 |
64 | 
65 |
66 | 
67 |
68 | 
69 |
70 | 
71 |
72 | 
73 |
74 | 
75 |
76 | 
77 |
78 | 
79 |
80 | 
81 |
82 | 
83 |
84 | 
85 |
86 | 
87 |
88 |
89 |
90 | # dev-open
91 |
92 | ### 大家可以向这个分支根据豆瓣UI做设计稿,提交代码
93 |
94 | * 这个分支供广大Flutter开发者来学习Flutter
95 | * **此分支囊括了Flutter 90%的组件的基本与组合使用**,是初学者真正实践的不错选择
96 | * 在此豆芽APP的首页实现与影片详情的UI特效,基于魔改Flutter源码。有兴趣可以看看
97 | * 大家想实战自己的Flutter能力,可以将某瓣APP作为设计稿,完成需求,并提交
98 | * 每位开发者提交的代码,我都会在文档中进行备注
99 |
100 | #### 注意!!!建议使用模拟数据(mock_request.dart)
101 | * 每个接口均有一定的调用限制
102 | * *大家pull下的代码,进行测试调试时,对于特定接口,返回的特定数据。尽量不要每次求请求一次。可以请求到一次真实数据后,转成json保存
103 | 到本地。然后,自己调试开发的时候,使用这个模拟数据即可。* [可参考mock_request.dart]
104 |
105 |
106 |
107 | # 未来版本计划(欢迎Flutter爱好者前来认领)
108 |
109 | **欢迎Flutter爱好者共同完成**
110 |
111 | > 涉及到UI,可参考豆瓣。
112 |
113 | ### 萌新TASK
114 |
115 | * 完成任意一个未实现的按钮
116 | * 完成任意一个未实现的页面
117 | * 优化原有Widget
118 | * 爱好者想实现但是未能实现的需求
119 | * More ...
120 |
121 | ### 进阶TASK
122 |
123 | * 优化代码
124 | * 适当缓存数据
125 | * 解决卡顿(可参考:https://flutter-io.cn/docs/testing/ui-performance)
126 | * 优化路由(可参考咸鱼方案:https://www.yuque.com/xytech/flutter/vf1dpf)
127 |
128 | ### 老手TASK
129 |
130 | * [接入rxdart](https://github.com/ReactiveX/rxdart)
131 | * [使用Fish Redux 重构](https://www.yuque.com/xytech/flutter/ycc9ni)
132 |
133 |
134 |
135 | # 对魔改源码或者喜欢翻源码的童鞋可以看看下面两个
136 |
137 | * 魔改Flutter AppBar源码实现豆瓣头部特效
138 |
139 | 
140 |
141 | * 魔改源码实现电影详情抽屉特效(GIF图如果加载不出来,多刷几次)
142 |
143 | 
144 |
145 | 
146 |
147 | 
148 |
149 |
150 | # 页面介绍
151 |
152 | * 首页 pages/home
153 |
154 | * homo_app_bar.dart 首页导航头
155 | * home_page.dart 首页
156 | * my_home_tab_bar.dart 首页tab
157 |
158 | * 书影音 pages/movie
159 |
160 | * book_audio_video_page.dart 书影音页面
161 | * detail_page.dart 影片、电视详情页面
162 | * person_detail_page.dart 演员页面介绍
163 | * ... 页面都有注释
164 |
165 | * 小组 pages/group
166 |
167 | * 市集 shop_page.dart
168 | * 市集的数据使用两个webview
169 |
170 | * 我的 page/person
171 |
172 |
173 |
174 | # 更新记录
175 |
176 | * dev-0.1
177 | * 魔改源码实现电影详情抽屉特效
178 | * 魔改Flutter AppBar源码实现豆瓣头部特效
179 | * 优化页面逻辑
180 | * 优化加载速度
181 |
182 | * master
183 | * 基本网络请求框架、UI框架
184 | * 已经填入了"最为复杂的电影TAB页面"、影视详情页面、小组页面(UI与豆瓣一致,数据使用的是热映榜)
185 | * 页面上下滑动
186 | * 页面上下+左右滑动
187 | * 数据加载
188 | * TAB页面滑动
189 | * ....
190 | * 作为基本版本,一些逻辑不够完善,有一些bug。
191 |
192 | # 默认条约
193 |
194 | 此项目仅供大家交流沟通使用,不得用于任何商业以及利益活动。由此引起的责任,跟我无关。谢谢!
195 |
196 | # **如果您觉得还可以的话,给个Star白~**
197 |
198 | # Thanks
199 |
200 |
201 |
202 | ## Getting Started
203 |
204 | This project is a starting point for a Flutter application.
205 |
206 | A few resources to get you started if this is your first Flutter project:
207 |
208 | - [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab)
209 | - [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook)
210 |
211 | For help getting started with Flutter, view our
212 | [online documentation](https://flutter.io/docs), which offers tutorials,
213 | samples, guidance on mobile development, and a full API reference.
214 |
215 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
26 |
27 | android {
28 | compileSdkVersion 28
29 |
30 | lintOptions {
31 | disable 'InvalidPackage'
32 | }
33 |
34 | defaultConfig {
35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
36 | applicationId "com.example.doubanapp"
37 | minSdkVersion 19
38 | targetSdkVersion 28
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 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
26 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/example/doubanapp/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.doubanapp;
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 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/android/app/src/main/res/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/android/app/src/main/res/mipmap-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_bg.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_fg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_fg.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.2.1'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | google()
15 | jcenter()
16 | }
17 | }
18 |
19 | rootProject.buildDir = '../build'
20 | subprojects {
21 | project.buildDir = "${rootProject.buildDir}/${project.name}"
22 | }
23 | subprojects {
24 | project.evaluationDependsOn(':app')
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/images/bg_books_stack_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/bg_books_stack_default.png
--------------------------------------------------------------------------------
/assets/images/bg_music_stack_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/bg_music_stack_default.png
--------------------------------------------------------------------------------
/assets/images/bg_person_center_default.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/bg_person_center_default.webp
--------------------------------------------------------------------------------
/assets/images/bg_videos_stack_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/bg_videos_stack_default.png
--------------------------------------------------------------------------------
/assets/images/douban_film_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/douban_film_list.png
--------------------------------------------------------------------------------
/assets/images/douban_guess.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/douban_guess.png
--------------------------------------------------------------------------------
/assets/images/douban_top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/douban_top.png
--------------------------------------------------------------------------------
/assets/images/find_movie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/find_movie.png
--------------------------------------------------------------------------------
/assets/images/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/home.png
--------------------------------------------------------------------------------
/assets/images/ic_action_playable_video_s.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_action_playable_video_s.png
--------------------------------------------------------------------------------
/assets/images/ic_arrow_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_arrow_back.png
--------------------------------------------------------------------------------
/assets/images/ic_circle_yellow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_circle_yellow.png
--------------------------------------------------------------------------------
/assets/images/ic_default_img_subject_movie.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_default_img_subject_movie.9.png
--------------------------------------------------------------------------------
/assets/images/ic_group_check_anonymous.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_group_check_anonymous.png
--------------------------------------------------------------------------------
/assets/images/ic_group_checked_anonymous.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_group_checked_anonymous.png
--------------------------------------------------------------------------------
/assets/images/ic_group_top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_group_top.png
--------------------------------------------------------------------------------
/assets/images/ic_info_done.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_info_done.png
--------------------------------------------------------------------------------
/assets/images/ic_info_wish.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_info_wish.png
--------------------------------------------------------------------------------
/assets/images/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_launcher.png
--------------------------------------------------------------------------------
/assets/images/ic_me_doulist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_me_doulist.png
--------------------------------------------------------------------------------
/assets/images/ic_me_follows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_me_follows.png
--------------------------------------------------------------------------------
/assets/images/ic_me_journal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_me_journal.png
--------------------------------------------------------------------------------
/assets/images/ic_me_photo_album.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_me_photo_album.png
--------------------------------------------------------------------------------
/assets/images/ic_me_wallet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_me_wallet.png
--------------------------------------------------------------------------------
/assets/images/ic_more.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_more.png
--------------------------------------------------------------------------------
/assets/images/ic_new_empty_view_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_new_empty_view_default.png
--------------------------------------------------------------------------------
/assets/images/ic_notification_tv_calendar_comments.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_notification_tv_calendar_comments.png
--------------------------------------------------------------------------------
/assets/images/ic_notify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_notify.png
--------------------------------------------------------------------------------
/assets/images/ic_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_pause.png
--------------------------------------------------------------------------------
/assets/images/ic_playing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_playing.png
--------------------------------------------------------------------------------
/assets/images/ic_status_detail_reshare_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_status_detail_reshare_icon.png
--------------------------------------------------------------------------------
/assets/images/ic_subject_mark_added.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_subject_mark_added.png
--------------------------------------------------------------------------------
/assets/images/ic_subject_rating_mark_wish.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_subject_rating_mark_wish.png
--------------------------------------------------------------------------------
/assets/images/ic_tab_group_active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_tab_group_active.png
--------------------------------------------------------------------------------
/assets/images/ic_tab_group_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_tab_group_normal.png
--------------------------------------------------------------------------------
/assets/images/ic_tab_home_active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_tab_home_active.png
--------------------------------------------------------------------------------
/assets/images/ic_tab_home_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_tab_home_normal.png
--------------------------------------------------------------------------------
/assets/images/ic_tab_profile_active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_tab_profile_active.png
--------------------------------------------------------------------------------
/assets/images/ic_tab_profile_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_tab_profile_normal.png
--------------------------------------------------------------------------------
/assets/images/ic_tab_shiji_active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_tab_shiji_active.png
--------------------------------------------------------------------------------
/assets/images/ic_tab_shiji_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_tab_shiji_normal.png
--------------------------------------------------------------------------------
/assets/images/ic_tab_subject_active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_tab_subject_active.png
--------------------------------------------------------------------------------
/assets/images/ic_tab_subject_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_tab_subject_normal.png
--------------------------------------------------------------------------------
/assets/images/ic_vote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_vote.png
--------------------------------------------------------------------------------
/assets/images/ic_vote_normal_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/ic_vote_normal_large.png
--------------------------------------------------------------------------------
/assets/images/landscape.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/landscape.png
--------------------------------------------------------------------------------
/assets/images/person_top_bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/person_top_bg.jpg
--------------------------------------------------------------------------------
/assets/images/sofa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/assets/images/sofa.png
--------------------------------------------------------------------------------
/douya-qr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/douya-qr.png
--------------------------------------------------------------------------------
/douya-release.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/douya-release.png
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 9.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
32 | end
33 |
34 | post_install do |installer|
35 | installer.pods_project.targets.each do |target|
36 | flutter_additional_ios_build_settings(target)
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
77 |
83 |
84 |
85 |
86 |
88 |
89 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : FlutterAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #include "AppDelegate.h"
2 | #include "GeneratedPluginRegistrant.h"
3 |
4 | @implementation AppDelegate
5 |
6 | - (BOOL)application:(UIApplication *)application
7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
8 | [GeneratedPluginRegistrant registerWithRegistry:self];
9 | // Override point for customization after application launch.
10 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
11 | }
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "size": "20x20",
5 | "idiom": "iphone",
6 | "filename": "icon-20@2x.png",
7 | "scale": "2x"
8 | },
9 | {
10 | "size": "20x20",
11 | "idiom": "iphone",
12 | "filename": "icon-20@3x.png",
13 | "scale": "3x"
14 | },
15 | {
16 | "size": "29x29",
17 | "idiom": "iphone",
18 | "filename": "icon-29.png",
19 | "scale": "1x"
20 | },
21 | {
22 | "size": "29x29",
23 | "idiom": "iphone",
24 | "filename": "icon-29@2x.png",
25 | "scale": "2x"
26 | },
27 | {
28 | "size": "29x29",
29 | "idiom": "iphone",
30 | "filename": "icon-29@3x.png",
31 | "scale": "3x"
32 | },
33 | {
34 | "size": "40x40",
35 | "idiom": "iphone",
36 | "filename": "icon-40@2x.png",
37 | "scale": "2x"
38 | },
39 | {
40 | "size": "40x40",
41 | "idiom": "iphone",
42 | "filename": "icon-40@3x.png",
43 | "scale": "3x"
44 | },
45 | {
46 | "size": "57x57",
47 | "idiom": "iphone",
48 | "filename": "icon-57.png",
49 | "scale": "1x"
50 | },
51 | {
52 | "size": "57x57",
53 | "idiom": "iphone",
54 | "filename": "icon-57@2x.png",
55 | "scale": "2x"
56 | },
57 | {
58 | "size": "60x60",
59 | "idiom": "iphone",
60 | "filename": "icon-60@2x.png",
61 | "scale": "2x"
62 | },
63 | {
64 | "size": "60x60",
65 | "idiom": "iphone",
66 | "filename": "icon-60@3x.png",
67 | "scale": "3x"
68 | },
69 | {
70 | "size": "20x20",
71 | "idiom": "ipad",
72 | "filename": "icon-20-ipad.png",
73 | "scale": "1x"
74 | },
75 | {
76 | "size": "20x20",
77 | "idiom": "ipad",
78 | "filename": "icon-20@2x-ipad.png",
79 | "scale": "2x"
80 | },
81 | {
82 | "size": "29x29",
83 | "idiom": "ipad",
84 | "filename": "icon-29-ipad.png",
85 | "scale": "1x"
86 | },
87 | {
88 | "size": "29x29",
89 | "idiom": "ipad",
90 | "filename": "icon-29@2x-ipad.png",
91 | "scale": "2x"
92 | },
93 | {
94 | "size": "40x40",
95 | "idiom": "ipad",
96 | "filename": "icon-40.png",
97 | "scale": "1x"
98 | },
99 | {
100 | "size": "40x40",
101 | "idiom": "ipad",
102 | "filename": "icon-40@2x.png",
103 | "scale": "2x"
104 | },
105 | {
106 | "size": "50x50",
107 | "idiom": "ipad",
108 | "filename": "icon-50.png",
109 | "scale": "1x"
110 | },
111 | {
112 | "size": "50x50",
113 | "idiom": "ipad",
114 | "filename": "icon-50@2x.png",
115 | "scale": "2x"
116 | },
117 | {
118 | "size": "72x72",
119 | "idiom": "ipad",
120 | "filename": "icon-72.png",
121 | "scale": "1x"
122 | },
123 | {
124 | "size": "72x72",
125 | "idiom": "ipad",
126 | "filename": "icon-72@2x.png",
127 | "scale": "2x"
128 | },
129 | {
130 | "size": "76x76",
131 | "idiom": "ipad",
132 | "filename": "icon-76.png",
133 | "scale": "1x"
134 | },
135 | {
136 | "size": "76x76",
137 | "idiom": "ipad",
138 | "filename": "icon-76@2x.png",
139 | "scale": "2x"
140 | },
141 | {
142 | "size": "83.5x83.5",
143 | "idiom": "ipad",
144 | "filename": "icon-83.5@2x.png",
145 | "scale": "2x"
146 | },
147 | {
148 | "size": "1024x1024",
149 | "idiom": "ios-marketing",
150 | "filename": "icon-1024.png",
151 | "scale": "1x"
152 | }
153 | ],
154 | "info": {
155 | "version": 1,
156 | "author": "xjh"
157 | }
158 | }
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-50.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-50@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-72.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/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 | doubanapp
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 |
--------------------------------------------------------------------------------
/ios/Runner/main.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char* argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/lib/bean/search_result_entity.dart:
--------------------------------------------------------------------------------
1 | class SearchResultEntity {
2 | int total;
3 | List subjects;
4 | int count;
5 | int start;
6 | String title;
7 |
8 | SearchResultEntity({this.total, this.subjects, this.count, this.start, this.title});
9 |
10 | SearchResultEntity.fromJson(Map json) {
11 | total = json['total'];
12 | if (json['subjects'] != null) {
13 | subjects = new List();
14 | json['subjects'].forEach((v) { subjects.add(new SearchResultSubject.fromJson(v)); });
15 | }
16 | count = json['count'];
17 | start = json['start'];
18 | title = json['title'];
19 | }
20 |
21 | Map toJson() {
22 | final Map data = new Map();
23 | data['total'] = this.total;
24 | if (this.subjects != null) {
25 | data['subjects'] = this.subjects.map((v) => v.toJson()).toList();
26 | }
27 | data['count'] = this.count;
28 | data['start'] = this.start;
29 | data['title'] = this.title;
30 | return data;
31 | }
32 | }
33 |
34 | class SearchResultSubject {
35 | SearchResultSubjectsImages images;
36 | String originalTitle;
37 | String year;
38 | List directors;
39 | SearchResultSubjectsRating rating;
40 | String alt;
41 | String title;
42 | int collectCount;
43 | bool hasVideo;
44 | List pubdates;
45 | List casts;
46 | String subtype;
47 | List genres;
48 | List durations;
49 | String mainlandPubdate;
50 | String id;
51 |
52 | SearchResultSubject({this.images, this.originalTitle, this.year, this.directors, this.rating, this.alt, this.title, this.collectCount, this.hasVideo, this.pubdates, this.casts, this.subtype, this.genres, this.durations, this.mainlandPubdate, this.id});
53 |
54 | SearchResultSubject.fromJson(Map json) {
55 | images = json['images'] != null ? new SearchResultSubjectsImages.fromJson(json['images']) : null;
56 | originalTitle = json['original_title'];
57 | year = json['year'];
58 | if (json['directors'] != null) {
59 | directors = new List();
60 | json['directors'].forEach((v) { directors.add(new SearchResultSubjectsDirector.fromJson(v)); });
61 | }
62 | rating = json['rating'] != null ? new SearchResultSubjectsRating.fromJson(json['rating']) : null;
63 | alt = json['alt'];
64 | title = json['title'];
65 | collectCount = json['collect_count'];
66 | hasVideo = json['has_video'];
67 | pubdates = json['pubdates'].cast();
68 | if (json['casts'] != null) {
69 | casts = new List();
70 | json['casts'].forEach((v) { casts.add(new SearchResultSubjectsCast.fromJson(v)); });
71 | }
72 | subtype = json['subtype'];
73 | genres = json['genres'].cast();
74 | durations = json['durations'].cast();
75 | mainlandPubdate = json['mainland_pubdate'];
76 | id = json['id'];
77 | }
78 |
79 | Map toJson() {
80 | final Map data = new Map();
81 | if (this.images != null) {
82 | data['images'] = this.images.toJson();
83 | }
84 | data['original_title'] = this.originalTitle;
85 | data['year'] = this.year;
86 | if (this.directors != null) {
87 | data['directors'] = this.directors.map((v) => v.toJson()).toList();
88 | }
89 | if (this.rating != null) {
90 | data['rating'] = this.rating.toJson();
91 | }
92 | data['alt'] = this.alt;
93 | data['title'] = this.title;
94 | data['collect_count'] = this.collectCount;
95 | data['has_video'] = this.hasVideo;
96 | data['pubdates'] = this.pubdates;
97 | if (this.casts != null) {
98 | data['casts'] = this.casts.map((v) => v.toJson()).toList();
99 | }
100 | data['subtype'] = this.subtype;
101 | data['genres'] = this.genres;
102 | data['durations'] = this.durations;
103 | data['mainland_pubdate'] = this.mainlandPubdate;
104 | data['id'] = this.id;
105 | return data;
106 | }
107 | }
108 |
109 | class SearchResultSubjectsImages {
110 | String small;
111 | String large;
112 | String medium;
113 |
114 | SearchResultSubjectsImages({this.small, this.large, this.medium});
115 |
116 | SearchResultSubjectsImages.fromJson(Map json) {
117 | small = json['small'];
118 | large = json['large'];
119 | medium = json['medium'];
120 | }
121 |
122 | Map toJson() {
123 | final Map data = new Map();
124 | data['small'] = this.small;
125 | data['large'] = this.large;
126 | data['medium'] = this.medium;
127 | return data;
128 | }
129 | }
130 |
131 | class SearchResultSubjectsDirector {
132 | var name;
133 | var alt;
134 | var id;
135 | var avatars;
136 | var nameEn;
137 |
138 | SearchResultSubjectsDirector({this.name, this.alt, this.id, this.avatars, this.nameEn});
139 |
140 | SearchResultSubjectsDirector.fromJson(Map json) {
141 | name = json['name'];
142 | alt = json['alt'];
143 | id = json['id'];
144 | avatars = json['avatars'];
145 | nameEn = json['name_en'];
146 | }
147 |
148 | Map toJson() {
149 | final Map data = new Map();
150 | data['name'] = this.name;
151 | data['alt'] = this.alt;
152 | data['id'] = this.id;
153 | data['avatars'] = this.avatars;
154 | data['name_en'] = this.nameEn;
155 | return data;
156 | }
157 | }
158 |
159 | class SearchResultSubjectsRating {
160 | var average;
161 | var min;
162 | var max;
163 | SearchResultSubjectsRatingDetails details;
164 | String stars;
165 |
166 | SearchResultSubjectsRating({this.average, this.min, this.max, this.details, this.stars});
167 |
168 | SearchResultSubjectsRating.fromJson(Map json) {
169 | average = json['average'];
170 | min = json['min'];
171 | max = json['max'];
172 | details = json['details'] != null ? new SearchResultSubjectsRatingDetails.fromJson(json['details']) : null;
173 | stars = json['stars'];
174 | }
175 |
176 | Map toJson() {
177 | final Map data = new Map();
178 | data['average'] = this.average;
179 | data['min'] = this.min;
180 | data['max'] = this.max;
181 | if (this.details != null) {
182 | data['details'] = this.details.toJson();
183 | }
184 | data['stars'] = this.stars;
185 | return data;
186 | }
187 | }
188 |
189 | class SearchResultSubjectsRatingDetails {
190 | var d1;
191 | var d2;
192 | var d3;
193 | var d4;
194 | var d5;
195 |
196 | SearchResultSubjectsRatingDetails({this.d1, this.d2, this.d3, this.d4, this.d5});
197 |
198 | SearchResultSubjectsRatingDetails.fromJson(Map json) {
199 | d1 = json['1'];
200 | d2 = json['2'];
201 | d3 = json['3'];
202 | d4 = json['4'];
203 | d5 = json['5'];
204 | }
205 |
206 | Map toJson() {
207 | final Map data = new Map();
208 | data['1'] = this.d1;
209 | data['2'] = this.d2;
210 | data['3'] = this.d3;
211 | data['4'] = this.d4;
212 | data['5'] = this.d5;
213 | return data;
214 | }
215 | }
216 |
217 | class SearchResultSubjectsCast {
218 | String name;
219 | var alt;
220 | var id;
221 | var avatars;
222 | String nameEn;
223 |
224 | SearchResultSubjectsCast({this.name, this.alt, this.id, this.avatars, this.nameEn});
225 |
226 | SearchResultSubjectsCast.fromJson(Map json) {
227 | name = json['name'];
228 | alt = json['alt'];
229 | id = json['id'];
230 | avatars = json['avatars'];
231 | nameEn = json['name_en'];
232 | }
233 |
234 | Map toJson() {
235 | final Map data = new Map();
236 | data['name'] = this.name;
237 | data['alt'] = this.alt;
238 | data['id'] = this.id;
239 | data['avatars'] = this.avatars;
240 | data['name_en'] = this.nameEn;
241 | return data;
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/lib/bean/subject_entity.dart:
--------------------------------------------------------------------------------
1 | class SubjectEntity {
2 |
3 | // "subject":Object{...},
4 | // "rank":1,
5 | // "delta":0
6 |
7 | Subject subject;
8 | var rank;
9 | var delta;
10 |
11 | SubjectEntity.fromMap(Map map){
12 | rank = map['rank'];
13 | delta = map['delta'];
14 | var subjectMap = map['subject'];
15 | subject = Subject.fromMap(subjectMap);
16 | }
17 | }
18 |
19 | class Subject {
20 | bool tag = false;
21 | Rating rating;
22 | var genres;
23 | var title;
24 | List casts;
25 | var durations;
26 | var collect_count;
27 | var mainland_pubdate;
28 | var has_video;
29 | var original_title;
30 | var subtype;
31 | var directors;
32 | var pubdates;
33 | var year;
34 | Images images;
35 | var alt;
36 | var id;
37 |
38 | ///构造函数
39 | Subject.fromMap(Map map) {
40 | var rating = map['rating'];
41 | this.rating = Rating(rating['average'], rating['max']);
42 | genres = map['genres'];
43 | title = map['title'];
44 | var castMap = map['casts'];
45 | casts = _converCasts(castMap);
46 | collect_count = map['collect_count'];
47 | original_title = map['original_title'];
48 | subtype = map['subtype'];
49 | directors = map['directors'];
50 | year = map['year'];
51 | var img = map['images'];
52 | images = Images(img['small'], img['large'], img['medium']);
53 | alt = map['alt'];
54 | id = map['id'];
55 | durations = map['durations'];
56 | mainland_pubdate = map['mainland_pubdate'];
57 | has_video = map['has_video'];
58 | pubdates = map['pubdates'];
59 | }
60 |
61 | _converCasts(casts) {
62 | return casts.map((item)=>Cast.fromMap(item)).toList();
63 | }
64 |
65 | }
66 |
67 | class Images {
68 | var small;
69 | var large;
70 | var medium;
71 |
72 | Images(this.small, this.large, this.medium);
73 | }
74 |
75 | class Rating {
76 | var average;
77 | var max;
78 | Rating(this.average, this.max);
79 | }
80 |
81 |
82 |
83 | class Cast {
84 | var id;
85 | var name_en;
86 | var name;
87 | Avatar avatars;
88 | var alt;
89 | Cast(this.avatars, this.name_en, this.name, this.alt, this.id);
90 |
91 | Cast.fromMap(Map map) {
92 | id = map['id'];
93 | name_en = map['name_en'];
94 | name = map['name'];
95 | alt = map['alt'];
96 | var tmp = map['avatars'];
97 | if(tmp == null){
98 | avatars = null;
99 | }else{
100 | avatars = Avatar(tmp['small'], tmp['large'], tmp['medium']);
101 | }
102 |
103 | }
104 | }
105 |
106 | class Avatar {
107 | var medium;
108 | var large;
109 | var small;
110 | Avatar(this.small, this.large, this.medium);
111 | }
112 |
--------------------------------------------------------------------------------
/lib/bean/top_item_bean.dart:
--------------------------------------------------------------------------------
1 |
2 | import 'package:doubanapp/bean/subject_entity.dart';
3 | import 'dart:math' as math;
4 |
5 | class TopItemBean {
6 | var count;//共多少部
7 | var imgUrl;//图片url
8 | List- items;//多少个电影
9 | TopItemBean(this.count, this.imgUrl, this.items);
10 |
11 | ///将周口碑榜数据转换成榜单item对应的数据类型
12 | static TopItemBean convertWeeklyBeans(List weeklyBeans){
13 | var count = '每周五更新 · 共${math.min(weeklyBeans.length, 10)}部';
14 | var imgUrl = weeklyBeans[0].subject.images.large;
15 | int itemCount = math.min(4, weeklyBeans.length);
16 | weeklyBeans = weeklyBeans.sublist(0, itemCount);
17 | List
- items = [];
18 | for(SubjectEntity bean in weeklyBeans){
19 | items.add(Item(bean.subject.title, bean.subject.rating.average, bean.delta > 0));
20 | }
21 | return TopItemBean(count, imgUrl, items);
22 | }
23 |
24 | ///将周热门数据转换成榜单item对应的数据类型
25 | static TopItemBean convertHotBeans(List hotBeans) {
26 | var count = '每周五更新 · 共${math.min(10, hotBeans.length)}部';
27 | var imgUrl = hotBeans[0].images.large;
28 | int itemCount = math.min(4, hotBeans.length);
29 | hotBeans = hotBeans.sublist(0, itemCount);
30 | List
- items = [];
31 | for(Subject bean in hotBeans){
32 | items.add(Item(bean.title, bean.rating.average, true));
33 | }
34 | return TopItemBean(count, imgUrl, items);
35 | }
36 | ///将Top250数据转换成榜单item对应的数据类型
37 | static TopItemBean convertTopBeans(List hotBeans) {
38 | var count = '豆瓣榜单 · 共250部';
39 | var imgUrl = hotBeans[0].images.large;
40 | int itemCount = math.min(4, hotBeans.length);
41 | hotBeans = hotBeans.sublist(0, itemCount);
42 | List
- items = [];
43 | for(Subject bean in hotBeans){
44 | items.add(Item(bean.title, bean.rating.average, true));
45 | }
46 | return TopItemBean(count, imgUrl, items);
47 | }
48 |
49 | }
50 |
51 | class Item {
52 | var title; //电影名称
53 | var average; //评分
54 | bool upOrDown; //热度上升还是下降
55 |
56 | Item(this.title, this.average, this.upOrDown);
57 | }
58 |
--------------------------------------------------------------------------------
/lib/constant/cache_key.dart:
--------------------------------------------------------------------------------
1 | class CacheKey {
2 | static String USE_NET_DATA = 'USE_NET_DATA';
3 | }
--------------------------------------------------------------------------------
/lib/constant/color_constant.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ColorConstant {
4 | static const Color colorRed277 = Colors.redAccent;
5 | static const Color colorDefaultTitle = Color.fromARGB(255, 45, 45, 45);
6 | static const Color colorOrigin = Color.fromARGB(255, 232, 145, 60);
7 | static const Color colorDetail = Color.fromARGB(196, 197, 145, 197);
8 | static const Color ThemeGreen = Color.fromARGB(255, 0, 189, 95);
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/lib/constant/constant.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class Constant {
4 | static const String BASE_URL = 'api.douban.com';
5 |
6 | static const String TOP_250 = '/v2/movie/top250';
7 |
8 | static const String IMG_TMP1 =
9 | 'https://upload-images.jianshu.io/upload_images/3884536-b21bfc556ffcc062.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240';
10 |
11 | static const String IMG_TMP2 =
12 | 'https://upload-images.jianshu.io/upload_images/3884536-bb35459fd52009d3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240';
13 |
14 | static const double MARGIN_LEFT = 13.0;
15 | static const double MARGIN_RIGHT = 13.0;
16 | static const String ASSETS_IMG = 'assets/images/';
17 |
18 | static const double TAB_BOTTOM = 8.0;
19 |
20 | static String URL_MP4_DEMO_0 = 'http://vt1.doubanio.com/201902111139/0c06a85c600b915d8c9cbdbbaf06ba9f/view/movie/M/302420330.mp4';
21 |
22 | static String URL_MP4_DEMO_1 = 'http://vt1.doubanio.com/201903032315/702b9ad25c0da91e1c693e5e4dc5a86e/view/movie/M/302430864.mp4';
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/lib/constant/text_size_constant.dart:
--------------------------------------------------------------------------------
1 | class TextSizeConstant {
2 | ///影院热映、即将上映、豆瓣热门字体大小
3 | static const BookAudioPartTabBar = 20.0;
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/lib/generated/i18n.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/foundation.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | // ignore_for_file: non_constant_identifier_names
7 | // ignore_for_file: camel_case_types
8 | // ignore_for_file: prefer_single_quotes
9 |
10 | //This file is automatically generated. DO NOT EDIT, all your changes would be lost.
11 | class S implements WidgetsLocalizations {
12 | const S();
13 |
14 | static const GeneratedLocalizationsDelegate delegate =
15 | GeneratedLocalizationsDelegate();
16 |
17 | static S of(BuildContext context) => Localizations.of
(context, S);
18 |
19 | @override
20 | TextDirection get textDirection => TextDirection.ltr;
21 |
22 | }
23 |
24 | class $en extends S {
25 | const $en();
26 | }
27 |
28 | class GeneratedLocalizationsDelegate extends LocalizationsDelegate {
29 | const GeneratedLocalizationsDelegate();
30 |
31 | List get supportedLocales {
32 | return const [
33 | Locale("en", ""),
34 | ];
35 | }
36 |
37 | LocaleListResolutionCallback listResolution({Locale fallback}) {
38 | return (List locales, Iterable supported) {
39 | if (locales == null || locales.isEmpty) {
40 | return fallback ?? supported.first;
41 | } else {
42 | return _resolve(locales.first, fallback, supported);
43 | }
44 | };
45 | }
46 |
47 | LocaleResolutionCallback resolution({Locale fallback}) {
48 | return (Locale locale, Iterable supported) {
49 | return _resolve(locale, fallback, supported);
50 | };
51 | }
52 |
53 | Locale _resolve(Locale locale, Locale fallback, Iterable supported) {
54 | if (locale == null || !isSupported(locale)) {
55 | return fallback ?? supported.first;
56 | }
57 |
58 | final Locale languageLocale = Locale(locale.languageCode, "");
59 | if (supported.contains(locale)) {
60 | return locale;
61 | } else if (supported.contains(languageLocale)) {
62 | return languageLocale;
63 | } else {
64 | final Locale fallbackLocale = fallback ?? supported.first;
65 | return fallbackLocale;
66 | }
67 | }
68 |
69 | @override
70 | Future load(Locale locale) {
71 | final String lang = getLang(locale);
72 | if (lang != null) {
73 | switch (lang) {
74 | case "en":
75 | return SynchronousFuture(const $en());
76 | default:
77 | // NO-OP.
78 | }
79 | }
80 | return SynchronousFuture(const S());
81 | }
82 |
83 | @override
84 | bool isSupported(Locale locale) =>
85 | locale != null && supportedLocales.contains(locale);
86 |
87 | @override
88 | bool shouldReload(GeneratedLocalizationsDelegate old) => false;
89 | }
90 |
91 | String getLang(Locale l) => l == null
92 | ? null
93 | : l.countryCode != null && l.countryCode.isEmpty
94 | ? l.languageCode
95 | : l.toString();
96 |
--------------------------------------------------------------------------------
/lib/http/http_request.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert' as Convert;
3 | import 'dart:io';
4 | import 'package:http/http.dart' as http;
5 |
6 | typedef RequestCallBack = void Function(Map data);
7 |
8 | class HttpRequest {
9 | static requestGET (
10 | String authority, String unencodedPath, RequestCallBack callBack,
11 | [Map queryParameters]) async {
12 | try {
13 | var httpClient = new HttpClient();
14 | //http://api.douban.com/v2/movie/top250?start=25&count=10
15 | var uri = new Uri.http(authority, unencodedPath, queryParameters);
16 | var request = await httpClient.getUrl(uri);
17 | var response = await request.close();
18 | var responseBody = await response.transform(Convert.utf8.decoder).join();
19 | Map data = Convert.jsonDecode(responseBody);
20 | callBack(data);
21 | } on Exception catch (e) {
22 | print(e.toString());
23 | }
24 | }
25 |
26 | final baseUrl;
27 |
28 | HttpRequest(this.baseUrl);
29 |
30 | Future get(String uri, {Map headers}) async {
31 | try {
32 | http.Response response = await http.get(baseUrl + uri, headers: headers);
33 | final statusCode = response.statusCode;
34 | final body = response.body;
35 | print('[uri=$uri][statusCode=$statusCode][response=$body]');
36 | var result = Convert.jsonDecode(body);
37 | return result;
38 | } on Exception catch (e) {
39 | print('[uri=$uri]exception e=${e.toString()}');
40 | return '';
41 | }
42 | }
43 |
44 | Future getResponseBody(String uri, {Map headers}) async {
45 | try {
46 | http.Response response = await http.get(baseUrl + uri, headers: headers);
47 | final statusCode = response.statusCode;
48 | final body = response.body;
49 | // var result = Convert.jsonDecode(body);
50 | print('[uri=$uri][statusCode=$statusCode][response=$body]');
51 | return body;
52 | } on Exception catch (e) {
53 | print('[uri=$uri]exception e=${e.toString()}');
54 | return null;
55 | }
56 | }
57 |
58 | Future post(String uri, dynamic body, {Map headers}) async {
59 | try {
60 | http.Response response = await http.post(baseUrl + uri, body: body, headers: headers);
61 | final statusCode = response.statusCode;
62 | final responseBody = response.body;
63 | var result = Convert.jsonDecode(responseBody);
64 | print('[uri=$uri][statusCode=$statusCode][response=$responseBody]');
65 | return result;
66 | } on Exception catch (e) {
67 | print('[uri=$uri]exception e=${e.toString()}');
68 | return '';
69 | }
70 | }
71 | }
72 |
73 |
74 |
--------------------------------------------------------------------------------
/lib/http/mock_request.dart:
--------------------------------------------------------------------------------
1 | import 'package:doubanapp/http/API.dart';
2 | import 'dart:async';
3 | import 'package:flutter/services.dart' show rootBundle;
4 | import 'dart:convert';
5 |
6 | ///模拟数据
7 | class MockRequest {
8 |
9 | Future get(String action, {Map params}) async {
10 | return mock(action: getJsonName(action), params: params);
11 | }
12 |
13 | Future post({String action, Map params}) async {
14 | return mock(action: action, params: params);
15 | }
16 |
17 | Future mock({String action, Map params}) async {
18 | var responseStr = await rootBundle.loadString('mock/$action.json');
19 | var responseJson = json.decode(responseStr);
20 | return responseJson;
21 | }
22 |
23 | Future mock2(String action) async {
24 | var responseStr = await rootBundle.loadString('mock/$action.json');
25 | var responseJson = json.decode(responseStr);
26 | return responseJson;
27 | }
28 |
29 | Map map = {
30 | API.IN_THEATERS: 'in_theaters',
31 | API.COMING_SOON: 'coming_soon',
32 | API.TOP_250: 'top250',
33 | API.WEEKLY: 'weekly',
34 | API.REIVIEWS: 'reviews',
35 | };
36 |
37 | getJsonName(String action) {
38 | return map[action];
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'dart:io';
3 | import 'package:flutter/services.dart';
4 | import 'package:doubanapp/widgets/bottom_drag_widget.dart';
5 | import 'package:doubanapp/pages/splash/splash_widget.dart';
6 |
7 | void main() {
8 | runApp(MyApp());
9 | if (Platform.isAndroid) {
10 | //设置Android头部的导航栏透明
11 | SystemUiOverlayStyle systemUiOverlayStyle =
12 | SystemUiOverlayStyle(statusBarColor: Colors.transparent);
13 | SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
14 | }
15 | }
16 |
17 | class MyApp extends StatelessWidget {
18 | // This widget is the root of your application.
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | return RestartWidget(
23 | child: MaterialApp(
24 | theme: ThemeData(backgroundColor: Colors.white),
25 | home: Scaffold(
26 | body: SplashWidget(),
27 | ),
28 | ),
29 | );
30 | }
31 | }
32 |
33 | ///这个组件用来重新加载整个child Widget的。当我们需要重启APP的时候,可以使用这个方案
34 | ///https://stackoverflow.com/questions/50115311/flutter-how-to-force-an-application-restart-in-production-mode
35 | class RestartWidget extends StatefulWidget {
36 | final Widget child;
37 |
38 | RestartWidget({Key key, @required this.child})
39 | : assert(child != null),
40 | super(key: key);
41 |
42 | static restartApp(BuildContext context) {
43 | final _RestartWidgetState state =
44 | context.findAncestorStateOfType<_RestartWidgetState>();
45 | state.restartApp();
46 | }
47 |
48 | @override
49 | _RestartWidgetState createState() => _RestartWidgetState();
50 | }
51 |
52 | class _RestartWidgetState extends State {
53 | Key key = UniqueKey();
54 |
55 | void restartApp() {
56 | setState(() {
57 | key = UniqueKey();
58 | });
59 | }
60 |
61 | @override
62 | Widget build(BuildContext context) {
63 | return Container(
64 | key: key,
65 | child: widget.child,
66 | );
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/lib/pages/container_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/pages/group/group_page.dart';
3 | import 'package:doubanapp/pages/movie/book_audio_video_page.dart';
4 | import 'package:doubanapp/pages/home/home_page.dart';
5 | import 'package:doubanapp/pages/person/person_center_page.dart';
6 | import 'package:doubanapp/pages/shop_page.dart';
7 |
8 |
9 | ///这个页面是作为整个APP的最外层的容器,以Tab为基础控制每个item的显示与隐藏
10 | class ContainerPage extends StatefulWidget {
11 | ContainerPage({Key key}) : super(key: key);
12 |
13 | @override
14 | State createState() {
15 | return _ContainerPageState();
16 | }
17 | }
18 |
19 | class _Item {
20 | String name, activeIcon, normalIcon;
21 |
22 | _Item(this.name, this.activeIcon, this.normalIcon);
23 | }
24 |
25 | class _ContainerPageState extends State {
26 | final ShopPageWidget shopPageWidget = ShopPageWidget();
27 | List pages;
28 |
29 | final defaultItemColor = Color.fromARGB(255, 125, 125, 125);
30 |
31 | final itemNames = [
32 | _Item('首页', 'assets/images/ic_tab_home_active.png',
33 | 'assets/images/ic_tab_home_normal.png'),
34 | _Item('书影音', 'assets/images/ic_tab_subject_active.png',
35 | 'assets/images/ic_tab_subject_normal.png'),
36 | _Item('小组', 'assets/images/ic_tab_group_active.png',
37 | 'assets/images/ic_tab_group_normal.png'),
38 | _Item('市集', 'assets/images/ic_tab_shiji_active.png',
39 | 'assets/images/ic_tab_shiji_normal.png'),
40 | _Item('我的', 'assets/images/ic_tab_profile_active.png',
41 | 'assets/images/ic_tab_profile_normal.png')
42 | ];
43 |
44 | List itemList;
45 |
46 | @override
47 | void initState() {
48 | super.initState();
49 | print('initState _ContainerPageState');
50 | if(pages == null){
51 | pages = [
52 | HomePage(),
53 | BookAudioVideoPage(),
54 | GroupPage(),
55 | shopPageWidget,
56 | PersonCenterPage()
57 | ];
58 | }
59 | if(itemList == null){
60 | itemList = itemNames
61 | .map((item) => BottomNavigationBarItem(
62 | icon: Image.asset(
63 | item.normalIcon,
64 | width: 30.0,
65 | height: 30.0,
66 | ),
67 | title: Text(
68 | item.name,
69 | style: TextStyle(fontSize: 10.0),
70 | ),
71 | activeIcon:
72 | Image.asset(item.activeIcon, width: 30.0, height: 30.0)))
73 | .toList();
74 | }
75 |
76 | }
77 |
78 |
79 | int _selectIndex = 0;
80 |
81 | //Stack(层叠布局)+Offstage组合,解决状态被重置的问题
82 | Widget _getPagesWidget(int index) {
83 | return Offstage(
84 | offstage: _selectIndex != index,
85 | child: TickerMode(
86 | enabled: _selectIndex == index,
87 | child: pages[index],
88 | ),
89 | );
90 | }
91 |
92 |
93 | @override
94 | void didUpdateWidget(ContainerPage oldWidget) {
95 | super.didUpdateWidget(oldWidget);
96 | print('didUpdateWidget');
97 | }
98 |
99 | @override
100 | Widget build(BuildContext context) {
101 | // Scaffold({
102 | // Key key,
103 | // this.appBar,
104 | // this.body,
105 | // this.floatingActionButton,
106 | // this.floatingActionButtonLocation,
107 | // this.floatingActionButtonAnimator,
108 | // this.persistentFooterButtons,
109 | // this.drawer,
110 | // this.endDrawer,
111 | // this.bottomNavigationBar,
112 | // this.bottomSheet,
113 | // this.backgroundColor,
114 | // this.resizeToAvoidBottomPadding = true,
115 | // this.primary = true,
116 | // })
117 | print('build _ContainerPageState');
118 | return Scaffold(
119 | body: new Stack(
120 | children: [
121 | _getPagesWidget(0),
122 | _getPagesWidget(1),
123 | _getPagesWidget(2),
124 | _getPagesWidget(3),
125 | _getPagesWidget(4),
126 | ],
127 | ),
128 | // List
129 | // @required this.icon,
130 | // this.title,
131 | // Widget activeIcon,
132 | // this.backgroundColor,
133 | backgroundColor: Color.fromARGB(255, 248, 248, 248),
134 | bottomNavigationBar: BottomNavigationBar(
135 | items: itemList,
136 | onTap: (int index) {
137 | ///这里根据点击的index来显示,非index的page均隐藏
138 | setState(() {
139 | _selectIndex = index;
140 | //这个是用来控制比较特别的shopPage中WebView不能动态隐藏的问题
141 | shopPageWidget.setShowState(pages.indexOf(shopPageWidget) == _selectIndex);
142 | });
143 | },
144 | //图标大小
145 | iconSize: 24,
146 | //当前选中的索引
147 | currentIndex: _selectIndex,
148 | //选中后,底部BottomNavigationBar内容的颜色(选中时,默认为主题色)(仅当type: BottomNavigationBarType.fixed,时生效)
149 | fixedColor: Color.fromARGB(255, 0, 188, 96),
150 | type: BottomNavigationBarType.fixed,
151 | ),
152 | );
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/lib/pages/detail/detail_title_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/bean/movie_detail_bean.dart';
3 | import 'package:doubanapp/constant/constant.dart';
4 | import 'package:doubanapp/pages/detail/look_confirm_button.dart';
5 |
6 | class DetailTitleWidget extends StatelessWidget {
7 | final MovieDetailBean bean;
8 | final Color shadowColor;
9 |
10 | DetailTitleWidget(this.bean, this.shadowColor, {Key key}) : super(key: key);
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | var screenW = MediaQuery.of(context).size.width;
15 | var imgW = screenW / 4;
16 | var imgH = imgW * 421 / 297;
17 | var countries = list2String(bean.countries);
18 | var genres = list2String(bean.genres);
19 | var pubdates = list2String(bean.pubdates);
20 | var durations = list2String(bean.durations);
21 | //将按下的颜色设置较为浅色
22 | var btnPressedColor =
23 | Color.fromARGB(100, shadowColor.red, shadowColor.red, shadowColor.red);
24 | return Row(
25 | children: [
26 | Card(
27 | //影音海报
28 | shape: RoundedRectangleBorder(
29 | borderRadius: BorderRadius.all(Radius.circular(6.0)),
30 | ),
31 | color: shadowColor,
32 | clipBehavior: Clip.antiAlias,
33 | elevation: 10.0,
34 | child: Image.network(
35 | bean.images.large,
36 | width: imgW,
37 | height: imgH,
38 | fit: BoxFit.cover,
39 | ),
40 | ),
41 | Expanded(
42 | child: Padding(
43 | padding: EdgeInsets.only(left: Constant.MARGIN_LEFT),
44 | child: Column(
45 | mainAxisAlignment: MainAxisAlignment.start,
46 | crossAxisAlignment: CrossAxisAlignment.start,
47 | children: [
48 | Text(
49 | bean.title,
50 | style: TextStyle(
51 | fontSize: 22.0,
52 | color: Colors.white,
53 | fontWeight: FontWeight.bold),
54 | ),
55 | Padding(
56 | padding: EdgeInsets.only(top: 5.0, bottom: 7.0),
57 | child: Text(
58 | '(${bean.year})',
59 | style: TextStyle(fontSize: 15.0, color: Colors.white),
60 | ),
61 | ),
62 | Padding(
63 | padding: EdgeInsets.only(bottom: 5.0),
64 | child: Text(
65 | '$countries/$genres/上映时间:$pubdates/片长:$durations',
66 | style: TextStyle(fontSize: 12.0, color: Colors.white70),
67 | ),
68 | ),
69 | Row(
70 | children: [
71 | Expanded(
72 | child: LookConfirmButton(
73 | btnText: '想看',
74 | iconAsset: 'assets/images/ic_info_wish.png',
75 | defaultColor: Colors.white,
76 | pressedColor: btnPressedColor,
77 | ),
78 | ),
79 | Padding(
80 | padding: EdgeInsets.only(left: 15.0),
81 | ),
82 | Expanded(
83 | child: LookConfirmButton(
84 | btnText: '看过',
85 | iconAsset: 'assets/images/ic_info_done.png',
86 | defaultColor: Colors.white,
87 | pressedColor: btnPressedColor,
88 | ),
89 | )
90 | ],
91 | ),
92 | ],
93 | ),
94 | ),
95 | )
96 | ],
97 | );
98 | }
99 |
100 | String list2String(List list) {
101 | var tmp = '';
102 | for (String item in list) {
103 | tmp = tmp + item;
104 | }
105 | return tmp;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/lib/pages/detail/long_comment_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import '../../bean/movie_long_comments_entity.dart';
3 | import '../../widgets/rating_bar.dart';
4 | import '../../constant/constant.dart';
5 | import 'package:doubanapp/router.dart';
6 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
7 |
8 | ///电影长评论
9 | class LongCommentWidget extends StatelessWidget {
10 | final MovieLongCommentsEntity movieLongCommentsEntity;
11 |
12 | LongCommentWidget({Key key, @required this.movieLongCommentsEntity})
13 | : assert(movieLongCommentsEntity != null),
14 | super(key: key);
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return LongCommentTabView(
19 | movieLongCommentsEntity: movieLongCommentsEntity,
20 | );
21 | }
22 | }
23 |
24 | class LongCommentTabView extends StatefulWidget {
25 | final MovieLongCommentsEntity movieLongCommentsEntity;
26 |
27 | LongCommentTabView({Key key, @required this.movieLongCommentsEntity})
28 | : super(key: key);
29 |
30 | @override
31 | _LongCommentTabViewState createState() => _LongCommentTabViewState();
32 | }
33 |
34 | class _LongCommentTabViewState extends State
35 | with SingleTickerProviderStateMixin {
36 | final List list = ['影评', '话题', '讨论'];
37 |
38 | TabController controller;
39 | Color selectColor, unselectedColor;
40 | TextStyle selectStyle, unselectedStyle;
41 |
42 | @override
43 | void initState() {
44 | controller = TabController(length: list.length, vsync: this);
45 | selectColor = Colors.black;
46 | unselectedColor = Color.fromARGB(255, 117, 117, 117);
47 | selectStyle = TextStyle(fontSize: 15, color: selectColor);
48 | unselectedStyle = TextStyle(fontSize: 15, color: selectColor);
49 | super.initState();
50 | }
51 |
52 | @override
53 | void dispose() {
54 | controller.dispose();
55 | super.dispose();
56 | }
57 |
58 | @override
59 | Widget build(BuildContext context) {
60 | return Column(
61 | children: [
62 | Container(
63 | height: 6.0,
64 | width: 45.0,
65 | margin: const EdgeInsets.only(top: 10.0),
66 | decoration: BoxDecoration(
67 | color: const Color.fromARGB(255, 214, 215, 218),
68 | borderRadius: BorderRadius.all(const Radius.circular(5.0))),
69 | ),
70 | Container(
71 | padding: const EdgeInsets.only(top: 15.0),
72 | child: TabBar(
73 | tabs: list
74 | .map((item) => Padding(
75 | padding:
76 | const EdgeInsets.only(bottom: Constant.TAB_BOTTOM),
77 | child: Text(item),
78 | ))
79 | .toList(),
80 | isScrollable: true,
81 | indicatorColor: selectColor,
82 | labelColor: selectColor,
83 | labelStyle: selectStyle,
84 | unselectedLabelColor: unselectedColor,
85 | unselectedLabelStyle: unselectedStyle,
86 | indicatorSize: TabBarIndicatorSize.label,
87 | controller: controller,
88 | ),
89 | alignment: Alignment.centerLeft,
90 | ),
91 | Expanded(
92 | child: TabBarView(
93 | children: [
94 | ListView.builder(
95 | itemBuilder: (BuildContext context, int index) {
96 | return Column(
97 | children: [
98 | Container(
99 | child: getItem(
100 | widget.movieLongCommentsEntity.reviews[index]),
101 | padding: const EdgeInsets.only(
102 | left: Constant.MARGIN_LEFT,
103 | right: Constant.MARGIN_RIGHT),
104 | color: Colors.white,
105 | ),
106 | Container(
107 | height: 10.0,
108 | color: Colors.transparent,
109 | )
110 | ],
111 | );
112 | },
113 | physics: const ClampingScrollPhysics(),
114 | itemCount: widget.movieLongCommentsEntity.reviews.length,
115 | ),
116 | Text('话题,暂无数据~'),
117 | Text('讨论,暂无数据~')
118 | ],
119 | controller: controller,
120 | ))
121 | ],
122 | );
123 | }
124 |
125 | Widget getItem(MovieLongCommentReviews review) {
126 | return GestureDetector(
127 | behavior: HitTestBehavior.translucent,
128 | child: Column(
129 | mainAxisAlignment: MainAxisAlignment.start,
130 | crossAxisAlignment: CrossAxisAlignment.start,
131 | children: [
132 | Row(
133 | mainAxisAlignment: MainAxisAlignment.start,
134 | children: [
135 | Padding(
136 | padding:
137 | const EdgeInsets.only(top: 10.0, bottom: 7.0, right: 5.0),
138 | child: CircleAvatar(
139 | radius: 10.0,
140 | backgroundImage: NetworkImage(review.author.avatar),
141 | backgroundColor: Colors.white,
142 | ),
143 | ),
144 | Padding(
145 | child: Text(review.author.name),
146 | padding: const EdgeInsets.only(right: 5.0),
147 | ),
148 | RatingBar(
149 | ((review.rating.value * 1.0) / (review.rating.max * 1.0)) *
150 | 10.0,
151 | size: 11.0,
152 | fontSize: 0.0,
153 | )
154 | ],
155 | ),
156 | Text(
157 | review.title,
158 | style: TextStyle(
159 | fontSize: 16.0,
160 | color: Colors.black,
161 | fontWeight: FontWeight.bold),
162 | ),
163 | Padding(
164 | padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
165 | child: Text(
166 | review.content,
167 | softWrap: true,
168 | maxLines: 3,
169 | overflow: TextOverflow.ellipsis,
170 | style: TextStyle(fontSize: 14.0, color: Color(0xff333333)),
171 | ),
172 | ),
173 | Padding(
174 | child: Text(
175 | '${getUsefulCount(review.commentsCount)}回复 · ${getUsefulCount(review.usefulCount)} 有用'),
176 | padding: const EdgeInsets.only(bottom: 10.0),
177 | ),
178 | ],
179 | ),
180 | onTap: () {
181 | Navigator.push(context, MaterialPageRoute(builder: (context) {
182 | return WebviewScaffold(
183 | url: review.shareUrl,
184 | appBar: new AppBar(
185 | backgroundColor: Colors.green,
186 | title: Row(
187 | mainAxisAlignment: MainAxisAlignment.start,
188 | children: [
189 | Padding(
190 | padding: const EdgeInsets.only(
191 | top: 10.0, bottom: 7.0, right: 5.0),
192 | child: CircleAvatar(
193 | radius: 10.0,
194 | backgroundImage: NetworkImage(review.author.avatar),
195 | backgroundColor: Colors.white,
196 | ),
197 | ),
198 | Padding(
199 | child: Text(review.author.name),
200 | padding: const EdgeInsets.only(right: 5.0),
201 | ),
202 | ],
203 | ),
204 | ),
205 | );
206 | }));
207 | },
208 | );
209 | }
210 |
211 | ///将34123转成3.4k
212 | getUsefulCount(int usefulCount) {
213 | double a = usefulCount / 1000;
214 | if (a < 1.0) {
215 | return usefulCount;
216 | } else {
217 | return '${a.toStringAsFixed(1)}k'; //保留一位小数
218 | }
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/lib/pages/detail/look_confirm_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/cupertino.dart';
3 |
4 | typedef VoidCallback = void Function();
5 |
6 | ///想看、看过的按钮
7 | class LookConfirmButton extends StatefulWidget {
8 | final String btnText;
9 | final String iconAsset;
10 | final Color pressedColor;
11 | final VoidCallback onPressed;
12 | final Color defaultColor;
13 |
14 | LookConfirmButton(
15 | {Key key,
16 | @required this.btnText,
17 | @required this.iconAsset,
18 | @required this.pressedColor,
19 | @required this.defaultColor,
20 | this.onPressed})
21 | : super(key: key);
22 |
23 | @override
24 | State createState() {
25 | return _State(defaultColor);
26 | }
27 | }
28 |
29 | class _State extends State {
30 | var _color;
31 | Color _defaultColor;
32 |
33 | _State(Color color) {
34 | _color = color;
35 | _defaultColor = color;
36 | }
37 |
38 | @override
39 | Widget build(BuildContext context) {
40 | return GestureDetector(
41 | child: Container(
42 | alignment: Alignment.center,
43 | height: 35.0,
44 | decoration: BoxDecoration(
45 | color: _color,
46 | borderRadius: BorderRadius.all(Radius.circular(5.0))),
47 | child: Row(
48 | mainAxisAlignment: MainAxisAlignment.center,
49 | children: [
50 | Padding(
51 | padding: EdgeInsets.only(right: 5.0),
52 | child: Image.asset(
53 | widget.iconAsset,
54 | width: 22.0,
55 | height: 22.0,
56 | ),
57 | ),
58 | Text(
59 | widget.btnText,
60 | style: TextStyle(fontSize: 17.0, color: Colors.black),
61 | )
62 | ],
63 | ),
64 | ),
65 | onTap: () {
66 | if (widget.onPressed != null) {
67 | widget.onPressed();
68 | }
69 | },
70 | onTapDown: (TapDownDetails details) {
71 | setState(() {
72 | _color = widget.pressedColor;
73 | });
74 | },
75 | onTapUp: (TapUpDetails details) {
76 | setState(() {
77 | _color = _defaultColor;
78 | });
79 | },
80 | onTapCancel: ((){
81 | setState(() {
82 | _color = _defaultColor;
83 | });
84 | }),
85 | );
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/lib/pages/detail/score_start.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/widgets/rating_bar.dart';
3 |
4 | class ScoreStartWidget extends StatefulWidget {
5 | final score;
6 | final p5; //五颗星的百分比
7 | final p4;
8 | final p3;
9 | final p2;
10 | final p1;
11 |
12 | ScoreStartWidget(
13 | {Key key,
14 | @required this.score,
15 | @required this.p1,
16 | @required this.p2,
17 | @required this.p3,
18 | @required this.p4,
19 | @required this.p5})
20 | : super(key: key);
21 |
22 | @override
23 | State createState() {
24 | return _ScoreStartState();
25 | }
26 | }
27 |
28 | class _ScoreStartState extends State {
29 | var lineW;
30 |
31 | @override
32 | Widget build(BuildContext context) {
33 | lineW = MediaQuery.of(context).size.width / 3;
34 | return Container(
35 | padding: EdgeInsets.all(13.0),
36 | decoration: BoxDecoration(
37 | color: Color(0x23000000),
38 | borderRadius: BorderRadius.all(Radius.circular(10.0))),
39 | child: Column(
40 | children: [
41 | Row(
42 | children: [
43 | Expanded(
44 | child: Text(
45 | '豆芽评分',
46 | style: TextStyle(fontSize: 12.0, color: Colors.white),
47 | )),
48 | Icon(
49 | Icons.chevron_right,
50 | color: Color(0x66ffffff),
51 | size: 26.0,
52 | )
53 | ],
54 | ),
55 | Row(
56 | children: [
57 | Padding(
58 | child: Column(
59 | mainAxisAlignment: MainAxisAlignment.start,
60 | //评分、星星
61 | children: [
62 | Text(
63 | '${widget.score}',
64 | style: TextStyle(fontSize: 30.0, color: Colors.white),
65 | ),
66 | RatingBar(
67 | widget.score,
68 | size: 11.0,
69 | fontSize: 0.0,
70 | )
71 | ],
72 | ),
73 | padding: EdgeInsets.only(left: 30.0, right: 10.0),
74 | ),
75 | Column(
76 | //星星-百分比
77 | mainAxisAlignment: MainAxisAlignment.end,
78 | crossAxisAlignment: CrossAxisAlignment.end,
79 | children: [
80 | startsLine(5, widget.p5),
81 | startsLine(4, widget.p4),
82 | startsLine(3, widget.p3),
83 | startsLine(2, widget.p2),
84 | startsLine(1, widget.p1),
85 | ],
86 | ),
87 | ],
88 | )
89 | ],
90 | ),
91 | );
92 | }
93 |
94 | Widget getStarts(int count) {
95 | List list = [];
96 | for (int i = 0; i < count; i++) {
97 | list.add(Icon(
98 | Icons.star,
99 | size: 9.0,
100 | color: Colors.white70,
101 | ));
102 | }
103 | return Row(
104 | children: list,
105 | );
106 | }
107 |
108 | ///percent 百分比(0.1 -1.0)
109 | Widget getLine(double percent) {
110 | return Stack(
111 | children: [
112 | Container(
113 | width: lineW,
114 | height: 7.0,
115 | decoration: BoxDecoration(
116 | color: Color(0x13000000),
117 | borderRadius: BorderRadius.all(Radius.circular(10.0))),
118 | ),
119 | Container(
120 | height: 7.0,
121 | width: lineW * percent,
122 | decoration: BoxDecoration(
123 | color: Color.fromARGB(255, 255, 170, 71),
124 | borderRadius: BorderRadius.all(Radius.circular(10.0))),
125 | )
126 | ],
127 | );
128 | }
129 |
130 | startsLine(int startCount, double percent) {
131 | if(percent == null || percent.isNaN){
132 | percent = 0.0;
133 | }
134 | return Padding(
135 | padding: EdgeInsets.only(bottom: 2.0),
136 | child: Row(
137 | children: [
138 | getStarts(startCount),
139 | Padding(
140 | padding: EdgeInsets.only(left: 5.0),
141 | ),
142 | getLine(percent)
143 | ],
144 | ),
145 | );
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/lib/pages/detail/summary_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class SummaryWidget extends StatelessWidget {
4 | @override
5 | Widget build(BuildContext context) {
6 | // TODO: implement build
7 | return null;
8 | }
9 |
10 | }
--------------------------------------------------------------------------------
/lib/pages/detail/tags_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class TagsWidget extends StatelessWidget {
4 | final List tags;
5 |
6 | TagsWidget({Key key, @required this.tags}) : super(key: key);
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return null;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lib/pages/group/group_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/widgets/search_text_field_widget.dart';
3 | import 'package:doubanapp/router.dart';
4 | import 'package:doubanapp/constant/constant.dart';
5 | import 'package:doubanapp/http/API.dart';
6 | import 'package:doubanapp/http/http_request.dart';
7 | import 'package:doubanapp/bean/subject_entity.dart';
8 | import 'package:doubanapp/widgets/loading_widget.dart';
9 | import 'package:doubanapp/widgets/image/radius_img.dart';
10 | ///小组
11 | class GroupPage extends StatelessWidget {
12 | @override
13 | Widget build(BuildContext context) {
14 | String hintText = '搜索书影音 小组 日记 用户等';
15 | return Scaffold(
16 | backgroundColor: Colors.white,
17 | body: SafeArea(
18 | child: Column(
19 | children: [
20 | SearchTextFieldWidget(
21 | margin: EdgeInsets.all(Constant.MARGIN_RIGHT),
22 | hintText: hintText,
23 | onTab: () {
24 | MyRouter.push(context, MyRouter.searchPage, hintText);
25 | },
26 | ),
27 | Expanded(
28 | child: _GroupWidget(),
29 | )
30 | ],
31 | )),
32 | );
33 | }
34 | }
35 |
36 | class _GroupWidget extends StatefulWidget {
37 | @override
38 | State createState() => _GroupWidgetState();
39 | }
40 |
41 | var _request = HttpRequest(API.BASE_URL);
42 |
43 | class _GroupWidgetState extends State<_GroupWidget> {
44 | List list;
45 | bool loading = true;
46 |
47 | @override
48 | void initState() {
49 | super.initState();
50 | Future(() {
51 | return _request.get(API.IN_THEATERS);
52 | }).then((result) {
53 | var resultList = result['subjects'];
54 | setState(() {
55 | list =
56 | resultList.map((item) => Subject.fromMap(item)).toList();
57 | loading = false;
58 | });
59 | });
60 | }
61 |
62 | @override
63 | Widget build(BuildContext context) {
64 | return LoadingWidget.containerLoadingBody(_getBody(), loading: loading);
65 | }
66 |
67 | Widget _getBody() {
68 | if (list == null) {
69 | return Container(
70 | child: Image.asset(Constant.ASSETS_IMG + 'ic_group_top.png'),
71 | );
72 | }
73 | return ListView.builder(
74 | physics: const BouncingScrollPhysics(),
75 | itemBuilder: (BuildContext context, int index) {
76 | if (index == 0) {
77 | return Image.asset(Constant.ASSETS_IMG + 'ic_group_top.png');
78 | }
79 |
80 | Subject bean = list[index - 1];
81 | return Padding(
82 | padding: const EdgeInsets.only(
83 | right: Constant.MARGIN_RIGHT, left: 6.0, top: 13.0),
84 | child: _getItem(bean, index - 1),
85 | );
86 | },
87 | itemCount: list.length + 1,
88 | );
89 | }
90 |
91 | Widget _getItem(Subject bean, int index) {
92 | return GestureDetector(
93 | behavior: HitTestBehavior.translucent,
94 | child: Row(
95 | children: [
96 | RadiusImg.get(bean.images.small, 50.0, radius: 3.0),
97 | Expanded(
98 | child: Container(
99 | alignment: Alignment.topLeft,
100 | margin: const EdgeInsets.only(left: 5.0),
101 | child: Column(
102 | crossAxisAlignment: CrossAxisAlignment.start,
103 | children: [
104 | Text(
105 | bean.title,
106 | style:
107 | TextStyle(fontSize: 17.0, fontWeight: FontWeight.bold),
108 | ),
109 | Text(bean.pubdates != null ? bean.pubdates[0] : '', style: TextStyle(fontSize: 13.0))
110 | ],
111 | ),
112 | ),
113 | ),
114 | Padding(
115 | padding: EdgeInsets.only(right: 10.0),
116 | child: Text('${bean.collect_count}人', style: TextStyle(fontSize: 13.0),),
117 | ),
118 | GestureDetector(
119 | child: Image.asset(
120 | Constant.ASSETS_IMG +
121 | (list[index].tag
122 | ? 'ic_group_checked_anonymous.png'
123 | : 'ic_group_check_anonymous.png'),
124 | width: 25.0,
125 | height: 25.0,
126 | ),
127 | onTap: () {
128 | setState(() {
129 | list[index].tag = !list[index].tag;
130 | });
131 | },
132 | )
133 | ],
134 | ),
135 | onTap: () {
136 | MyRouter.push(context, MyRouter.detailPage, bean.id);
137 | },
138 | );
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/lib/pages/home/my_home_tab_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/widgets/search_text_field_widget.dart';
3 | import 'package:doubanapp/util/screen_utils.dart';
4 | import 'package:doubanapp/router.dart';
5 |
6 | const double _kTabHeight = 46.0;
7 | const double _kTextAndIconTabHeight = 42.0;
8 |
9 | class HomeTabBar extends StatefulWidget implements PreferredSizeWidget {
10 | final TabBar tabBar;
11 | final double translate;
12 |
13 | HomeTabBar({Key key, this.tabBar, this.translate}) : super(key: key);
14 |
15 | @override
16 | _HomeTabBarState createState() => _HomeTabBarState();
17 |
18 | @override
19 | Size get preferredSize {
20 | print('preferredSize');
21 | for (Widget item in tabBar.tabs) {
22 | if (item is Tab) {
23 | final Tab tab = item;
24 | if (tab.text != null && tab.icon != null)
25 | return Size.fromHeight(
26 | _kTextAndIconTabHeight + tabBar.indicatorWeight);
27 | }
28 | }
29 | return Size.fromHeight(_kTabHeight + tabBar.indicatorWeight);
30 | }
31 | }
32 |
33 | class _HomeTabBarState extends State {
34 | double get allHeight => widget.preferredSize.height;
35 |
36 | @override
37 | Widget build(BuildContext context) {
38 | MediaQuery.of(context);
39 | var value = ScreenUtils.screenW(context) * 0.75 - 10.0;
40 | return Stack(
41 | children: [
42 | Positioned(
43 | ///搜索框
44 | left: 15.0,
45 | right: value,
46 | top: getTop(widget.translate),
47 | child: getOpacityWidget(Container(
48 | padding: const EdgeInsets.only(
49 | top: 3.0, bottom: 3.0, right: 10.0, left: 5.0),
50 | decoration: BoxDecoration(
51 | color: const Color.fromARGB(245, 236, 236, 236),
52 | borderRadius: BorderRadius.all(Radius.circular(17.0))),
53 | child: Row(
54 | children: [
55 | Icon(
56 | Icons.search,
57 | color: const Color.fromARGB(255, 128, 128, 129),
58 | ),
59 | Expanded(
60 | child: GestureDetector(
61 | child: Align(
62 | alignment: Alignment(1.0, 0.0),
63 | child: Text(
64 | '搜索',
65 | style: TextStyle(
66 | fontSize: 16.0,
67 | color: const Color.fromARGB(255, 192, 192, 192)),
68 | ),
69 | ),
70 | onTap: () {
71 | MyRouter.push(context, MyRouter.searchPage, '搜索流浪地球试一试');
72 | },
73 | ),
74 | )
75 | ],
76 | ),
77 | )),
78 | ),
79 | Padding(
80 | padding: const EdgeInsets.only(bottom: 5.0),
81 | child: Row(
82 | children: [
83 | Expanded(
84 | flex: 1,
85 | child: Container(),
86 | ),
87 | Expanded(
88 | flex: 3,
89 | child: widget.tabBar,
90 | ),
91 | Expanded(
92 | flex: 1,
93 | child: Container(),
94 | ),
95 | ],
96 | ),
97 | )
98 | ],
99 | );
100 | }
101 |
102 | double getTop(double translate) {
103 | return Tween(begin: allHeight, end: 0.0)
104 | .transform(widget.translate);
105 | }
106 |
107 | Widget getOpacityWidget(Widget child) {
108 | if (widget.translate == 1) {
109 | return child;
110 | }
111 | return Opacity(
112 | opacity: const Interval(0.0, 1.0, curve: Curves.fastOutSlowIn)
113 | .transform(widget.translate),
114 | child: child,
115 | );
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/lib/pages/movie/book_audio_video_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/rendering.dart';
3 | import 'dart:math' as math;
4 | import 'package:doubanapp/widgets/my_tab_bar_widget.dart';
5 | import 'package:doubanapp/widgets/search_text_field_widget.dart';
6 | import 'package:doubanapp/router.dart';
7 |
8 | var titleList = ['电影', '电视', '综艺', '读书', '音乐', '同城'];
9 |
10 | List tabList;
11 |
12 | ///书影音
13 | ///包含了'电影', '电视', '综艺', '读书', '音乐', '同城' item Widget
14 | ///这个Widget是整个项目中,十分复杂的Widget之一
15 | ///
16 | class BookAudioVideoPage extends StatefulWidget {
17 | @override
18 | State createState() {
19 | return _BookAudioVideoPageState();
20 | }
21 | }
22 |
23 | TabController _tabController;
24 |
25 | class _BookAudioVideoPageState extends State
26 | with SingleTickerProviderStateMixin {
27 | var tabBar;
28 |
29 | @override
30 | void initState() {
31 | super.initState();
32 | tabBar = HomePageTabBar();
33 | tabList = getTabList();
34 | _tabController = TabController(vsync: this, length: tabList.length);
35 | }
36 |
37 | List getTabList() {
38 | return titleList
39 | .map((item) => Text(
40 | '$item',
41 | style: TextStyle(fontSize: 15),
42 | ))
43 | .toList();
44 | }
45 |
46 | @override
47 | Widget build(BuildContext context) {
48 | return Container(
49 | color: Colors.white,
50 | child: SafeArea(
51 | child: DefaultTabController(
52 | length: titleList.length, child: _getNestedScrollView(tabBar))),
53 | );
54 | }
55 | }
56 |
57 | Widget _getNestedScrollView(Widget tabBar) {
58 | String hintText = '用一部电影来形容你的2018';
59 | return NestedScrollView(
60 | headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
61 | return [
62 | SliverToBoxAdapter(
63 | child: Container(
64 | color: Colors.white,
65 | padding: const EdgeInsets.all(10.0),
66 | child: SearchTextFieldWidget(
67 | hintText: hintText,
68 | onTab: () {
69 | MyRouter.push(context, MyRouter.searchPage, hintText);
70 | },
71 | ),
72 | ),
73 | ),
74 | SliverPersistentHeader(
75 | floating: true,
76 | pinned: true,
77 | delegate: _SliverAppBarDelegate(
78 | maxHeight: 49.0,
79 | minHeight: 49.0,
80 | child: Container(
81 | color: Colors.white,
82 | child: tabBar,
83 | )))
84 | ];
85 | },
86 | body: FlutterTabBarView(
87 | tabController: _tabController,
88 | ));
89 | }
90 |
91 | class HomePageTabBar extends StatefulWidget {
92 | HomePageTabBar({Key key}) : super(key: key);
93 |
94 | @override
95 | State createState() {
96 | return _HomePageTabBarState();
97 | }
98 | }
99 |
100 | class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
101 | _SliverAppBarDelegate({
102 | @required this.minHeight,
103 | @required this.maxHeight,
104 | @required this.child,
105 | });
106 |
107 | final double minHeight;
108 | final double maxHeight;
109 | final Widget child;
110 |
111 | @override
112 | double get minExtent => minHeight;
113 |
114 | @override
115 | double get maxExtent => math.max((minHeight ?? kToolbarHeight), minExtent);
116 |
117 | @override
118 | Widget build(
119 | BuildContext context, double shrinkOffset, bool overlapsContent) {
120 | return child;
121 | }
122 |
123 | @override
124 | bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
125 | return maxHeight != oldDelegate.maxHeight ||
126 | minHeight != oldDelegate.minHeight ||
127 | child != oldDelegate.child;
128 | }
129 | }
130 |
131 | class _HomePageTabBarState extends State {
132 | Color selectColor, unselectedColor;
133 | TextStyle selectStyle, unselectedStyle;
134 |
135 | @override
136 | void initState() {
137 | super.initState();
138 | selectColor = Colors.black;
139 | unselectedColor = Color.fromARGB(255, 117, 117, 117);
140 | selectStyle = TextStyle(fontSize: 18, color: selectColor);
141 | unselectedStyle = TextStyle(fontSize: 18, color: selectColor);
142 | }
143 |
144 | @override
145 | void dispose() {
146 | _tabController.dispose();
147 | super.dispose();
148 | }
149 |
150 | @override
151 | Widget build(BuildContext context) {
152 | //Tab小部件列表
153 | // List @required this.tabs,
154 | //组件选中以及动画的状态
155 | // TabController this.controller,
156 | //Tab是否可滑动(false->整个tab会把宽度填满,true-> tab包裹)
157 | // bool this.isScrollable = false,
158 | //选项卡下方的导航条的颜色
159 | // Color this.indicatorColor,
160 | //选项卡下方的导航条的线条粗细
161 | // double this.indicatorWeight = 2.0,
162 | // EdgeInsetsGeometry this.indicatorPadding = EdgeInsets.zero,
163 | // Decoration this.indicator,
164 | // TabBarIndicatorSize this.indicatorSize,导航条的长度,(tab:默认等分;label:跟标签长度一致)
165 | // Color this.labelColor,所选标签标签的颜色
166 | // TextStyle this.labelStyle,所选标签标签的文本样式
167 | // EdgeInsetsGeometry this.labelPadding,,所选标签标签的内边距
168 | // Color this.unselectedLabelColor,未选定标签标签的颜色
169 | // TextStyle this.unselectedLabelStyle,未选中标签标签的文字样式
170 | // void Function(T value) this.onTap,按下时的响应事件
171 |
172 | return Container(
173 | margin: EdgeInsets.only(top: 10.0, bottom: 10.0),
174 | child: TabBar(
175 | tabs: tabList,
176 | isScrollable: true,
177 | controller: _tabController,
178 | indicatorColor: selectColor,
179 | labelColor: selectColor,
180 | labelStyle: selectStyle,
181 | unselectedLabelColor: unselectedColor,
182 | unselectedLabelStyle: unselectedStyle,
183 | indicatorSize: TabBarIndicatorSize.label,
184 | ),
185 | );
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/lib/pages/movie/hot_soon_movie_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/bean/subject_entity.dart';
3 | import 'package:doubanapp/widgets/subject_mark_image_widget.dart';
4 | import '../../constant/constant.dart';
5 | ///影院热映、即将上映
6 | class HotSoonMovieWidget extends StatefulWidget {
7 | final state = _HotSoonMovieWidgetState();
8 |
9 | @override
10 | State createState() {
11 | return state;
12 | }
13 |
14 | ///设置影院热映数据
15 | void setHotMovieBeanList(List list) {
16 | state.setHotMovieBeanList(list);
17 | }
18 | }
19 |
20 | TabController _tabController;
21 | var movieCount = 16;
22 |
23 | class _HotSoonMovieWidgetState extends State
24 | with SingleTickerProviderStateMixin {
25 | Color selectColor, unselectedColor;
26 | TextStyle selectStyle, unselectedStyle;
27 | Widget tabBar;
28 | double childAspectRatio = 355.0 / 506.0;
29 | var hotCount, soonCount; //热映数量、即将上映数量、
30 | List hotMovieBeans, soonMovieBeans;
31 |
32 | @override
33 | void initState() {
34 | super.initState();
35 | selectColor = Color.fromARGB(255, 45, 45, 45);
36 | unselectedColor = Color.fromARGB(255, 135, 135, 135);
37 | selectStyle = TextStyle(
38 | fontSize: 20, color: selectColor, fontWeight: FontWeight.bold);
39 | unselectedStyle = TextStyle(fontSize: 20, color: unselectedColor);
40 | _tabController = TabController(vsync: this, length: 2);
41 | _tabController.addListener(listener);
42 | tabBar = TabBar(
43 | tabs: [
44 | Padding(
45 | padding: const EdgeInsets.only(bottom: Constant.TAB_BOTTOM),
46 | child: Text('影院热映'),
47 | ),
48 | Padding(
49 | padding: const EdgeInsets.only(bottom: Constant.TAB_BOTTOM),
50 | child: Text('即将上映'),
51 | )
52 | ],
53 | indicatorColor: selectColor,
54 | labelColor: selectColor,
55 | labelStyle: selectStyle,
56 | unselectedLabelColor: unselectedColor,
57 | unselectedLabelStyle: unselectedStyle,
58 | indicatorSize: TabBarIndicatorSize.label,
59 | controller: _tabController,
60 | isScrollable: true,
61 | // onTap: (index) {
62 | // setState(() {
63 | // if (index == 0) {
64 | // movieCount = hotCount;
65 | // } else {
66 | // movieCount = 20;
67 | // }
68 | // });
69 | // },
70 | );
71 | }
72 |
73 | void listener() {
74 | if (_tabController.indexIsChanging) {
75 | var index = _tabController.index;
76 | print("HotSoonMovieWidget index changing=$index");
77 | setState(() {
78 | if (index == 0) {
79 | movieCount = hotCount;
80 | } else {
81 | movieCount = 20;
82 | }
83 | });
84 | }
85 | }
86 |
87 | @override
88 | Widget build(BuildContext context) {
89 | return Column(
90 | children: [
91 | Row(
92 | children: [
93 | Expanded(
94 | child: tabBar,
95 | flex: 1,
96 | ),
97 | Text(
98 | '全部 $movieCount > ',
99 | style: TextStyle(
100 | fontSize: 12,
101 | color: Colors.black,
102 | fontWeight: FontWeight.bold),
103 | )
104 | ],
105 | ),
106 | // GridView.builder(
107 | // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
108 | // crossAxisCount: 3,
109 | // crossAxisSpacing: 10.0,
110 | // mainAxisSpacing: 10.0,
111 | // childAspectRatio: childAspectRatio),
112 | // //Widget Function(BuildContext context, int index);
113 | // itemBuilder: (BuildContext context, int index) {
114 | // return SubjectMarkImageWidget(hotMovieBeans[index].images.large);
115 | // })
116 | ],
117 | );
118 | }
119 |
120 | @override
121 | void dispose() {
122 | _tabController.removeListener(listener);
123 | _tabController.dispose();
124 | super.dispose();
125 | }
126 |
127 | void setHotMovieBeanList(List list) {
128 | if (list != null) {
129 | setState(() {
130 | hotMovieBeans = list;
131 | hotCount = hotMovieBeans.length;
132 | });
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/lib/pages/movie/hot_soon_tab_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/constant/text_size_constant.dart';
3 | import 'package:doubanapp/constant/color_constant.dart';
4 | import '../../constant/constant.dart';
5 | typedef TabCallBack = void Function(int index);
6 | //影院热映、即将上映 tab
7 | class HotSoonTabBar extends StatefulWidget {
8 | final state = _HotSoonTabBarState();
9 |
10 | HotSoonTabBar({Key key, TabCallBack onTabCallBack}) : super(key: key) {
11 | state.setTabCallBack(onTabCallBack);
12 | }
13 |
14 | @override
15 | State createState() {
16 | return state;
17 | }
18 |
19 | void setCount(List list) {
20 | state.setCount(list.length);
21 | }
22 |
23 | void setComingSoon(List list) {
24 | state.setComingSoonCount(list.length);
25 | }
26 | }
27 |
28 | class _HotSoonTabBarState extends State
29 | with SingleTickerProviderStateMixin {
30 | int movieCount = 0;
31 | Color selectColor, unselectedColor;
32 | TextStyle selectStyle, unselectedStyle;
33 | Widget tabBar;
34 | TabController _tabController;
35 | var hotCount, soonCount; //热映数量、即将上映数量、
36 | TabCallBack onTabCallBack;
37 | int comingSoonCount = 0;
38 | int selectIndex = 0;
39 |
40 |
41 |
42 | @override
43 | void initState() {
44 | super.initState();
45 | selectColor = ColorConstant.colorDefaultTitle;
46 | unselectedColor = Color.fromARGB(255, 135, 135, 135);
47 | selectStyle = TextStyle(
48 | fontSize: TextSizeConstant.BookAudioPartTabBar,
49 | color: selectColor,
50 | fontWeight: FontWeight.bold);
51 | unselectedStyle = TextStyle(
52 | fontSize: TextSizeConstant.BookAudioPartTabBar, color: unselectedColor);
53 | _tabController = TabController(vsync: this, length: 2);
54 | _tabController.addListener(listener);
55 | tabBar = TabBar(
56 | tabs: [Padding(
57 | padding: const EdgeInsets.only(bottom: Constant.TAB_BOTTOM),
58 | child: Text('影院热映'),
59 | ),
60 | Padding(
61 | padding: const EdgeInsets.only(bottom: Constant.TAB_BOTTOM),
62 | child: Text('即将上映'),
63 | )],
64 | indicatorColor: selectColor,
65 | labelColor: selectColor,
66 | labelStyle: selectStyle,
67 | unselectedLabelColor: unselectedColor,
68 | unselectedLabelStyle: unselectedStyle,
69 | indicatorSize: TabBarIndicatorSize.label,
70 | controller: _tabController,
71 | isScrollable: true,
72 | // onTap: (index) {
73 | // selectIndex = index;
74 | // setState(() {
75 | // if (index == 0) {
76 | // movieCount = hotCount;
77 | // } else {
78 | // movieCount = comingSoonCount;
79 | // }
80 | // if (onTabCallBack != null) {
81 | // onTabCallBack(index);
82 | // }
83 | // });
84 | // },
85 | );
86 | }
87 |
88 |
89 | void listener() {
90 | if(_tabController.indexIsChanging){
91 | var index = _tabController.index;
92 | print("HotSoonTabBar index changing=$index");
93 | selectIndex = index;
94 | setState(() {
95 | if (index == 0) {
96 | movieCount = hotCount;
97 | } else {
98 | movieCount = comingSoonCount;
99 | }
100 | if (onTabCallBack != null) {
101 | onTabCallBack(index);
102 | }
103 | });
104 | }
105 | }
106 |
107 |
108 |
109 | @override
110 | void dispose() {
111 | _tabController.removeListener(listener);
112 | _tabController.dispose();
113 | super.dispose();
114 | }
115 |
116 | @override
117 | Widget build(BuildContext context) {
118 | return Row(
119 | children: [
120 | Expanded(
121 | child: tabBar,
122 | flex: 1,
123 | ),
124 | Text(
125 | '全部 $movieCount > ',
126 | style: TextStyle(
127 | fontSize: 12, color: Colors.black, fontWeight: FontWeight.bold),
128 | )
129 | ],
130 | );
131 | }
132 |
133 | ///影院热映数量
134 | void setCount(int count) {
135 | setState(() {
136 | this.hotCount = count;
137 | if (selectIndex == 0) {
138 | setState(() {
139 | movieCount = hotCount;
140 | });
141 | }
142 | });
143 | }
144 |
145 | ///即将上映数量
146 | void setComingSoonCount(int length) {
147 | setState(() {
148 | this.comingSoonCount = length;
149 | if (selectIndex == 1) {
150 | setState(() {
151 | movieCount = comingSoonCount;
152 | });
153 | }
154 | });
155 | }
156 |
157 | void setTabCallBack(TabCallBack onTabCallBack) {
158 | this.onTabCallBack = onTabCallBack;
159 | }
160 |
161 | }
162 |
--------------------------------------------------------------------------------
/lib/pages/movie/movie_list_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | ///院线电影
4 | class MovieListPage extends StatelessWidget {
5 |
6 | MovieListPage({Key key}) : super(key: key);
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return null;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lib/pages/movie/title_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/router.dart';
3 |
4 | typedef TapCallback = void Function();
5 |
6 | ///《书影业》顶部四个TAB
7 | class TitleWidget extends StatelessWidget {
8 | @override
9 | Widget build(BuildContext context) {
10 | return Row(
11 | mainAxisAlignment: MainAxisAlignment.spaceAround,
12 | children: [
13 | _TextImgWidget(
14 | '找电影',
15 | 'assets/images/find_movie.png',
16 | tabCallBack: () {
17 | print('点击找电影');
18 | MyRouter.push(context, MyRouter.searchPage, '找电影');
19 | },
20 | ),
21 | _TextImgWidget(
22 | '豆瓣榜单',
23 | 'assets/images/douban_top.png',
24 | tabCallBack: () {
25 | print('点击豆瓣榜单');
26 | MyRouter.push(context, MyRouter.searchPage, '豆瓣榜单');
27 | },
28 | ),
29 | _TextImgWidget(
30 | '豆瓣猜',
31 | 'assets/images/douban_guess.png',
32 | tabCallBack: () {
33 | MyRouter.push(context, MyRouter.searchPage, '豆瓣猜');
34 | },
35 | ),
36 | _TextImgWidget(
37 | '豆瓣片单',
38 | 'assets/images/douban_film_list.png',
39 | tabCallBack: () {
40 | MyRouter.push(context, MyRouter.searchPage, '豆瓣片单');
41 | },
42 | )
43 | ],
44 | );
45 | }
46 | }
47 |
48 | class _TextImgWidget extends StatelessWidget {
49 | final String text;
50 | final String imgAsset;
51 | final TapCallback tabCallBack;
52 |
53 | _TextImgWidget(
54 | this.text,
55 | this.imgAsset, {
56 | Key key,
57 | this.tabCallBack,
58 | }) : super(key: key);
59 |
60 | @override
61 | Widget build(BuildContext context) {
62 | return GestureDetector(
63 | onTap: () {
64 | if (tabCallBack != null) {
65 | tabCallBack();
66 | }
67 | },
68 | child: Column(
69 | children: [
70 | Image.asset(
71 | imgAsset,
72 | width: 45,
73 | height: 45,
74 | ),
75 | Text(
76 | text,
77 | style: TextStyle(
78 | fontSize: 13,
79 | color: Color.fromARGB(
80 | 255,
81 | 128,
82 | 128,
83 | 128,
84 | )),
85 | )
86 | ],
87 | ),
88 | );
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lib/pages/movie/today_play_movie_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/widgets/image/LaminatedImage.dart';
3 |
4 | ///今日可播放电影已更新 Widget
5 | class TodayPlayMovieWidget extends StatelessWidget {
6 | final urls;
7 | final backgroundColor;
8 | TodayPlayMovieWidget(this.urls, {Key key, this.backgroundColor}) : super(key: key);
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 |
13 | if(urls == null || (urls.isEmpty)){
14 | return Container();
15 | }
16 | return Stack(
17 | alignment: AlignmentDirectional(1.0, 1.0),
18 | children: [
19 | Stack(
20 | alignment: AlignmentDirectional.bottomStart,
21 | children: [
22 | Container(
23 | height: 120.0,
24 | width: double.infinity,
25 | decoration: BoxDecoration(
26 | color: backgroundColor == null ? Color.fromARGB(255, 47, 22, 74) : backgroundColor,
27 | shape: BoxShape.rectangle,
28 | borderRadius: BorderRadius.all(Radius.circular(4.0))),
29 | ),
30 | Container(
31 | height: 140.0,
32 | margin: EdgeInsets.only(left: 13.0, bottom: 14.0),
33 | child: Row(
34 | children: [
35 | Stack(
36 | alignment: Alignment.centerLeft,
37 | children: [
38 | LaminatedImage(urls: urls, w: 90.0),
39 | Positioned(
40 | left: 90.0 / 3,
41 | child: Image.asset(
42 | 'assets/images/ic_action_playable_video_s.png',
43 | width: 30.0,
44 | height: 30.0,
45 | ),
46 | )
47 | ],
48 | ),
49 | Expanded(
50 | child: Padding(
51 | padding: EdgeInsets.only(top: 40.0, left: 20.0),
52 | child: Column(
53 | mainAxisAlignment: MainAxisAlignment.center,
54 | crossAxisAlignment: CrossAxisAlignment.start,
55 | children: [
56 | Text(
57 | '今日可播放电影已更新',
58 | style: TextStyle(fontSize: 15, color: Colors.white),
59 | ),
60 | Padding(
61 | padding: EdgeInsets.only(top: 6.0),
62 | child: Text(
63 | '全部 30 > ',
64 | style:
65 | TextStyle(fontSize: 13, color: Colors.white),
66 | ),
67 | )
68 | ],
69 | ),
70 | ),
71 | )
72 | ],
73 | ),
74 | ),
75 | ],
76 | ),
77 | Row(
78 | mainAxisAlignment: MainAxisAlignment.end,
79 | children: [
80 | Padding(
81 | padding: EdgeInsets.only(bottom: 10.0),
82 | child: Image.asset(
83 | 'assets/images/sofa.png',
84 | width: 15.0,
85 | height: 15.0,
86 | ),
87 | ),
88 | Padding(
89 | padding: EdgeInsets.only(bottom: 10.0, right: 10.0, left: 5.0),
90 | child: Text(
91 | '看电影',
92 | style: TextStyle(fontSize: 11, color: Colors.white),
93 | ),
94 | )
95 | ],
96 | )
97 | ],
98 | );
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/lib/pages/movie/tv_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class TVPage extends StatefulWidget {
4 | @override
5 | State createState() {
6 | return _TVPageState();
7 | }
8 | }
9 |
10 | class _TVPageState extends State{
11 | @override
12 | Widget build(BuildContext context) {
13 | return null;
14 | }
15 |
16 | }
--------------------------------------------------------------------------------
/lib/pages/photo_hero_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/widgets/title_bar.dart';
3 | import 'package:doubanapp/router.dart';
4 |
5 | class PhotoHeroPage extends StatelessWidget {
6 | final String photoUrl;
7 | final double width;
8 |
9 | PhotoHeroPage({Key key, this.photoUrl, this.width}) : super(key: key);
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Container(
14 | color: Colors.transparent,
15 | alignment: Alignment.center,
16 | child: _PhotoHero(
17 | photoUrl: photoUrl,
18 | width: width,
19 | onTap: () {
20 | Navigator.of(context).pop();
21 | },
22 | ),
23 | );
24 | }
25 | }
26 |
27 | class _PhotoHero extends StatelessWidget {
28 | const _PhotoHero({Key key, this.photoUrl, this.onTap, this.width})
29 | : super(key: key);
30 | final double width;
31 | final String photoUrl;
32 | final VoidCallback onTap;
33 |
34 | Widget build(BuildContext context) {
35 | return SizedBox(
36 | width: width,
37 | child: Hero(
38 | tag: photoUrl,
39 | child: Material(
40 | color: Colors.transparent,
41 | child: InkWell(
42 | onTap: onTap,
43 | child: Image.network(
44 | photoUrl,
45 | fit: BoxFit.contain,
46 | ),
47 | ),
48 | ),
49 | ),
50 | );
51 | }
52 | }
53 | //
54 | //class HeroAnimation extends StatelessWidget {
55 | // final String url;
56 | //
57 | // HeroAnimation(this.url, {Key key}) : super(key: key);
58 | //
59 | // Widget build(BuildContext context) {
60 | // return Scaffold(
61 | // appBar: AppBar(
62 | // title: const Text('Basic Hero Animation'),
63 | // ),
64 | // body: Center(
65 | // child: PhotoHero(
66 | // photo: url,
67 | // width: 300.0,
68 | // onTap: () {
69 | // Router.push(
70 | // context, Router.photoHero, {'photoUrl': url, 'width': 100.0});
71 | // }),
72 | // ),
73 | // );
74 | // }
75 | //}
76 | //
77 | //class PhotoHero extends StatelessWidget {
78 | // const PhotoHero({Key key, this.photo, this.onTap, this.width})
79 | // : super(key: key);
80 | //
81 | // final String photo;
82 | // final VoidCallback onTap;
83 | // final double width;
84 | //
85 | // Widget build(BuildContext context) {
86 | // return SizedBox(
87 | // width: width,
88 | // child: Hero(
89 | // tag: photo,
90 | // child: Material(
91 | // color: Colors.transparent,
92 | // child: InkWell(
93 | // onTap: onTap,
94 | // child: ListView.builder(itemBuilder: (BuildContext context, int index){
95 | // return Image.network(
96 | // photo,
97 | // fit: BoxFit.contain,
98 | // );
99 | // }, itemCount: 20,),
100 | // ),
101 | // ),
102 | // ),
103 | // );
104 | // }
105 | //}
106 |
--------------------------------------------------------------------------------
/lib/pages/search/search_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/widgets/search_text_field_widget.dart';
3 | import 'package:doubanapp/http/API.dart';
4 | import 'package:doubanapp/bean/search_result_entity.dart';
5 | //import 'package:doubanapp/widgets/image/cached_network_image.dart';
6 | import 'package:doubanapp/router.dart';
7 | import 'package:flutter/cupertino.dart';
8 |
9 | ///搜索
10 | class SearchPage extends StatefulWidget {
11 | final String searchHintContent;
12 |
13 | ///搜索框中的默认显示内容
14 | SearchPage({Key key, this.searchHintContent = '用一部电影来形容你的2018'})
15 | : super(key: key);
16 |
17 | @override
18 | State createState() => _SearchPageState();
19 | }
20 |
21 | class _SearchPageState extends State {
22 | final API _api = API();
23 | SearchResultEntity _searchResultEntity;
24 | var imgW;
25 | var imgH;
26 | bool showLoading = false;
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | if (imgW == null) {
31 | imgW = MediaQuery.of(context).size.width / 7;
32 | imgH = imgW / 0.75;
33 | }
34 | if (_searchResultEntity != null &&
35 | _searchResultEntity.subjects.isNotEmpty) {
36 | _searchResultEntity.subjects.sort((a, b) => (b.year.compareTo(a.year)));
37 | }
38 | return Scaffold(
39 | body: SafeArea(
40 | child: showLoading
41 | ? Center(
42 | child: CupertinoActivityIndicator(),
43 | )
44 | : _searchResultEntity == null
45 | ? getSearchWidget()
46 | : Column(
47 | children: [
48 | getSearchWidget(),
49 | Expanded(
50 | child: ListView.builder(
51 | itemBuilder: (BuildContext context, int index) {
52 | SearchResultSubject bean =
53 | _searchResultEntity.subjects[index];
54 | return Padding(
55 | padding: EdgeInsets.all(10.0),
56 | child: GestureDetector(
57 | behavior: HitTestBehavior.translucent,
58 | child: _getItem(bean, index),
59 | onTap: () {
60 | MyRouter.push(
61 | context, MyRouter.detailPage, bean.id);
62 | },
63 | ),
64 | );
65 | },
66 | itemCount: _searchResultEntity.subjects.length,
67 | ),
68 | )
69 | ],
70 | )),
71 | );
72 | }
73 |
74 | String getType(String subtype) {
75 | switch (subtype) {
76 | case 'movie':
77 | return '电影';
78 | }
79 | return '';
80 | }
81 |
82 | String listConvertString2(List genres) {
83 | if (genres.isEmpty) {
84 | return '';
85 | } else {
86 | String tmp = '';
87 | for (SearchResultSubjectsDirector item in genres) {
88 | tmp = tmp + item.name;
89 | }
90 | return tmp;
91 | }
92 | }
93 |
94 | String listConvertString(List genres) {
95 | if (genres.isEmpty) {
96 | return '';
97 | } else {
98 | String tmp = '';
99 | for (String item in genres) {
100 | tmp = tmp + item;
101 | }
102 | return tmp;
103 | }
104 | }
105 |
106 | Widget getSearchWidget() {
107 | return Padding(
108 | padding:
109 | EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 20.0),
110 | child: Row(
111 | children: [
112 | Expanded(
113 | child: SearchTextFieldWidget(
114 | hintText: widget.searchHintContent,
115 | onSubmitted: (searchContent) {
116 | showLoading = true;
117 | _api.searchMovie(searchContent, (searchResultEntity) {
118 | setState(() {
119 | showLoading = false;
120 | _searchResultEntity = searchResultEntity;
121 | });
122 | });
123 | },
124 | ),
125 | ),
126 | GestureDetector(
127 | child: Padding(
128 | padding: EdgeInsets.only(left: 10.0),
129 | child: Text(
130 | '取消',
131 | style: getStyle(Colors.green, 17.0),
132 | ),
133 | ),
134 | onTap: () {
135 | Navigator.pop(context);
136 | },
137 | )
138 | ],
139 | ),
140 | );
141 | }
142 |
143 | Widget _getItem(SearchResultSubject bean, int index) {
144 | return Row(
145 | crossAxisAlignment: CrossAxisAlignment.start,
146 | children: [
147 | Card(
148 | child: Image.network(
149 | bean.images.medium,
150 | fit: BoxFit.cover,
151 | width: imgW,
152 | height: imgH,
153 | ),
154 | clipBehavior: Clip.antiAlias,
155 | elevation: 5.0,
156 | shape: RoundedRectangleBorder(
157 | borderRadius: BorderRadius.all(Radius.circular(5.0))),
158 | ),
159 | Padding(
160 | padding: EdgeInsets.all(5.0),
161 | ),
162 | Expanded(
163 | child: Column(
164 | crossAxisAlignment: CrossAxisAlignment.start,
165 | children: [
166 | Text(
167 | getType(bean.subtype),
168 | style: getStyle(Colors.grey, 12.0),
169 | ),
170 | Text(bean.title + '(${bean.year})',
171 | style: getStyle(Colors.black, 15.0, bold: true)),
172 | Text(
173 | '${bean.rating.average} 分 / ${listConvertString(bean.pubdates)} / ${listConvertString(bean.genres)} / ${listConvertString2(bean.directors)}',
174 | style: getStyle(Colors.grey, 13.0))
175 | ],
176 | ),
177 | )
178 | ],
179 | );
180 | }
181 |
182 | TextStyle getStyle(Color color, double fontSize, {bool bold = false}) {
183 | return TextStyle(
184 | color: color,
185 | fontSize: fontSize,
186 | fontWeight: bold ? FontWeight.bold : FontWeight.normal);
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/lib/pages/shop_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/rendering.dart';
4 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
5 | import 'package:doubanapp/util/screen_utils.dart';
6 |
7 | String url1 = 'https://flutterchina.club/';
8 | String url2 = 'http://flutterall.com/';
9 | bool _closed = false;
10 | bool _isShow = true;
11 | ///提供链接到一个唯一webview的单例实例,以便您可以从应用程序的任何位置控制webview
12 | final _webviewReference = FlutterWebviewPlugin();
13 |
14 | ///市集 市集使用两个webView代替,因为豆瓣中 这个就是WebView
15 | class ShopPageWidget extends StatelessWidget {
16 |
17 | void setShowState(bool isShow) {
18 | _isShow = isShow;
19 | if(!isShow){
20 | _closed = true;
21 | _webviewReference.hide();
22 | _webviewReference.close();
23 | }
24 | }
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | return WebViewPageWidget();
29 | }
30 | }
31 |
32 |
33 | class WebViewPageWidget extends StatefulWidget {
34 | @override
35 | _WebViewPageWidgetState createState() => _WebViewPageWidgetState();
36 | }
37 |
38 |
39 | class _WebViewPageWidgetState extends State
40 | with SingleTickerProviderStateMixin {
41 | var list = ['豆芽豆品', '豆芽时间'];
42 | int selectIndex = 0;
43 | Color selectColor, unselectColor;
44 | TextStyle selectStyle, unselectedStyle;
45 | TabController tabController;
46 |
47 | @override
48 | void initState() {
49 | super.initState();
50 | print('_ShopPageWidgetState initState');
51 | _webviewReference.close();
52 | tabController = new TabController(length: list.length, vsync: this);
53 | selectColor = Colors.green;
54 | unselectColor = Color.fromARGB(255, 117, 117, 117);
55 | selectStyle = TextStyle(fontSize: 18);
56 | unselectedStyle = TextStyle(fontSize: 18);
57 | _webviewReference.onUrlChanged.listen((String url) {
58 | if(url != url1 || url != url2){
59 | print("new Url=$url");
60 | }
61 | });
62 | }
63 |
64 | @override
65 | void dispose() {
66 | super.dispose();
67 | print('_ShopPageWidgetState dispose');
68 | tabController.dispose();
69 | _webviewReference.close();
70 | _webviewReference.dispose();
71 | }
72 |
73 | @override
74 | Widget build(BuildContext context) {
75 | if(!_isShow){
76 | return Container();
77 | }
78 | return Container(
79 | child: SafeArea(
80 | child: Column(
81 | children: [
82 | Row(
83 | children: [
84 | Expanded(
85 | flex: 1,
86 | child: Container(),
87 | ),
88 | Expanded(
89 | flex: 3,
90 | child: Container(
91 | padding:
92 | const EdgeInsets.only(top: 20.0),
93 | child: TabBar(
94 | tabs: list.map((item) => Text(item)).toList(),
95 | isScrollable: false,
96 | controller: tabController,
97 | indicatorColor: selectColor,
98 | labelColor: selectColor,
99 | labelStyle: selectStyle,
100 | unselectedLabelColor: unselectColor,
101 | unselectedLabelStyle: unselectedStyle,
102 | indicatorSize: TabBarIndicatorSize.label,
103 | onTap: (selectIndex) {
104 | print('select=$selectIndex');
105 | this.selectIndex = selectIndex;
106 | print('_closed=$_closed');
107 | _webviewReference.reloadUrl(selectIndex == 0 ? url1 : url2);
108 | },
109 | ),
110 | ),
111 | ),
112 | Expanded(
113 | flex: 1,
114 | child: Container(),
115 | )
116 | ],
117 | ),
118 | Expanded(
119 | child: _WebViewWidget(selectIndex == 0 ? url1 : url2),
120 | )
121 | ],
122 | )),
123 | color: Colors.white,
124 | );
125 | }
126 |
127 | }
128 |
129 | class _WebViewWidget extends StatefulWidget {
130 | final String url;
131 |
132 | _WebViewWidget(this.url, {Key key}) : super(key: key);
133 |
134 | @override
135 | _WebViewWidgetState createState() => _WebViewWidgetState();
136 | }
137 |
138 | class _WebViewWidgetState extends State<_WebViewWidget> {
139 | Rect _rect;
140 | bool needFullScreen = false;
141 | @override
142 | void initState() {
143 | super.initState();
144 | _webviewReference.close();
145 | }
146 |
147 | @override
148 | void dispose() {
149 | super.dispose();
150 | _webviewReference.close();
151 | _webviewReference.dispose();
152 | }
153 |
154 | @override
155 | Widget build(BuildContext context) {
156 | print('build widget.url=${widget.url}');
157 | return _WebviewPlaceholder(onRectChanged: (Rect value) {
158 | if (_rect == null || _closed) {
159 | if(_rect != value){
160 | _rect = value;
161 | }
162 | print('_webviewReference.launch');
163 | _webviewReference.launch(widget.url,
164 | withJavascript: true,
165 | withLocalStorage: true,
166 | scrollBar: true,
167 | rect: getRect());
168 | } else {
169 | print('_webviewReference.launch else');
170 | if (_rect != value) {
171 | _rect = value;
172 | }
173 | _webviewReference.reloadUrl(widget.url);
174 | }
175 | }, child: const Center(child: const CircularProgressIndicator()),);
176 | }
177 |
178 | getRect() {
179 | if(needFullScreen){
180 | return null;
181 | }else{
182 | return Rect.fromLTRB(0.0, ScreenUtils.getStatusBarH(context) + 60.0,
183 | ScreenUtils.screenW(context), ScreenUtils.screenH(context) - 60.0);
184 | }
185 | }
186 |
187 | }
188 |
189 | class _WebviewPlaceholder extends SingleChildRenderObjectWidget {
190 | const _WebviewPlaceholder({
191 | Key key,
192 | @required this.onRectChanged,
193 | Widget child,
194 | }) : super(key: key, child: child);
195 |
196 | final ValueChanged onRectChanged;
197 |
198 | @override
199 | RenderObject createRenderObject(BuildContext context) {
200 | return _WebviewPlaceholderRender(
201 | onRectChanged: onRectChanged,
202 | );
203 | }
204 |
205 | @override
206 | void updateRenderObject(
207 | BuildContext context, _WebviewPlaceholderRender renderObject) {
208 | renderObject..onRectChanged = onRectChanged;
209 | }
210 | }
211 |
212 | class _WebviewPlaceholderRender extends RenderProxyBox {
213 | _WebviewPlaceholderRender({
214 | RenderBox child,
215 | ValueChanged onRectChanged,
216 | }) : _callback = onRectChanged,
217 | super(child);
218 |
219 | ValueChanged _callback;
220 | Rect _rect;
221 |
222 | Rect get rect => _rect;
223 |
224 | set onRectChanged(ValueChanged callback) {
225 | if (callback != _callback) {
226 | _callback = callback;
227 | notifyRect();
228 | }
229 | }
230 |
231 | void notifyRect() {
232 | if (_callback != null && _rect != null) {
233 | _callback(_rect);
234 | }
235 | }
236 |
237 | @override
238 | void paint(PaintingContext context, Offset offset) {
239 | super.paint(context, offset);
240 | final rect = offset & size;
241 | if (_rect != rect) {
242 | _rect = rect;
243 | notifyRect();
244 | }
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/lib/pages/splash/splash_widget.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:doubanapp/pages/container_page.dart';
5 | import 'package:doubanapp/util/screen_utils.dart';
6 | import 'package:doubanapp/constant/constant.dart';
7 | import 'package:fluttertoast/fluttertoast.dart';
8 | ///打开APP首页
9 | class SplashWidget extends StatefulWidget {
10 | @override
11 | _SplashWidgetState createState() => _SplashWidgetState();
12 | }
13 |
14 | class _SplashWidgetState extends State {
15 | var container = ContainerPage();
16 |
17 | bool showAd = true;
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | print('build splash');
22 | return Stack(
23 | children: [
24 | Offstage(
25 | child: container,
26 | offstage: showAd,
27 | ),
28 | Offstage(
29 | child: Container(
30 | color: Colors.white,
31 | child: Stack(
32 | children: [
33 | Align(
34 | alignment: Alignment(0.0, 0.0),
35 | child: Column(
36 | mainAxisAlignment: MainAxisAlignment.center,
37 | children: [
38 | CircleAvatar(
39 | radius: ScreenUtils.screenW(context) / 3,
40 | backgroundColor: Colors.white,
41 | backgroundImage:
42 | AssetImage(Constant.ASSETS_IMG + 'home.png'),
43 | ),
44 | Padding(
45 | padding: const EdgeInsets.only(top: 20.0),
46 | child: Text(
47 | '落花有意随流水,流水无心恋落花',
48 | style: TextStyle(fontSize: 15.0, color: Colors.black),
49 | ),
50 | )
51 | ],
52 | ),
53 | ),
54 | SafeArea(
55 | child: Column(
56 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
57 | children: [
58 | Align(
59 | alignment: Alignment(1.0, 0.0),
60 | child: Container(
61 | margin: const EdgeInsets.only(right: 30.0, top: 20.0),
62 | padding: const EdgeInsets.only(
63 | left: 10.0, right: 10.0, top: 2.0, bottom: 2.0),
64 | child: CountDownWidget(
65 | onCountDownFinishCallBack: (bool value) {
66 | if (value) {
67 | setState(() {
68 | showAd = false;
69 | });
70 | }
71 | },
72 | ),
73 | decoration: BoxDecoration(
74 | color: Color(0xffEDEDED),
75 | borderRadius:
76 | const BorderRadius.all(Radius.circular(10.0))),
77 | ),
78 | ),
79 | Padding(
80 | padding: const EdgeInsets.only(bottom: 40.0),
81 | child: Row(
82 | mainAxisAlignment: MainAxisAlignment.center,
83 | children: [
84 | Image.asset(
85 | Constant.ASSETS_IMG + 'ic_launcher.png',
86 | width: 50.0,
87 | height: 50.0,
88 | ),
89 | Padding(
90 | padding: const EdgeInsets.only(left: 10.0),
91 | child: Text(
92 | 'Hi,豆芽',
93 | style: TextStyle(
94 | color: Colors.green,
95 | fontSize: 30.0,
96 | fontWeight: FontWeight.bold),
97 | ),
98 | )
99 | ],
100 | ),
101 | )
102 | ],
103 | ))
104 | ],
105 | ),
106 | width: ScreenUtils.screenW(context),
107 | height: ScreenUtils.screenH(context),
108 | ),
109 | offstage: !showAd,
110 | )
111 | ],
112 | );
113 | }
114 | }
115 |
116 | class CountDownWidget extends StatefulWidget {
117 | final onCountDownFinishCallBack;
118 |
119 | CountDownWidget({Key key, @required this.onCountDownFinishCallBack})
120 | : super(key: key);
121 |
122 | @override
123 | _CountDownWidgetState createState() => _CountDownWidgetState();
124 | }
125 |
126 | class _CountDownWidgetState extends State {
127 | var _seconds = 6;
128 | Timer _timer;
129 |
130 | @override
131 | void initState() {
132 | super.initState();
133 | _startTimer();
134 | }
135 |
136 | @override
137 | Widget build(BuildContext context) {
138 | return Text(
139 | '$_seconds',
140 | style: TextStyle(fontSize: 17.0),
141 | );
142 | }
143 |
144 | /// 启动倒计时的计时器。
145 | void _startTimer() {
146 | _timer = Timer.periodic(Duration(seconds: 1), (timer) {
147 | setState(() {});
148 | if (_seconds <= 1) {
149 | widget.onCountDownFinishCallBack(true);
150 | _cancelTimer();
151 | return;
152 | }
153 | _seconds--;
154 | });
155 | }
156 |
157 | /// 取消倒计时的计时器。
158 | void _cancelTimer() {
159 | _timer?.cancel();
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/lib/pages/videos_play_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:doubanapp/widgets/video_widget.dart';
4 | import 'package:doubanapp/bean/movie_detail_bean.dart';
5 |
6 | class VideoPlayPage extends StatefulWidget {
7 | final List beans;
8 |
9 | VideoPlayPage(this.beans, {Key key}) : super(key: key);
10 |
11 | @override
12 | State createState() => _VideoPlayPageState();
13 | }
14 |
15 | class _VideoPlayPageState extends State {
16 | double mediumW, mediumH; //309 X 177
17 | int _showPlayIndex = 0;
18 | VideoWidget playWidget;
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | if (mediumW == null) {
23 | mediumW = MediaQuery.of(context).size.width / 4;
24 | mediumH = mediumW / 309 * 177;
25 | playWidget = VideoWidget(
26 | widget.beans[0].resource_url,
27 | previewImgUrl: widget.beans[0].medium,
28 | );
29 | }
30 | return Scaffold(
31 | backgroundColor: Colors.black,
32 | body: SafeArea(
33 | child: Column(
34 | children: [
35 | Container(
36 | color: Colors.black,
37 | child: Column(
38 | crossAxisAlignment: CrossAxisAlignment.start,
39 | children: [
40 | IconButton(
41 | icon: Icon(
42 | Icons.arrow_back_ios,
43 | color: Colors.white,
44 | ),
45 | onPressed: () {
46 | Navigator.pop(context);
47 | }),
48 | playWidget
49 | ],
50 | ),
51 | ),
52 | Expanded(
53 | child: Container(
54 | color: Colors.white,
55 | child: ListView.builder(
56 | itemBuilder: (BuildContext context, int index) {
57 | if (index == 0) {
58 | return Padding(
59 | padding: EdgeInsets.all(10.0),
60 | child: Row(
61 | children: [
62 | Expanded(
63 | child: Text(
64 | '观看预告片/片段/花絮',
65 | style: TextStyle(fontSize: 12.0),
66 | ),
67 | ),
68 | Text('${widget.beans.length}',
69 | style: TextStyle(fontSize: 12.0))
70 | ],
71 | ),
72 | );
73 | }
74 | return getItem(widget.beans[index - 1], index - 1);
75 | },
76 | itemCount: widget.beans.length + 1,
77 | ),
78 | ),
79 | )
80 | ],
81 | )),
82 | );
83 | }
84 |
85 | Widget getItem(Blooper bean, int index) {
86 | return GestureDetector(
87 | behavior: HitTestBehavior.translucent,
88 | child: Column(
89 | children: [
90 | Row(
91 | crossAxisAlignment: CrossAxisAlignment.start,
92 | children: [
93 | Padding(
94 | padding: EdgeInsets.all(10.0),
95 | child: Stack(
96 | children: [
97 | CachedNetworkImage(
98 | imageUrl: bean.medium,
99 | width: mediumW,
100 | height: mediumH,
101 | fit: BoxFit.cover,
102 | ),
103 | Align(
104 | alignment: Alignment.center,
105 | child: getPlay(index),
106 | )
107 | ],
108 | ),
109 | ),
110 | Expanded(
111 | child: Padding(
112 | padding: EdgeInsets.only(right: 10.0),
113 | child: Text(
114 | bean.title,
115 | softWrap: true,
116 | style: TextStyle(fontSize: 15.0),
117 | ),
118 | ),
119 | ),
120 | ],
121 | ),
122 | Divider()
123 | ],
124 | ),
125 | onTap: () {
126 | setState(() {
127 | _showPlayIndex = index;
128 | });
129 | playWidget.updateUrl(bean.resource_url);
130 | },
131 | );
132 | }
133 |
134 | getPlay(int index) {
135 | if (index == _showPlayIndex) {
136 | return Container(
137 | width: mediumW,
138 | height: mediumH,
139 | alignment: Alignment.center,
140 | child: Icon(
141 | Icons.play_circle_outline,
142 | color: Colors.amber,
143 | ),
144 | );
145 | } else {
146 | return Container();
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/lib/pages/web_view_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
3 |
4 | class WebViewPage extends StatelessWidget {
5 | final String url;
6 | final dynamic params;
7 | static final String TITLE = 'title';
8 |
9 | WebViewPage(this.url, {Key key, this.params}) : super(key: key);
10 | // final _webviewReference = FlutterWebviewPlugin();
11 | @override
12 | Widget build(BuildContext context) {
13 | // _webviewReference.close();
14 | // _webviewReference.dispose();
15 |
16 | return WebviewScaffold(
17 | url: url,
18 | appBar: AppBar(
19 | title: Text(params[TITLE]),
20 | backgroundColor: Colors.green,
21 | ),
22 | );
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/lib/repository/movie_repository.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/bean/subject_entity.dart';
3 | import 'package:doubanapp/bean/top_item_bean.dart';
4 | import 'package:doubanapp/http/http_request.dart';
5 | import 'dart:math' as math;
6 | //import 'package:palette_generator/palette_generator.dart';
7 | import 'package:doubanapp/http/API.dart';
8 | import 'package:doubanapp/http/mock_request.dart';
9 | import 'package:palette_generator/palette_generator.dart';
10 | import 'package:shared_preferences/shared_preferences.dart';
11 | import 'package:doubanapp/constant/cache_key.dart';
12 |
13 | class MovieRepository {
14 | var _request;
15 |
16 | // var _request = HttpRequest(API.BASE_URL);
17 |
18 | List hotShowBeans; //影院热映
19 | List comingSoonBeans; //即将上映
20 | List hotBeans; //豆瓣榜单
21 | List weeklyBeans; //一周口碑电影榜
22 | List top250Beans; //Top250
23 | List todayUrls;
24 | TopItemBean weeklyTopBean, weeklyHotBean, weeklyTop250Bean;
25 | Color weeklyTopColor, weeklyHotColor, weeklyTop250Color, todayPlayBg;
26 |
27 | MovieRepository(
28 | {this.hotShowBeans,
29 | this.comingSoonBeans,
30 | this.hotBeans,
31 | this.weeklyBeans,
32 | this.top250Beans,
33 | this.todayUrls,
34 | this.weeklyTopBean,
35 | this.weeklyHotBean,
36 | this.weeklyTop250Bean,
37 | this.weeklyTopColor,
38 | this.weeklyHotColor,
39 | this.weeklyTop250Color,
40 | this.todayPlayBg});
41 |
42 | Future requestAPI() async {
43 | SharedPreferences prefs = await SharedPreferences.getInstance();
44 | bool useNetData = prefs.getBool(CacheKey.USE_NET_DATA) ?? false;
45 | if (useNetData) {
46 | _request = HttpRequest(API.BASE_URL);
47 | } else {
48 | _request = MockRequest();
49 | }
50 |
51 | ///影院热映
52 | var result = await _request.get(API.IN_THEATERS);
53 | var resultList = result['subjects'];
54 | hotShowBeans =
55 | resultList.map((item) => Subject.fromMap(item)).toList();
56 |
57 | ///即将上映
58 | result = await _request.get(API.COMING_SOON);
59 | resultList = result['subjects'];
60 | comingSoonBeans =
61 | resultList.map((item) => Subject.fromMap(item)).toList();
62 | int start = math.Random().nextInt(220);
63 |
64 | ///这里使用了下面的模拟请求
65 |
66 | if (useNetData) {
67 | result = await _request.get(API.TOP_250 +
68 | '?start=$start&count=7&apikey=0b2bdeda43b5688921839c8ecb20399b');
69 | } else {
70 | result = await _request.get(API.TOP_250);
71 | }
72 | resultList = result['subjects'];
73 |
74 | ///豆瓣榜单
75 | hotBeans =
76 | resultList.map((item) => Subject.fromMap(item)).toList();
77 |
78 | ///一周热门电影榜
79 | weeklyHotBean = TopItemBean.convertHotBeans(hotBeans);
80 | var paletteGenerator = await PaletteGenerator.fromImageProvider(
81 | NetworkImage(hotBeans[0].images.medium));
82 | if (paletteGenerator != null && paletteGenerator.colors.isNotEmpty) {
83 | weeklyHotColor = (paletteGenerator.colors.toList()[0]);
84 | }
85 |
86 | ///一周口碑电影榜
87 | result = await _request.get(API.WEEKLY);
88 | resultList = result['subjects'];
89 | weeklyBeans = resultList
90 | .map((item) => SubjectEntity.fromMap(item))
91 | .toList();
92 | weeklyTopBean = TopItemBean.convertWeeklyBeans(weeklyBeans);
93 | paletteGenerator = await PaletteGenerator.fromImageProvider(
94 | NetworkImage(weeklyBeans[0].subject.images.medium));
95 | if (paletteGenerator != null && paletteGenerator.colors.isNotEmpty) {
96 | weeklyTopColor = (paletteGenerator.colors.toList()[0]);
97 | }
98 |
99 | ///今日可播放电影
100 | start = math.Random().nextInt(220);
101 |
102 | ///这里使用了下面的模拟请求
103 |
104 | if (useNetData) {
105 | result = await _request.get(API.TOP_250 +
106 | '?start=$start&count=7&apikey=0b2bdeda43b5688921839c8ecb20399b');
107 | } else {
108 | result = await _request.get(API.TOP_250);
109 | }
110 | resultList = result['subjects'];
111 | List beans =
112 | resultList.map((item) => Subject.fromMap(item)).toList();
113 | todayUrls = [];
114 | todayUrls.add(beans[0].images.medium);
115 | todayUrls.add(beans[1].images.medium);
116 | todayUrls.add(beans[2].images.medium);
117 | paletteGenerator =
118 | await PaletteGenerator.fromImageProvider(NetworkImage(todayUrls[0]));
119 | if (paletteGenerator != null && paletteGenerator.colors.isNotEmpty) {
120 | todayPlayBg = (paletteGenerator.colors.toList()[0]);
121 | }
122 |
123 | ///豆瓣TOP250
124 | /// ///这里使用了下面的模拟请求
125 | // result = await _request.get(API.TOP_250 + '?start=0&count=5&apikey=0b2bdeda43b5688921839c8ecb20399b');
126 |
127 | if (useNetData) {
128 | result = await _request.get(API.TOP_250 +
129 | '?start=0&count=5&apikey=0b2bdeda43b5688921839c8ecb20399b');
130 | } else {
131 | result = await _request.get(API.TOP_250);
132 | }
133 | resultList = result['subjects'];
134 | top250Beans =
135 | resultList.map((item) => Subject.fromMap(item)).toList();
136 | weeklyTop250Bean = TopItemBean.convertTopBeans(top250Beans);
137 | paletteGenerator = await PaletteGenerator.fromImageProvider(
138 | NetworkImage(top250Beans[0].images.medium));
139 | if (paletteGenerator != null && paletteGenerator.colors.isNotEmpty) {
140 | weeklyTop250Color = (paletteGenerator.colors.toList()[0]);
141 | }
142 | return MovieRepository(
143 | hotShowBeans: hotShowBeans,
144 | comingSoonBeans: comingSoonBeans,
145 | hotBeans: hotBeans,
146 | weeklyBeans: weeklyBeans,
147 | top250Beans: top250Beans,
148 | todayUrls: todayUrls,
149 | weeklyTopBean: weeklyTopBean,
150 | weeklyHotBean: weeklyHotBean,
151 | weeklyTop250Bean: weeklyTop250Bean,
152 | weeklyTopColor: weeklyTopColor,
153 | weeklyHotColor: weeklyHotColor,
154 | weeklyTop250Color: weeklyTop250Color,
155 | todayPlayBg: todayPlayBg);
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/lib/repository/person_detail_repository.dart:
--------------------------------------------------------------------------------
1 | import 'package:doubanapp/bean/celebrity_entity.dart';
2 | import 'package:doubanapp/bean/celebrity_work_entity.dart';
3 | import 'package:doubanapp/http/http_request.dart';
4 | import 'package:doubanapp/http/API.dart';
5 |
6 | class PersonDetailRepository {
7 | CelebrityEntity celebrityEntity;
8 | CelebrityWorkEntity celebrityWorkEntity;
9 | HttpRequest _httpRequest = HttpRequest(API.BASE_URL);
10 | PersonDetailRepository({this.celebrityEntity, this.celebrityWorkEntity});
11 |
12 | Future requestAPI(String id) async {
13 | var result = await _httpRequest.get('/v2/movie/celebrity/$id/works?apikey=0b2bdeda43b5688921839c8ecb20399b');
14 | celebrityWorkEntity = CelebrityWorkEntity.fromJson(result);
15 |
16 | result = await _httpRequest.get('/v2/movie/celebrity/$id?apikey=0b2bdeda43b5688921839c8ecb20399b');
17 | celebrityEntity = CelebrityEntity.fromJson(result);
18 | return PersonDetailRepository(celebrityEntity: celebrityEntity, celebrityWorkEntity: celebrityWorkEntity);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/router.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:doubanapp/pages/detail/detail_page.dart';
4 | import 'package:doubanapp/pages/container_page.dart';
5 | import 'package:doubanapp/pages/videos_play_page.dart';
6 | import 'package:doubanapp/pages/search/search_page.dart';
7 | import 'package:doubanapp/pages/photo_hero_page.dart';
8 | import 'package:doubanapp/pages/person_detail_page.dart';
9 | import 'package:doubanapp/pages/web_view_page.dart';
10 |
11 | ///https://www.jianshu.com/p/b9d6ec92926f
12 |
13 | class MyRouter {
14 | static const homePage = 'app://';
15 | static const detailPage = 'app://DetailPage';
16 | static const playListPage = 'app://VideosPlayPage';
17 | static const searchPage = 'app://SearchPage';
18 | static const photoHero = 'app://PhotoHero';
19 | static const personDetailPage = 'app://PersonDetailPage';
20 |
21 | // Widget _router(String url, dynamic params) {
22 | // String pageId = _pageIdMap[url];
23 | // return _getPage(pageId, params);
24 | // }
25 | //
26 | // Map _pageIdMap = {
27 | // 'app/': 'ContainerPageWidget',
28 | // detailPage: 'DetailPage',
29 | // };
30 |
31 | Widget _getPage(String url, dynamic params) {
32 | if (url.startsWith('https://') || url.startsWith('http://')) {
33 | return WebViewPage(url, params: params);
34 | } else {
35 | switch (url) {
36 | case detailPage:
37 | return DetailPage(params);
38 | case homePage:
39 | return ContainerPage();
40 | case playListPage:
41 | return VideoPlayPage(params);
42 | case searchPage:
43 | return SearchPage(searchHintContent: params);
44 | case photoHero:
45 | return PhotoHeroPage(
46 | photoUrl: params['photoUrl'], width: params['width']);
47 | case personDetailPage:
48 | return PersonDetailPage(params['personImgUrl'], params['id']);
49 | }
50 | }
51 | return null;
52 | }
53 |
54 | //
55 | // void push(BuildContext context, String url, dynamic params) {
56 | // Navigator.push(context, MaterialPageRoute(builder: (context) {
57 | // return _getPage(url, params);
58 | // }));
59 | // }
60 |
61 | MyRouter.pushNoParams(BuildContext context, String url) {
62 | Navigator.push(context, MaterialPageRoute(builder: (context) {
63 | return _getPage(url, null);
64 | }));
65 | }
66 |
67 | MyRouter.push(BuildContext context, String url, dynamic params) {
68 | Navigator.push(context, MaterialPageRoute(builder: (context) {
69 | return _getPage(url, params);
70 | }));
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/lib/util/pick_img_main_color.dart:
--------------------------------------------------------------------------------
1 | //import 'package:palette_generator/palette_generator.dart';
2 | import 'package:flutter/rendering.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:palette_generator/palette_generator.dart';
5 |
6 | typedef ColorCallBack = void Function(Color color);
7 |
8 | ///提取图片的主色调
9 | class PickImgMainColor {
10 | static Future pick(
11 | ImageProvider imageProvider, ColorCallBack callBack) async {
12 | var paletteGenerator =
13 | await PaletteGenerator.fromImageProvider(imageProvider);
14 | if (paletteGenerator != null && paletteGenerator.colors.isNotEmpty) {
15 | callBack(paletteGenerator.colors.toList()[0]);
16 | } else {
17 | callBack(null);
18 | }
19 | }
20 |
21 | static Future pick2(
22 | ImageProvider imageProvider) async {
23 | var paletteGenerator =
24 | await PaletteGenerator.fromImageProvider(imageProvider);
25 | if (paletteGenerator != null && paletteGenerator.colors.isNotEmpty) {
26 | return (paletteGenerator.colors.toList()[0]);
27 | } else {
28 | return (null);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lib/util/screen_utils.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'dart:ui' as ui;
3 | //系统默认的appBar等高度
4 | //位于Dart Packages/flutter/src/material/constans.dart
5 |
6 | /**
7 | * @Author: thl
8 | * @GitHub: https://github.com/Sky24n
9 | * @Email: 863764940@qq.com
10 | * @Email: sky24no@gmail.com
11 | * @Description: Screen Util.
12 | * @Date: 2018/9/8
13 | */
14 |
15 | ///默认设计稿尺寸(单位 dp or pt)
16 | double _designW = 360.0;
17 | double _designH = 640.0;
18 | double _designD = 3.0;
19 |
20 | /**
21 | * 配置设计稿尺寸(单位 dp or pt)
22 | * w 宽
23 | * h 高
24 | * density 像素密度
25 | */
26 |
27 | /// 配置设计稿尺寸 屏幕 宽,高,密度。
28 | /// Configuration design draft size screen width, height, density.
29 | void setDesignWHD(double w, double h, {double density = 3.0}) {
30 | _designW = w;
31 | _designH = h;
32 | _designD = density;
33 | }
34 |
35 | /// Screen Util.
36 | class ScreenUtils {
37 | double _screenWidth = 0.0;
38 | double _screenHeight = 0.0;
39 | double _screenDensity = 0.0;
40 | double _statusBarHeight = 0.0;
41 | double _bottomBarHeight = 0.0;
42 | double _appBarHeight = 0.0;
43 | MediaQueryData _mediaQueryData;
44 |
45 | static final ScreenUtils _singleton = ScreenUtils();
46 |
47 | static ScreenUtils getInstance() {
48 | _singleton._init();
49 | return _singleton;
50 | }
51 |
52 | _init() {
53 | MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window);
54 | if (_mediaQueryData != mediaQuery) {
55 | _mediaQueryData = mediaQuery;
56 | _screenWidth = mediaQuery.size.width;
57 | _screenHeight = mediaQuery.size.height;
58 | _screenDensity = mediaQuery.devicePixelRatio;
59 | _statusBarHeight = mediaQuery.padding.top;
60 | _bottomBarHeight = mediaQuery.padding.bottom;
61 | _appBarHeight = kToolbarHeight;
62 | }
63 | }
64 |
65 | /// screen width
66 | /// 屏幕 宽
67 | double get screenWidth => _screenWidth;
68 |
69 | /// screen height
70 | /// 屏幕 高
71 | double get screenHeight => _screenHeight;
72 |
73 | /// appBar height
74 | /// appBar 高
75 | double get appBarHeight => _appBarHeight;
76 |
77 | /// screen density
78 | /// 屏幕 像素密度
79 | double get screenDensity => _screenDensity;
80 |
81 | /// status bar Height
82 | /// 状态栏高度
83 | double get statusBarHeight => _statusBarHeight;
84 |
85 | /// bottom bar Height
86 | double get bottomBarHeight => _bottomBarHeight;
87 |
88 | /// media Query Data
89 | MediaQueryData get mediaQueryData => _mediaQueryData;
90 |
91 | /// screen width
92 | /// 当前屏幕 宽
93 | static double screenW(BuildContext context) {
94 | MediaQueryData mediaQuery = MediaQuery.of(context);
95 | return mediaQuery.size.width;
96 | }
97 |
98 | /// screen height
99 | /// 当前屏幕 高
100 | static double screenH(BuildContext context) {
101 | MediaQueryData mediaQuery = MediaQuery.of(context);
102 | return mediaQuery.size.height;
103 | }
104 |
105 | /// screen density
106 | /// 当前屏幕 像素密度
107 | static double getScreenDensity(BuildContext context) {
108 | MediaQueryData mediaQuery = MediaQuery.of(context);
109 | return mediaQuery.devicePixelRatio;
110 | }
111 |
112 | /// status bar Height
113 | /// 当前状态栏高度
114 | static double getStatusBarH(BuildContext context) {
115 | MediaQueryData mediaQuery = MediaQuery.of(context);
116 | return mediaQuery.padding.top;
117 | }
118 |
119 | /// status bar Height
120 | /// 当前BottomBar高度
121 | static double getBottomBarH(BuildContext context) {
122 | MediaQueryData mediaQuery = MediaQuery.of(context);
123 | return mediaQuery.padding.bottom;
124 | }
125 |
126 | /// 当前MediaQueryData
127 | static MediaQueryData getMediaQueryData(BuildContext context) {
128 | MediaQueryData mediaQuery = MediaQuery.of(context);
129 | return mediaQuery;
130 | }
131 |
132 | /// returns the size after adaptation according to the screen width.(unit dp or pt)
133 | /// 返回根据屏幕宽适配后尺寸(单位 dp or pt)
134 | /// size 单位 dp or pt
135 | static double getScaleW(BuildContext context, double size) {
136 | if (context == null || screenW(context) == 0.0) return size;
137 | return size * screenW(context) / _designW;
138 | }
139 |
140 | /// returns the size after adaptation according to the screen height.(unit dp or pt)
141 | /// 返回根据屏幕高适配后尺寸 (单位 dp or pt)
142 | /// size unit dp or pt
143 | static double getScaleH(BuildContext context, double size) {
144 | if (context == null || screenH(context) == 0.0) return size;
145 | return size * screenH(context) / _designH;
146 | }
147 |
148 | /// returns the font size after adaptation according to the screen density.
149 | /// 返回根据屏幕宽适配后字体尺寸
150 | /// fontSize 字体尺寸
151 | static double getScaleSp(BuildContext context, double fontSize) {
152 | if (context == null || getScreenDensity(context) == 0.0) return fontSize;
153 | return fontSize * screenW(context) / _designW;
154 | }
155 |
156 | /// Orientation
157 | /// 设备方向(portrait, landscape)
158 | static Orientation getOrientation(BuildContext context) {
159 | MediaQueryData mediaQuery = MediaQuery.of(context);
160 | return mediaQuery.orientation;
161 | }
162 |
163 | /// returns the size after adaptation according to the screen width.(unit dp or pt)
164 | /// 返回根据屏幕宽适配后尺寸(单位 dp or pt)
165 | /// size 单位 dp or pt
166 | double getWidth(double size) {
167 | return _screenWidth == 0.0 ? size : (size * _screenWidth / _designW);
168 | }
169 |
170 | /// returns the size after adaptation according to the screen height.(unit dp or pt)
171 | /// 返回根据屏幕高适配后尺寸(单位 dp or pt)
172 | /// size unit dp or pt
173 | double getHeight(double size) {
174 | return _screenHeight == 0.0 ? size : (size * _screenHeight / _designH);
175 | }
176 |
177 | /// returns the size after adaptation according to the screen width.(unit dp or pt)
178 | /// 返回根据屏幕宽适配后尺寸(单位 dp or pt)
179 | /// sizePx unit px
180 | double getWidthPx(double sizePx) {
181 | return _screenWidth == 0.0
182 | ? (sizePx / _designD)
183 | : (sizePx * _screenWidth / (_designW * _designD));
184 | }
185 |
186 | /// returns the size after adaptation according to the screen height.(unit dp or pt)
187 | /// 返回根据屏幕高适配后尺寸(单位 dp or pt)
188 | /// sizePx unit px
189 | double getHeightPx(double sizePx) {
190 | return _screenHeight == 0.0
191 | ? (sizePx / _designD)
192 | : (sizePx * _screenHeight / (_designH * _designD));
193 | }
194 |
195 | /// returns the font size after adaptation according to the screen density.
196 | /// 返回根据屏幕宽适配后字体尺寸
197 | /// fontSize 字体尺寸
198 | double getSp(double fontSize) {
199 | if (_screenDensity == 0.0) return fontSize;
200 | return fontSize * _screenWidth / _designW;
201 | }
202 | }
--------------------------------------------------------------------------------
/lib/widgets/animal_photo.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 |
4 | ///点击图片放大显示
5 | class AnimalPhoto {
6 | AnimalPhoto.show(BuildContext context, String url, {double width}) {
7 | if (width == null) {
8 | width = MediaQuery.of(context).size.width;
9 | }
10 | Navigator.of(context)
11 | .push(MaterialPageRoute(builder: (BuildContext context) {
12 | return Container(
13 | // The blue background emphasizes that it's a new route.
14 | color: Colors.transparent,
15 | padding: const EdgeInsets.all(10.0),
16 | alignment: Alignment.center,
17 | child: _PhotoHero(
18 | photo: url,
19 | width: width,
20 | onTap: () {
21 | Navigator.of(context).pop();
22 | },
23 | ),
24 | );
25 | }));
26 | }
27 | }
28 |
29 | class _PhotoHero extends StatelessWidget {
30 | const _PhotoHero({Key key, this.photo, this.onTap, this.width})
31 | : super(key: key);
32 |
33 | final String photo;
34 | final VoidCallback onTap;
35 | final double width;
36 |
37 | Widget build(BuildContext context) {
38 | return SizedBox(
39 | width: width,
40 | child: Hero(
41 | tag: photo,
42 | child: Material(
43 | color: Colors.transparent,
44 | child: InkWell(
45 | onTap: onTap,
46 | child: Image.network(
47 | photo,
48 | fit: BoxFit.contain,
49 | ),
50 | ),
51 | ),
52 | ),
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/widgets/image/LaminatedImage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | ///层叠的图片,三张图片层叠显示
4 | class LaminatedImage extends StatelessWidget {
5 | final urls;
6 | final w;
7 |
8 | LaminatedImage({Key key, @required this.urls, this.w}) : super(key: key);
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | double h = w * 1.5;
13 | double dif = w * 0.14;
14 | double secondLeftPadding = w * 0.42;
15 | double thirdLeftPadding = w * 0.78;
16 | return Container(
17 | height: h,
18 | width: w + thirdLeftPadding,
19 | child: Stack(
20 | alignment: Alignment.bottomLeft,
21 | children: [
22 | Positioned(
23 | left: w * 0.78,
24 | child: ClipRRect(
25 | borderRadius: BorderRadius.circular(6.0),
26 | child: Image.network(
27 | urls[2],
28 | width: w,
29 | height: h - dif - dif / 2,
30 | fit: BoxFit.cover,
31 | color: Color.fromARGB(100, 246, 246, 246),
32 | colorBlendMode: BlendMode.screen,
33 | ),
34 | ),
35 | ),
36 | Positioned(
37 | left: secondLeftPadding,
38 | child: ClipRRect(
39 | borderRadius: BorderRadius.circular(6.0),
40 | child: Image.network(
41 | urls[1],
42 | width: w,
43 | height: h - dif,
44 | fit: BoxFit.cover,
45 | color: Color.fromARGB(100, 246, 246, 246),
46 | colorBlendMode: BlendMode.screen,
47 | ),
48 | ),
49 | ),
50 | Positioned(
51 | left: 0,
52 | child: ClipRRect(
53 | borderRadius: BorderRadius.circular(6.0),
54 | child: Image.network(
55 | urls[0],
56 | width: w,
57 | height: h,
58 | fit: BoxFit.cover,
59 | ),
60 | ),
61 | ),
62 | ],
63 | ),
64 | );
65 | }
66 |
67 | //圆角图片
68 | getImage(var imgUrl, var w, var h) {
69 | // this.color,
70 | // this.elevation = 1.0,
71 | // this.shape,
72 | // this.margin = const EdgeInsets.all(4.0),
73 | // this.clipBehavior = Clip.none,
74 | // this.child,
75 | // this.semanticContainer = true,
76 | return Card(
77 | child: Image.network(
78 | imgUrl,
79 | width: w,
80 | height: h,
81 | fit: BoxFit.cover,
82 | ),
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/lib/widgets/image/cache_img_radius.dart:
--------------------------------------------------------------------------------
1 | //import 'package:doubanapp/widgets/image/cached_network_image.dart';
2 | import 'package:cached_network_image/cached_network_image.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | typedef OnTab = void Function();
6 |
7 | class CacheImgRadius extends StatelessWidget {
8 | final String imgUrl;
9 | final double radius;
10 | final OnTab onTab;
11 | CacheImgRadius({Key key, @required this.imgUrl, this.radius, this.onTab})
12 | : super(key: key);
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return GestureDetector(
17 | child: ClipRRect(
18 | borderRadius: BorderRadius.all(Radius.circular(radius)),
19 | child: CachedNetworkImage(imageUrl: imgUrl),
20 | ),
21 | onTap: () {
22 | onTab();
23 | },
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/widgets/image/heart_img_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class HeartImgWidget extends StatefulWidget {
4 | final Image img;
5 |
6 | HeartImgWidget(this.img, {Key key}) : super(key: key);
7 |
8 | @override
9 | State createState() => _HeartImgWidgetState();
10 | }
11 |
12 | class _HeartImgWidgetState extends State
13 | with SingleTickerProviderStateMixin {
14 | AnimationController controller;
15 | Animation animation;
16 |
17 | @override
18 | void initState() {
19 | super.initState();
20 | controller = new AnimationController(
21 | duration: const Duration(milliseconds: 1500), vsync: this);
22 | animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn)
23 | ..addStatusListener((status) {
24 | if (status == AnimationStatus.completed) {
25 | controller.reverse();
26 | } else if (status == AnimationStatus.dismissed) {
27 | controller.forward();
28 | }
29 | });
30 |
31 | controller.forward();
32 | }
33 |
34 | @override
35 | Widget build(BuildContext context) {
36 | return _AnimatedImg(widget.img, animation: animation);
37 | }
38 |
39 | @override
40 | void dispose() {
41 | controller.dispose();
42 | super.dispose();
43 | }
44 | }
45 |
46 | class _AnimatedImg extends AnimatedWidget {
47 | static final _opacityTween = new Tween(begin: 0.5, end: 1.0);
48 | static final _sizeTween = new Tween(begin: 290.0, end: 300.0);
49 | final Image img;
50 | _AnimatedImg(this.img, {Key key, Animation animation})
51 | : super(key: key, listenable: animation);
52 |
53 | Widget build(BuildContext context) {
54 | final Animation animation = listenable;
55 | return new Center(
56 | child: new Opacity(
57 | opacity: _opacityTween.evaluate(animation),
58 | child: new Container(
59 | margin: new EdgeInsets.symmetric(vertical: 10.0),
60 | height: _sizeTween.evaluate(animation),
61 | width: _sizeTween.evaluate(animation),
62 | child: img,
63 | ),
64 | ),
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/lib/widgets/image/network_img_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class NetworkImgWidget extends StatefulWidget {
4 | final imgUrl;
5 | final placeHolderAsset;
6 |
7 | NetworkImgWidget({Key key, this.placeHolderAsset, this.imgUrl})
8 | : super(key: key);
9 |
10 | @override
11 | State createState() {
12 | return _NetworkImgWidgetState(placeHolderAsset, imgUrl);
13 | }
14 | }
15 |
16 | class _NetworkImgWidgetState extends State {
17 | final imgUrl;
18 | final placeHolderAsset;
19 | Image img, netImg;
20 | _NetworkImgWidgetState(this.placeHolderAsset, this.imgUrl);
21 |
22 | @override
23 | void initState() {
24 | super.initState();
25 | img = Image.asset(placeHolderAsset);
26 | try{
27 | netImg = Image.network(imgUrl);
28 | }on Exception catch(e){
29 | print(e);
30 | }
31 | }
32 |
33 | @override
34 | Widget build(BuildContext context) {
35 | return Stack(children: [
36 | img,
37 | netImg
38 | ],);
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/lib/widgets/image/radius_img.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class RadiusImg {
4 | static Widget get(String imgUrl, double imgW, {double imgH, Color shadowColor, double elevation, double radius = 6.0, RoundedRectangleBorder shape}) {
5 | if (shadowColor == null) {
6 | shadowColor = Colors.transparent;
7 | }
8 | return Card(
9 | //影音海报
10 | shape:shape?? RoundedRectangleBorder(
11 | borderRadius: BorderRadius.all(Radius.circular(radius)),
12 | ),
13 | color: shadowColor,
14 | clipBehavior: Clip.antiAlias,
15 | elevation: elevation == null ? 0.0 : 5.0,
16 | child: imgW == null ? Image.network(
17 | imgUrl,
18 | height: imgH,
19 | fit: BoxFit.cover,
20 | ):Image.network(
21 | imgUrl,
22 | width: imgW,
23 | height: imgH,
24 | fit: imgH == null ? BoxFit.contain : BoxFit.cover,
25 | ),
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/widgets/item_count_title.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/constant/text_size_constant.dart';
3 | import 'package:doubanapp/constant/color_constant.dart';
4 |
5 | typedef OnClick = void Function();
6 |
7 | ///左边是豆瓣热门,右边是全部
8 | class ItemCountTitle extends StatelessWidget {
9 | // final state = _ItemCountTitleState();
10 | final count;
11 | final OnClick onClick;
12 | final String title;
13 | final double fontSize;
14 |
15 | ItemCountTitle(this.title, {Key key, this.onClick, this.count, this.fontSize})
16 | : super(key: key);
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return GestureDetector(
21 | child: Row(
22 | children: [
23 | Expanded(
24 | child: Text(
25 | title,
26 | style: TextStyle(
27 | fontSize: fontSize == null
28 | ? TextSizeConstant.BookAudioPartTabBar
29 | : fontSize,
30 | fontWeight: FontWeight.bold,
31 | color: ColorConstant.colorDefaultTitle),
32 | )),
33 | Text(
34 | '全部 ${count == null ? 0 : count} > ',
35 | style: TextStyle(
36 | fontSize: 12, color: Colors.grey, ),
37 | )
38 | ],
39 | ),
40 | onTap: () {
41 | if (onClick != null) {
42 | onClick();
43 | }
44 | },
45 | );
46 | }
47 | }
48 | //
49 | //class _ItemCountTitleState extends State {
50 | // @override
51 | // Widget build(BuildContext context) {
52 | // return GestureDetector(
53 | // child: Row(
54 | // children: [
55 | // Expanded(
56 | // child: Text(
57 | // widget.title,
58 | // style: TextStyle(
59 | // fontSize: TextSizeConstant.BookAudioPartTabBar,
60 | // fontWeight: FontWeight.bold,
61 | // color: ColorConstant.colorDefaultTitle),
62 | // )),
63 | // Text(
64 | // '全部 ${widget.count} > ',
65 | // style: TextStyle(
66 | // fontSize: 12, color: Colors.black, fontWeight: FontWeight.bold),
67 | // )
68 | // ],
69 | // ),
70 | // onTap: () {
71 | // if (widget.onClick != null) {
72 | // widget.onClick();
73 | // }
74 | // },
75 | // );
76 | // }
77 | //
78 | //}
79 |
--------------------------------------------------------------------------------
/lib/widgets/loading_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/cupertino.dart';
3 |
4 | class LoadingWidget {
5 | static Widget getLoading({Color backgroundColor, Color loadingBgColor}) {
6 | return Container(
7 | alignment: AlignmentDirectional.center,
8 | decoration: new BoxDecoration(
9 | color: backgroundColor == null ? Colors.transparent : backgroundColor,
10 | ),
11 | child: new Container(
12 | decoration: new BoxDecoration(
13 | color: loadingBgColor == null ? Colors.white : loadingBgColor, borderRadius: new BorderRadius.circular(10.0)),
14 | width: 70.0,
15 | height: 70.0,
16 | alignment: AlignmentDirectional.center,
17 | child: SizedBox(
18 | height: 25.0,
19 | width: 25.0,
20 | child: CupertinoActivityIndicator(
21 | radius: 15.0,
22 | ),
23 | ),
24 | ),
25 | );
26 | }
27 |
28 | static Widget containerLoadingBody(Widget body,
29 | {bool loading = true, Color backgroundColor, Color loadingBgColor}) {
30 | return Stack(
31 | children: [
32 | body,
33 | Offstage(
34 | child: getLoading(
35 | backgroundColor: backgroundColor, loadingBgColor: loadingBgColor),
36 | offstage: !loading,
37 | )
38 | ],
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/widgets/my_tab_bar_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/pages/douya_top_250_list_widget.dart';
3 | import 'package:doubanapp/pages/movie/movie_page.dart';
4 |
5 | class FlutterTabBarView extends StatelessWidget {
6 | final TabController tabController;
7 |
8 | FlutterTabBarView({Key key, @required this.tabController}) : super(key: key);
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | print('build FlutterTabBarView');
13 | var viewList = [
14 | MoviePage(key: PageStorageKey('MoviePage'),),
15 | Page2(),
16 | DouBanListView(key: PageStorageKey('DouBanListView'),),
17 | Page4(),
18 | Page5(),
19 | Page1(),
20 | ];
21 | return TabBarView(
22 | children: viewList,
23 | controller: tabController,
24 | );
25 | }
26 | }
27 |
28 | class Page1 extends StatelessWidget {
29 | @override
30 | Widget build(BuildContext context) {
31 | print('build Page1');
32 |
33 | return Center(
34 | child: Text('Page1'),
35 | );
36 | }
37 | }
38 |
39 | class Page2 extends StatelessWidget {
40 | @override
41 | Widget build(BuildContext context) {
42 | print('build Page2');
43 | return Center(
44 | child: Text('Page2'),
45 | );
46 | }
47 | }
48 |
49 | class Page3 extends StatelessWidget {
50 | @override
51 | Widget build(BuildContext context) {
52 | print('build Page3');
53 | return Center(
54 | child: Text('Page3'),
55 | );
56 | }
57 | }
58 |
59 | class Page4 extends StatelessWidget {
60 | @override
61 | Widget build(BuildContext context) {
62 | print('build Page4');
63 | return Center(
64 | child: Text('Page4'),
65 | );
66 | }
67 | }
68 |
69 | class Page5 extends StatelessWidget {
70 | @override
71 | Widget build(BuildContext context) {
72 | print('build Page5');
73 | return Center(
74 | child: Text('Page5'),
75 | );
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/lib/widgets/rating_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class RatingBar extends StatelessWidget {
4 | var stars;
5 | final double size;
6 | final double fontSize;
7 | final color = Color.fromARGB(255, 255, 170, 71);
8 |
9 | RatingBar(this.stars, {Key key, this.size = 18.0, this.fontSize = 13.0})
10 | : super(key: key);
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | stars = stars * 1.0;
15 | List startList = [];
16 | //实心星星
17 | var startNumber = stars ~/ 2;
18 | //半实心星星
19 | var startHalf = 0;
20 | if (stars.toString().contains('.')) {
21 | int tmp = int.parse((stars.toString().split('.')[1]));
22 | if (tmp >= 5) {
23 | startHalf = 1;
24 | }
25 | }
26 | //空心星星
27 | var startEmpty = 5 - startNumber - startHalf;
28 |
29 | for (var i = 0; i < startNumber; i++) {
30 | startList.add(Icon(
31 | Icons.star,
32 | color: color,
33 | size: size,
34 | ));
35 | }
36 | if (startHalf > 0) {
37 | startList.add(Icon(
38 | Icons.star_half,
39 | color: color,
40 | size: size,
41 | ));
42 | }
43 | for (var i = 0; i < startEmpty; i++) {
44 | startList.add(Icon(
45 | Icons.star_border,
46 | color: Colors.grey,
47 | size: size,
48 | ));
49 | }
50 | startList.add(Text(
51 | '$stars',
52 | style: TextStyle(color: Colors.grey, fontSize: fontSize),
53 | ));
54 | return Container(
55 | alignment: Alignment.topLeft,
56 | child: Row(
57 | children: startList,
58 | ),
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/widgets/search_text_field_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | ///文本搜索框
3 | class SearchTextFieldWidget extends StatelessWidget {
4 | final ValueChanged onSubmitted;
5 | final VoidCallback onTab;
6 | final String hintText;
7 | final EdgeInsetsGeometry margin;
8 |
9 | SearchTextFieldWidget({Key key, this.hintText, this.onSubmitted, this.onTab, this.margin})
10 | : super(key: key);
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | return Container(
15 | margin: margin == null ? EdgeInsets.all(0.0) : margin,
16 | width: MediaQuery.of(context).size.width,
17 | alignment: AlignmentDirectional.center,
18 | height: 37.0,
19 | decoration: BoxDecoration(
20 | color: Color.fromARGB(255, 237, 236, 237),
21 | borderRadius: BorderRadius.circular(24.0)),
22 | child: TextField(
23 | onSubmitted: onSubmitted,
24 | onTap: onTab,
25 | cursorColor: Color.fromARGB(255, 0, 189, 96),
26 | decoration: InputDecoration(
27 | contentPadding: const EdgeInsets.only(top: 8.0),
28 | border: InputBorder.none,
29 | hintText: hintText,
30 | hintStyle: TextStyle(
31 | fontSize: 17, color: Color.fromARGB(255, 192, 191, 191)),
32 | prefixIcon: Icon(
33 | Icons.search,
34 | size: 25,
35 | color: Color.fromARGB(255, 128, 128, 128),
36 | )),
37 | style: TextStyle(fontSize: 17),
38 | ),
39 | );
40 | }
41 |
42 | getContainer(BuildContext context, ValueChanged onSubmitted) {
43 | return Container(
44 | width: MediaQuery.of(context).size.width,
45 | alignment: AlignmentDirectional.center,
46 | height: 40.0,
47 | decoration: BoxDecoration(
48 | color: Color.fromARGB(255, 237, 236, 237),
49 | borderRadius: BorderRadius.circular(24.0)),
50 | child: TextField(
51 | onSubmitted: onSubmitted,
52 | cursorColor: Color.fromARGB(255, 0, 189, 96),
53 | decoration: InputDecoration(
54 | contentPadding: EdgeInsets.zero,
55 | border: InputBorder.none,
56 | hintText: hintText,
57 | hintStyle: TextStyle(fontSize: 20),
58 | prefixIcon: Icon(
59 | Icons.search,
60 | size: 29,
61 | color: Color.fromARGB(255, 128, 128, 128),
62 | )),
63 | style: TextStyle(fontSize: 20),
64 | ),
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/lib/widgets/subject_mark_image_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 | //import 'package:doubanapp/widgets/image/cached_network_image.dart';
4 | //import 'package:connectivity/connectivity.dart';
5 |
6 | typedef BoolCallback = void Function(bool markAdded);
7 |
8 | //test http://img1.doubanio.com/view/photo/s_ratio_poster/public/p457760035.webp
9 | ///点击图片变成订阅状态的缓存图片控件
10 | class SubjectMarkImageWidget extends StatefulWidget {
11 | final imgNetUrl;
12 | final BoolCallback markAdd;
13 | var height;
14 | final width;
15 | ///360 x 513
16 |
17 | SubjectMarkImageWidget(this.imgNetUrl,
18 | {Key key, this.markAdd, this.width = 150.0})
19 | : super(key: key);
20 |
21 | @override
22 | State createState() {
23 | height = this.width / 150.0 * 210.0;
24 | return _SubjectMarkImageState(imgNetUrl, markAdd, width, height);
25 | }
26 | }
27 |
28 | class _SubjectMarkImageState extends State {
29 | var markAdded = false;
30 | String imgLocalPath, imgNetUrl;
31 | final BoolCallback markAdd;
32 | var markAddedIcon, defaultMarkIcon;
33 | var loadImg;
34 | var imgWH = 28.0;
35 | var height;
36 | var width;
37 |
38 | _SubjectMarkImageState(this.imgNetUrl, this.markAdd, this.width, this.height);
39 |
40 | @override
41 | void initState() {
42 | super.initState();
43 | markAddedIcon = Image(
44 | image: AssetImage('assets/images/ic_subject_mark_added.png'),
45 | width: imgWH,
46 | height: imgWH,
47 | );
48 | defaultMarkIcon = ClipRRect(
49 | borderRadius: BorderRadius.only(topLeft: Radius.circular(5.0), bottomRight: Radius.circular(5.0)),
50 | child: Image(
51 | image: AssetImage('assets/images/ic_subject_rating_mark_wish.png'),
52 | width: imgWH,
53 | height: imgWH,
54 | ),
55 | );
56 | var defaultImg = Image.asset('assets/images/ic_default_img_subject_movie.9.png');
57 |
58 | loadImg = ClipRRect(
59 | child: CachedNetworkImage(
60 | imageUrl: imgNetUrl,
61 | width: width,
62 | height: height,
63 | fit: BoxFit.fill,
64 | placeholder: (BuildContext context, String url){
65 | return defaultImg;
66 | },
67 | fadeInDuration: const Duration(milliseconds: 80),
68 | fadeOutDuration: const Duration(milliseconds: 80),
69 | ),
70 | borderRadius: BorderRadius.all(Radius.circular(5.0)),
71 | );
72 | }
73 |
74 | @override
75 | Widget build(BuildContext context) {
76 | return Stack(
77 | children: [
78 | loadImg,
79 | GestureDetector(
80 | child: markAdded ? markAddedIcon : defaultMarkIcon,
81 | onTap: () {
82 | if (markAdd != null) {
83 | markAdd(markAdded);
84 | }
85 | setState(() {
86 | markAdded = !markAdded;
87 | });
88 | },
89 | ),
90 | ],
91 | );
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/lib/widgets/title_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:doubanapp/constant/constant.dart';
3 |
4 | typedef OnTabBack = void Function();
5 |
6 | ///导航头,如果设置了body,则不用再次使用Scaffold
7 | class TitleBar extends StatelessWidget {
8 | final String title;
9 | final Color backgroundColor;
10 | final Color textColor;
11 | final Widget body;
12 | final OnTabBack onTabBack;
13 | final EdgeInsetsGeometry padding;
14 |
15 | TitleBar(
16 | {Key key,
17 | this.title,
18 | this.backgroundColor = Colors.white,
19 | this.textColor,
20 | this.onTabBack,
21 | this.padding,
22 | this.body})
23 | : super(key: key);
24 |
25 | @override
26 | Widget build(BuildContext context) {
27 | if (body == null) {
28 | return _title(context);
29 | } else {}
30 | return Scaffold(
31 | body: Container(
32 | padding: padding ?? EdgeInsets.all(10.0),
33 | alignment: Alignment.center,
34 | child: SafeArea(
35 | child: Column(
36 | mainAxisAlignment: MainAxisAlignment.start,
37 | children: [
38 | _title(context),
39 | Expanded(
40 | child: Padding(
41 | padding: EdgeInsets.all(12.0),
42 | child: body,
43 | ),
44 | )
45 | ],
46 | )),
47 | color: backgroundColor,
48 | ),
49 | );
50 | }
51 |
52 | Widget _title(BuildContext context) {
53 | return Stack(
54 | children: [
55 | Align(
56 | alignment: Alignment.centerLeft,
57 | child: GestureDetector(
58 | child: Padding(
59 | padding: padding ?? EdgeInsets.all(10.0),
60 | child: Image.asset(
61 | Constant.ASSETS_IMG + 'ic_arrow_back.png',
62 | width: 25.0,
63 | height: 25.0,
64 | ),
65 | ),
66 | onTap: () {
67 | if (onTabBack == null) {
68 | Navigator.of(context).pop();
69 | } else {
70 | onTabBack();
71 | }
72 | },
73 | ),
74 | ),
75 | Align(
76 | alignment: Alignment.center,
77 | child: Text(
78 | title == null ? '' : title,
79 | style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold),
80 | ),
81 | )
82 | ],
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/logo.png
--------------------------------------------------------------------------------
/mock/group.json:
--------------------------------------------------------------------------------
1 | {
2 | "groups":[
3 | {
4 | "imgUrl":"",
5 | "title":"",
6 | "comment":"",
7 | "count":""
8 | },
9 | {
10 | "imgUrl":"",
11 | "title":"",
12 | "comment":"",
13 | "count":""
14 | },
15 | {
16 | "imgUrl":"",
17 | "title":"",
18 | "comment":"",
19 | "count":""
20 | },
21 | {
22 | "imgUrl":"",
23 | "title":"",
24 | "comment":"",
25 | "count":""
26 | },
27 | {
28 | "imgUrl":"",
29 | "title":"",
30 | "comment":"",
31 | "count":""
32 | },
33 | {
34 | "imgUrl":"",
35 | "title":"",
36 | "comment":"",
37 | "count":""
38 | },
39 | {
40 | "imgUrl":"",
41 | "title":"",
42 | "comment":"",
43 | "count":""
44 | },
45 | {
46 | "imgUrl":"",
47 | "title":"",
48 | "comment":"",
49 | "count":""
50 | },
51 | {
52 | "imgUrl":"",
53 | "title":"",
54 | "comment":"",
55 | "count":""
56 | },
57 | {
58 | "imgUrl":"",
59 | "title":"",
60 | "comment":"",
61 | "count":""
62 | }
63 |
64 | ]
65 |
66 | }
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: doubanapp
2 | description: A new Flutter project.
3 |
4 | # The following defines the version and build number for your application.
5 | # A version number is three numbers separated by dots, like 1.2.43
6 | # followed by an optional build number separated by a +.
7 | # Both the version and the builder number may be overridden in flutter
8 | # build by specifying --build-name and --build-number, respectively.
9 | # In Android, build-name is used as versionName while build-number used as versionCode.
10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
12 | # Read more about iOS versioning at
13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
14 | version: 1.0.0+1
15 |
16 | environment:
17 | sdk: ">=2.1.0 <3.0.0"
18 |
19 | dependencies:
20 | flutter:
21 | sdk: flutter
22 |
23 | # The following adds the Cupertino Icons font to your application.
24 | # Use with the CupertinoIcons class for iOS style icons.
25 | cupertino_icons: ^1.0.4
26 |
27 | flutter_cache_manager: ^3.3.0
28 |
29 | palette_generator: ^0.3.2
30 |
31 | flutter_webview_plugin: 0.4.0
32 |
33 | webview_flutter: ^3.0.0
34 |
35 | video_player: ^2.2.17
36 |
37 |
38 | # 用来模拟网络返回的数据,这些数据是真实的,这里模拟
39 | dio: ^4.0.4
40 |
41 | fluttertoast: ^8.0.8
42 |
43 | cached_network_image: ^3.2.0
44 |
45 | shared_preferences: ^2.0.13
46 |
47 | dev_dependencies:
48 | flutter_test:
49 | sdk: flutter
50 |
51 |
52 | # For information on the generic Dart part of this file, see the
53 | # following page: https://www.dartlang.org/tools/pub/pubspec
54 |
55 | # The following section is specific to Flutter.
56 | flutter:
57 |
58 | # The following line ensures that the Material Icons font is
59 | # included with your application, so that you can use the icons in
60 | # the material Icons class.
61 | uses-material-design: true
62 | assets:
63 | - assets/images/
64 | - mock/
65 |
66 | # To add assets to your application, add an assets section, like this:
67 | # assets:
68 | # - images/a_dot_burr.jpeg
69 | # - images/a_dot_ham.jpeg
70 |
71 | # An image asset can refer to one or more resolution-specific "variants", see
72 | # https://flutter.io/assets-and-images/#resolution-aware.
73 |
74 | # For details regarding adding assets from package dependencies, see
75 | # https://flutter.io/assets-and-images/#from-packages
76 |
77 | # To add custom fonts to your application, add a fonts section here,
78 | # in this "flutter" section. Each entry in this list should have a
79 | # "family" key with the font family name, and a "fonts" key with a
80 | # list giving the asset and other descriptors for the font. For
81 | # example:
82 | # fonts:
83 | # - family: Schyler
84 | # fonts:
85 | # - asset: fonts/Schyler-Regular.ttf
86 | # - asset: fonts/Schyler-Italic.ttf
87 | # style: italic
88 | # - family: Trajan Pro
89 | # fonts:
90 | # - asset: fonts/TrajanPro.ttf
91 | # - asset: fonts/TrajanPro_Bold.ttf
92 | # weight: 700
93 | #
94 | # For details regarding fonts from package dependencies,
95 | # see https://flutter.io/custom-fonts/#from-packages
96 |
--------------------------------------------------------------------------------
/res/values/strings_en.arb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaina404/FlutterDouBan/f8b5c021a2bca1afdfb3897f1f7330cb789b4da6/res/values/strings_en.arb
--------------------------------------------------------------------------------