();
68 | data['coinCount'] = coinCount;
69 | data['level'] = level;
70 | data['nickname'] = nickname;
71 | data['rank'] = rank;
72 | data['userId'] = userId;
73 | data['username'] = username;
74 | return data;
75 | }
76 | }
--------------------------------------------------------------------------------
/lib/model/mine/collect_model.dart:
--------------------------------------------------------------------------------
1 |
2 |
3 | /// curPage : 1
4 | /// datas : [{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"今天意外在崩溃上报平台发现一个异常为UndeclaredThrowableException,看名字就比较好奇,大家可以搜索下,尝试回答:
\r\n\r\n- 什么时候会抛出此异常?
\r\n- 为什么[1]中重新封装为此异常抛出,这么设计的原因是?
\r\n
","envelopePic":"","id":228235,"link":"https://www.wanandroid.com/wenda/show/20514","niceDate":"2021-11-19 09:16","origin":"","originId":20514,"publishTime":1637284614000,"title":"每日一问 UndeclaredThrowableException 是什么异常?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"问题如题:
\r\nViewGroup 的 measureChild 方法和 measureChildWithMargins 方法的区别是什么,如何在实际开发中决定选择使用哪一个?
\r\n来源:可以从这里提问,欢迎大家踊跃提问~
","envelopePic":"","id":222612,"link":"https://wanandroid.com/wenda/show/20130","niceDate":"2021-10-13 14:05","origin":"","originId":20130,"publishTime":1634105137000,"title":"【大家提问】 | ViewGroup 的 measureChild 方法和 measureChildWithMargins 方法的区别是什么?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":360,"chapterName":"小编发布","courseId":13,"desc":"这是一个收集建议、功能的帖子。
\r\n如果你有:
\r\n\r\n- 想要添加的功能;
\r\n- 觉得目前需要改进的地方;
\r\n
\r\n欢迎提出,会在评估后安排更新~
","envelopePic":"","id":222380,"link":"https://www.wanandroid.com/wenda/show/20087","niceDate":"2021-10-12 15:35","origin":"","originId":20087,"publishTime":1634024135000,"title":"给 wanandroid 提个意见吧!","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"Gson大家一定不陌生,在很多项目中都大规模使用。
\r\n例如常见的:
\r\n网络请求\r\n ->返回Json数据\r\n ->Gson解析为对象\r\n ->渲染页面\r\n
很多时候,历史项目包含很多Gson解析对象在UI线程的操作,或者说即使在子线程其实也会影响页面展现速度。
\r\n大家都了解Gson对于对象的解析,如果不单独的配置TypeAdapter,那么其实内部是充满反射的。
\r\n问题来了:
\r\n有没有什么低侵入的方案可以尽可能去除反射操作,从而提升运行效率?描述思路即可。
","envelopePic":"","id":216415,"link":"https://wanandroid.com/wenda/show/19623","niceDate":"2021-08-31 18:51","origin":"","originId":19623,"publishTime":1630407076000,"title":"每日一问 | Gson中序列化对象的操作有低侵入的优化方案吗?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"了解应用启动相关代码的同学一定知道:
\r\n我们的应用启动时,每个进程会对应一个ActivityThread对象,而Application对象在正常情况下也是每个进程只有一个?
\r\n但是如果你看ActivityThread的源码,你会发现:
\r\npublic final class ActivityThread {\r\n final ArrayList<Application> mAllApplications\r\n = new ArrayList<Application>();\r\n ...\r\n}\r\n
源码直达
\r\n问题来了:
\r\n\r\n- 什么情况下一个ActivityThread对象,会对应多个Application对象,即mAllApplications.size() > 1;
\r\n- 如果找到了1的情况,支持这个目的是?
\r\n
\r\n\r\n本问题归因为好奇,硬说使用场景在一些插件化中会尝试构造Application会调用这个,但是这个肯定不是google的本意。
\r\n
","envelopePic":"","id":215620,"link":"https://wanandroid.com/wenda/show/19550","niceDate":"2021-08-27 09:00","origin":"","originId":19550,"publishTime":1630026023000,"title":"每日一问 | 好奇ActivityThread中为什么会有一个 Application的集合?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"今天我们来讨论下 Jetpack 中的 ViewModel:
\r\n大家都知道 ViewModel 有一个特点就是能够在 Activity 发生重建时做数据的恢复。
\r\n我们就针对这个「重建」与「恢复」问一些问题:
\r\n\r\n- ViewModel 在 Activity 发生旋转等配置发生变化所导致的重建,能恢复数据吗?
\r\n- 如果 1 能,尝试从源码角度分析,数据存在哪?怎么存储的?怎么读取的?
\r\n- 当 Activity 切换到后台,被系统杀死(进程存活),此时回到 Activity 导致的重建,ViewModel 的数据能恢复吗?为什么?
\r\n
","envelopePic":"","id":207249,"link":"https://www.wanandroid.com/wenda/show/18930","niceDate":"2021-07-15 09:06","origin":"","originId":18930,"publishTime":1626311178000,"title":"每日一问 | ViewModel 在什么情况下的「销毁重建」能够对数据进行无缝恢复?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"关于 Activity 重建,我们探究几个问题:
\r\n\r\n- 当前 app 正在前台运行,不在栈顶的 Activity 有可能会因为系统资源,例如内存等不足回收吗?
\r\n- 当 app 处于后台运行,app 进程未被杀死,其内部的 Activity 会被回收吗?
\r\n- 当 app 处于后台运行,app 的进程会被杀死吗?
\r\n
\r\n如果有能力,建议解释过程中可以配合源码,不一定要全部答出来~
","envelopePic":"","id":207248,"link":"https://www.wanandroid.com/wenda/show/18965","niceDate":"2021-07-15 09:05","origin":"","originId":18965,"publishTime":1626311143000,"title":"每日一问 | 关于 Activity 重建,值得探究的几个问题","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"欢迎分享:
1. 你觉得不错的学习习惯;
2. 正在使用的不错的 App
","envelopePic":"","id":204923,"link":"https://www.wanandroid.com/wenda/show/8483","niceDate":"2021-07-03 21:55","origin":"","originId":8483,"publishTime":1625320512000,"title":"每日一问 你有什么好的学习习惯 或者 不错的 app 推荐给大家?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"最近很多同学烦恼,Flutter和Kotlin不知道该重点学习哪个。
如果是你要做选择,你会怎么选?
答题格式:
我会选Kotlin,因为...
","envelopePic":"","id":204922,"link":"https://wanandroid.com/wenda/show/8435","niceDate":"2021-07-03 21:54","origin":"","originId":8435,"publishTime":1625320490000,"title":"讨论 | Flutter Kotlin 如果二选一学习,你会怎么选?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"可以从常见出问题场景、检测方案等方面回答。
","envelopePic":"","id":204921,"link":"https://www.wanandroid.com/wenda/show/8206","niceDate":"2021-07-03 21:54","origin":"","originId":8206,"publishTime":1625320446000,"title":"每日一问 | Android 中关于内存泄露有哪些注意点?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"这个问题其实不算一个太好的问题,但是也能考察事件的分发流程,搞清楚 Window,Activity,DecorView 在事件分发环节的调用流程。
","envelopePic":"","id":204920,"link":"https://www.wanandroid.com/wenda/show/11363","niceDate":"2021-07-03 21:53","origin":"","originId":11363,"publishTime":1625320401000,"title":"每日一问 为什么 Dialog 默认弹出后 Activity 就无法响应用户事件了?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"以后偶尔会出现这种作业,感兴趣的可以做一下。
\r\n\r\n该效果来自app: 莫比健身
\r\n
\r\n
\r\n注意看小船。
\r\n大家感兴趣可以实现一下,留言区可以贴:
\r\n\r\n- 实现思路讨论;
\r\n- 或者自己实现的博客 ;
\r\n- 或者自己实现的开源项目;
\r\n
","envelopePic":"","id":204919,"link":"https://wanandroid.com/wenda/show/12773","niceDate":"2021-07-03 21:51","origin":"","originId":12773,"publishTime":1625320266000,"title":"一期讨论 | 有趣的效果 小船儿游而游","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"之前我在公众号写了一篇:这些年“崛起”的Android技术博主们,也有很多朋友自荐、推荐。
\r\n于是我就开了个帖子收集吧,大家直接留言即可。
\r\n所有收录博客会展现在:
\r\nwanandroid 导航 优秀的博客一栏。
\r\n希望与大家共建内容生态。
","envelopePic":"","id":204917,"link":"https://wanandroid.com/wenda/show/13347","niceDate":"2021-07-03 21:49","origin":"","originId":13347,"publishTime":1625320190000,"title":"博客收集 | 欢迎推荐优秀博主","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"谈到 RecyclerView,相信不少同学,张口都能说出它的几级缓存机制:
\r\n例如:
\r\n\r\n- 一级缓存:mAttachedScrap 和 mChangedScrap
\r\n- 二级缓存:mCachedViews
\r\n- 三级缓存:ViewCacheExtension
\r\n- 四级缓存:RecycledViewPool
\r\n
\r\n然后说怎么用,就是先从 1 级找,然后 2 级...然后4 级,找不到 create ViewHolder。
\r\n那么,有没有思考过,其实上面几级缓存都属于“内存缓存",那么这么分级肯定有一定区别。
\r\n问题来了:
\r\n\r\n- 每一级缓存具体作用是什么?
\r\n- 分别在什么场景下会用到哪些缓存呢?
\r\n
","envelopePic":"","id":204916,"link":"https://www.wanandroid.com/wenda/show/14222","niceDate":"2021-07-03 21:47","origin":"","originId":14222,"publishTime":1625320079000,"title":"每日一问 | RecyclerView的多级缓存机制,每级缓存到底起到什么样的作用?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"View 的三大流程:测量、布局、绘制,我想大家应该都烂熟于心。
\r\n而在绘制阶段,ViewGroup 不光要绘制自身,还需循环绘制其一众子 View,这个绘制策略默认为顺序绘制,即 [0 ~ childCount)。
\r\n这个默认的策略,有办法调整吗?
\r\n例如修改成 (childCount ~ 0],或是修成某个 View 最后绘制。同时又有什么场景需要我们做这样的修改?
\r\n问题来了:
\r\n\r\n- 这个默认的策略,有办法调整吗?
\r\n- 修改了之后,事件分发需要特殊处理吗?还是需要特殊处理。
\r\n
","envelopePic":"","id":204915,"link":"https://www.wanandroid.com/wenda/show/14409","niceDate":"2021-07-03 21:46","origin":"","originId":14409,"publishTime":1625320009000,"title":"每日一问| View 绘制的一个细节,如何修改 View 绘制的顺序?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"之前写代码,需要在一些特殊时机做一些事情,例如释放内存等,特殊时机包含:
\r\n\r\n- 应用退出(用户back 退出,没有任何 Activity 了,但进程还存活的情况)
\r\n- 应用 Home 按键置于后台
\r\n
\r\n问题来了,怎么方便的判断这两种时机呢?
\r\n注意:需要考虑屏幕旋转异常情况。
","envelopePic":"","id":204914,"link":"https://wanandroid.com/wenda/show/14774","niceDate":"2021-07-03 21:46","origin":"","originId":14774,"publishTime":1625319989000,"title":"每日一问 | 如何判断应用退出,或者到后台了?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"最近实在是太忙了,抽空更新一问。
\r\n想到一个非常有意思的问题:
\r\n如果 app 启动了一个 Activity,那么在这个 Activity 展示的情况下,问题来了:
\r\n1.上述场景背后至少有多少个线程?
2.每个线程具体的作用是什么?
","envelopePic":"","id":204913,"link":"https://www.wanandroid.com/wenda/show/15188","niceDate":"2021-07-03 21:41","origin":"","originId":15188,"publishTime":1625319685000,"title":"每日一问 | 启动了Activity 的 app 至少有几个线程?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"记得mipmap刚出来的时候,出现过很多言论,XXX类型图片放mipmap更好。
\r\n如今的观念基本停留在,仅将app icon放置到mipmap,其他的图片都放到drawable。
\r\n那么我们想想:
\r\n\r\n- google 为啥要搞个mipmap,或者mipmap有什么特殊的能力?
\r\n- 从源码上能做出相关分析吗?
\r\n
","envelopePic":"","id":204912,"link":"https://wanandroid.com/wenda/show/17666","niceDate":"2021-07-03 21:38","origin":"","originId":17666,"publishTime":1625319531000,"title":"每日一问 | mipmap vs drawable,傻傻分不清楚?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"很多时候我们在自定义 View 的需要做动画的时候,我们可以依赖属性动画的回调周期性修改 自定义的属性值,然后调用 invalidate 方法实现。
\r\n不过我还见过一个比较野的路子,它在 onDraw 里面直接修改属性,然后调用 invalidate() 方法。
\r\n运行起来好像也没问题。
\r\n那么问题来了:
\r\n\r\n- 在 onDraw 里面调用 修改绘制相关属性(例如画圆,修改半径) invalidate() ,这种与属性动画的回调调用 invalidate()源码分析有什么区别?
\r\n- 在 onDraw 里面调用 invalidate() 会存在什么问题?
\r\n
","envelopePic":"","id":204911,"link":"https://www.wanandroid.com/wenda/show/17629","niceDate":"2021-07-03 21:35","origin":"","originId":17629,"publishTime":1625319310000,"title":"每日一问 | onDraw 里面调用 invalidate 做动画,有什么问题?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"大家应该都清楚app上内存是非常宝贵的资源,而Bitmap几乎是app里面占据内存最大的一个部分。
\r\n不少同学也清楚,Bitmap占据的内存计算为:
\r\n宽 * 高 * 单个像素所需字节数\r\n
今天有个很常规,但是你可能没有太关注的问题:
\r\n\r\n- Bitmap所占用的内存,是app的哪部分的内存?或者说app运行时可使用Java内存为512M,Bitmap占据的内存可以超过512M吗?
\r\n- 问题1中所描述的,需要区分Android版本吗(5.0以下不考虑)?
\r\n- 问题1,问题2如果都搞清楚,经常在一些blog看到这样的代码:设置fresco图片缓存空间为Java内存的白分比,例如1/4,合适吗?
\r\n
","envelopePic":"","id":204910,"link":"https://wanandroid.com/wenda/show/17874","niceDate":"2021-07-03 21:34","origin":"","originId":17874,"publishTime":1625319256000,"title":"每日一问 | 听说你做过内存优化 之 Bitmap内存占用到底在哪?","userId":31008,"visible":0,"zan":0}]
5 | /// offset : 0
6 | /// over : false
7 | /// pageCount : 2
8 | /// size : 20
9 | /// total : 25
10 |
11 | class CollectModel {
12 | int? curPage;
13 | List? datas;
14 | int? offset;
15 | bool? over;
16 | int? pageCount;
17 | int? size;
18 | int? total;
19 |
20 | CollectModel(
21 | {this.curPage,
22 | this.datas,
23 | this.offset,
24 | this.over,
25 | this.pageCount,
26 | this.size,
27 | this.total});
28 |
29 | CollectModel.fromJson(Map json) {
30 | curPage = json['curPage'];
31 | if (json['datas'] != null) {
32 | datas = new List.empty(growable: true);
33 | json['datas'].forEach((v) {
34 | datas?.add(new Article.fromJson(v));
35 | });
36 | }
37 | offset = json['offset'];
38 | over = json['over'];
39 | pageCount = json['pageCount'];
40 | size = json['size'];
41 | total = json['total'];
42 | }
43 |
44 | Map toJson() {
45 | final Map data = new Map();
46 | data['curPage'] = this.curPage;
47 | if (this.datas != null) {
48 | data['datas'] = this.datas?.map((v) => v.toJson()).toList();
49 | }
50 | data['offset'] = this.offset;
51 | data['over'] = this.over;
52 | data['pageCount'] = this.pageCount;
53 | data['size'] = this.size;
54 | data['total'] = this.total;
55 | return data;
56 | }
57 | }
58 |
59 | class Article {
60 | String? author;
61 | int? chapterId;
62 | String? chapterName;
63 | int? courseId;
64 | String? desc;
65 | String? envelopePic;
66 | int? id;
67 | String? link;
68 | String? niceDate;
69 | String? origin;
70 | int? originId;
71 | int? publishTime;
72 | String? title;
73 | int? userId;
74 | int? visible;
75 | int? zan;
76 |
77 | Article(
78 | {this.author,
79 | this.chapterId,
80 | this.chapterName,
81 | this.courseId,
82 | this.desc,
83 | this.envelopePic,
84 | this.id,
85 | this.link,
86 | this.niceDate,
87 | this.origin,
88 | this.originId,
89 | this.publishTime,
90 | this.title,
91 | this.userId,
92 | this.visible,
93 | this.zan});
94 |
95 | Article.fromJson(Map json) {
96 | author = json['author'];
97 | chapterId = json['chapterId'];
98 | chapterName = json['chapterName'];
99 | courseId = json['courseId'];
100 | desc = json['desc'];
101 | envelopePic = json['envelopePic'];
102 | id = json['id'];
103 | link = json['link'];
104 | niceDate = json['niceDate'];
105 | origin = json['origin'];
106 | originId = json['originId'];
107 | publishTime = json['publishTime'];
108 | title = json['title'];
109 | userId = json['userId'];
110 | visible = json['visible'];
111 | zan = json['zan'];
112 | }
113 |
114 | Map toJson() {
115 | final Map data = new Map();
116 | data['author'] = this.author;
117 | data['chapterId'] = this.chapterId;
118 | data['chapterName'] = this.chapterName;
119 | data['courseId'] = this.courseId;
120 | data['desc'] = this.desc;
121 | data['envelopePic'] = this.envelopePic;
122 | data['id'] = this.id;
123 | data['link'] = this.link;
124 | data['niceDate'] = this.niceDate;
125 | data['origin'] = this.origin;
126 | data['originId'] = this.originId;
127 | data['publishTime'] = this.publishTime;
128 | data['title'] = this.title;
129 | data['userId'] = this.userId;
130 | data['visible'] = this.visible;
131 | data['zan'] = this.zan;
132 | return data;
133 | }}
--------------------------------------------------------------------------------
/lib/model/mine/mine_item_model.dart:
--------------------------------------------------------------------------------
1 | class MineItemModel{
2 | String? iconUrl;
3 | String? title;
4 |
5 | MineItemModel(this.iconUrl, this.title);
6 | }
--------------------------------------------------------------------------------
/lib/model/mine/user.dart:
--------------------------------------------------------------------------------
1 |
2 | class User {
3 |
4 | bool? admin;
5 | List? chapterTops;
6 | List? collectIds;
7 | String? email;
8 | String? icon;
9 | int? id;
10 | String? nickname;
11 | String? password;
12 | String? token;
13 | int? type;
14 | String? username;
15 |
16 |
17 | User(
18 | {this.admin,
19 | this.chapterTops,
20 | this.collectIds,
21 | this.email,
22 | this.icon,
23 | this.id,
24 | this.nickname,
25 | this.password,
26 | this.token,
27 | this.type,
28 | this.username});
29 |
30 | User.fromJsonMap( dynamic map):
31 | admin = map["admin"],
32 | chapterTops = map["chapterTops"],
33 | collectIds = map["collectIds"],
34 | email = map["email"],
35 | icon = map["icon"],
36 | id = map["id"],
37 | nickname = map["nickname"],
38 | password = map["password"],
39 | token = map["token"],
40 | type = map["type"],
41 | username = map["username"];
42 |
43 | Map toJson() {
44 | final Map data = new Map();
45 | data['admin'] = admin;
46 | data['chapterTops'] = chapterTops;
47 | data['collectIds'] = collectIds;
48 | data['email'] = email;
49 | data['icon'] = icon;
50 | data['id'] = id;
51 | data['nickname'] = nickname;
52 | data['password'] = password;
53 | data['token'] = token;
54 | data['type'] = type;
55 | data['username'] = username;
56 | return data;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/lib/model/owner.dart:
--------------------------------------------------------------------------------
1 | class Owner {
2 | String? name;
3 | String? face;
4 | int? fans;
5 |
6 | Owner({this.name, this.face, this.fans});
7 |
8 | Owner.fromJson(Map json) {
9 | name = json['name'];
10 | face = json['face'];
11 | fans = json['fans'];
12 | }
13 |
14 | Map toJson() {
15 | final Map data = Map();
16 | data['name'] = this.name;
17 | data['face'] = this.face;
18 | data['fans'] = this.fans;
19 | return data;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/model/project/project_model.dart:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | /// apkLink : ""
5 | /// audit : 1
6 | /// author : "manqianzhuang"
7 | /// canEdit : false
8 | /// chapterId : 294
9 | /// chapterName : "完整项目"
10 | /// collect : false
11 | /// courseId : 13
12 | /// desc : "项目使用Android官方的Jetpack Compose完成,遵循MVVM架构思路,以下为本项目用到的框架:\r\njetpack compose, viewModel, retrofit, okhttp, coroutine/flow, paging3,\r\nroom, accompanist, hilt, gson, glide/picasso, navigation."
13 | /// descMd : ""
14 | /// envelopePic : "https://www.wanandroid.com/resources/image/pc/default_project_img.jpg"
15 | /// fresh : false
16 | /// host : ""
17 | /// id : 20275
18 | /// link : "https://www.wanandroid.com/blog/show/3090"
19 | /// niceDate : "2021-10-24 22:51"
20 | /// niceShareDate : "2021-10-24 22:51"
21 | /// origin : ""
22 | /// prefix : ""
23 | /// projectLink : "https://github.com/manqianzhuang/HamApp.git"
24 | /// publishTime : 1635087073000
25 | /// realSuperChapterId : 293
26 | /// selfVisible : 0
27 | /// shareDate : 1635087073000
28 | /// shareUser : ""
29 | /// superChapterId : 294
30 | /// superChapterName : "开源项目主Tab"
31 | /// tags : [{"name":"项目","url":"/project/list/1?cid=294"}]
32 | /// title : "用Jetpack Compose做一个完成度较高的WanAndroid app"
33 | /// type : 0
34 | /// userId : -1
35 | /// visible : 1
36 | /// zan : 0
37 |
38 | class ProjectModel {
39 | int? curPage;
40 | List? datas;
41 | int? offset;
42 | bool? over;
43 | int? pageCount;
44 | int? size;
45 | int? total;
46 |
47 | ProjectModel(
48 | {this.curPage,
49 | this.datas,
50 | this.offset,
51 | this.over,
52 | this.pageCount,
53 | this.size,
54 | this.total});
55 |
56 | ProjectModel.fromJson(Map json) {
57 | curPage = json['curPage'];
58 | print("fromJson111 = ${json['datas']}");
59 | if (json['datas'] != null) {
60 | datas = new List.empty(growable: true);
61 | json['datas'].forEach((v) {
62 | datas?.add(new ProjectInfo.fromJson(v));
63 | });
64 | }
65 | print("fromJson = ${datas}");
66 | offset = json['offset'];
67 | over = json['over'];
68 | pageCount = json['pageCount'];
69 | size = json['size'];
70 | total = json['total'];
71 | }
72 |
73 | Map toJson() {
74 | final Map data = new Map();
75 | data['curPage'] = this.curPage;
76 | if (this.datas != null) {
77 | data['datas'] = this.datas?.map((v) => v.toJson()).toList();
78 | }
79 | data['offset'] = this.offset;
80 | data['over'] = this.over;
81 | data['pageCount'] = this.pageCount;
82 | data['size'] = this.size;
83 | data['total'] = this.total;
84 | return data;
85 | }
86 | }
87 |
88 | class ProjectInfo {
89 | String? apkLink;
90 | int? audit;
91 | String? author;
92 | bool? canEdit;
93 | int? chapterId;
94 | String? chapterName;
95 | bool? collect;
96 | int? courseId;
97 | String? desc;
98 | String? descMd;
99 | String? envelopePic;
100 | bool? fresh;
101 | String? host;
102 | int? id;
103 | String? link;
104 | String? niceDate;
105 | String? niceShareDate;
106 | String? origin;
107 | String? prefix;
108 | String? projectLink;
109 | int? publishTime;
110 | int? realSuperChapterId;
111 | int? selfVisible;
112 | int? shareDate;
113 | String? shareUser;
114 | int? superChapterId;
115 | String? superChapterName;
116 | List? tags;
117 | String? title;
118 | int? type;
119 | int? userId;
120 | int? visible;
121 | int? zan;
122 |
123 | ProjectInfo(
124 | {this.apkLink,
125 | this.audit,
126 | this.author,
127 | this.canEdit,
128 | this.chapterId,
129 | this.chapterName,
130 | this.collect,
131 | this.courseId,
132 | this.desc,
133 | this.descMd,
134 | this.envelopePic,
135 | this.fresh,
136 | this.host,
137 | this.id,
138 | this.link,
139 | this.niceDate,
140 | this.niceShareDate,
141 | this.origin,
142 | this.prefix,
143 | this.projectLink,
144 | this.publishTime,
145 | this.realSuperChapterId,
146 | this.selfVisible,
147 | this.shareDate,
148 | this.shareUser,
149 | this.superChapterId,
150 | this.superChapterName,
151 | this.tags,
152 | this.title,
153 | this.type,
154 | this.userId,
155 | this.visible,
156 | this.zan});
157 |
158 | ProjectInfo.fromJson(Map json) {
159 | apkLink = json['apkLink'];
160 | audit = json['audit'];
161 | author = json['author'];
162 | canEdit = json['canEdit'];
163 | chapterId = json['chapterId'];
164 | chapterName = json['chapterName'];
165 | collect = json['collect'];
166 | courseId = json['courseId'];
167 | desc = json['desc'];
168 | descMd = json['descMd'];
169 | envelopePic = json['envelopePic'];
170 | fresh = json['fresh'];
171 | host = json['host'];
172 | id = json['id'];
173 | link = json['link'];
174 | niceDate = json['niceDate'];
175 | niceShareDate = json['niceShareDate'];
176 | origin = json['origin'];
177 | prefix = json['prefix'];
178 | projectLink = json['projectLink'];
179 | publishTime = json['publishTime'];
180 | realSuperChapterId = json['realSuperChapterId'];
181 | selfVisible = json['selfVisible'];
182 | shareDate = json['shareDate'];
183 | shareUser = json['shareUser'];
184 | superChapterId = json['superChapterId'];
185 | superChapterName = json['superChapterName'];
186 | if (json['tags'] != null) {
187 | tags = new List.empty(growable: true);
188 | json['tags'].forEach((v) {
189 | tags?.add(new Tags.fromJson(v));
190 | });
191 | }
192 | title = json['title'];
193 | type = json['type'];
194 | userId = json['userId'];
195 | visible = json['visible'];
196 | zan = json['zan'];
197 | }
198 |
199 | Map toJson() {
200 | final Map data = new Map();
201 | data['apkLink'] = this.apkLink;
202 | data['audit'] = this.audit;
203 | data['author'] = this.author;
204 | data['canEdit'] = this.canEdit;
205 | data['chapterId'] = this.chapterId;
206 | data['chapterName'] = this.chapterName;
207 | data['collect'] = this.collect;
208 | data['courseId'] = this.courseId;
209 | data['desc'] = this.desc;
210 | data['descMd'] = this.descMd;
211 | data['envelopePic'] = this.envelopePic;
212 | data['fresh'] = this.fresh;
213 | data['host'] = this.host;
214 | data['id'] = this.id;
215 | data['link'] = this.link;
216 | data['niceDate'] = this.niceDate;
217 | data['niceShareDate'] = this.niceShareDate;
218 | data['origin'] = this.origin;
219 | data['prefix'] = this.prefix;
220 | data['projectLink'] = this.projectLink;
221 | data['publishTime'] = this.publishTime;
222 | data['realSuperChapterId'] = this.realSuperChapterId;
223 | data['selfVisible'] = this.selfVisible;
224 | data['shareDate'] = this.shareDate;
225 | data['shareUser'] = this.shareUser;
226 | data['superChapterId'] = this.superChapterId;
227 | data['superChapterName'] = this.superChapterName;
228 | if (this.tags != null) {
229 | data['tags'] = this.tags?.map((v) => v.toJson()).toList();
230 | }
231 | data['title'] = this.title;
232 | data['type'] = this.type;
233 | data['userId'] = this.userId;
234 | data['visible'] = this.visible;
235 | data['zan'] = this.zan;
236 | return data;
237 | }
238 | }
239 |
240 | class Tags {
241 | String? name;
242 | String? url;
243 |
244 | Tags({this.name, this.url});
245 |
246 | Tags.fromJson(Map json) {
247 | name = json['name'];
248 | url = json['url'];
249 | }
250 |
251 | Map toJson() {
252 | final Map data = new Map();
253 | data['name'] = this.name;
254 | data['url'] = this.url;
255 | return data;
256 | }
257 | }
--------------------------------------------------------------------------------
/lib/model/project/project_tab_model.dart:
--------------------------------------------------------------------------------
1 | class ProjectTabModel {
2 | String? name;
3 | int? id;
4 | List? children;
5 | String? tagIndex;
6 |
7 | ProjectTabModel.fromJson(Map json)
8 | : name = json['name'],
9 | id = json['id'],
10 | children = (json['children'] as List)
11 | .map((e) => e == null
12 | ? null
13 | : new ProjectTabModel.fromJson(e as Map))
14 | .toList();
15 |
16 | Map toJson() => {
17 | 'name': name,
18 | 'id': id,
19 | 'children': children,
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/lib/model/video_model.dart:
--------------------------------------------------------------------------------
1 | class VideoModel{
2 | int videoId;
3 |
4 | VideoModel(this.videoId);
5 | }
--------------------------------------------------------------------------------
/lib/navigator/bottom_navigator.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_project/navigator/f_navigatior.dart';
3 | import 'package:flutter_project/page/category_page.dart';
4 | import 'package:flutter_project/page/home_page.dart';
5 | import 'package:flutter_project/page/mine_page.dart';
6 | import 'package:flutter_project/page/project_page.dart';
7 | import 'package:flutter_project/utils/color.dart';
8 |
9 | ///底部tab
10 | class BottomNavigator extends StatefulWidget {
11 | const BottomNavigator({Key? key}) : super(key: key);
12 |
13 | @override
14 | _BottomNavigatorState createState() => _BottomNavigatorState();
15 | }
16 |
17 | class _BottomNavigatorState extends State {
18 | int _curIndex = 0;
19 |
20 | final PageController _pageController = PageController(initialPage: 0);
21 | List _pages = [];
22 |
23 | static int initialPage = 0;
24 | bool _hasBuild = false;
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | _pages = [
29 | HomePage(
30 | onIntentTo: (index) {
31 | _onJumpTo(index, pageChange: false);
32 | },
33 | ),
34 | ProjectPage(),
35 | CategoryPage(),
36 | MinePage(),
37 | ];
38 |
39 | if (!_hasBuild) {
40 | FRouter.getInstance()
41 | ?.onBottomTabChange(initialPage, _pages[initialPage]);
42 | _hasBuild = true;
43 | }
44 | return Scaffold(
45 | body: PageView(
46 | controller: _pageController,
47 | children: _pages,
48 | onPageChanged: (index) {
49 | _onJumpTo(index, pageChange: true);
50 | },
51 | physics: NeverScrollableScrollPhysics(), //viewpage禁止左右滑动
52 | ),
53 | bottomNavigationBar: BottomNavigationBar(
54 | currentIndex: _curIndex,
55 | onTap: (index) => _onJumpTo(index),
56 | type: BottomNavigationBarType.fixed,
57 | //显示bottom bar底部文字
58 | selectedItemColor: primary[50],
59 | items: [
60 | _bottomItem('首页', Icons.home, 0),
61 | _bottomItem('项目', Icons.local_fire_department, 1),
62 | _bottomItem('分类', Icons.category_outlined, 2),
63 | _bottomItem('我的', Icons.person, 3),
64 | ],
65 | ),
66 | );
67 | }
68 |
69 | _bottomItem(String s, IconData icon, int i) {
70 | return BottomNavigationBarItem(
71 | icon: Icon(icon, color: Colors.grey),
72 | activeIcon: Icon(icon, color: primary[50]),
73 | label: s);
74 | }
75 |
76 | void _onJumpTo(int value, {pageChange = false}) {
77 | if (!pageChange) {
78 | _pageController.jumpToPage(value);
79 | } else {
80 | FRouter.getInstance()?.onBottomTabChange(value, _pages[value]);
81 | }
82 | setState(() {
83 | _curIndex = value;
84 | });
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/lib/navigator/f_navigatior.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_project/navigator/bottom_navigator.dart';
4 | import 'package:flutter_project/page/about_page.dart';
5 | import 'package:flutter_project/page/article_page.dart';
6 | import 'package:flutter_project/page/coin_rank_page.dart';
7 | import 'package:flutter_project/page/home_page.dart';
8 | import 'package:flutter_project/page/login_page.dart';
9 | import 'package:flutter_project/page/my_collect_page.dart';
10 | import 'package:flutter_project/page/register_page.dart';
11 | import 'package:flutter_project/page/setting_page.dart';
12 | import 'package:flutter_project/page/video_detail_page.dart';
13 | import 'package:flutter_project/page/webview_page.dart';
14 |
15 | ///路由状态变化listener
16 | ///通过堆栈信息发送变化来判断
17 | typedef RouteChangeListener(RouteStatusInfo curInfo, RouteStatusInfo preInfo);
18 |
19 | ///创建页面
20 | pageWrap(Widget child) {
21 | return MaterialPage(key: ValueKey(child.hashCode), child: child);
22 | }
23 |
24 | ///page在堆栈中的位置
25 | int getPageIndex(List pages, RouteStatus status) {
26 | for (int i = 0; i < pages.length; i++) {
27 | MaterialPage page = pages[i];
28 | if (getStatus(page) == status) {
29 | return i;
30 | }
31 | }
32 |
33 | return -1;
34 | }
35 |
36 | ///枚举,代表页面
37 | enum RouteStatus {
38 | login,
39 | register,
40 | home,
41 | detail,
42 | webview,
43 | article,
44 | collect,
45 | about,
46 | setting,
47 | coinRank,
48 | unknown
49 | }
50 |
51 | RouteStatus getStatus(MaterialPage page) {
52 | if (page.child is LoginPage) {
53 | return RouteStatus.login;
54 | } else if (page.child is RegisterPage) {
55 | return RouteStatus.register;
56 | } else if (page.child is BottomNavigator) {
57 | return RouteStatus.home;
58 | } else if (page.child is VideoDetailPage) {
59 | return RouteStatus.detail;
60 | } else if (page.child is WebViewPage) {
61 | return RouteStatus.webview;
62 | } else if (page.child is ArticlePage) {
63 | return RouteStatus.article;
64 | } else if (page.child is MyCollectPage) {
65 | return RouteStatus.collect;
66 | } else if (page.child is AboutPage) {
67 | return RouteStatus.about;
68 | } else if (page.child is SettingPage) {
69 | return RouteStatus.setting;
70 | } else if(page.child is CoinRankPage){
71 | return RouteStatus.coinRank;
72 | }
73 |
74 | else {
75 | return RouteStatus.unknown;
76 | }
77 | }
78 |
79 | class RouteStatusInfo {
80 | final RouteStatus routeStatus;
81 | final Widget page;
82 |
83 | RouteStatusInfo(this.routeStatus, this.page);
84 | }
85 |
86 | ///页面跳转
87 | class FRouter extends _RouteIntentListener {
88 | static FRouter? _instance;
89 | RouteIntentListener? routeJumpListener;
90 | List _listener = [];
91 |
92 | RouteStatusInfo? _cur;
93 | RouteStatusInfo? _bottomTab;
94 |
95 | static FRouter? getInstance() {
96 | if (_instance == null) {
97 | _instance = FRouter();
98 | }
99 | return _instance;
100 | }
101 |
102 | void addRouteListener(RouteChangeListener listener) {
103 | if (!_listener.contains(listener)) {
104 | _listener.add(listener);
105 | }
106 | }
107 |
108 | void removeRouteListener(RouteChangeListener listener) {
109 | if (_listener.contains(listener)) {
110 | _listener.remove(listener);
111 | }
112 | }
113 |
114 | void notify(List curPages, List prePages) {
115 | if (curPages == prePages) return;
116 |
117 | //取最顶部
118 | var cur = RouteStatusInfo(getStatus(curPages.last), curPages.last.child);
119 | _notify(cur);
120 | }
121 |
122 | //注册跳转listener
123 | void registerRouteJumpListener(RouteIntentListener listener) {
124 | this.routeJumpListener = listener;
125 | }
126 |
127 | ///底部tab切换监听
128 | void onBottomTabChange(int index, Widget page) {
129 | _bottomTab = RouteStatusInfo(RouteStatus.home, page);
130 | _notify(_bottomTab!);
131 | }
132 |
133 | @override
134 | void onIntentTo(RouteStatus status, {Map? args}) {
135 | routeJumpListener!.onJumpTo!(status, args: args);
136 | }
137 |
138 | void _notify(RouteStatusInfo cur) {
139 | if (cur.page is BottomNavigator && _bottomTab != null) {
140 | cur = _bottomTab!;
141 | }
142 | _listener.forEach((element) {
143 | element(cur, _cur!);
144 | });
145 | _cur = cur;
146 | }
147 | }
148 |
149 | abstract class _RouteIntentListener {
150 | void onIntentTo(RouteStatus status, {Map args});
151 | }
152 |
153 | typedef OnJumpTo = void Function(RouteStatus routeStatus, {Map? args});
154 |
155 | class RouteIntentListener {
156 | final OnJumpTo? onJumpTo;
157 |
158 | RouteIntentListener({this.onJumpTo});
159 | }
160 |
--------------------------------------------------------------------------------
/lib/page/about_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_project/widget/app_toolbar.dart';
3 |
4 | class AboutPage extends StatefulWidget {
5 | const AboutPage({Key? key}) : super(key: key);
6 |
7 | @override
8 | _AboutPageState createState() => _AboutPageState();
9 | }
10 |
11 | class _AboutPageState extends State {
12 | @override
13 | Widget build(BuildContext context) {
14 | return Scaffold(
15 | appBar: articleAppBar("关于"),
16 | body: Container(
17 | alignment: Alignment.topCenter,
18 | child: Padding(padding: EdgeInsets.only(top: 130),
19 | child: Image.asset("images/launch_image.png",width: 400,height: 300,),)
20 |
21 | ),
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/page/article_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_project/base/base_refresh_load_state.dart';
3 | import 'package:flutter_project/http/core/f_error.dart';
4 | import 'package:flutter_project/http/dao/home_dao.dart';
5 | import 'package:flutter_project/model/home/home_article_model.dart';
6 | import 'package:flutter_project/widget/app_toolbar.dart';
7 | import 'package:flutter_project/widget/article_item.dart';
8 |
9 | class ArticlePage extends StatefulWidget {
10 | final int cid;
11 | final String? title;
12 |
13 | const ArticlePage({Key? key, required this.cid, this.title})
14 | : super(key: key);
15 |
16 | @override
17 | _ArticlePageState createState() => _ArticlePageState();
18 | }
19 |
20 | class _ArticlePageState extends BaseRefreshLoadStateState {
22 |
23 | @override
24 | void initState() {
25 | super.initState();
26 | }
27 |
28 | @override
29 | get child => Scaffold(
30 | appBar: articleAppBar(widget.title!),
31 | body: ListView.builder(
32 | controller: scrollController,
33 | itemBuilder: (BuildContext context, int index) {
34 | return ArticleItem(
35 | articleInfo: dataList[index],
36 | );
37 | },
38 | itemCount: dataList.length,
39 | ),
40 | );
41 |
42 | @override
43 | Future getData(int curPage) async {
44 | HomeArticleModel articleModel =
45 | await HomeDao.getArticleByCid(curPage, widget.cid);
46 | return articleModel;
47 | }
48 |
49 | @override
50 | List parseList(HomeArticleModel result) {
51 | return result.datas!;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/page/category_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/cupertino.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_project/http/dao/project_dao.dart';
6 | import 'package:flutter_project/model/category/category_model.dart';
7 | import 'package:flutter_project/navigator/f_navigatior.dart';
8 |
9 | class CategoryPage extends StatefulWidget {
10 | const CategoryPage({Key? key}) : super(key: key);
11 |
12 | @override
13 | _CategoryPageState createState() => _CategoryPageState();
14 | }
15 |
16 | class _CategoryPageState extends State
17 | with AutomaticKeepAliveClientMixin {
18 | List categoryList = [];
19 | List childList = [];
20 |
21 | @override
22 | void initState() {
23 | super.initState();
24 | _loadCategoryList();
25 | }
26 |
27 | @override
28 | Widget build(BuildContext context) {
29 | return Scaffold(
30 | appBar: AppBar(
31 | centerTitle: true,
32 | title: Text(
33 | "分类",
34 | style: TextStyle(fontSize: 18),
35 | ),
36 | ),
37 | body: ListView.builder(
38 | itemBuilder: (BuildContext context, int index) {
39 | return Container(
40 | child: Padding(
41 | padding: EdgeInsets.only(left: 15, right: 15, top: 15),
42 | child: Column(
43 | mainAxisAlignment: MainAxisAlignment.start,
44 | crossAxisAlignment: CrossAxisAlignment.start,
45 | children: [
46 | Text(
47 | categoryList[index].name!,
48 | style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700),
49 | ),
50 | Divider(
51 | height: 1,
52 | color: Colors.black26,
53 | ),
54 | Padding(
55 | padding: EdgeInsets.only(top: 10),
56 | child: Container(
57 | child: Wrap(
58 | spacing: 12.0,
59 | runSpacing: 8.0,
60 | children: _buildItem(categoryList[index].children),
61 | ))),
62 | ],
63 | ),
64 | ));
65 | },
66 | itemCount: categoryList.length,
67 | ),
68 | );
69 | }
70 |
71 | List _buildItem(List? list) {
72 | return list!.map((item) {
73 | return Padding(
74 | padding: EdgeInsets.only(top: 10, left: 10, right: 10),
75 | child: ActionChip(
76 | onPressed: () {
77 | //跳转到相应的列表
78 | FRouter.getInstance()!.onIntentTo(RouteStatus.article,
79 | args: {"article_cid": item.id, "article_title": item.name});
80 | },
81 | label: Text(item.name!),
82 | elevation: 5.0,
83 | backgroundColor:
84 | Colors.primaries[Random().nextInt(Colors.primaries.length)],
85 | labelStyle: TextStyle(
86 | color: Colors.white,
87 | ),
88 | ),
89 | );
90 | }).toList();
91 | }
92 |
93 | void _loadCategoryList() async {
94 | List categoryList = await ProjectDao.getCategory();
95 | setState(() {
96 | this.categoryList = categoryList;
97 | });
98 | }
99 |
100 | @override
101 | bool get wantKeepAlive => true;
102 | }
103 |
--------------------------------------------------------------------------------
/lib/page/coin_rank_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_project/http/core/f_error.dart';
4 | import 'package:flutter_project/http/dao/person_dao.dart';
5 | import 'package:flutter_project/model/mine/coin_model.dart';
6 | import 'package:flutter_project/utils/constants.dart';
7 | import 'package:flutter_project/utils/view_util.dart';
8 |
9 | class CoinRankPage extends StatefulWidget {
10 | const CoinRankPage({Key? key}) : super(key: key);
11 |
12 | @override
13 | _CoinRankPageState createState() => _CoinRankPageState();
14 | }
15 |
16 | class _CoinRankPageState extends State {
17 | List? coinRankList = [];
18 |
19 | @override
20 | void initState() {
21 | super.initState();
22 | _loadCoin();
23 | }
24 |
25 | Widget _buildItem(BuildContext context, int index) {
26 | return Container(
27 | height: 60,
28 | decoration: BoxDecoration(
29 | color: Colors.white,
30 | borderRadius: BorderRadius.all(Radius.circular(4.0)),
31 | border: Border.all(width: 1, color: Colors.white)),
32 | child: Padding(
33 | padding: EdgeInsets.only(left: 20, right: 20),
34 | child: Row(
35 |
36 | children: [
37 | Text(
38 | "${index + 4}",
39 | style: TextStyle(fontSize: 20, color: Colors.red),
40 | ),
41 | viewSpace(width: 20),
42 | Text(
43 | "${coinRankList![index+3].username}",
44 | style: TextStyle(fontSize: 20, color: Colors.black),
45 | ),
46 | viewSpace(width: 20),
47 | Expanded(child: Text(
48 | "积分:${coinRankList![index+3].coinCount}",
49 | style: TextStyle(fontSize: 20, color: Colors.black26),
50 | ))
51 |
52 | ],
53 | ),
54 | ));
55 | }
56 |
57 | @override
58 | Widget build(BuildContext context) {
59 | return Scaffold(
60 | backgroundColor: Colors.red,
61 | body: CustomScrollView(
62 | slivers: [
63 | SliverAppBar(
64 | pinned: true,
65 | centerTitle: true,
66 | title: Text("排行榜",style: TextStyle(fontSize: 22,color: Colors.white),),
67 | backgroundColor: Colors.red,
68 | ),
69 | SliverToBoxAdapter(
70 | child: Container(
71 | height: 270,
72 | child: Stack(
73 | children: [
74 | Positioned(
75 | top: 100,
76 | left: 50,
77 | child: Column(
78 | children: [
79 | Image(
80 | width: 60,
81 | height: 100,
82 | image: AssetImage('images/ic_second.png')),
83 | Text(
84 | coinRankList!.length > 0
85 | ? coinRankList![1].username!
86 | : '',
87 | style: TextStyle(fontSize: 16, color: Colors.white),
88 | ),
89 | Text(
90 | coinRankList!.length > 0
91 | ? "${coinRankList![1].coinCount!}"
92 | : '',
93 | style: TextStyle(fontSize: 12, color: Colors.white),
94 | )
95 | ],
96 | )),
97 | Positioned(
98 | top: 20,
99 | left: 10,
100 | right: 10,
101 | child: Column(
102 | children: [
103 | Image(
104 | width: 60,
105 | height: 100,
106 | image: AssetImage('images/ic_first.png')),
107 | Text(
108 | coinRankList!.length > 0
109 | ? coinRankList![0].username!
110 | : '',
111 | style: TextStyle(fontSize: 22, color: Colors.white),
112 | ),
113 | Text(
114 | coinRankList!.length > 0
115 | ? "${coinRankList![0].coinCount!}"
116 | : '',
117 | style: TextStyle(fontSize: 18, color: Colors.white),
118 | )
119 | ],
120 | )),
121 | Positioned(
122 | top: 100,
123 | right: 50,
124 | child: Column(
125 | children: [
126 | Image(
127 | width: 60,
128 | height: 100,
129 | image: AssetImage('images/ic_three.png')),
130 | Text(
131 | coinRankList!.length > 0
132 | ? coinRankList![2].username!
133 | : '',
134 | style: TextStyle(fontSize: 16, color: Colors.white),
135 | ),
136 | Text(
137 | coinRankList!.length > 0
138 | ? "${coinRankList![2].coinCount!}"
139 | : '',
140 | style: TextStyle(fontSize: 12, color: Colors.white),
141 | )
142 | ],
143 | )),
144 | ],
145 | ),
146 | )),
147 | Container(
148 | child: SliverList(
149 | delegate: SliverChildBuilderDelegate(_buildItem,
150 | childCount:
151 | coinRankList!.length > 0 ? coinRankList!.length : 0),
152 | ),
153 | )
154 | ],
155 | ),
156 | );
157 | }
158 |
159 | ///加载积分排行榜
160 | void _loadCoin() async {
161 | try {
162 | CoinModel coinModel = await PersonDao.getCoinRank();
163 | List? coinList = coinModel.datas;
164 | setState(() {
165 | coinRankList = coinList;
166 | });
167 | } on FNetError catch (e) {
168 | print(e);
169 | }
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/lib/page/home_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_project/base/base_refresh_load_state.dart';
4 | import 'package:flutter_project/db/sp_cache.dart';
5 | import 'package:flutter_project/http/core/dio_adapter.dart';
6 | import 'package:flutter_project/http/core/f_error.dart';
7 | import 'package:flutter_project/http/core/f_net_state.dart';
8 | import 'package:flutter_project/http/dao/home_dao.dart';
9 | import 'package:flutter_project/model/home/banner_model.dart';
10 | import 'package:flutter_project/model/home/home_article_model.dart';
11 | import 'package:flutter_project/navigator/f_navigatior.dart';
12 | import 'package:flutter_project/page/search_delegate.dart';
13 | import 'package:flutter_project/utils/color.dart';
14 | import 'package:flutter_project/widget/article_item.dart';
15 | import 'package:flutter_project/widget/f_banner.dart';
16 | import 'package:flutter_project/widget/navigation_bar.dart';
17 |
18 | ///首页
19 | class HomePage extends StatefulWidget {
20 | final ValueChanged? onIntentTo;
21 |
22 | const HomePage({Key? key, this.onIntentTo}) : super(key: key);
23 |
24 | @override
25 | _HomePageState createState() => _HomePageState();
26 | }
27 |
28 | ///state继承BaseRefreshLoadStateState,BaseRefreshLoadStateState是封装了刷新和下拉加载更多的框架
29 | ///[HomeArticleModel] 相应列表model
30 | ///[ArticleInfo] 列表item model,
31 | ///[HomePage] 当前页面page
32 | ///实现父类的三个方法
33 | class _HomePageState
34 | extends BaseRefreshLoadStateState {
35 | var listener;
36 | List? bannerList = [];
37 | List? articleList = [];
38 | int pageIndex = 0;
39 |
40 | @override
41 | void initState() {
42 | super.initState();
43 | FRouter.getInstance()?.addRouteListener(
44 | this.listener = (curInfo, preInfo) => {print('home ${curInfo.page}')});
45 | _loadBanner();
46 | //_loadArticle();
47 | }
48 |
49 | @override
50 | void dispose() {
51 | super.dispose();
52 | FRouter.getInstance()?.removeRouteListener(this.listener);
53 | }
54 |
55 | // @override
56 | // Widget build(BuildContext context) {
57 | // super.build(context);
58 | // return Scaffold(
59 | // backgroundColor: Colors.white,
60 | // body: Column(
61 | // children: [
62 | // NavigationBar(
63 | // childWidget: _appBar(),
64 | // statusStyle: StatusStyle.DARK_STYLE,
65 | // ),
66 | // MediaQuery.removePadding(
67 | // context: context,
68 | // removeTop: true,
69 | // child: _buildTypeList(),
70 | // )
71 | // ],
72 | // ),
73 | // );
74 | // }
75 |
76 | @override
77 | bool get wantKeepAlive => true;
78 |
79 | //bannerUI
80 | _banner() {
81 | return Padding(
82 | padding: EdgeInsets.only(left: 20, right: 20, top: 20),
83 | child: FBanner(
84 | bannerList!,
85 | ));
86 | }
87 |
88 | //请求banner
89 | void _loadBanner() async {
90 | try {
91 | List banList = await HomeDao.getBanner();
92 | setState(() {
93 | bannerList?.addAll(banList);
94 | });
95 | } on FNetError catch (e) {
96 | print(e);
97 | }
98 | }
99 |
100 | void _loadArticle() async {
101 | try {
102 | HomeArticleModel articleModel = await HomeDao.getHomeArticle(pageIndex);
103 | List articleList = articleModel.datas!;
104 | setState(() {
105 | this.articleList = articleList;
106 | });
107 | } on FNetError catch (e) {
108 | print(e);
109 | }
110 | }
111 |
112 | _appBar() {
113 | return Padding(
114 | padding: EdgeInsets.only(left: 15, right: 15),
115 | child: Row(
116 | children: [
117 | InkWell(
118 | onTap: () {
119 | widget.onIntentTo!(3);
120 | },
121 | child: ClipRRect(
122 | borderRadius: BorderRadius.circular(18),
123 | child: Image(
124 | height: 36,
125 | width: 36,
126 | image: AssetImage('images/default_avatar.png'),
127 | ),
128 | ),
129 | ),
130 | Expanded(
131 | child: Padding(
132 | padding: EdgeInsets.only(left: 15, right: 15),
133 | child: InkWell(
134 | child: ClipRRect(
135 | borderRadius: BorderRadius.circular(16),
136 | child: Container(
137 | padding: EdgeInsets.only(left: 10),
138 | height: 32,
139 | alignment: Alignment.centerLeft,
140 | child: Icon(
141 | Icons.search,
142 | color: Colors.grey,
143 | ),
144 | decoration: BoxDecoration(color: Colors.grey[100]),
145 | ),
146 | ),
147 | onTap: () {
148 | showSearch(context: context, delegate: SearchDelegatePage());
149 | },
150 | ),
151 | )),
152 | InkWell(
153 | child: Icon(
154 | Icons.message,
155 | color: Colors.grey,
156 | ),
157 | onTap: () {
158 | //点击bar消息
159 | },
160 | )
161 | ],
162 | ),
163 | );
164 | }
165 |
166 | ///首页网络布局
167 | _gridView() {
168 | return Padding(
169 | padding: EdgeInsets.only(top: 20, left: 5, right: 5),
170 | child: Container(
171 | child: GridView.count(
172 | physics: NeverScrollableScrollPhysics(),
173 | shrinkWrap: true,
174 | mainAxisSpacing: 0,
175 | crossAxisSpacing: 5,
176 | crossAxisCount: 4,
177 | children: [
178 | _buildGridItem('images/icon_iv.png', '面试', cid: 73),
179 | _buildGridItem('images/icon_big.png', '大厂分享', cid: 510),
180 | _buildGridItem('images/icon_op.png', '性能优化', cid: 78),
181 | _buildGridItem('images/icon_daily.png', '官方发布', cid: 269),
182 | _buildGridItem('images/icon_jetpack.png', 'Jetpack', cid: 422),
183 | _buildGridItem('images/icon_open.png', '开源库源码', cid: 460),
184 | _buildGridItem('images/icon_framework.png', 'Framework', cid: 152),
185 | _buildGridItem('images/icon_kotlin.png', 'Kotlin', cid: 231),
186 | ],
187 | )),
188 | );
189 | }
190 |
191 | ///网格布局item
192 | _buildGridItem(String imgUrl, String title, {cid}) {
193 | return GestureDetector(
194 | onTap: () {
195 | FRouter.getInstance()!.onIntentTo(RouteStatus.article,
196 | args: {"article_cid": cid, "article_title": title});
197 | },
198 | child: Column(
199 | children: [
200 | Image(
201 | image: AssetImage(imgUrl),
202 | width: 30,
203 | height: 30,
204 | ),
205 | Padding(
206 | padding: EdgeInsets.only(top: 4),
207 | child: Text(
208 | title,
209 | style: TextStyle(fontSize: 12),
210 | ),
211 | )
212 | ],
213 | ),
214 | );
215 | }
216 |
217 | ///首页文章列表
218 | _articleView(int index) {
219 | return MediaQuery.removePadding(
220 | context: context,
221 | removeTop: true,
222 | child: ArticleItem(
223 | articleInfo: dataList[index],
224 | onCollect: () {
225 | print("!!!!!!!!收藏");
226 | if (!SpCache.getInstance()!.isLogin()) {
227 | FRouter.getInstance()!.onIntentTo(RouteStatus.login);
228 | } else {
229 | //点击收藏或者取消
230 | _collectArticle(dataList[index].id!);
231 | }
232 | },
233 | ),
234 | );
235 | }
236 |
237 | _collectArticle(int id) async {
238 | try {
239 | await HomeDao.collectArticle(id);
240 | } on FNetError catch (e) {
241 | print(e);
242 | }
243 | }
244 |
245 | _buildTypeList() {
246 | return Container(
247 | child: Expanded(
248 | child: Container(
249 | child: ListView.builder(
250 | itemCount: dataList.length,
251 | controller: scrollController,
252 | itemBuilder: (BuildContext context, int index) {
253 | if (index == 0) {
254 | return _banner();
255 | } else if (index == 1) {
256 | return _gridView();
257 | } else if (index == 2) {
258 | return Container(
259 | child: Text(
260 | "热门博文",
261 | style: TextStyle(
262 | fontSize: 18,
263 | color: Colors.black,
264 | fontWeight: FontWeight.w600),
265 | ),
266 | padding: EdgeInsets.only(left: 15, bottom: 10),
267 | );
268 | }
269 | return _articleView(index - 3);
270 | }),
271 | )),
272 | );
273 | }
274 |
275 | @override
276 | get child => Scaffold(
277 | backgroundColor: Colors.white,
278 | body: Column(
279 | children: [
280 | NavigationBar(
281 | childWidget: _appBar(),
282 | statusStyle: StatusStyle.DARK_STYLE,
283 | ),
284 | MediaQuery.removePadding(
285 | context: context,
286 | removeTop: true,
287 | child: _buildTypeList(),
288 | )
289 | ],
290 | ),
291 | );
292 |
293 | @override
294 | Future getData(int curPage) async {
295 | HomeArticleModel articleModel = await HomeDao.getHomeArticle(curPage);
296 | return articleModel;
297 | }
298 |
299 | @override
300 | List parseList(HomeArticleModel result) {
301 | return result.datas!;
302 | }
303 | }
304 |
--------------------------------------------------------------------------------
/lib/page/login_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_project/db/sp_cache.dart';
3 | import 'package:flutter_project/http/core/f_error.dart';
4 | import 'package:flutter_project/http/core/f_net_state.dart';
5 | import 'package:flutter_project/http/dao/login_dao.dart';
6 | import 'package:flutter_project/model/mine/user.dart';
7 | import 'package:flutter_project/navigator/f_navigatior.dart';
8 | import 'package:flutter_project/provider/user_provider.dart';
9 | import 'package:flutter_project/utils/string_util.dart';
10 | import 'package:flutter_project/utils/toast_util.dart';
11 | import 'package:flutter_project/widget/login_button.dart';
12 | import 'package:flutter_project/widget/login_input.dart';
13 | import 'package:provider/provider.dart';
14 |
15 | class LoginPage extends StatefulWidget {
16 | const LoginPage({Key? key}) : super(key: key);
17 |
18 | @override
19 | _LoginPageState createState() => _LoginPageState();
20 | }
21 |
22 | class _LoginPageState extends FNetState {
23 | String? userName;
24 | String? passWord;
25 | bool loginEnable = false;
26 |
27 | @override
28 | Widget build(BuildContext context) {
29 | return Scaffold(
30 | body: Container(
31 | child: Stack(
32 | children: [
33 |
34 | Positioned(
35 | height: 220,
36 | child: Container(
37 | width: MediaQuery.of(context).size.width,
38 | child: Image(image: AssetImage('images/ic_login.png'),fit: BoxFit.cover,),
39 | )),
40 | Positioned.fill(
41 | top: 200,
42 | child: Container(
43 | decoration: BoxDecoration(
44 | color: Colors.white,
45 | borderRadius: BorderRadius.only(
46 | topLeft: Radius.circular(20),
47 | topRight: Radius.circular(20)),
48 | ),
49 | child: ListView(
50 | children: [
51 | LoginInput(
52 | title: "用户名",
53 | hint: "请输入用户名",
54 | onChanged: (text) {
55 | userName = text;
56 | checkInput();
57 | },
58 | ),
59 | LoginInput(
60 | title: "密码",
61 | hint: "请输入密码",
62 | onChanged: (text) {
63 | passWord = text;
64 | checkInput();
65 | },
66 | ),
67 | Padding(
68 | padding: EdgeInsets.only(left: 20, right: 20, top: 50),
69 | child: LoginButton(
70 | title: '登录',
71 | enable: loginEnable,
72 | onPressed: sendLogin,
73 | ),
74 | ),
75 | Padding(
76 | padding: EdgeInsets.only(top: 20),
77 | child: InkWell(
78 | onTap: () {
79 | FRouter.getInstance()!
80 | .onIntentTo(RouteStatus.register);
81 | },
82 | child: Text(
83 | "注册",
84 | style:
85 | TextStyle(fontSize: 16, color: Colors.black26),
86 | textAlign: TextAlign.center,
87 | ),
88 | )),
89 | ],
90 | ),
91 | )),
92 | Positioned(
93 | top: 60,
94 | left: 20,
95 | child: InkWell(
96 | onTap: (){
97 | Navigator.of(context).pop();
98 | },
99 | child: Icon(Icons.arrow_back_ios,color: Colors.white,),
100 | )),
101 | ],
102 | )),
103 | );
104 | }
105 |
106 | ///检查用户名和密码是否为空等情况
107 | void checkInput() {
108 | bool enable;
109 | if (isNotEmpty(userName) && isNotEmpty(passWord)) {
110 | enable = true;
111 | } else {
112 | enable = false;
113 | }
114 |
115 | setState(() {
116 | loginEnable = enable;
117 | print('loginEnable = $loginEnable');
118 | });
119 | }
120 |
121 | ///登录请求发送
122 | void sendLogin() async {
123 | try {
124 | print("$userName $passWord ");
125 | var result = await LoginDao.login(userName!, passWord!);
126 | if (result['errorCode'] == 0) {
127 | print('登录成功');
128 | User user = User.fromJsonMap(result['data']);
129 | var userProvider = context.read();
130 | userProvider.saveUser(user);
131 | SpCache.getInstance()!.saveUser(user);
132 | Navigator.of(context).pop(true);
133 | showToast('登录成功');
134 | } else {
135 | print(result['errorMsg']);
136 | }
137 | } on NeedAuth catch (e) {} on FNetError catch (e) {}
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/lib/page/mine_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/rendering.dart';
3 | import 'package:flutter_project/db/sp_cache.dart';
4 | import 'package:flutter_project/http/core/f_net_state.dart';
5 | import 'package:flutter_project/model/mine/mine_item_model.dart';
6 | import 'package:flutter_project/model/mine/user.dart';
7 | import 'package:flutter_project/navigator/f_navigatior.dart';
8 | import 'package:flutter_project/provider/user_provider.dart';
9 | import 'package:flutter_project/utils/color.dart';
10 | import 'package:flutter_project/utils/view_util.dart';
11 | import 'package:flutter_project/widget/setting_item.dart';
12 | import 'package:provider/provider.dart';
13 |
14 | ///个人中心
15 | class MinePage extends StatefulWidget {
16 | const MinePage({Key? key}) : super(key: key);
17 |
18 | @override
19 | _MinePageState createState() => _MinePageState();
20 | }
21 |
22 | class _MinePageState extends FNetState
23 | with AutomaticKeepAliveClientMixin {
24 | List mineItemList = [
25 | MineItemModel("images/ic_zan.png", "我的收藏"),
26 | MineItemModel("images/ic_rank.png", "积分排行"),
27 | MineItemModel("images/ic_setting.png", "设置"),
28 | MineItemModel("images/ic_about.png", "关于"),
29 | ];
30 |
31 | @override
32 | void initState() {
33 | super.initState();
34 | }
35 |
36 | @override
37 | Widget build(BuildContext context) {
38 | return Scaffold(body: Consumer(
39 | builder:
40 | (BuildContext context, UserProvider userProvider, Widget? child) {
41 | return NestedScrollView(
42 | headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
43 | return [
44 | SliverAppBar(
45 | expandedHeight: 150,
46 | pinned: true, //固定顶部
47 | flexibleSpace: FlexibleSpaceBar(
48 | collapseMode: CollapseMode.parallax,
49 | titlePadding: EdgeInsets.only(left: 0),
50 | title: _buildHead(userProvider),
51 | background: Stack(
52 | children: [
53 | Positioned.fill(
54 | child: Container(
55 | color: primary,
56 | )),
57 | // Positioned.fill(
58 | // child: BlurView(
59 | // sigma: 20,
60 | // ))
61 | ],
62 | ),
63 | ),
64 | )
65 | ];
66 | },
67 | body: ListView.builder(
68 | itemBuilder: (BuildContext context, int index) {
69 | return SettingItem(
70 | iconPath: mineItemList[index].iconUrl,
71 | title: mineItemList[index].title,
72 | index: index,
73 | );
74 | },
75 | itemCount: mineItemList.length,
76 | ),
77 | );
78 | },
79 | ));
80 | }
81 |
82 | @override
83 | bool get wantKeepAlive => true;
84 |
85 | _buildHead([UserProvider? userProvider]) {
86 | String name = "登录/注册";
87 | if (SpCache.getInstance()!.isLogin()) {
88 | var user = SpCache.getInstance()!.getUser();
89 | print("user ${user.toJson()}");
90 | name = user.username!;
91 | }
92 | User? user = userProvider!.user;
93 | return GestureDetector(
94 | child: Container(
95 | alignment: Alignment.bottomLeft,
96 | padding: EdgeInsets.only(bottom: 30, left: 10),
97 | child: Row(
98 | children: [
99 | ClipRRect(
100 | borderRadius: BorderRadius.circular(100),
101 | child: Image(
102 | width: 60,
103 | height: 60,
104 | image: AssetImage('images/default_avatar.png'),
105 | ),
106 | ),
107 | viewSpace(width: 15),
108 | Text(
109 | name,
110 | style: TextStyle(fontSize: 14),
111 | )
112 | ],
113 | ),
114 | ),
115 | onTap: () {
116 | //是否需要登录,跳转到登录界面
117 | FRouter.getInstance()!.onIntentTo(RouteStatus.login);
118 | },
119 | );
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/lib/page/my_collect_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/widgets.dart';
4 | import 'package:flutter_project/base/base_refresh_load_state.dart';
5 | import 'package:flutter_project/http/dao/person_dao.dart';
6 | import 'package:flutter_project/model/mine/collect_model.dart';
7 | import 'package:flutter_project/utils/color.dart';
8 | import 'package:flutter_project/utils/view_util.dart';
9 | import 'package:flutter_project/widget/app_toolbar.dart';
10 |
11 | class MyCollectPage extends StatefulWidget {
12 | const MyCollectPage({Key? key}) : super(key: key);
13 |
14 | @override
15 | _MyCollectPageState createState() => _MyCollectPageState();
16 | }
17 |
18 | ///state继承BaseRefreshLoadStateState,BaseRefreshLoadStateState是封装了刷新和下拉加载更多的框架
19 | ///[CollectModel] 相应列表model
20 | ///[Article] 列表item model,
21 | ///[MyCollectPage] 当前页面page
22 | class _MyCollectPageState extends BaseRefreshLoadStateState {
23 |
24 |
25 | _buildItem(Article article) {
26 | return Container(
27 | padding: EdgeInsets.only(left: 15, right: 15, top: 10),
28 | child: Column(
29 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
30 | crossAxisAlignment: CrossAxisAlignment.start,
31 | children: [
32 | Text(
33 | article.title!,
34 | style: TextStyle(fontSize: 16, color: Colors.black),
35 | maxLines: 1,
36 | overflow: TextOverflow.ellipsis,
37 | ),
38 | Padding(
39 | padding: EdgeInsets.only(top: 10),
40 | child: Row(
41 | children: [
42 | Container(
43 | height: 20,
44 | padding: EdgeInsets.only(left: 4,right: 4),
45 | child: Text(
46 | article.chapterName!,
47 | textAlign: TextAlign.center,
48 | style: TextStyle(fontSize: 12, color: primary)),
49 | decoration: BoxDecoration(
50 | borderRadius: BorderRadius.all(Radius.circular(10)),
51 | border: Border.all(width: 1, color: primary)),
52 | ),
53 | viewSpace(width: 10),
54 | Text(
55 | article.niceDate!,
56 | style: TextStyle(fontSize: 14, color: Colors.black26),
57 | )
58 | ],
59 | ),
60 | ),
61 | Padding(
62 | padding: EdgeInsets.only(top: 6),
63 | child: Divider(
64 | height: 1,
65 | color: Colors.black26,
66 | ),
67 | )
68 | ],
69 | ),
70 | );
71 | }
72 |
73 |
74 |
75 | @override
76 | get child => Scaffold(
77 | appBar: articleAppBar("我的收藏"),
78 | body: ListView.builder(
79 | controller: scrollController,
80 | itemBuilder: (BuildContext context, int index) {
81 | return _buildItem(dataList[index]);
82 | },
83 | itemCount: dataList.length,
84 | ));
85 |
86 | @override
87 | Future getData(int curPage) async{
88 | CollectModel model = await PersonDao.getMyCollectList(curPage);
89 | return model;
90 | }
91 |
92 | @override
93 | List parseList(CollectModel result) {
94 | return result.datas!;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/lib/page/project_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_project/http/core/f_error.dart';
3 | import 'package:flutter_project/http/core/f_net_state.dart';
4 | import 'package:flutter_project/http/dao/project_dao.dart';
5 | import 'package:flutter_project/model/project/project_tab_model.dart';
6 | import 'package:flutter_project/utils/color.dart';
7 |
8 | import 'project_top_tab_page.dart';
9 |
10 | ///项目模块
11 | class ProjectPage extends StatefulWidget {
12 | const ProjectPage({Key? key}) : super(key: key);
13 |
14 | @override
15 | _ProjectPageState createState() => _ProjectPageState();
16 | }
17 |
18 | class _ProjectPageState extends FNetState
19 | with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
20 | List projectTabList = [];
21 |
22 | TabController? _tabController;
23 |
24 | @override
25 | void initState() {
26 | super.initState();
27 | _tabController = TabController(length: projectTabList.length, vsync: this);
28 | loadData();
29 | }
30 |
31 | @override
32 | void dispose() {
33 | super.dispose();
34 | _tabController?.dispose();
35 | }
36 |
37 | @override
38 | Widget build(BuildContext context) {
39 | super.build(context);
40 | var top = MediaQuery.of(context).padding.top; //刘海屏 刘海的高度
41 | return Scaffold(
42 | body: Column(
43 | children: [
44 | Container(
45 | child: _topTabBar(),
46 | color: Colors.white,
47 | padding: EdgeInsets.only(top: top),
48 | ),
49 | Flexible(
50 | child: TabBarView(
51 | children: projectTabList.map((tab) {
52 | return ProjectTabPage(cid: tab.id);
53 | }).toList(),
54 | controller: _tabController,
55 | ))
56 | ],
57 | ),
58 | );
59 | }
60 |
61 | @override
62 | bool get wantKeepAlive => true;
63 |
64 | ///top tab样式
65 | _topTabBar() {
66 | return TabBar(
67 | unselectedLabelColor: Colors.black,
68 | labelColor: primary,
69 | controller: _tabController,
70 | isScrollable: true,
71 | tabs: projectTabList.map((tab) {
72 | return Tab(
73 | child: Padding(
74 | padding: EdgeInsets.only(left: 5, right: 5),
75 | child: Text(
76 | tab.name!,
77 | style: TextStyle(fontSize: 16),
78 | ),
79 | ),
80 | );
81 | }).toList(),
82 | indicator: UnderlineTabIndicator(
83 | borderSide: BorderSide(color: primary, width: 3),
84 | insets: EdgeInsets.only(left: 15, right: 15)),
85 | );
86 | }
87 |
88 | ///请求
89 | void loadData() async {
90 | try {
91 | List? tabModel = await ProjectDao.getTab();
92 | print('loadData $tabModel');
93 | if (tabModel != null) {
94 | _tabController = TabController(length: tabModel.length, vsync: this);
95 | }
96 | setState(() {
97 | projectTabList = tabModel!;
98 | });
99 | } on NeedAuth catch (e) {
100 | print(e);
101 | } on FNetError catch (e) {
102 | print(e);
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/lib/page/project_top_tab_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/rendering.dart';
3 | import 'package:flutter_project/base/base_refresh_load_state.dart';
4 | import 'package:flutter_project/http/dao/project_dao.dart';
5 | import 'package:flutter_project/model/project/project_model.dart';
6 | import 'package:flutter_project/widget/card_view.dart';
7 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
8 |
9 | ///项目模块下,每个tab下的公用页面
10 | class ProjectTabPage extends StatefulWidget {
11 | final int? cid;
12 |
13 | const ProjectTabPage({Key? key, this.cid}) : super(key: key);
14 |
15 | @override
16 | _ProjectTabPageState createState() => _ProjectTabPageState();
17 | }
18 |
19 | class _ProjectTabPageState extends BaseRefreshLoadStateState {
21 | @override
22 | bool get wantKeepAlive => true;
23 |
24 | @override
25 | get child => MediaQuery.removePadding(
26 | context: context,
27 | removeTop: true,
28 | child: StaggeredGridView.countBuilder(
29 | controller: scrollController,
30 | padding: EdgeInsets.only(top: 10, left: 10, right: 10),
31 | crossAxisCount: 2,
32 | itemCount: dataList.length,
33 | itemBuilder: (BuildContext context, int index) {
34 | return CardView(
35 | projectInfo: dataList[index],
36 | );
37 | },
38 | staggeredTileBuilder: (int index) {
39 | return StaggeredTile.fit(1);
40 | }));
41 |
42 | @override
43 | Future getData(int curPage) async {
44 | ProjectModel projectModel =
45 | await ProjectDao.getProjectFromTab(curPage, widget.cid!);
46 | return projectModel;
47 | }
48 |
49 | @override
50 | List parseList(ProjectModel result) {
51 | return result.datas!;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/page/register_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_project/http/core/f_error.dart';
3 | import 'package:flutter_project/http/core/f_net_state.dart';
4 | import 'package:flutter_project/http/dao/login_dao.dart';
5 | import 'package:flutter_project/navigator/f_navigatior.dart';
6 | import 'package:flutter_project/utils/string_util.dart';
7 | import 'package:flutter_project/utils/toast_util.dart';
8 | import 'package:flutter_project/widget/login_button.dart';
9 | import 'package:flutter_project/widget/login_input.dart';
10 |
11 | ///注册页面
12 | class RegisterPage extends StatefulWidget {
13 | const RegisterPage({Key? key}) : super(key: key);
14 |
15 | @override
16 | _RegisterPageState createState() => _RegisterPageState();
17 | }
18 |
19 | class _RegisterPageState extends FNetState {
20 | String? userName;
21 | String? passWord;
22 | String? rePassword;
23 | bool loginEnable = false;
24 |
25 | @override
26 | Widget build(BuildContext context) {
27 | return Scaffold(
28 | body: Container(
29 | child: Stack(
30 | children: [
31 | Positioned(
32 | height: 220,
33 | child: Container(
34 | width: MediaQuery.of(context).size.width,
35 | child: Image(image: AssetImage('images/ic_login.png'),fit: BoxFit.cover,),
36 | )),
37 | Positioned.fill(
38 | top: 200,
39 | child: Container(
40 | decoration: BoxDecoration(
41 | color: Colors.white,
42 | borderRadius: BorderRadius.only(
43 | topLeft: Radius.circular(20),
44 | topRight: Radius.circular(20)),
45 | ),
46 | child: ListView(
47 | children: [
48 | LoginInput(
49 | title: "用户名",
50 | hint: "请输入用户名",
51 | onChanged: (text) {
52 | userName = text;
53 | checkInput();
54 | },
55 | ),
56 | LoginInput(
57 | title: "密码",
58 | hint: "请输入密码",
59 | onChanged: (text) {
60 | passWord = text;
61 | checkInput();
62 | },
63 | ),
64 | LoginInput(
65 | title: "确认密码",
66 | hint: "请输入密码",
67 | onChanged: (text) {
68 | rePassword = text;
69 | checkInput();
70 | },
71 | ),
72 | Padding(
73 | padding: EdgeInsets.only(top: 20, left: 20, right: 20),
74 | child: _loginButton(),
75 | ),
76 | ],
77 | ),
78 | )),
79 | Positioned(
80 | top: 60,
81 | left: 20,
82 | child: InkWell(
83 | onTap: () {
84 | Navigator.of(context).pop();
85 | },
86 | child: Icon(Icons.arrow_back_ios,color: Colors.white,),
87 | )),
88 | ],
89 | )),
90 | );
91 | }
92 |
93 | void checkInput() {
94 | bool enable;
95 | if (isNotEmpty(userName) &&
96 | isNotEmpty(passWord) &&
97 | isNotEmpty(rePassword)) {
98 | enable = true;
99 | } else {
100 | enable = false;
101 | }
102 |
103 | setState(() {
104 | loginEnable = enable;
105 | });
106 | }
107 |
108 | _loginButton() {
109 | return InkWell(
110 | onTap: () {
111 | if (loginEnable) {
112 | checkParams();
113 | } else {}
114 | },
115 | child: LoginButton(title: "注册", enable: loginEnable),
116 | );
117 | }
118 |
119 | void checkParams() {
120 | String? tips;
121 | if (passWord != rePassword) {
122 | tips = '两次密码不一致';
123 | }
124 | if (tips != null) {
125 | showToast(tips);
126 | return;
127 | }
128 | send();
129 | }
130 |
131 | void send() async {
132 | try {
133 | print("$userName $passWord $rePassword");
134 | var result =
135 | await LoginDao.registration(userName!, passWord!, rePassword!);
136 | if (result['code'] == 0) {
137 | print('注册成功');
138 | FRouter.getInstance()?.onIntentTo(RouteStatus.login);
139 | } else {
140 | print(result['message']);
141 | }
142 | } on NeedAuth catch (e) {} on FNetError catch (e) {}
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/lib/page/search_delegate.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_project/http/dao/home_dao.dart';
3 | import 'package:flutter_project/model/home/home_article_model.dart';
4 | import 'package:flutter_project/navigator/f_navigatior.dart';
5 |
6 | class SearchDelegatePage extends SearchDelegate {
7 | @override
8 | List buildActions(BuildContext context) {
9 | return [
10 | IconButton(
11 | onPressed: () {
12 | query = "";
13 | },
14 | icon: Icon(Icons.clear))
15 | ];
16 | }
17 |
18 | @override
19 | Widget buildLeading(BuildContext context) {
20 | return Container(
21 | child: IconButton(
22 | icon: Icon(Icons.arrow_back_ios_sharp),
23 | onPressed: () {
24 | close(context, '');
25 | },
26 | ),
27 | );
28 | }
29 |
30 | @override
31 | Widget buildResults(BuildContext context) {
32 | return FutureBuilder(
33 | future: getSearchResult(query),
34 | builder: (context, snapshot) {
35 | switch (snapshot.connectionState) {
36 | case ConnectionState.none:
37 | break;
38 | case ConnectionState.waiting:
39 | break;
40 | case ConnectionState.active:
41 | break;
42 | case ConnectionState.done:
43 | if (snapshot.hasData) {
44 | List? result = snapshot.data as List?;
45 | return Container(
46 | child: ListView.builder(
47 | itemBuilder: (BuildContext context, int index) {
48 | return GestureDetector(
49 | behavior: HitTestBehavior.opaque,
50 | onTap: () {
51 | FRouter.getInstance()!
52 | .onIntentTo(RouteStatus.webview, args: {
53 | "article_path": result![index].link!,
54 | "article_title": result[index].title
55 | });
56 | },
57 | child: Container(
58 | height: 60,
59 | child: Padding(
60 | padding: EdgeInsets.only(left: 20, right: 20),
61 | child: Column(
62 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
63 | children: [
64 | Text(
65 | result![index].title!,
66 | textAlign: TextAlign.start,
67 | style: TextStyle(
68 | fontSize: 16, color: Colors.black),
69 | overflow: TextOverflow.ellipsis,
70 | maxLines: 1,
71 | ),
72 | Divider(
73 | height: 1,
74 | color: Colors.black26,
75 | )
76 | ],
77 | ),
78 | ),
79 | ),
80 | );
81 | },
82 | itemCount: result?.length,
83 | ),
84 | );
85 | }
86 | break;
87 | }
88 |
89 | return Container();
90 | });
91 | }
92 |
93 | @override
94 | Widget buildSuggestions(BuildContext context) {
95 | return Container(
96 | child: ListView.builder(
97 | itemBuilder: (BuildContext context, int index) {
98 | return ListTile(
99 | title: Text(
100 | "",
101 | style: TextStyle(fontSize: 12, color: Colors.black),
102 | ),
103 | );
104 | },
105 | itemCount: 10,
106 | ),
107 | );
108 | }
109 |
110 | Future?> getSearchResult(String query) async {
111 | HomeArticleModel articleModel = await HomeDao.getSearchResult(0, query);
112 | List? articleList = articleModel.datas;
113 | return articleList;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/lib/page/setting_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_project/db/sp_cache.dart';
4 | import 'package:flutter_project/http/dao/login_dao.dart';
5 | import 'package:flutter_project/provider/user_provider.dart';
6 | import 'package:flutter_project/utils/color.dart';
7 | import 'package:flutter_project/widget/app_toolbar.dart';
8 | import 'package:flutter_project/widget/login_button.dart';
9 | import 'package:provider/provider.dart';
10 |
11 | class SettingPage extends StatefulWidget {
12 | const SettingPage({Key? key}) : super(key: key);
13 |
14 | @override
15 | _SettingPageState createState() => _SettingPageState();
16 | }
17 |
18 | class _SettingPageState extends State {
19 | bool flag = false;
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return Scaffold(
24 | appBar: articleAppBar("设置"),
25 | body: ConstrainedBox(
26 | constraints: BoxConstraints.expand(),
27 | child: Stack(
28 | children: [
29 | Column(
30 | children: [
31 | Row(
32 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
33 | children: [
34 | Padding(
35 | padding: EdgeInsets.only(left: 20),
36 | child: Text(
37 | "暗黑模式",
38 | style: TextStyle(fontSize: 18),
39 | ),
40 | ),
41 | Switch(
42 | onChanged: (bool value) {
43 | setState(() {
44 | flag = value;
45 | });
46 | },
47 | value: flag,
48 | activeColor: primary,
49 | )
50 | ],
51 | ),
52 | Divider(
53 | height: 1,
54 | color: Colors.black26,
55 | ),
56 | ],
57 | ),
58 | Positioned(
59 | bottom: 60,
60 | left: 40,
61 | right: 40,
62 | child: Container(
63 | child: MaterialButton(
64 | shape: RoundedRectangleBorder(
65 | borderRadius: BorderRadius.circular(20)),
66 | height: 50,
67 | disabledColor: Colors.grey.shade300,
68 | color: primary,
69 | onPressed: () {
70 | _showDialog();
71 | },
72 | child: Text(
73 | "退出登录",
74 | style: TextStyle(color: Colors.white, fontSize: 18),
75 | ),
76 | )))
77 | ],
78 | ),
79 | ));
80 | }
81 |
82 | void _showDialog() {
83 | CupertinoAlertDialog dialog = CupertinoAlertDialog(
84 | content: Text(
85 | "确定是否退出登录?",
86 | style: TextStyle(fontSize: 14),
87 | ),
88 | actions: [
89 | TextButton(onPressed: () {
90 | Navigator.of(context, rootNavigator: true).pop();
91 | }, child: Text("取消")),
92 | TextButton(onPressed: () {
93 | _sendLogout();
94 | }, child: Text("确定")),
95 | ],
96 | );
97 | showCupertinoDialog(
98 | context: context,
99 | builder: (BuildContext context) {
100 | return dialog;
101 | },
102 | );
103 | }
104 |
105 | void _sendLogout() async {
106 | LoginDao.logout();
107 | SpCache.getInstance()!.clearLoginInfo();
108 | var userProvider = context.read();
109 | userProvider.clearUser();
110 | Navigator.of(context, rootNavigator: true).pop();
111 | Navigator.of(context).pop();
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/lib/page/video_detail_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_project/model/video_model.dart';
3 |
4 | class VideoDetailPage extends StatefulWidget {
5 |
6 | final VideoModel videoModel;
7 |
8 | const VideoDetailPage(this.videoModel);
9 |
10 | @override
11 | _VideoDetailPageState createState() => _VideoDetailPageState();
12 | }
13 |
14 | class _VideoDetailPageState extends State {
15 | @override
16 | Widget build(BuildContext context) {
17 | return Scaffold(
18 | appBar: AppBar(),
19 | body: Container(
20 | child: Column(
21 | children: [
22 | Text('视频详情 ${widget.videoModel.videoId}'),
23 | MaterialButton(
24 | onPressed: () => {},
25 | child: Text('跳转'),
26 | )
27 | ],
28 | ),
29 | ),
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/page/webview_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_inappwebview/flutter_inappwebview.dart';
3 | import 'package:flutter_project/widget/app_toolbar.dart';
4 |
5 | class WebViewPage extends StatefulWidget {
6 |
7 | final String? url;//h5链接
8 | final String? title;//toolbar 名称
9 |
10 | const WebViewPage({Key? key, this.url, this.title}) : super(key: key);
11 |
12 | @override
13 | _WebViewPageState createState() => _WebViewPageState();
14 | }
15 |
16 | class _WebViewPageState extends State {
17 | @override
18 | Widget build(BuildContext context) {
19 | return Scaffold(
20 | appBar: articleAppBar(widget.title!),
21 | body: _buildWbContent(),
22 | );
23 | }
24 |
25 | _buildWbContent() {
26 | return Column(
27 | children: [
28 | SizedBox(
29 | height: 1,
30 | width: double.infinity,
31 | child: const DecoratedBox(decoration: BoxDecoration(color: Color(0xFFEEEEEE))),
32 | ),
33 | Expanded(child: InAppWebView(
34 | initialOptions: InAppWebViewGroupOptions(
35 | crossPlatform: InAppWebViewOptions(
36 | useShouldOverrideUrlLoading: true,
37 | javaScriptEnabled: true,
38 | )
39 | ),
40 | initialUrlRequest: URLRequest(url: Uri.parse(widget.url!)),
41 |
42 |
43 | ))
44 | ],
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/provider/provider_manager.dart:
--------------------------------------------------------------------------------
1 |
2 |
3 | import 'package:flutter_project/provider/theme_provider.dart';
4 | import 'package:flutter_project/provider/user_provider.dart';
5 | import 'package:provider/provider.dart';
6 | import 'package:provider/single_child_widget.dart';
7 |
8 | List topProviders = [
9 | ChangeNotifierProvider(create: (_) => UserProvider()),
10 | ChangeNotifierProvider(create: (_) => ThemeProvider()),
11 |
12 | ];
13 |
--------------------------------------------------------------------------------
/lib/provider/theme_provider.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_project/db/sp_cache.dart';
4 |
5 | extension ThemeModeExtension on ThemeMode {
6 | String get value => ['System', 'Light', 'Dark'][index];
7 | }
8 |
9 | class ThemeProvider extends ChangeNotifier {
10 | ThemeMode? _themeMode;
11 |
12 | bool isDark(){
13 | return _themeMode == ThemeMode.dark;
14 | }
15 |
16 | ThemeMode? getThemeMode() {
17 | var theme = SpCache.getInstance()!.get("key");
18 | switch (theme) {
19 | case "Dark":
20 | _themeMode = ThemeMode.dark;
21 | break;
22 | case "Light":
23 | _themeMode = ThemeMode.light;
24 | break;
25 | case "System":
26 | _themeMode = ThemeMode.system;
27 | break;
28 | default:
29 | _themeMode = ThemeMode.light;
30 | break;
31 | }
32 |
33 | return _themeMode;
34 | }
35 |
36 | void setThemeMode(String mode) {
37 | SpCache.getInstance()!.setString("key", mode);
38 | notifyListeners();
39 | }
40 |
41 | ThemeData getTheme({bool isDarkMode = false}) {
42 | var themeData = ThemeData(
43 | brightness: isDarkMode ? Brightness.dark : Brightness.light,
44 | primaryColor: isDarkMode ? Colors.black : Colors.white,
45 | accentColor: isDarkMode ? Colors.black26 : Colors.white,
46 | scaffoldBackgroundColor: isDarkMode ? Colors.black : Colors.white);
47 | return themeData;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/provider/user_provider.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter_project/model/mine/user.dart';
3 |
4 | class UserProvider extends ChangeNotifier {
5 | User? _user;
6 |
7 | User? get user => _user;
8 |
9 | bool get hasUser => user != null;
10 |
11 | saveUser(User user){
12 | _user = user;
13 | notifyListeners();
14 | }
15 |
16 | clearUser(){
17 | _user = null;
18 | notifyListeners();
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/lib/utils/cache_util.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 |
4 |
5 | ///图片缓存
6 | Widget cachedImage(String url, {double? width, double? height}) {
7 | return CachedNetworkImage(
8 | height: height,
9 | width: width,
10 | fit: BoxFit.cover,
11 | placeholder: (
12 | BuildContext context,
13 | String url,
14 | ) =>
15 | Container(color: Colors.grey[200]),
16 | errorWidget: (
17 | BuildContext context,
18 | String url,
19 | dynamic error,
20 | ) =>
21 | Icon(Icons.error),
22 | imageUrl: url);
23 | }
24 |
--------------------------------------------------------------------------------
/lib/utils/color.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | const MaterialColor white = const MaterialColor(0xFFFFFFFF, const {
4 | 50: const Color(0xFFFFFFFF),
5 | 100: Color(0xFFFFFFFF),
6 | 200: Color(0xFFFFFFFF),
7 | 300: Color(0xFFFFFFFF),
8 | 400: Color(0xFFFFFFFF),
9 | 500: Color(0xFFFFFFFF),
10 | 600: Color(0xFFFFFFFF),
11 | 700: Color(0xFFFFFFFF),
12 | 800: Color(0xFFFFFFFF),
13 | 900: Color(0xFFFFFFFF),
14 | });
15 |
16 | const MaterialColor primary = const MaterialColor(0xff739AF0, const {
17 | 50: const Color(0xff739AF0),
18 | 100: Color(0xff739AF0),
19 | 200: Color(0xff739AF0),
20 | 300: Color(0xff739AF0),
21 | 400: Color(0xff739AF0),
22 | 500: Color(0xff739AF0),
23 | 600: Color(0xff739AF0),
24 | 700: Color(0xff739AF0),
25 | 800: Color(0xff739AF0),
26 | 900: Color(0xff739AF0)
27 | });
28 |
29 | class BaColors{
30 |
31 | static const Color dark_bg= Colors.black;
32 | }
33 |
--------------------------------------------------------------------------------
/lib/utils/constants.dart:
--------------------------------------------------------------------------------
1 | class Constants{
2 |
3 | }
--------------------------------------------------------------------------------
/lib/utils/string_util.dart:
--------------------------------------------------------------------------------
1 | bool isNotEmpty(String? text) {
2 | return text?.isNotEmpty ?? false;
3 | }
4 |
5 | bool isEmpty(String? text){
6 | return text?.isEmpty ?? false;
7 | }
8 |
--------------------------------------------------------------------------------
/lib/utils/toast_util.dart:
--------------------------------------------------------------------------------
1 |
2 |
3 | import 'package:fluttertoast/fluttertoast.dart';
4 |
5 | void showToast(String text){
6 | Fluttertoast.showToast(msg: text,toastLength: Toast.LENGTH_LONG,gravity: ToastGravity.CENTER);
7 | }
--------------------------------------------------------------------------------
/lib/utils/view_util.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | ///view间距
5 | ///[height] 竖向,[width]横向
6 | SizedBox viewSpace({double height: 1, double width: 1}) {
7 | return SizedBox(height: height, width: width);
8 | }
9 |
10 | ///listView间隔线
11 | borderLine(BuildContext context, {bottom: true, top: false}) {
12 | BorderSide borderSide = BorderSide(width: 0.5, color: Colors.grey[200]!);
13 | return Border(
14 | bottom: bottom ? borderSide : BorderSide.none,
15 | top: top ? borderSide : BorderSide.none,
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/lib/widget/app_toolbar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | appBar(String title, String rightTitle, VoidCallback rightButtonClick) {
5 | return AppBar(
6 | centerTitle: false,
7 | titleSpacing: 0,
8 | leading: BackButton(),
9 | title: Text(
10 | title,
11 | style: TextStyle(fontSize: 18),
12 | ),
13 | actions: [
14 | InkWell(
15 | onTap: rightButtonClick,
16 | child: Container(
17 | padding: EdgeInsets.only(left: 15, right: 15),
18 | alignment: Alignment.center,
19 | child: Text(
20 | rightTitle,
21 | style: TextStyle(fontSize: 18, color: Colors.grey[500]),
22 | textAlign: TextAlign.center,
23 | ),
24 | ),
25 | )
26 | ],
27 | );
28 | }
29 |
30 | articleAppBar(String title){
31 | return AppBar(
32 | centerTitle: true,
33 | leading: BackButton(),
34 | title: Text(
35 | title,
36 | style: TextStyle(fontSize: 18),
37 | ),
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/lib/widget/article_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_project/model/home/home_article_model.dart';
3 | import 'package:flutter_project/navigator/f_navigatior.dart';
4 | import 'package:flutter_project/utils/color.dart';
5 | import 'package:flutter_project/utils/view_util.dart';
6 |
7 | ///文章list item组件
8 | class ArticleItem extends StatelessWidget {
9 | final ArticleInfo? articleInfo;
10 | final VoidCallback? onCollect;
11 |
12 | const ArticleItem({Key? key, this.articleInfo, this.onCollect})
13 | : super(key: key);
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | String name = "";
18 | bool isCollect = articleInfo!.collect!;//是否已经收藏
19 |
20 | if (articleInfo!.shareUser!.isEmpty) {
21 | name = articleInfo!.author!;
22 | } else {
23 | name = articleInfo!.shareUser!;
24 | }
25 | return GestureDetector(
26 | behavior: HitTestBehavior.opaque, //空白处点击响应
27 | onTap: () {
28 | FRouter.getInstance()!.onIntentTo(RouteStatus.webview, args: {
29 | "article_path": articleInfo!.link!,
30 | "article_title": articleInfo!.title
31 | });
32 | },
33 | child: Container(
34 | padding: EdgeInsets.only(left: 15, right: 15, bottom: 5),
35 | height: 90,
36 | child: Column(
37 | crossAxisAlignment: CrossAxisAlignment.start,
38 | mainAxisAlignment: MainAxisAlignment.spaceAround,
39 | children: [
40 | Text(
41 | articleInfo!.title!,
42 | style: TextStyle(fontSize: 16, color: Colors.black),
43 | maxLines: 2,
44 | overflow: TextOverflow.ellipsis,
45 | ),
46 | Row(
47 | children: [
48 | Container(
49 | height: 20,
50 | padding: EdgeInsets.only(left: 4, right: 4),
51 | child: Text(articleInfo!.chapterName!,
52 | textAlign: TextAlign.center,
53 | style: TextStyle(fontSize: 12, color: primary)),
54 | decoration: BoxDecoration(
55 | borderRadius: BorderRadius.all(Radius.circular(10)),
56 | border: Border.all(width: 1, color: primary)),
57 | ),
58 | viewSpace(width: 10),
59 | InkWell(
60 | onTap: onCollect,
61 | child: Image(
62 | image: isCollect
63 | ? AssetImage('images/ic_favorite.png')
64 | : AssetImage('images/ic_un_favorite.png'),
65 | width: 20,
66 | height: 20,
67 | ),
68 | ),
69 | Expanded(
70 | child: Padding(
71 | padding: EdgeInsets.only(left: 10),
72 | child: Text(
73 | name,
74 | style: TextStyle(fontSize: 12, color: Colors.black26),
75 | ),
76 | ))
77 | ],
78 | verticalDirection: VerticalDirection.up,
79 | mainAxisAlignment: MainAxisAlignment.start,
80 | ),
81 | Divider(
82 | height: 1,
83 | color: Colors.black26,
84 | )
85 | ],
86 | )),
87 | );
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/lib/widget/blur_view.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | ///高斯模糊组件
6 | class BlurView extends StatelessWidget {
7 | final Widget? child;
8 | final double sigma;
9 |
10 | const BlurView({Key? key, this.sigma = 10, this.child}) : super(key: key);
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | return BackdropFilter(
15 | filter: ImageFilter.blur(sigmaX: sigma, sigmaY: sigma),
16 | child: Container(
17 | color: Colors.white10,
18 | child: child,
19 | ),
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/widget/card_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_project/model/project/project_model.dart';
5 | import 'package:flutter_project/navigator/f_navigatior.dart';
6 | import 'package:flutter_project/utils/cache_util.dart';
7 | import 'package:flutter_project/utils/color.dart';
8 |
9 | class CardView extends StatelessWidget {
10 | final ProjectInfo? projectInfo;
11 |
12 | const CardView({Key? key, this.projectInfo}) : super(key: key);
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return InkWell(
17 | onTap: () {
18 | FRouter.getInstance()?.onIntentTo(RouteStatus.webview, args: {
19 | "article_path": projectInfo!.link!,
20 | "article_title": projectInfo!.title
21 | });
22 | },
23 | child: SizedBox(
24 | height: 280,
25 | child: Card(
26 | margin: EdgeInsets.only(left: 10, right: 10, bottom: 10, top: 10),
27 | child: ClipRRect(
28 | borderRadius: BorderRadius.circular(6),
29 | child: Column(
30 | crossAxisAlignment: CrossAxisAlignment.start,
31 | children: [_image(context), _itemTitle()],
32 | ),
33 | ),
34 | ),
35 | ),
36 | );
37 | }
38 |
39 | _image(BuildContext context) {
40 | final size = MediaQuery.of(context).size;
41 | return Stack(
42 | children: [
43 | cachedImage(projectInfo!.envelopePic!,
44 | width: size.width / 2 - 10, height: 180),
45 | Positioned(
46 | left: 0,
47 | right: 0,
48 | bottom: 0,
49 | child: Container(
50 | padding: EdgeInsets.only(left: 8, right: 8, bottom: 3, top: 5),
51 | decoration: BoxDecoration(
52 | //渐变
53 | gradient: LinearGradient(
54 | begin: Alignment.bottomCenter,
55 | end: Alignment.topCenter,
56 | colors: [Colors.black54, Colors.transparent])),
57 | child: Row(
58 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
59 | children: [],
60 | ),
61 | ))
62 | ],
63 | );
64 | }
65 |
66 | _itemTitle() {
67 | return Expanded(
68 | child: Container(
69 | child: Column(
70 | crossAxisAlignment: CrossAxisAlignment.start,
71 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
72 | children: [
73 | Padding(
74 | padding: EdgeInsets.only(left: 6, right: 6),
75 | child: Text(
76 | projectInfo!.title!,
77 | style: TextStyle(color: Colors.black, fontSize: 16),
78 | maxLines: 2,
79 | overflow: TextOverflow.ellipsis,
80 | ),
81 | ),
82 | Expanded(
83 | child: Row(
84 | children: [
85 | ClipRRect(
86 | borderRadius: BorderRadius.circular(12),
87 | child: Icon(
88 | Icons.person,
89 | color: primary,
90 | ),
91 | ),
92 | Padding(
93 | padding: EdgeInsets.only(left: 8),
94 | child: Text(
95 | projectInfo!.author!,
96 | style: TextStyle(fontSize: 12, color: Colors.black54),
97 | ),
98 | ),
99 | ],
100 | ))
101 | ],
102 | ),
103 | ));
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/lib/widget/f_banner.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_project/model/home/banner_model.dart';
3 | import 'package:flutter_swiper_null_safety/flutter_swiper_null_safety.dart';
4 |
5 | ///banner组件
6 | ///Swiper
7 | class FBanner extends StatelessWidget {
8 | final List? bannerList; //数据
9 | final double? bannerHeight; //banner高度
10 | final EdgeInsetsGeometry? padding;
11 |
12 | const FBanner(this.bannerList, {Key? key, this.bannerHeight = 110, this.padding})
13 | : super(key: key);
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return Container(
18 | height: bannerHeight,
19 | child: _banner(),
20 | );
21 | }
22 |
23 | _banner() {
24 | var right = 10 + (padding?.horizontal ?? 0) / 2;
25 | return Swiper(
26 | key: UniqueKey(),
27 | itemCount: bannerList!.length,
28 | autoplay: true,
29 | itemBuilder: (BuildContext context, int index) {
30 | return _image(bannerList![index]);
31 | },
32 | pagination: SwiperPagination(
33 | alignment: Alignment.bottomRight,
34 | margin: EdgeInsets.only(right: right, bottom: 10),
35 | builder: DotSwiperPaginationBuilder(
36 | color: Colors.white60, size: 6, activeSize: 6)),
37 | );
38 | }
39 |
40 | _image(BannerModel bannerModel) {
41 | return InkWell(
42 | onTap: () {
43 | print('11');
44 | },
45 | child: Container(
46 | padding: padding,
47 | child: ClipRRect(
48 | borderRadius: BorderRadius.all(Radius.circular(6)),
49 | child: Image.network(bannerModel.imagePath, fit: BoxFit.cover),
50 | ),
51 | ),
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/widget/login_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_project/utils/color.dart';
3 |
4 | ///登录、注册界面按钮
5 | class LoginButton extends StatelessWidget {
6 | final String? title;
7 | final bool enable;
8 |
9 | final VoidCallback? onPressed;
10 |
11 | const LoginButton({Key? key, this.title, this.enable = true, this.onPressed})
12 | : super(key: key);
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return FractionallySizedBox(
17 | widthFactor: 1,
18 | child: MaterialButton(
19 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
20 | height: 50,
21 | onPressed: enable ? onPressed : null,
22 | disabledColor: Colors.grey.shade300,
23 | color: primary,
24 | child: Text(
25 | title!,
26 | style: TextStyle(color: Colors.white, fontSize: 18),
27 | ),
28 | ),
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lib/widget/login_input.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_project/utils/color.dart';
3 |
4 | ///登录、注册界面输入框组件
5 | class LoginInput extends StatefulWidget {
6 | final String? title;
7 | final String? hint;
8 | final ValueChanged? onChanged;
9 | final ValueChanged? focusChanged;
10 | final bool lineStretch;
11 | final bool obscureText;
12 | final TextInputType? inputType;
13 |
14 | const LoginInput(
15 | {Key? key,
16 | this.title,
17 | this.hint,
18 | this.onChanged,
19 | this.focusChanged,
20 | this.lineStretch = false,
21 | this.obscureText = false,
22 | this.inputType})
23 | : super(key: key);
24 |
25 | @override
26 | _LoginInputState createState() => _LoginInputState();
27 | }
28 |
29 | class _LoginInputState extends State {
30 | final _focusNode = FocusNode();
31 |
32 | @override
33 | void initState() {
34 | super.initState();
35 | _focusNode.addListener(() {
36 | if (widget.focusChanged != null) {
37 | widget.focusChanged!(_focusNode.hasFocus);
38 | }
39 | });
40 | }
41 |
42 | @override
43 | void dispose() {
44 | _focusNode.dispose();
45 | super.dispose();
46 | }
47 |
48 | @override
49 | Widget build(BuildContext context) {
50 | return Column(
51 | crossAxisAlignment: CrossAxisAlignment.start,
52 | children: [
53 | Padding(
54 | padding: EdgeInsets.only(left: 25,top: 15),
55 | child: Text(
56 | widget.title!,
57 | style: TextStyle(fontSize: 18),
58 | ),
59 | ),
60 | _input(),
61 | Padding(
62 | padding: EdgeInsets.only(
63 | left: !widget.lineStretch ? 25 : 0,
64 | right: !widget.lineStretch ? 25 : 0),
65 | child: Divider(
66 | height: 1,
67 | thickness: 0.5,
68 | ),
69 | )
70 | ],
71 | );
72 | }
73 |
74 | _input() {
75 | return Container(
76 | child: TextField(
77 | focusNode: _focusNode,
78 | onChanged: widget.onChanged,
79 | obscureText: widget.obscureText,
80 | keyboardType: widget.inputType,
81 | autofocus: !widget.obscureText,
82 | cursorColor: primary,
83 | style: TextStyle(
84 | fontSize: 16, color: Colors.black, fontWeight: FontWeight.w300),
85 | decoration: InputDecoration(
86 | contentPadding: EdgeInsets.only(left: 25, right: 25),
87 | border: InputBorder.none,
88 | hintText: widget.hint ?? '',
89 | hintStyle: TextStyle(fontSize: 15, color: Colors.grey),
90 | ),
91 | ));
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/lib/widget/navigation_bar.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter/services.dart';
5 | import 'package:flutter_project/provider/theme_provider.dart';
6 | import 'package:flutter_project/utils/color.dart';
7 | import 'package:provider/provider.dart';
8 |
9 | class NavigationBar extends StatefulWidget {
10 | final StatusStyle statusStyle;
11 | final Color color;
12 | final double height;
13 | final Widget? childWidget;
14 |
15 | const NavigationBar(
16 | {Key? key,
17 | this.statusStyle = StatusStyle.DARK_STYLE,
18 | this.color = Colors.white,
19 | this.height = 45,
20 | this.childWidget})
21 | : super(key: key);
22 |
23 | @override
24 | _NavigationBarState createState() => _NavigationBarState();
25 | }
26 |
27 | class _NavigationBarState extends State {
28 | var _color;
29 | var _statusStyle;
30 |
31 | @override
32 | Widget build(BuildContext context) {
33 | //沉浸式状态栏样式
34 | var brightness;
35 |
36 | var themeProvider = context.watch();
37 | if (themeProvider.isDark()) {
38 | _color = BaColors.dark_bg;
39 | _statusStyle = StatusStyle.LIGHT_STYLE;
40 | }else{
41 | _color = widget.color;
42 | _statusStyle = StatusStyle.DARK_STYLE;
43 | }
44 |
45 | if (Platform.isIOS) {
46 | brightness = _statusStyle == StatusStyle.LIGHT_STYLE
47 | ? Brightness.dark
48 | : Brightness.light;
49 | } else {
50 | brightness = _statusStyle == StatusStyle.LIGHT_STYLE
51 | ? Brightness.light
52 | : Brightness.dark;
53 | }
54 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light.copyWith(
55 | statusBarColor: Colors.transparent,
56 | statusBarBrightness: brightness,
57 | statusBarIconBrightness: brightness,
58 | ));
59 |
60 | var top = MediaQuery.of(context).padding.top; //刘海屏 刘海的高度
61 | print('top : $top');
62 | return Container(
63 | width: MediaQuery.of(context).size.width,
64 | height: top + widget.height,
65 | child: widget.childWidget,
66 | padding: EdgeInsets.only(top: top),
67 | decoration: BoxDecoration(color: _color),
68 | );
69 | }
70 | }
71 |
72 | enum StatusStyle { LIGHT_STYLE, DARK_STYLE }
73 |
--------------------------------------------------------------------------------
/lib/widget/setting_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_project/http/dao/person_dao.dart';
3 | import 'package:flutter_project/navigator/f_navigatior.dart';
4 | import 'package:flutter_project/utils/view_util.dart';
5 |
6 | ///个人中心Item组件
7 | ///包含icon和title
8 | class SettingItem extends StatelessWidget {
9 | final String? iconPath;
10 | final String? title;
11 | final int? index;
12 |
13 | const SettingItem({Key? key, this.iconPath, this.title, this.index})
14 | : super(key: key);
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return InkWell(
19 | onTap: () {
20 | switch (index) {
21 | case 0:
22 | //我的收藏
23 | FRouter.getInstance()!.onIntentTo(RouteStatus.collect);
24 | break;
25 | case 1:
26 | FRouter.getInstance()!.onIntentTo(RouteStatus.coinRank);
27 | break;
28 | case 2:
29 | //设置
30 | FRouter.getInstance()!.onIntentTo(RouteStatus.setting);
31 | break;
32 | case 3:
33 | //关于
34 | FRouter.getInstance()!.onIntentTo(RouteStatus.about);
35 | break;
36 | }
37 | },
38 | child: Container(
39 | margin: EdgeInsets.only(left: 20, right: 20),
40 | padding: EdgeInsets.only(top: 5, bottom: 5),
41 | height: 70,
42 | decoration: BoxDecoration(border: borderLine(context)),
43 | child: Column(
44 | mainAxisAlignment: MainAxisAlignment.center,
45 | children: [
46 | Row(
47 | children: [
48 | Image(
49 | image: AssetImage(iconPath!),
50 | height: 30,
51 | width: 30,
52 | ),
53 | viewSpace(width: 30),
54 | Text(title!, style: TextStyle(fontSize: 14)),
55 | ],
56 | ),
57 | ],
58 | )));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_project
2 | description: A new Flutter project.
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `pub publish`. This is preferred for private packages.
6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
7 |
8 | # The following defines the version and build number for your application.
9 | # A version number is three numbers separated by dots, like 1.2.43
10 | # followed by an optional build number separated by a +.
11 | # Both the version and the builder number may be overridden in flutter
12 | # build by specifying --build-name and --build-number, respectively.
13 | # In Android, build-name is used as versionName while build-number used as versionCode.
14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
16 | # Read more about iOS versioning at
17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
18 | version: 1.0.0+1
19 |
20 | environment:
21 | sdk: ">=2.12.0 <3.0.0"
22 |
23 | dependencies:
24 | flutter:
25 | sdk: flutter
26 |
27 |
28 | # The following adds the Cupertino Icons font to your application.
29 | # Use with the CupertinoIcons class for iOS style icons.
30 | cupertino_icons: ^1.0.2
31 | dio: ^4.0.4
32 | json_annotation: ^4.0.1
33 | shared_preferences: ^2.0.6
34 | fluttertoast: ^8.0.6
35 | underline_indicator: ^0.0.2 #指示器
36 | flutter_swiper_null_safety: ^1.0.2
37 | flutter_staggered_grid_view: ^0.4.0
38 | transparent_image: ^2.0.0
39 | cached_network_image: ^3.0.0
40 | flutter_inappwebview: ^5.3.2
41 | provider: ^5.0.0
42 | dio_cookie_manager: ^2.0.0
43 | cookie_jar: 3.0.1
44 | path_provider: ^2.0.1
45 |
46 | dev_dependencies:
47 | flutter_test:
48 | sdk: flutter
49 | json_serializable: ^4.1.3
50 | build_runner: ^2.0.4
51 |
52 | # For information on the generic Dart part of this file, see the
53 | # following page: https://dart.dev/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 |
63 | # To add assets to your application, add an assets section, like this:
64 | assets:
65 | - images/
66 | # - images/a_dot_ham.jpeg
67 |
68 | # An image asset can refer to one or more resolution-specific "variants", see
69 | # https://flutter.dev/assets-and-images/#resolution-aware.
70 |
71 | # For details regarding adding assets from package dependencies, see
72 | # https://flutter.dev/assets-and-images/#from-packages
73 |
74 | # To add custom fonts to your application, add a fonts section here,
75 | # in this "flutter" section. Each entry in this list should have a
76 | # "family" key with the font family name, and a "fonts" key with a
77 | # list giving the asset and other descriptors for the font. For
78 | # example:
79 | # fonts:
80 | # - family: Schyler
81 | # fonts:
82 | # - asset: fonts/Schyler-Regular.ttf
83 | # - asset: fonts/Schyler-Italic.ttf
84 | # style: italic
85 | # - family: Trajan Pro
86 | # fonts:
87 | # - asset: fonts/TrajanPro.ttf
88 | # - asset: fonts/TrajanPro_Bold.ttf
89 | # weight: 700
90 | #
91 | # For details regarding fonts from package dependencies,
92 | # see https://flutter.dev/custom-fonts/#from-packages
93 |
--------------------------------------------------------------------------------
/screenshots/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/1.jpg
--------------------------------------------------------------------------------
/screenshots/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/2.jpg
--------------------------------------------------------------------------------
/screenshots/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/3.jpg
--------------------------------------------------------------------------------
/screenshots/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/4.jpg
--------------------------------------------------------------------------------
/screenshots/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/5.jpg
--------------------------------------------------------------------------------
/screenshots/6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/6.jpg
--------------------------------------------------------------------------------
/screenshots/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/7.png
--------------------------------------------------------------------------------
/screenshots/8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/8.jpg
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:flutter_project/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------