├── .gitignore
├── .metadata
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── android_generated.zip
├── flutter_hybrid.iml
├── flutter_hybrid_android.iml
├── lib
├── Model
│ ├── CLCommentsModel.dart
│ └── CLMomentsModel.dart
├── Moment
│ ├── CLMomentsDetailPage.dart
│ ├── CLMomentsPage.dart
│ ├── CLOptionsPage.dart
│ ├── CLPhotoViewBrowser.dart
│ └── CLPublishMomentPage.dart
├── Utils
│ ├── CLDioUtil.dart
│ ├── CLPushUtil.dart
│ └── CLUtil.dart
├── custom
│ ├── CLAppbar.dart
│ ├── CLFlow.dart
│ ├── CLListViewRefresh.dart
│ ├── CLPhotoView.dart
│ ├── CLText.dart
│ └── HUD.dart
└── main.dart
├── pubspec.lock
├── pubspec.yaml
├── sources.gif
└── test
└── widget_test.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://www.dartlang.org/guides/libraries/private-files
2 |
3 | # Files and directories created by pub
4 | .dart_tool/
5 | .packages
6 | .pub/
7 | build/
8 | # If you're building an application, you may want to check-in your pubspec.lock
9 | pubspec.lock
10 |
11 | # Directory created by dartdoc
12 | # If you don't generate documentation locally you can remove this line.
13 | doc/api/
14 |
15 | .DS_Store
16 | .dart_tool/
17 |
18 | .packages
19 | .pub/
20 |
21 | .idea/
22 | .vagrant/
23 | .sconsign.dblite
24 | .svn/
25 |
26 | *.swp
27 | profile
28 |
29 | DerivedData/
30 |
31 | .generated/
32 |
33 | *.pbxuser
34 | *.mode1v3
35 | *.mode2v3
36 | *.perspectivev3
37 |
38 | !default.pbxuser
39 | !default.mode1v3
40 | !default.mode2v3
41 | !default.perspectivev3
42 |
43 | xcuserdata
44 |
45 | *.moved-aside
46 |
47 | *.pyc
48 | *sync/
49 | Icon?
50 | .tags*
51 |
52 | build/
53 | .android/
54 | .ios/
55 | .flutter-plugins
56 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 8661d8aecd626f7f57ccbcb735553edc05a2e713
8 | channel: stable
9 |
10 | project_type: module
11 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "java.configuration.updateBuildConfiguration": "automatic"
3 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 cleven
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 先上效果图
2 | 
3 |
4 | # 此项目主要是Android及时通讯项目中的朋友圈模块
5 |
6 | 1. 为什么选择用fultter实现朋友圈模块呢?
7 |
8 | 2. 使用flutter是什么感受?
9 |
10 | 3. 你认为的flutter未来发展趋势是什么样的?
11 |
12 |
13 | ### 先讲第一个问题
14 | 为什么选择flutter,主要是谷歌2018年推出flutter,后来看了flutter发布会视频,当时的感觉是作为一个iOS开发人员真累,老是有很多公司砸我的饭碗,心塞啊~😭,但是没办法,人总要面对现实往前走,所以在过完年这段时间仔细研究了一下flutter,学完总要做点东西,要不然就是纸上谈兵说不定过几天就忘了,写点东西总能加深影响,哪怕后续忘了,回头看看自己的代码也能回忆回忆
15 |
16 | ### 第二个问题
17 | 使用flutter的感受,说实话挺爽的,跟原生比画界面flutter要快很多,而且flutter性能更高,我已经深深的爱上这么技术了,真希望能早点在公司项目中使用,flutter总体使用感受还是特别好的,比Android好十倍,Android开发是真的麻烦,可能是我习惯了iOS开发,但是Android开发给我的感觉不是很好,如果这三种排优先级的话,flutter,iOS,Android,
18 |
19 | ### 第三个问题
20 |
21 | flutter的未来发展趋势,我认为应该是很不错的,就以目前跨平台的技术来看,RN的性能并不能像flutter一样达到120fps,RN虽然是跨平台技术,但是底层还是映射成原生控件,而且RN的适配很麻烦,包体积也比Flutter要大,
22 | flutter是基于skia,skia是一套轻量级而且很成熟的引擎,flutter抛弃了以往的局限性,直接自己渲染,所以性能要更高,最后希望flutter发展越来越好吧~~
23 |
--------------------------------------------------------------------------------
/android_generated.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cleven1/Flutter_Moments/fe4133e5efb5392ed6fbf90bb63818d38efacb46/android_generated.zip
--------------------------------------------------------------------------------
/flutter_hybrid.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/flutter_hybrid_android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/lib/Model/CLCommentsModel.dart:
--------------------------------------------------------------------------------
1 |
2 |
3 | class CLCommentModel {
4 | String momentType;
5 | String momentId;
6 | List comments;
7 | String content;
8 | CLUserinfo userInfo;
9 | List momentPics;
10 | String timeStamp;
11 | String avatarUrl = "";
12 | String aliasName = "";
13 |
14 | bool isDidFullButton = false;
15 | bool isShowFullButton = false;
16 |
17 | CLCommentModel({this.momentType, this.avatarUrl, this.momentId, this.comments, this.content, this.userInfo, this.momentPics, this.timeStamp});
18 |
19 | CLCommentModel.fromJson(Map json) {
20 | momentType = json['moment_type'];
21 | momentId = json['moment_id'];
22 | if (json['comments'] != null) {
23 | comments = new List();
24 | json['comments'].forEach((v) { comments.add(new CLCommentsDetailModel.fromJson(v)); });
25 | }
26 | content = json['content'];
27 | isShowFullButton = content.length > 100;
28 | userInfo = json['user_info'] != null ? new CLUserinfo.fromJson(json['user_info']) : null;
29 | if (json['moment_pics'] != null) {
30 | momentPics = new List();
31 | json['moment_pics'].forEach((v) { momentPics.add(v); });
32 | }
33 | timeStamp = json['time_stamp'];
34 | avatarUrl = json['avatar_url'];
35 | aliasName = json['alias_name'];
36 | }
37 |
38 | Map toJson() {
39 | final Map data = new Map();
40 | data['moment_type'] = this.momentType;
41 | data['moment_id'] = this.momentId;
42 | if (this.comments != null) {
43 | data['comments'] = this.comments.map((v) => v.toJson()).toList();
44 | }
45 | data['content'] = this.content;
46 | if (this.userInfo != null) {
47 | data['user_info'] = this.userInfo.toJson();
48 | }
49 | if (this.momentPics != null) {
50 | // data['moment_pics'] = this.momentPics.map((v) => v.toJson()).toList();
51 | }
52 | data['time_stamp'] = this.timeStamp;
53 | data["avatar_url"] = this.avatarUrl;
54 | data["alias_name"] = this.aliasName;
55 | return data;
56 | }
57 | }
58 |
59 | class CLCommentsDetailModel {
60 | CLUserinfo replyUserInfo;
61 | String momentId;
62 | String content;
63 | CLUserinfo userInfo;
64 | String replyUserName;
65 | String timeStamp;
66 | String aliasName;
67 | String avatarUrl;
68 | /// 判断是否有评论数据
69 | bool isEmptyContent = false;
70 |
71 | CLCommentsDetailModel({this.replyUserInfo, this.momentId, this.content, this.userInfo, this.replyUserName, this.timeStamp, this.aliasName, this.avatarUrl, this.isEmptyContent});
72 |
73 | CLCommentsDetailModel.fromJson(Map json) {
74 | replyUserInfo = json['reply_user_info'] != null ? new CLUserinfo.fromJson(json['reply_user_info']) : null;
75 | momentId = json['moment_id'];
76 | content = json['content'];
77 | userInfo = json['user_info'] != null ? new CLUserinfo.fromJson(json['user_info']) : null;
78 | replyUserName = json['reply_user_name'];
79 | timeStamp = json['time_stamp'];
80 | aliasName = json['alias_name'];
81 | avatarUrl = json['avatar_url'];
82 | }
83 |
84 | Map toJson() {
85 | final Map data = new Map();
86 | if (this.replyUserInfo != null) {
87 | data['reply_user_info'] = this.replyUserInfo.toJson();
88 | }
89 | data['moment_id'] = this.momentId;
90 | data['content'] = this.content;
91 | if (this.userInfo != null) {
92 | data['user_info'] = this.userInfo.toJson();
93 | }
94 | data['reply_user_name'] = this.replyUserName;
95 | data['time_stamp'] = this.timeStamp;
96 | data['alias_name'] = this.aliasName;
97 | data['avatar_url'] = this.avatarUrl;
98 | return data;
99 | }
100 | }
101 |
102 | class CLUserinfo {
103 | String city;
104 | String birthday;
105 | String aliasName;
106 | String name;
107 | String avatarUrl;
108 | int gender;
109 | String userId;
110 | String description;
111 |
112 | CLUserinfo(
113 | {this.city,
114 | this.birthday,
115 | this.aliasName,
116 | this.name,
117 | this.avatarUrl,
118 | this.gender,
119 | this.userId,
120 | this.description});
121 |
122 | CLUserinfo.fromJson(Map json) {
123 | city = json['city'];
124 | birthday = json['birthday'];
125 | aliasName = json['aliasName'];
126 | name = json['name'];
127 | avatarUrl = json['avatarUrl'];
128 | gender = json['gender'];
129 | userId = json['userId'];
130 | description = json['description'];
131 | }
132 |
133 | Map toJson() {
134 | final Map data = new Map();
135 | data['city'] = this.city;
136 | data['birthday'] = this.birthday;
137 | data['aliasName'] = this.aliasName;
138 | data['name'] = this.name;
139 | data['avatarUrl'] = this.avatarUrl;
140 | data['gender'] = this.gender;
141 | data['userId'] = this.userId;
142 | data['description'] = this.description;
143 | return data;
144 | }
145 | }
--------------------------------------------------------------------------------
/lib/Model/CLMomentsModel.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CLMomentsModel {
4 | String content;
5 | CLUserInfo userInfo;
6 | int likeCount;
7 | List momentPics;
8 | String timeStamp;
9 | int momentType;
10 | String aliasName;
11 | String avatarUrl;
12 | String momentId;
13 | int commentCout;
14 |
15 | /// 是否显示全文按钮
16 | bool isShowFullButton;
17 | bool isDidFullButton = false;
18 |
19 | CLMomentsModel(
20 | {this.content,
21 | this.userInfo,
22 | this.likeCount,
23 | this.momentPics,
24 | this.timeStamp,
25 | this.momentType,
26 | this.aliasName,
27 | this.avatarUrl,
28 | this.momentId,
29 | this.commentCout});
30 |
31 | CLMomentsModel.fromJson(Map json) {
32 | content = json['content'];
33 | isShowFullButton = content.length > 100;
34 | userInfo = json['user_info'] != null
35 | ? new CLUserInfo.fromJson(json['user_info'])
36 | : null;
37 | likeCount = json['like_count'];
38 | momentPics = json['moment_pics'];
39 | timeStamp = json['time_stamp'];
40 | momentType = json['moment_type'];
41 | aliasName = json['alias_name'];
42 | avatarUrl = json['avatar_url'];
43 | momentId = json['moment_id'];
44 | commentCout = json['comment_cout'];
45 | }
46 |
47 | Map toJson() {
48 | final Map data = new Map();
49 | data['content'] = this.content;
50 | if (this.userInfo != null) {
51 | data['user_info'] = this.userInfo.toJson();
52 | }
53 | data['like_count'] = this.likeCount;
54 | data['moment_pics'] = this.momentPics;
55 | data['time_stamp'] = this.timeStamp;
56 | data['moment_type'] = this.momentType;
57 | data['alias_name'] = this.aliasName;
58 | data['avatar_url'] = this.avatarUrl;
59 | data['moment_id'] = this.momentId;
60 | data['comment_cout'] = this.commentCout;
61 | return data;
62 | }
63 | }
64 |
65 | class CLUserInfo {
66 | String city;
67 | String birthday;
68 | String aliasName;
69 | String name;
70 | String avatarUrl;
71 | int gender;
72 | String userId;
73 | String description;
74 |
75 | CLUserInfo(
76 | {this.city,
77 | this.birthday,
78 | this.aliasName,
79 | this.name,
80 | this.avatarUrl,
81 | this.gender,
82 | this.userId,
83 | this.description});
84 |
85 | CLUserInfo.fromJson(Map json) {
86 | city = json['city'];
87 | birthday = json['birthday'];
88 | aliasName = json['aliasName'];
89 | name = json['name'];
90 | avatarUrl = json['avatarUrl'];
91 | gender = json['gender'];
92 | userId = json['userId'];
93 | description = json['description'];
94 | }
95 |
96 | Map toJson() {
97 | final Map data = new Map();
98 | data['city'] = this.city;
99 | data['birthday'] = this.birthday;
100 | data['aliasName'] = this.aliasName;
101 | data['name'] = this.name;
102 | data['avatarUrl'] = this.avatarUrl;
103 | data['gender'] = this.gender;
104 | data['userId'] = this.userId;
105 | data['description'] = this.description;
106 | return data;
107 | }
108 | }
--------------------------------------------------------------------------------
/lib/Moment/CLMomentsDetailPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:extended_image/extended_image.dart';
3 | import '../custom/CLText.dart';
4 | import '../custom/CLListViewRefresh.dart';
5 | import '../Utils/CLUtil.dart';
6 | import 'package:common_utils/common_utils.dart';
7 | import '../Utils/CLDioUtil.dart';
8 | import '../Model/CLCommentsModel.dart';
9 | import '../custom/HUD.dart';
10 | import '../custom/CLFlow.dart';
11 | import 'package:flutter/services.dart';
12 |
13 |
14 | class CLMomentsDetailPage extends StatefulWidget {
15 | final Widget child;
16 |
17 | CLMomentsDetailPage({Key key, this.child}): super(key: key);
18 |
19 | _CLMomentsDetailPageState createState() => _CLMomentsDetailPageState();
20 | }
21 |
22 | class _CLMomentsDetailPageState extends State {
23 |
24 | bool isReply = false;
25 | String replyId = "";
26 | String _momentId = "";
27 | String _userId = "123456";
28 | CLCommentModel _commentModel;
29 |
30 | /// 接受客户端发送过来的数据
31 | EventChannel eventChannel = EventChannel("detailChannel");
32 | MethodChannel methodChannel = MethodChannel("detailMethodChannel");
33 |
34 | /// 发布评论
35 | _publishMomentComment() async{
36 | if (textEditingController.text.isEmpty) {
37 | HUD().showMessageHud(context,content: "文本不能为空");
38 | return;
39 | }
40 | Map params = {
41 | "user_id": _userId,
42 | "content": textEditingController.text,
43 | "moment_id": _momentId,
44 | "reply_user_id": replyId,
45 | };
46 | print("object == $params");
47 |
48 | HUD().showHud(context);
49 | CLResultModel result = await CLDioUtil().requestPost("http://api.cleven1.com/api/moments/addComments",params: params);
50 | if(result.success){
51 | HUD().hideHud();
52 | HUD().showMessageHud(context,content: "发送成功");
53 | _getMomentComentsData();
54 | setState(() {
55 | isReply = false;
56 | });
57 | replyId = "";
58 | textEditingController.clear();
59 | FocusScope.of(context).requestFocus(FocusNode());
60 | }else{
61 | HUD().hideHud();
62 | HUD().showMessageHud(context,content: "发送失败");
63 | }
64 | }
65 |
66 | /// 获取评论数据
67 | _getMomentComentsData({bool isLoadMore = false,String lastCommentId = ""}) async{
68 | String url = "http://api.cleven1.com/api/moments/commentsInfo?isLoadMore=${isLoadMore ? "1" : "0"}&offset_id=$lastCommentId&moment_id=$_momentId";
69 | print(url);
70 | CLResultModel result = await CLDioUtil().requestGet(url);
71 | if(result.success){
72 | Map data = result.data["data"];
73 | CLCommentModel commentModel = CLCommentModel.fromJson(data);
74 | print("data === $data");
75 | print("model === ${commentModel.toJson()}");
76 | if (isLoadMore) {
77 | /// 把数据更新放到setState中会刷新页面
78 | List tempArray = _commentModel.comments;
79 | if (commentModel.comments.isEmpty == false || commentModel.comments.length <= 0) {
80 | tempArray.addAll(commentModel.comments);
81 | }
82 | commentModel.comments = tempArray;
83 | setState(() {
84 | _commentModel = commentModel;
85 | });
86 | }else{
87 | setState(() {
88 | _commentModel = commentModel;
89 | });
90 | }
91 | }else{
92 | HUD().showMessageHud(context,content: "获取失败");
93 | }
94 | }
95 |
96 | @override
97 | void initState() {
98 | super.initState();
99 | _reviceNativeParams();
100 | }
101 |
102 | /// 获取客户端传递过来的参数
103 | _reviceNativeParams() async {
104 | try {
105 | eventChannel.receiveBroadcastStream().listen((result){
106 | print("object == $result");
107 | _momentId = result["moment_id"];
108 | _userId = result["user_id"];
109 | _getMomentComentsData();
110 | });
111 | } on PlatformException catch (e) {
112 | print("event get data err: '${e.message}'.");
113 | }
114 | }
115 |
116 |
117 | @override
118 | void dispose() {
119 | FocusScope.of(context).requestFocus(FocusNode());
120 | super.dispose();
121 | }
122 |
123 | @override
124 | Widget build(BuildContext context) {
125 | return Scaffold(
126 | backgroundColor: Colors.white,
127 | body: _getListViewContainer(),
128 | );
129 | }
130 |
131 | _getHeaderContainer(CLCommentModel momentModel){
132 | return _getBaseContainer(
133 | momentModel,
134 | Column(
135 | crossAxisAlignment: CrossAxisAlignment.start,
136 | children: [
137 | CLText(text: momentModel.content, maxLines: momentModel.isDidFullButton ? 10000 : 6, style: TextStyle(color: Colors.pink),),
138 | SizedBox(height: 10,),
139 | momentModel.isShowFullButton ? GestureDetector(
140 | onTap: (){
141 | setState(() {
142 | momentModel.isDidFullButton = !momentModel.isDidFullButton;
143 | });
144 | },
145 | child: CLText(text: momentModel.isDidFullButton == true ? "收起" : "全文",style: TextStyle(color: Colors.blue),),) : Container(),
146 | SizedBox(height: 10,),
147 | momentModel.momentType == "1" ? CLFlow(
148 | count: momentModel.momentPics.length,
149 | children: _getImageContaniner(momentModel),
150 | ) : Container()
151 | ],
152 | ),
153 | subChild: Column(
154 | crossAxisAlignment: CrossAxisAlignment.start,
155 | children: [
156 | SizedBox(height: 10,),
157 | CLText(text: "留言板",style: setTextStyle(fontWeight: true,fontSize: 15),),
158 | SizedBox(height: 10,),
159 | ],
160 | )
161 | );
162 | }
163 |
164 |
165 | _getImageContaniner(model) {
166 | List images = [];
167 | List pics = [];
168 | for (var i = 0; i < model.momentPics.length; i++) {
169 | String imageUrl = model.momentPics[i];
170 | pics.add(imageUrl);
171 | images.add(GestureDetector(
172 | onTap: (){
173 | // print("imageUrl == $imageUrl index == $i");
174 | methodChannel.invokeMethod("photoBrowser",{
175 | "index": i,
176 | "pics": pics
177 | });
178 | // CLPushUtil().pushNavigatiton(context, CLPhotoViewBrowser(pics: pics, currentIndex: i,));
179 | },
180 | child: ExtendedImage.network(imageUrl,cache: true,fit: BoxFit.cover,),
181 | ));
182 | }
183 | return images;
184 | }
185 |
186 | _getListViewContainer(){
187 | List listData = [];
188 | int count = 0;
189 | if (_commentModel == null){
190 | listData = [];
191 | }else if(_commentModel.comments.isEmpty || _commentModel.comments.length <= 0){
192 | count = 1;
193 | listData = [1];
194 | }else{
195 | listData = _commentModel.comments;
196 | count = _commentModel.comments.length + 1;
197 | }
198 | return Column(
199 | crossAxisAlignment: CrossAxisAlignment.start,
200 | children: [
201 | Expanded(
202 | child: NotificationListener(
203 | onNotification: (ScrollNotification note) {
204 | print(note.metrics.pixels.toInt()); // 滚动位置。
205 | FocusScope.of(context).requestFocus(FocusNode());
206 | },
207 | child: CLListViewRefresh(
208 | listData: listData,
209 | child: ListView.builder(
210 | itemCount: count,
211 | itemBuilder: (context,index) {
212 | if (index == 0) {
213 | return _getHeaderContainer(_commentModel);
214 | }
215 | CLCommentsDetailModel commentsModel = _commentModel.comments[index - 1];
216 | return _getListViewItemContainer(commentsModel);
217 | },
218 | ),
219 | onRefresh: (){
220 | _getMomentComentsData();
221 | },
222 | loadMore: (){
223 | if (_commentModel.comments.isEmpty == false || _commentModel.comments.length > 0){
224 | CLCommentsDetailModel commentsModel = _commentModel.comments.last;
225 | _getMomentComentsData(isLoadMore: true,lastCommentId: commentsModel.momentId);
226 | }
227 | },
228 | ),
229 | ),
230 | ),
231 | _getTextFieldContainer(),
232 | SizedBox(height: 10,)
233 | ],
234 | );
235 | }
236 |
237 | TextEditingController textEditingController = TextEditingController();
238 | FocusNode _focusNode = new FocusNode();
239 | _getTextFieldContainer(){
240 | return Row(
241 | children: [
242 | Expanded(
243 | child: TextField(
244 | controller: textEditingController,
245 | focusNode: _focusNode,
246 | maxLines: 1,
247 | maxLength: 100,
248 | textInputAction: TextInputAction.done,
249 | decoration: InputDecoration(
250 | hintText: isReply ? "回复: " : "留言:",
251 | hintStyle: TextStyle(color: Colors.grey),
252 | border: InputBorder.none
253 | ),
254 | onSubmitted: (text){
255 | /// 隐藏键盘
256 | FocusScope.of(context).requestFocus(FocusNode());
257 | },
258 | ),
259 | ),
260 | FlatButton(
261 | child: CLText(text: "发送",style: TextStyle(color: Colors.redAccent),),
262 | onPressed: (){
263 | _publishMomentComment();
264 | },
265 | )
266 | ],
267 | );
268 | }
269 |
270 | _getListViewItemContainer(CLCommentsDetailModel commentsModel){
271 | return GestureDetector(
272 | onTap: (){
273 | FocusScope.of(context).requestFocus(_focusNode);
274 | setState(() {
275 | replyId = commentsModel.userInfo.userId;
276 | isReply = true;
277 | });
278 | },
279 | child: _getBaseContainer(
280 | commentsModel,
281 | RichText(
282 | text: TextSpan(
283 | text: commentsModel.replyUserInfo == null ? commentsModel.content : "回复",
284 | style: TextStyle(color: Colors.black),
285 | children: [
286 | TextSpan(
287 | text: commentsModel.replyUserInfo == null ? "" : "@${commentsModel.replyUserName}:",
288 | style: TextStyle(color: Colors.red)
289 | ),
290 | TextSpan(
291 | text: commentsModel.replyUserInfo == null ? "" : " ${commentsModel.content}",
292 | style: TextStyle(color: Colors.black)
293 | )
294 | ]
295 | ),
296 | ),
297 | nameColor: Colors.blueAccent,
298 | subChild: Column(
299 | children: [
300 | SizedBox(height: 5,),
301 | Divider(height: 1.0,),
302 | ],
303 | ),
304 | name: commentsModel.replyUserName == null ? "" : commentsModel.aliasName
305 | ),
306 | );
307 |
308 | }
309 |
310 | _getBaseContainer(var model, Widget child ,{Widget subChild, Color nameColor, String name = ""}) {
311 | int timeStamp = model.timeStamp == null ? CLUtil.currentTimeMillis() : int.parse(model.timeStamp);
312 | String formatTime = TimelineUtil.format(timeStamp,dayFormat: DayFormat.Simple);
313 | String avatarUrl = model.avatarUrl == null ? model.userInfo.avatarUrl : model.avatarUrl;
314 | return Container(
315 | color: Colors.white,
316 | padding: EdgeInsets.only(left: 10,right: 10,top: 5),
317 | child: Column(
318 | crossAxisAlignment: CrossAxisAlignment.start,
319 | children: [
320 | Row(
321 | crossAxisAlignment: CrossAxisAlignment.start,
322 | children: [
323 | ExtendedImage.network(
324 | avatarUrl,
325 | width: 40,
326 | height: 40,
327 | shape: BoxShape.circle,
328 | borderRadius: BorderRadius.circular(20),
329 | cache: true,
330 | ),
331 | SizedBox(width: 10,),
332 | Expanded(
333 | child: Column(
334 | crossAxisAlignment: CrossAxisAlignment.start,
335 | children: [
336 | CLText(text: name.isEmpty ? model.aliasName : name,style: setTextStyle(textColor: nameColor == null ? Colors.black87 : nameColor),),
337 | CLText(text: formatTime,style: setTextStyle(textColor: Colors.grey,fontSize: 12),),
338 | SizedBox(height: 5,),
339 | child,
340 | ],
341 | ),
342 | ),
343 | ],
344 | ),
345 | subChild == null ? Container() : subChild
346 | ],
347 | ),
348 | );
349 | }
350 |
351 | }
352 |
353 |
--------------------------------------------------------------------------------
/lib/Moment/CLMomentsPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import '../custom/CLText.dart';
3 | import 'package:extended_image/extended_image.dart';
4 | import '../custom/CLFlow.dart';
5 | import '../custom/CLListViewRefresh.dart';
6 | import '../Utils/CLDioUtil.dart';
7 | import '../Model/CLMomentsModel.dart';
8 | import 'package:common_utils/common_utils.dart';
9 | import '../Utils/CLUtil.dart';
10 | import '../Utils/CLPushUtil.dart';
11 | import 'CLPublishMomentPage.dart';
12 | import './CLOptionsPage.dart';
13 | import './CLPhotoViewBrowser.dart';
14 | import 'package:flutter/services.dart';
15 | import '../custom/HUD.dart';
16 |
17 | class CLMomentsPage extends StatefulWidget {
18 | final Widget child;
19 |
20 | CLMomentsPage({Key key, this.child}) : super(key: key);
21 |
22 | _CLMomentsPageState createState() => _CLMomentsPageState();
23 | }
24 |
25 | class _CLMomentsPageState extends State with AutomaticKeepAliveClientMixin{
26 |
27 | @override
28 | bool get wantKeepAlive => true;
29 |
30 | List mList = [];
31 |
32 | /// 传值channel
33 | MethodChannel channel = MethodChannel("moment");
34 |
35 | void initState() {
36 | super.initState();
37 | /// 监听发布完成,更新数据
38 | channel.setMethodCallHandler((MethodCall call) async {
39 | if (call.method == "updateMomentsData"){
40 | getMomentsData();
41 | }
42 | });
43 | getMomentsData();
44 | }
45 |
46 | getMomentsData({bool isLoadMore = false,String lastId}) async {
47 |
48 | CLResultModel result = await CLDioUtil().requestGet("http://api.cleven1.com/api/moments/momentsList?isLoadMore=${isLoadMore ? 1 : 0}&offset_id=$lastId");
49 | List jsons = result.data['data'];
50 | List tempModel = [];
51 | jsons.forEach((model){
52 | tempModel.add(CLMomentsModel.fromJson(model));
53 | });
54 | if (isLoadMore) {
55 | /// 把数据更新放到setState中会刷新页面
56 | setState(() {
57 | mList.addAll(tempModel);
58 | });
59 | }else{
60 | setState(() {
61 | mList = tempModel;
62 | });
63 | }
64 |
65 | }
66 |
67 | @override
68 | Widget build(BuildContext context) {
69 | return MaterialApp(
70 | color: Colors.white,
71 | home: Scaffold(
72 | backgroundColor: Colors.white,
73 | body: Stack(
74 | children: [
75 | getListViewContainer(),
76 | Positioned(
77 | right: 15,
78 | bottom: 25,
79 | child: FloatingActionButton(
80 | child: Icon(Icons.add),
81 | onPressed: () async{
82 | final String greeting = await channel.invokeMethod("gotoMomentPublish");
83 | print(greeting);
84 | // CLPushUtil().pushNavigatiton(context,
85 | // CLPublishMomentPage(pubilshMomentsSuccess: (){
86 | // getMomentsData();
87 | // },)
88 | // );
89 | },
90 | ),
91 | )
92 | ],
93 | ),
94 | ),
95 | );
96 | }
97 |
98 | getListViewContainer() {
99 |
100 | return CLListViewRefresh(
101 | listData: mList,
102 | child: ListView.builder(
103 | itemCount: mList.length,
104 | itemBuilder: (BuildContext context, int index) {
105 | CLMomentsModel model = mList[index];
106 | return model.momentType == 0 ? getItemTextContainer(model, index) : getItemImageContainer(model, index);
107 | },
108 | ),
109 | onRefresh: (){
110 | getMomentsData();
111 | },
112 | loadMore: (){
113 | getMomentsData(isLoadMore: true,lastId: mList.last.momentId);
114 | },
115 | );
116 | }
117 |
118 | /// 文本布局
119 | getItemTextContainer(CLMomentsModel model ,int index){
120 |
121 | return getItemBaseContainer(
122 | model: model,
123 | index: index,
124 | subChild: Column(
125 | crossAxisAlignment: CrossAxisAlignment.start,
126 | children: [
127 | getTextContainer(model),
128 | model.isShowFullButton ? getFullContainer(model) : Container(),
129 | ],
130 | )
131 | );
132 | }
133 |
134 | /// 图片布局
135 | getItemImageContainer(CLMomentsModel model, int index){
136 | return getItemBaseContainer(
137 | model: model,
138 | index: index,
139 | subChild: Column(
140 | crossAxisAlignment: CrossAxisAlignment.start,
141 | children: [
142 | getTextContainer(model),
143 | model.isShowFullButton ? getFullContainer(model) : Container(),
144 | SizedBox(height: 10,),
145 | CLFlow(
146 | count: model.momentPics.length,
147 | children: getImageContaniner(model),
148 | ),
149 | ],
150 | ),
151 | );
152 | }
153 |
154 | getTextContainer(CLMomentsModel model) {
155 | GlobalKey _myKey = new GlobalKey();
156 |
157 | CLText text = CLText(
158 | key: _myKey,
159 | text: model.content,
160 | maxLines: model.isDidFullButton ? 100000 : 6,
161 | style: setTextStyle(textColor: Colors.pinkAccent),
162 | );
163 | // RenderObject renderObject = _myKey.currentContext.findRenderObject();
164 | // print("semanticBounds:${renderObject.semanticBounds.size} paintBounds:${renderObject.paintBounds.size} size:${_myKey.currentContext.size}");
165 | return text;
166 | }
167 |
168 | getImageContaniner(CLMomentsModel model) {
169 | List images = [];
170 | List pics = [];
171 | for (var i = 0; i < model.momentPics.length; i++) {
172 | String imageUrl = model.momentPics[i];
173 | pics.add(imageUrl);
174 | images.add(GestureDetector(
175 | onTap: (){
176 | // print("imageUrl == $imageUrl index == $i");
177 | print("pics == ${pics.length}");
178 | channel.invokeMethod("photoBrowser",{
179 | "index": i,
180 | "pics": pics
181 | });
182 | // CLPushUtil().pushNavigatiton(context, CLPhotoViewBrowser(pics: pics, currentIndex: i,));
183 | },
184 | child: ExtendedImage.network(imageUrl,cache: true,fit: BoxFit.cover,),
185 | ));
186 | }
187 | return images;
188 | }
189 |
190 | getFullContainer(CLMomentsModel model){
191 | return Container(
192 | padding: EdgeInsets.only(top: 10),
193 | child: GestureDetector(
194 | onTap: (){
195 | setState(() {
196 | model.isDidFullButton = !model.isDidFullButton;
197 | });
198 | },
199 | child: CLText(text: model.isDidFullButton ? '收起' : '全文',style: setTextStyle(textColor: Colors.blue),),
200 | )
201 | );
202 | }
203 |
204 | getItemBaseContainer({CLMomentsModel model, Widget subChild, int index}){
205 | int timeStamp = model.timeStamp == null ? CLUtil.currentTimeMillis() : int.parse(model.timeStamp);
206 | String formatTime = TimelineUtil.format(timeStamp,dayFormat: DayFormat.Simple);
207 | String avatarUrl = model.avatarUrl == null ? model.userInfo.avatarUrl : model.avatarUrl;
208 | return GestureDetector(
209 | onTap: () async{
210 | print("object == $index content == ${model.content}");
211 | final String greeting = await channel.invokeMethod("gotoDetailPage",model.momentId);
212 | print(greeting);
213 | // CLPushUtil().pushNavigatiton(context, CLMomentsDetailPage(momentModel: model,));
214 | },
215 | child: Container(
216 | padding: EdgeInsets.only(left: 15,right: 15,top: 15),
217 | child: Row(
218 | crossAxisAlignment: CrossAxisAlignment.start,
219 | children: [
220 | ExtendedImage.network(
221 | avatarUrl,
222 | width: 40,
223 | height: 40,
224 | shape: BoxShape.circle,
225 | borderRadius: BorderRadius.circular(20),
226 | cache: true,
227 | ),
228 | SizedBox(width: 10,),
229 | Expanded(
230 | child: Column(
231 | crossAxisAlignment: CrossAxisAlignment.start,
232 | children: [
233 | CLText(
234 | text: "${model.aliasName}",
235 | textAlign: TextAlign.start,
236 | style: setTextStyle(textColor: Colors.black87),
237 | ),
238 | CLText(
239 | text: formatTime,//TimelineUtil.format(int.parse(model.timeStamp),dayFormat: DayFormat.Simple),//.formatByDateTime(formatTime,locale: 'zh').toString(),
240 | style: setTextStyle(textColor: Colors.grey,fontSize: 12),
241 | ),
242 | SizedBox(height: 5,),
243 | subChild,
244 | ],
245 | ),
246 | ),
247 | GestureDetector(
248 | onTap: (){
249 | CLOptionPage.showView(
250 | context,
251 | clickItemCallback: (CLOptionType type){
252 | if (type == CLOptionType.delete){
253 | print("删除");
254 | CLOptionPage.hiddenView();
255 | HUD().showMessageHud(context,content: "等待实现");
256 | }
257 | });
258 | },
259 | child: Icon(Icons.more_horiz,),
260 | )
261 | ],
262 | ),
263 | ),
264 | );
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/lib/Moment/CLOptionsPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import '../custom/HUD.dart';
3 |
4 | enum CLOptionType {
5 | delete,
6 | report
7 | }
8 | /// 定义回调类型
9 | typedef Future OnClickItemCallback(CLOptionType optionType);
10 |
11 | class CLOptionPage extends PopupRoute {
12 |
13 | static CLOptionPage _currentOption;
14 | Duration delayed = Duration(milliseconds: 2000);
15 | OnClickItemCallback clickItemCallback;
16 |
17 | static showView(BuildContext context,{OnClickItemCallback clickItemCallback}) async{
18 |
19 | try {
20 | if (_currentOption != null) {
21 | _currentOption.navigator.pop();
22 | }
23 | CLOptionPage optionPage = CLOptionPage();
24 | optionPage.clickItemCallback = clickItemCallback;
25 | _currentOption = optionPage;
26 | Navigator.push(context, optionPage);
27 | } catch (e) {
28 | _currentOption = null;
29 | }
30 |
31 | }
32 |
33 | static hiddenView() async{
34 | _currentOption.navigator.pop();
35 | _currentOption = null;
36 | }
37 |
38 | @override
39 | // TODO: implement barrierColor
40 | Color get barrierColor => null;
41 |
42 | @override
43 | // TODO: implement barrierLabel
44 | String get barrierLabel => null;
45 |
46 | @override
47 | // TODO: implement transitionDuration
48 | Duration get transitionDuration => kThemeAnimationDuration;
49 |
50 | @override
51 | // TODO: implement barrierDismissible
52 | bool get barrierDismissible => true;
53 |
54 | @override
55 | Widget buildPage(BuildContext context, Animation animation,
56 | Animation secondaryAnimation) {
57 |
58 | return GestureDetector(
59 | onTap: (){
60 | hiddenView();
61 | },
62 | child: Container(
63 | color: Color.fromRGBO(0, 0, 0, 0.4),
64 | child: Container(
65 | color: Colors.black38,
66 | child: Column(
67 | crossAxisAlignment: CrossAxisAlignment.start,
68 | children: [
69 | Expanded(
70 | child: Container(color: Colors.black38,),
71 | ),
72 | Container(
73 | color: Colors.white,
74 | height: 120,
75 | child: Row(
76 | crossAxisAlignment: CrossAxisAlignment.center,
77 | mainAxisAlignment: MainAxisAlignment.center,
78 | children: [
79 | Expanded(
80 | child: Center(
81 | child: FlatButton(
82 | onPressed: (){
83 | clickItemCallback(CLOptionType.delete);
84 | },
85 | child: Text("删除",style: TextStyle(color: Colors.black,fontSize: 18, decoration: TextDecoration.none),),
86 | ))
87 | ),
88 | SizedBox(width: 10,),
89 | Expanded(
90 | child: Center(child: FlatButton(
91 | onPressed: (){
92 | clickItemCallback(CLOptionType.report);
93 | hiddenView();
94 | HUD().showMessageHud(context,content: "举报成功");
95 | },
96 | child: Text("举报",style: TextStyle(color: Colors.black,fontSize: 18, decoration: TextDecoration.none),),
97 | )),
98 | ),
99 | ],
100 | ),
101 | )
102 | ],
103 | ),
104 | ),
105 | ),
106 | );
107 | }
108 |
109 | @override
110 | Widget buildTransitions(BuildContext context, Animation animation,
111 | Animation secondaryAnimation, Widget child) {
112 | // TODO: implement buildTransitions
113 | return super
114 | .buildTransitions(context, animation, secondaryAnimation, child);
115 | }
116 | }
--------------------------------------------------------------------------------
/lib/Moment/CLPhotoViewBrowser.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:extended_image/extended_image.dart';
3 |
4 | /// 图片查看
5 | class CLPhotoViewBrowser extends StatelessWidget {
6 | final Widget child;
7 | final List pics;
8 |
9 | int currentIndex = 0;
10 |
11 | CLPhotoViewBrowser({Key key, this.child, @required this.pics, this.currentIndex}) : super(key: key);
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | backgroundColor: Colors.black,
17 | body: GestureDetector(
18 | onTap: (){
19 | Navigator.pop(context);
20 | },
21 | child: ExtendedImageGesturePageView.builder(
22 | itemBuilder: (BuildContext context, int index) {
23 | var url = pics[index];
24 | Widget image = ExtendedImage.network(
25 | url,
26 | fit: BoxFit.contain,
27 | mode: ExtendedImageMode.Gesture,
28 | gestureConfig: GestureConfig(
29 | inPageView: true, initialScale: 1.0,
30 | //you can cache gesture state even though page view page change.
31 | //remember call clearGestureDetailsCache() method at the right time.(for example,this page dispose)
32 | cacheGesture: false
33 | ),
34 | );
35 | image = Container(
36 | child: image,
37 | padding: EdgeInsets.all(5.0),
38 | );
39 | if (index == currentIndex) {
40 | return Hero(
41 | tag: url + index.toString(),
42 | child: image,
43 | );
44 | } else {
45 | return image;
46 | }
47 | },
48 | itemCount: pics.length,
49 | onPageChanged: (int index) {
50 | currentIndex = index;
51 | },
52 | controller: PageController(
53 | initialPage: currentIndex,
54 | ),
55 | scrollDirection: Axis.horizontal,
56 | ),
57 | ),
58 | );
59 | }
60 | }
--------------------------------------------------------------------------------
/lib/Moment/CLPublishMomentPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'dart:ui';
3 | import '../custom/CLText.dart';
4 | // import 'package:photo/photo.dart';
5 | // import 'package:photo_manager/photo_manager.dart';
6 | import '../Utils/CLDioUtil.dart';
7 | import '../custom/HUD.dart';
8 | import 'package:flutter/services.dart';
9 |
10 | /// 定义回调类型
11 | typedef Future PubilshMomentsSuccess();
12 |
13 | class CLPublishMomentPage extends StatefulWidget {
14 | final Widget child;
15 | final String title = "发布";
16 | final PubilshMomentsSuccess pubilshMomentsSuccess;
17 |
18 | CLPublishMomentPage({Key key, this.child, this.pubilshMomentsSuccess}): super(key: key);
19 |
20 | _CLPublishMomentPageState createState() => _CLPublishMomentPageState();
21 | }
22 |
23 | class _CLPublishMomentPageState extends State {
24 |
25 | // List imageList = [];
26 |
27 | String _content = '';
28 | String _userId = '123456';
29 |
30 | EventChannel eventChannel = EventChannel("publishChannel");
31 | MethodChannel methodChannel = MethodChannel("publishChannel");
32 |
33 | @override
34 | Widget build(BuildContext context) {
35 | return Scaffold(
36 | resizeToAvoidBottomPadding: false,
37 | backgroundColor: Colors.white,
38 | body: _getPublishContainer(),
39 | );
40 | }
41 |
42 | @override
43 | void initState() {
44 | super.initState();
45 | _reviceNativeParams();
46 |
47 | /// 监听客户端点击发布按钮
48 | methodChannel.setMethodCallHandler((MethodCall call) async {
49 | if (call.method == "publish") {
50 | _publishMoment();
51 | }
52 | });
53 | }
54 |
55 | /// 获取客户端传递过来的参数
56 | _reviceNativeParams() async {
57 | try {
58 | eventChannel.receiveBroadcastStream().listen((result){
59 | print("object == $result");
60 | _userId = result["user_id"];
61 | });
62 | } on PlatformException catch (e) {
63 | print("event get data err: '${e.message}'.");
64 | }
65 | }
66 |
67 | @override
68 | void dispose() {
69 | FocusScope.of(context).requestFocus(FocusNode());
70 | super.dispose();
71 | }
72 |
73 | /// 发布
74 | _publishMoment() async{
75 | print("content == $_content");
76 |
77 | if (_content.isEmpty) {
78 | HUD().showMessageHud(context,content: "内容不能为空");
79 | return;
80 | }
81 | Map params = {
82 | "content": _content,
83 | "user_id": _userId,
84 | "moment_pics": [],
85 | "moment_type": "0"
86 | };
87 | HUD().showHud(context);
88 | CLResultModel result = await CLDioUtil().requestPost("http://api.cleven1.com/api/moments/publishMoments",params: params);
89 | if(result.success){
90 | print(result.data);
91 | // widget.pubilshMomentsSuccess();
92 | // Navigator.pop(context);
93 | /// 发布成功
94 | methodChannel.invokeListMethod("publish_finish");
95 | HUD().hideHud();
96 | }else{
97 | print("发布失败== ${result.data}");
98 | HUD().hideHud();
99 | HUD().showMessageHud(context,content: "发布失败");
100 | }
101 | }
102 |
103 | _getRightActions() {
104 | return [
105 | Container(
106 | width: 65,
107 | child: FlatButton(child: CLText(text: "发布",style: TextStyle(
108 | color: Colors.orangeAccent
109 | ),),
110 | onPressed: (){
111 | print("发布");
112 | _publishMoment();
113 | },),
114 | )
115 | ];
116 | }
117 |
118 | _getAddPhotoContainer() {
119 | return Container(
120 | height: 80,
121 | width: 80,
122 | child: RaisedButton(child: Icon(Icons.add_to_photos),onPressed: () {
123 |
124 | print('选择照片');
125 | // _photoListParams();
126 | },),
127 | );
128 | }
129 |
130 | _getPublishContainer() {
131 | return Container(
132 | color: Colors.white,
133 | padding: EdgeInsets.only(left: 10,right: 10),
134 | child: Column(
135 | crossAxisAlignment: CrossAxisAlignment.start,
136 | children: [
137 | Expanded(
138 | flex: 1,
139 | child: _getTextFieldContainer(),
140 | ),
141 | SizedBox(height: 50,),
142 | // imageList.length > 0 ? CLPhotoView(list: imageList,
143 | // onDeleteItem: (list) {
144 | // setState(() {
145 | // imageList = list;
146 | // });
147 | // },) : _getAddPhotoContainer(),
148 | SizedBox(height: 11,)
149 |
150 | ],
151 | ),
152 |
153 |
154 | );
155 | }
156 |
157 | _getTextFieldContainer(){
158 | return TextField(
159 | maxLines: 100,
160 | maxLength: 500,
161 | textInputAction: TextInputAction.done,
162 | decoration: InputDecoration(
163 | hintText: "请输入文本:",
164 | hintStyle: TextStyle(color: Colors.grey),
165 | border: InputBorder.none
166 | ),
167 | onChanged: (text){
168 | _content = text;
169 | },
170 | onSubmitted: (text){
171 | /// 隐藏键盘
172 | FocusScope.of(context).requestFocus(FocusNode());
173 | },
174 | );
175 | }
176 |
177 | // void _photoListParams() async {
178 | // var assetPathList = await PhotoManager.getImageAsset();
179 | // _pickAsset(PickType.onlyImage, pathList: assetPathList);
180 | // }
181 |
182 | // void _pickAsset(PickType type, {List pathList}) async {
183 |
184 | // List imgList = await PhotoPicker.pickAsset(
185 | // context: context,
186 | // // BuildContext requied
187 |
188 | // /// The following are optional parameters.
189 | // themeColor: Colors.blue,
190 | // // the title color and bottom color
191 | // padding: 3.0,
192 | // // item padding
193 | // dividerColor: Colors.white,
194 | // // divider color
195 | // disableColor: Colors.grey.shade300,
196 | // // the check box disable color
197 | // itemRadio: 0.88,
198 | // // the content item radio
199 | // maxSelected: 9,
200 | // // max picker image count
201 | // provider: I18nProvider.chinese,
202 | // // i18n provider ,default is chinese. , you can custom I18nProvider or use ENProvider()
203 | // rowCount: 5,
204 | // // item row count
205 | // textColor: Colors.white,
206 | // // text color
207 | // thumbSize: 150,
208 | // // preview thumb size , default is 64
209 | // sortDelegate: SortDelegate.common,
210 | // // default is common ,or you make custom delegate to sort your gallery
211 | // checkBoxBuilderDelegate: DefaultCheckBoxBuilderDelegate(
212 | // activeColor: Colors.white,
213 | // unselectedColor: Colors.white,
214 | // ), // default is DefaultCheckBoxBuilderDelegate ,or you make custom delegate to create checkbox
215 |
216 | // // loadingDelegate: this, // if you want to build custom loading widget,extends LoadingDelegate [see example/lib/main.dart]
217 |
218 | // badgeDelegate: const DefaultBadgeDelegate(), /// or custom class extends [BadgeDelegate]
219 |
220 | // pickType: type, // all/image/video
221 |
222 | // photoPathList: pathList,
223 | // );
224 | // String currentSelected = '';
225 | // if (imgList == null) {
226 | // currentSelected = "not select item";
227 | // } else {
228 | // /// 图片路径
229 | // List r = [];
230 | // for (var e in imgList) {
231 | // var file = await e.file;
232 | // r.add(file.absolute.path);
233 | // }
234 | // currentSelected = r.join("\n\n");
235 | // print(currentSelected);
236 |
237 | // print(imgList[0].file.toString());
238 | // print(imgList[0].originFile.toString());
239 | // print(imgList[0].fullData);
240 |
241 |
242 | // setState(() {
243 | // imageList = imgList;
244 | // });
245 | // }
246 |
247 | // }
248 | }
--------------------------------------------------------------------------------
/lib/Utils/CLDioUtil.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:dio/dio.dart';
3 |
4 | class CLDioUtil {
5 | static String baseUrl = '';
6 | static Map baseHeaders = {
7 | // "platform":"iOS",
8 | // "application/json":"text/json"
9 | };
10 |
11 | /// 单例实现
12 | factory CLDioUtil() => _getInstance();
13 | static CLDioUtil get instance => _getInstance();
14 | static CLDioUtil _instance;
15 | CLDioUtil._internal(){
16 | /// 初始化
17 | }
18 | static CLDioUtil _getInstance() {
19 | if (_instance == null) {
20 | _instance = new CLDioUtil._internal();
21 | }
22 | return _instance;
23 | }
24 |
25 | int timeOut = 30 * 1000;
26 | setTimeOut(int timeOut){
27 | this.timeOut = timeOut * 1000;
28 | }
29 |
30 | requestPost(String url,{Map params}) async {
31 | return await _requestBase(url, params, baseHeaders, Options(method: 'post'));
32 | }
33 |
34 | requestGet(String url,{Map params}) async {
35 | return await _requestBase(url, params, baseHeaders, Options(method: 'get'));
36 | }
37 |
38 | _requestBase(String url, Map params, Mapheader, Options option, {noTip = false}) async {
39 |
40 | /// 处理请求头
41 | Map headers = Map();
42 | if (header !=null){
43 | headers.addAll(header);
44 | }
45 | /// options 处理
46 | if (option !=null) {
47 | option.headers = headers;
48 | }else{
49 | option = new Options(method: 'get');
50 | option.headers = headers;
51 | }
52 | /// 设置连接超时
53 | option.connectTimeout = this.timeOut;
54 | var dio = new Dio();
55 | Response response;
56 | Response errorResponse;
57 | try {
58 | if (!url.startsWith('http')) {
59 | url = baseUrl + url;
60 | }
61 | response = await dio.request(url,data: params,options: option);
62 | } on DioError catch (error) {
63 | if (error.response != null){
64 | errorResponse = error.response;
65 | }else{
66 | errorResponse = new Response(statusCode: 500);
67 | }
68 | if (error.type ==DioErrorType.CONNECT_TIMEOUT) {
69 | errorResponse.statusCode = -2;
70 | }
71 | return new CLResultModel(errorResponse.data, false, errorResponse.statusCode);
72 | }
73 | try {
74 | if (response.statusCode == 200 || response.statusCode == 201) {
75 | return new CLResultModel(response.data, true, response.statusCode);
76 | }else{
77 | return new CLResultModel(errorResponse.data, false, errorResponse.statusCode);
78 | }
79 | } catch (error) {
80 | return new CLResultModel(errorResponse.data, false, errorResponse.statusCode);
81 | }
82 |
83 | }
84 |
85 | }
86 | class CLResultModel {
87 | Map data;
88 | bool success;
89 | int code;
90 | var headers;
91 |
92 | CLResultModel(this.data, this.success, this.code, {this.headers});
93 | }
94 |
95 |
--------------------------------------------------------------------------------
/lib/Utils/CLPushUtil.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CLPushUtil {
4 |
5 | pushNavigatiton(BuildContext context, Widget target){
6 | Navigator.push(
7 | context,
8 | new PageRouteBuilder(pageBuilder: (BuildContext context,
9 | Animation animation, Animation secondaryAnimation) {
10 | // 跳转的路由对象
11 | return target;
12 | }, transitionsBuilder: (
13 | BuildContext context,
14 | Animation animation,
15 | Animation secondaryAnimation,
16 | Widget child,
17 | ) {
18 | return _createTransition(animation, child);
19 | }));
20 | }
21 |
22 |
23 | SlideTransition _createTransition(Animation animation, Widget child) {
24 | return new SlideTransition(
25 | position: new Tween(
26 | begin: const Offset(1.0, 0.0),
27 | end: const Offset(0.0, 0.0),
28 | ).animate(animation),
29 | child: child,
30 | );
31 | }
32 | }
--------------------------------------------------------------------------------
/lib/Utils/CLUtil.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CLUtil {
4 |
5 | /** 获取屏幕宽度 */
6 | static double getScreenWidth(BuildContext context) {
7 | return MediaQuery.of(context).size.width;
8 | }
9 |
10 | /** 获取屏幕高度 */
11 | static double getScreenHeight(BuildContext context) {
12 | return MediaQuery.of(context).size.height;
13 | }
14 |
15 | /** 获取系统状态栏高度 */
16 | static double getSysStatsHeight(BuildContext context) {
17 | return MediaQuery.of(context).padding.top;
18 | }
19 |
20 | /** 返回当前时间戳 */
21 | static int currentTimeMillis() {
22 | return new DateTime.now().millisecondsSinceEpoch;
23 | }
24 |
25 | static const RollupSize_Units = ["GB", "MB", "KB", "B"];
26 | /** 返回文件大小字符串 */
27 | static String getRollupSize(int size) {
28 | int idx = 3;
29 | int r1 = 0;
30 | String result = "";
31 | while (idx >= 0) {
32 | int s1 = size % 1024;
33 | size = size >> 10;
34 | if (size == 0 || idx == 0) {
35 | r1 = (r1 * 100) ~/ 1024;
36 | if (r1 > 0) {
37 | if (r1 >= 10)
38 | result = "$s1.$r1${RollupSize_Units[idx]}";
39 | else
40 | result = "$s1.0$r1${RollupSize_Units[idx]}";
41 | } else
42 | result = s1.toString() + RollupSize_Units[idx];
43 | break;
44 | }
45 | r1 = s1;
46 | idx--;
47 | }
48 | return result;
49 | }
50 | /** 返回两个日期相差的天数 */
51 | static int daysBetween(DateTime a, DateTime b, [bool ignoreTime = false]) {
52 | if (ignoreTime) {
53 | int v = a.millisecondsSinceEpoch ~/ 86400000 -
54 | b.millisecondsSinceEpoch ~/ 86400000;
55 | if (v < 0) return -v;
56 | return v;
57 | } else {
58 | int v = a.millisecondsSinceEpoch - b.millisecondsSinceEpoch;
59 | if (v < 0) v = -v;
60 | return v ~/ 86400000;
61 | }
62 | }
63 |
64 | }
--------------------------------------------------------------------------------
/lib/custom/CLAppbar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CLAppBar extends PreferredSize {
4 |
5 | final Widget child;
6 | final String title;
7 | final Widget leading;
8 | final List actions;
9 | static final double navHeight = 44;
10 |
11 | CLAppBar({Key key, @required this.child, this.title, this.leading, this.actions}) : super(key: key, child:child, preferredSize: Size.fromHeight(navHeight));
12 |
13 | @override
14 | PreferredSize build(BuildContext context) {
15 | return PreferredSize(
16 | preferredSize: Size.fromHeight(navHeight),
17 | child: AppBar(
18 | iconTheme: IconThemeData(color: Colors.black54,size: 15.0),
19 | textTheme: TextTheme(title: TextStyle(color: Colors.black,fontSize: 16,fontWeight: FontWeight.normal),),
20 | backgroundColor: Colors.white,
21 | title: Text('$title',),
22 | centerTitle: true, /// 标题居中
23 | /// 设置状态栏颜色
24 | brightness: Brightness.light,
25 | /// 设置导航栏阴影效果
26 | elevation: 0.0,
27 | /// 左侧按钮
28 | leading: leading,
29 | /// 右侧按钮
30 | actions: actions
31 | ),
32 | );
33 | }
34 | }
--------------------------------------------------------------------------------
/lib/custom/CLFlow.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// 仿朋友圈九宫格布局
4 | class CLFlow extends StatelessWidget {
5 | final List children;
6 | /// 子view总数
7 | final int count;
8 | /// 间距
9 | final double gap;
10 |
11 | CLFlow({
12 | Key key,
13 | this.children,
14 | @required this.count,
15 | this.gap}) : super(key: key);
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Flow(
20 | delegate: CLFlowDelegate(
21 | count: count,
22 | ),
23 | children: children,
24 | );
25 | }
26 | }
27 |
28 | class CLFlowDelegate extends FlowDelegate {
29 | final int count;
30 | final double gap;
31 | CLFlowDelegate({
32 | @required this.count,
33 | this.gap = 10.0,
34 | });
35 |
36 | var columns = 3;
37 | var rows = 3;
38 | double itemW = 0;
39 | double itemH = 0;
40 | double totalW = 0;
41 |
42 | @override
43 | void paintChildren(FlowPaintingContext context) {
44 | var x = gap;
45 | var y = 0.0;
46 |
47 | /// 需要重新计算,解决刷新值为0的问题
48 | getItemSize();
49 | getColumnsNumber(count);
50 | totalW = (itemW * rows) + (gap * (rows + 1));
51 |
52 | //计算每一个子widget的位置
53 | for (int i = 0; i < count; i++) {
54 | var w = context.getChildSize(i).width + x;
55 | if (w < totalW) {
56 | context.paintChild(i,
57 | transform: new Matrix4.translationValues(
58 | x, y, 0.0));
59 | x += context.getChildSize(i).width + gap;
60 | } else {
61 | x = gap;
62 | y += context.getChildSize(i).height + gap;
63 | context.paintChild(i,
64 | transform: new Matrix4.translationValues(
65 | x, y, 0.0));
66 | x += context.getChildSize(i).width + gap;
67 | }
68 | }
69 | }
70 |
71 | getColumnsNumber(int length) {
72 | if (length <= 3) {
73 | rows = length;
74 | columns = 1;
75 | } else if (length <= 6) {
76 | rows = 3;
77 | columns = 2;
78 | if (length == 4) {
79 | rows = 2;
80 | }
81 | } else {
82 | rows = 3;
83 | columns = 3;
84 | }
85 | }
86 |
87 | getItemSize() {
88 | if (count == 1) {
89 | itemW = 120;
90 | itemH = 120;
91 | }else if (count <= 3){
92 | itemW = 80;
93 | itemH = 80;
94 | }else if (count <= 6) {
95 | itemW = 70;
96 | itemH = 70;
97 | if (count == 4) {
98 | itemW = 80;
99 | itemH = 80;
100 | }
101 | } else {
102 | itemW = 60;
103 | itemH = 60;
104 | }
105 | }
106 |
107 | /// 设置每个子view的size
108 | getConstraintsForChild(int i, BoxConstraints constraints) {
109 | getItemSize();
110 | return BoxConstraints(
111 | minWidth: itemW,
112 | minHeight: itemH,
113 | maxWidth: itemW,
114 | maxHeight: itemH
115 | );
116 |
117 | }
118 |
119 | /// 设置flow的size
120 | getSize(BoxConstraints constraints){
121 | getColumnsNumber(count);
122 | getItemSize();
123 | double h = (columns * itemH) + ((columns - 1) * gap);
124 | totalW = (itemW * rows) + (gap * (rows + 1));
125 | return Size(totalW, h);
126 | }
127 |
128 | @override
129 | bool shouldRepaint(FlowDelegate oldDelegate) {
130 | return oldDelegate != this;
131 | }
132 | }
--------------------------------------------------------------------------------
/lib/custom/CLListViewRefresh.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_easyrefresh/easy_refresh.dart';
3 | import 'package:flutter/cupertino.dart';
4 | import 'package:flutter_easyrefresh/ball_pulse_header.dart';
5 | import 'package:flutter_easyrefresh/ball_pulse_footer.dart';
6 |
7 |
8 | /// 定义回调类型
9 | typedef Future OnRefresh();
10 | typedef Future LoadMore();
11 |
12 | class CLListViewRefresh extends StatefulWidget {
13 | final List listData;
14 | final Widget child;
15 | final OnRefresh onRefresh;
16 | final LoadMore loadMore;
17 |
18 | CLListViewRefresh({
19 | Key key ,
20 | @required this.child,
21 | @required this.listData,
22 | this.onRefresh,
23 | this.loadMore}) : super(key: key);
24 |
25 | _CLListViewRefreshState createState() => _CLListViewRefreshState();
26 | }
27 |
28 | class _CLListViewRefreshState extends State {
29 | @override
30 | Widget build(BuildContext context) {
31 | return getListViewContainer();
32 | }
33 |
34 | GlobalKey easyRefreshKey;
35 | GlobalKey headerKey;
36 | GlobalKey footerKey;
37 | void initState() {
38 | super.initState();
39 | easyRefreshKey = new GlobalKey();
40 | headerKey = new GlobalKey();
41 | footerKey = new GlobalKey();
42 | }
43 |
44 | getListViewContainer() {
45 |
46 | if (widget.listData.isEmpty){ /// 没有数据时显示loading状态
47 | return Center(child: CupertinoActivityIndicator(),);
48 | }
49 |
50 | return EasyRefresh(
51 | key: easyRefreshKey,
52 | refreshHeader: getBallHeader(),
53 | refreshFooter: getBallFotter(),
54 | /// 下拉刷新
55 | onRefresh: widget.onRefresh,
56 | loadMore: widget.loadMore,/// 上拉加载
57 | behavior: ScrollOverBehavior(),
58 | autoLoad: true,
59 | child: widget.child,
60 | );
61 | }
62 |
63 | getBallHeader(){
64 | return BallPulseHeader(
65 | key: headerKey,
66 | );
67 | }
68 |
69 | getBallFotter() {
70 | return BallPulseFooter(
71 | key: footerKey,
72 | );
73 | }
74 |
75 | getCustomHeader(){
76 | var now = DateTime.now();
77 | var formatter = '${now.hour.toString()}:${now.minute.toString()}:${now.second.toString()}';
78 | return ClassicsHeader(
79 | key: headerKey,
80 | refreshText: '下拉刷新',
81 | refreshReadyText: '松手刷新',
82 | refreshingText: '正在刷新',
83 | refreshedText: '刷新结束',
84 | moreInfo: '更新时间: ${formatter}',
85 | bgColor: Colors.transparent,
86 | textColor: Colors.black87,
87 | moreInfoColor: Colors.black54,
88 | showMore: true,
89 | );
90 | }
91 |
92 | getCustomFooter(){
93 | var now = DateTime.now();
94 | var formatter = '${now.hour.toString()}:${now.minute.toString()}:${now.second.toString()}';
95 | return ClassicsFooter(
96 | key: footerKey,
97 | loadText: '上拉刷新',
98 | loadReadyText: '松手刷新',
99 | loadingText: '正在加载...',
100 | loadedText: '加载完成',
101 | noMoreText: '加载完成',
102 | moreInfo: '更新时间: ${formatter}',
103 | bgColor: Colors.transparent,
104 | textColor: Colors.black87,
105 | moreInfoColor: Colors.black54,
106 | showMore: true,
107 | );
108 | }
109 | }
--------------------------------------------------------------------------------
/lib/custom/CLPhotoView.dart:
--------------------------------------------------------------------------------
1 | // import 'package:flutter/material.dart';
2 | // import 'dart:typed_data';
3 | // import 'package:photo_manager/photo_manager.dart';
4 |
5 |
6 | // /// 定义回调类型
7 | // typedef Future OnDeleteItem(List list);
8 |
9 | // class AssetImageWidget extends StatelessWidget {
10 | // final AssetEntity assetEntity;
11 | // final double width;
12 | // final double height;
13 | // final BoxFit boxFit;
14 |
15 | // const AssetImageWidget({
16 | // Key key,
17 | // // @required this.assetEntity,
18 | // this.width,
19 | // this.height,
20 | // this.boxFit,
21 | // }) : super(key: key);
22 |
23 | // @override
24 | // Widget build(BuildContext context) {
25 | // if (assetEntity == null) {
26 | // return _buildContainer();
27 | // }
28 | // return FutureBuilder(
29 | // builder: (c, s) {
30 | // if (!s.hasData) {
31 | // return Container();
32 | // }
33 | // var size = s.data;
34 | // return FutureBuilder(
35 | // builder: (BuildContext context, snapshot) {
36 | // if (snapshot.hasData) {
37 | // return _buildContainer(
38 | // child: Image.memory(
39 | // snapshot.data,
40 | // width: width,
41 | // height: height,
42 | // fit: boxFit,
43 | // ),
44 | // );
45 | // } else {
46 | // return _buildContainer();
47 | // }
48 | // },
49 | // future: assetEntity.thumbDataWithSize(
50 | // size.width.toInt(),
51 | // size.height.toInt(),
52 | // ),
53 | // );
54 | // },
55 | // future: assetEntity.size,
56 | // );
57 | // }
58 |
59 | // Widget _buildContainer({Widget child}) {
60 | // child ??= Container(width: width,height: height,);
61 | // return Container(
62 | // width: width,
63 | // height: height,
64 | // child: child,
65 | // );
66 | // }
67 | // }
68 |
69 | // class CLPhotoView extends StatefulWidget {
70 | // final List list;
71 | // final OnDeleteItem onDeleteItem;
72 |
73 | // CLPhotoView({
74 | // Key key,
75 | // @required this.list,
76 | // this.onDeleteItem,
77 | // }) : super(key: key);
78 |
79 | // _CLPhotoViewState createState() => _CLPhotoViewState();
80 | // }
81 |
82 | // class _CLPhotoViewState extends State {
83 | // double spacing = 10;
84 | // @override
85 | // Widget build(BuildContext context) {
86 | // return Wrap(
87 | // spacing: spacing,
88 | // runSpacing: spacing,
89 | // alignment: WrapAlignment.start,
90 | // children: widget.list
91 | // .map((item) => Stack(
92 | // children: [
93 | // AssetImageWidget(
94 | // assetEntity: item,
95 | // width: (MediaQuery.of(context).size.width - (4 * spacing) - (2 * spacing)) / 5,
96 | // height: 60,
97 | // boxFit: BoxFit.cover,
98 | // ),
99 | // Positioned(
100 | // right: -8,
101 | // top: -3,
102 | // child: IconButton(
103 | // icon: Icon(Icons.delete_forever,color: Colors.red,size: 20,),
104 | // iconSize: 25,
105 | // alignment: Alignment.topRight,
106 | // onPressed: (){
107 | // setState(() {
108 | // widget.list.remove(item);
109 | // widget.onDeleteItem(widget.list);
110 | // });
111 | // },),
112 | // )
113 | // ],
114 | // ))
115 | // .toList(),
116 | // );
117 | // }
118 | // }
--------------------------------------------------------------------------------
/lib/custom/CLText.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CLText extends StatelessWidget {
4 |
5 | final String text;
6 | int maxLines;
7 | final TextStyle style;
8 | final TextAlign textAlign;
9 | final TextDirection textDirection;
10 | final TextOverflow overflow;
11 |
12 | CLText({
13 | Key key,
14 | @required this.text,
15 | this.maxLines = 1,
16 | this.textAlign = TextAlign.left,
17 | this.textDirection,
18 | this.overflow = TextOverflow.ellipsis,
19 | this.style}):super(key:key);
20 |
21 | @override
22 | Text build(BuildContext context) {
23 | return Text(
24 | text,
25 | maxLines: maxLines,
26 | textAlign: textAlign,
27 | textDirection: textDirection,
28 | style: style,
29 | overflow: TextOverflow.ellipsis,
30 | );
31 | }
32 | }
33 | TextStyle setTextStyle({Color textColor, double fontSize, bool fontWeight}) {
34 | return TextStyle(
35 | color: textColor,
36 | fontSize: fontSize,
37 | fontWeight: fontWeight == true ? FontWeight.bold :FontWeight.normal,
38 | );
39 | }
--------------------------------------------------------------------------------
/lib/custom/HUD.dart:
--------------------------------------------------------------------------------
1 | import 'package:xs_progress_hud/xs_progress_hud.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class HUD {
5 |
6 | showHud(BuildContext context) async{
7 | XsProgressHud.show(context);
8 | }
9 |
10 | hideHud(){
11 | XsProgressHud.hide();
12 | // Future.delayed(Duration(milliseconds: 1500)).then((val) {
13 |
14 | // });
15 | }
16 |
17 | Future showMessageHud(BuildContext context,{String content}) async {
18 | XsProgressHud.showMessage(context, content);
19 | }
20 |
21 |
22 | }
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'Moment/CLMomentsPage.dart';
3 | import './Moment/CLMomentsDetailPage.dart';
4 | import './Moment/CLPublishMomentPage.dart';
5 |
6 | void main() => runApp(MyApp());
7 |
8 | class MyApp extends StatelessWidget {
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 |
13 | return MaterialApp(
14 | // initialRoute: "moments",
15 | routes: {
16 | "moments": (BuildContext ctx) => CLMomentsPage(),
17 | "detailPage": (BuildContext ctx) => CLMomentsDetailPage(),
18 | "publishPage": (BuildContext ctx) => CLPublishMomentPage(),
19 | },
20 | color: Colors.white,
21 | home: CLMomentsPage(),
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile
3 | packages:
4 | after_layout:
5 | dependency: transitive
6 | description:
7 | name: after_layout
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "1.0.7"
11 | async:
12 | dependency: transitive
13 | description:
14 | name: async
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "2.0.8"
18 | boolean_selector:
19 | dependency: transitive
20 | description:
21 | name: boolean_selector
22 | url: "https://pub.dartlang.org"
23 | source: hosted
24 | version: "1.0.4"
25 | charcode:
26 | dependency: transitive
27 | description:
28 | name: charcode
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "1.1.2"
32 | collection:
33 | dependency: transitive
34 | description:
35 | name: collection
36 | url: "https://pub.dartlang.org"
37 | source: hosted
38 | version: "1.14.11"
39 | common_utils:
40 | dependency: "direct main"
41 | description:
42 | name: common_utils
43 | url: "https://pub.dartlang.org"
44 | source: hosted
45 | version: "1.1.1"
46 | convert:
47 | dependency: transitive
48 | description:
49 | name: convert
50 | url: "https://pub.dartlang.org"
51 | source: hosted
52 | version: "2.1.1"
53 | cookie_jar:
54 | dependency: transitive
55 | description:
56 | name: cookie_jar
57 | url: "https://pub.dartlang.org"
58 | source: hosted
59 | version: "1.0.0"
60 | crypto:
61 | dependency: transitive
62 | description:
63 | name: crypto
64 | url: "https://pub.dartlang.org"
65 | source: hosted
66 | version: "2.0.6"
67 | cupertino_icons:
68 | dependency: "direct main"
69 | description:
70 | name: cupertino_icons
71 | url: "https://pub.dartlang.org"
72 | source: hosted
73 | version: "0.1.2"
74 | decimal:
75 | dependency: transitive
76 | description:
77 | name: decimal
78 | url: "https://pub.dartlang.org"
79 | source: hosted
80 | version: "0.3.3"
81 | dio:
82 | dependency: "direct main"
83 | description:
84 | name: dio
85 | url: "https://pub.dartlang.org"
86 | source: hosted
87 | version: "2.1.0"
88 | extended_image:
89 | dependency: "direct main"
90 | description:
91 | name: extended_image
92 | url: "https://pub.dartlang.org"
93 | source: hosted
94 | version: "0.2.2"
95 | flutter:
96 | dependency: "direct main"
97 | description: flutter
98 | source: sdk
99 | version: "0.0.0"
100 | flutter_easyrefresh:
101 | dependency: "direct main"
102 | description:
103 | name: flutter_easyrefresh
104 | url: "https://pub.dartlang.org"
105 | source: hosted
106 | version: "1.2.7"
107 | flutter_test:
108 | dependency: "direct dev"
109 | description: flutter
110 | source: sdk
111 | version: "0.0.0"
112 | http:
113 | dependency: transitive
114 | description:
115 | name: http
116 | url: "https://pub.dartlang.org"
117 | source: hosted
118 | version: "0.12.0+1"
119 | http_client_helper:
120 | dependency: transitive
121 | description:
122 | name: http_client_helper
123 | url: "https://pub.dartlang.org"
124 | source: hosted
125 | version: "0.1.9"
126 | http_parser:
127 | dependency: transitive
128 | description:
129 | name: http_parser
130 | url: "https://pub.dartlang.org"
131 | source: hosted
132 | version: "3.1.3"
133 | matcher:
134 | dependency: transitive
135 | description:
136 | name: matcher
137 | url: "https://pub.dartlang.org"
138 | source: hosted
139 | version: "0.12.3+1"
140 | meta:
141 | dependency: transitive
142 | description:
143 | name: meta
144 | url: "https://pub.dartlang.org"
145 | source: hosted
146 | version: "1.1.6"
147 | path:
148 | dependency: transitive
149 | description:
150 | name: path
151 | url: "https://pub.dartlang.org"
152 | source: hosted
153 | version: "1.6.2"
154 | path_provider:
155 | dependency: transitive
156 | description:
157 | name: path_provider
158 | url: "https://pub.dartlang.org"
159 | source: hosted
160 | version: "0.5.0+1"
161 | pedantic:
162 | dependency: transitive
163 | description:
164 | name: pedantic
165 | url: "https://pub.dartlang.org"
166 | source: hosted
167 | version: "1.4.0"
168 | photo_view:
169 | dependency: "direct main"
170 | description:
171 | name: photo_view
172 | url: "https://pub.dartlang.org"
173 | source: hosted
174 | version: "0.2.2"
175 | quiver:
176 | dependency: transitive
177 | description:
178 | name: quiver
179 | url: "https://pub.dartlang.org"
180 | source: hosted
181 | version: "2.0.1"
182 | rational:
183 | dependency: transitive
184 | description:
185 | name: rational
186 | url: "https://pub.dartlang.org"
187 | source: hosted
188 | version: "0.3.2"
189 | sky_engine:
190 | dependency: transitive
191 | description: flutter
192 | source: sdk
193 | version: "0.0.99"
194 | source_span:
195 | dependency: transitive
196 | description:
197 | name: source_span
198 | url: "https://pub.dartlang.org"
199 | source: hosted
200 | version: "1.5.4"
201 | stack_trace:
202 | dependency: transitive
203 | description:
204 | name: stack_trace
205 | url: "https://pub.dartlang.org"
206 | source: hosted
207 | version: "1.9.3"
208 | stream_channel:
209 | dependency: transitive
210 | description:
211 | name: stream_channel
212 | url: "https://pub.dartlang.org"
213 | source: hosted
214 | version: "1.6.8"
215 | string_scanner:
216 | dependency: transitive
217 | description:
218 | name: string_scanner
219 | url: "https://pub.dartlang.org"
220 | source: hosted
221 | version: "1.0.4"
222 | term_glyph:
223 | dependency: transitive
224 | description:
225 | name: term_glyph
226 | url: "https://pub.dartlang.org"
227 | source: hosted
228 | version: "1.1.0"
229 | test_api:
230 | dependency: transitive
231 | description:
232 | name: test_api
233 | url: "https://pub.dartlang.org"
234 | source: hosted
235 | version: "0.2.2"
236 | transparent_image:
237 | dependency: transitive
238 | description:
239 | name: transparent_image
240 | url: "https://pub.dartlang.org"
241 | source: hosted
242 | version: "0.1.0"
243 | typed_data:
244 | dependency: transitive
245 | description:
246 | name: typed_data
247 | url: "https://pub.dartlang.org"
248 | source: hosted
249 | version: "1.1.6"
250 | vector_math:
251 | dependency: transitive
252 | description:
253 | name: vector_math
254 | url: "https://pub.dartlang.org"
255 | source: hosted
256 | version: "2.0.8"
257 | xs_progress_hud:
258 | dependency: "direct main"
259 | description:
260 | name: xs_progress_hud
261 | url: "https://pub.dartlang.org"
262 | source: hosted
263 | version: "1.0.2"
264 | zoomable_image:
265 | dependency: "direct main"
266 | description:
267 | name: zoomable_image
268 | url: "https://pub.dartlang.org"
269 | source: hosted
270 | version: "1.3.1"
271 | sdks:
272 | dart: ">=2.1.0 <3.0.0"
273 | flutter: ">=0.1.4 <2.0.0"
274 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_hybrid
2 | description: A new flutter module project.
3 |
4 | # The following defines the version and build number for your application.
5 | # A version number is three numbers separated by dots, like 1.2.43
6 | # followed by an optional build number separated by a +.
7 | # Both the version and the builder number may be overridden in flutter
8 | # build by specifying --build-name and --build-number, respectively.
9 | # In Android, build-name is used as versionName while build-number used as versionCode.
10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
12 | # Read more about iOS versioning at
13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
14 | #
15 | # This version is used _only_ for the Runner app, which is used if you just do
16 | # a `flutter run` or a `flutter make-host-app-editable`. It has no impact
17 | # on any other native host app that you embed your Flutter project into.
18 | version: 1.0.0+1
19 |
20 | environment:
21 | sdk: ">=2.1.0 <3.0.0"
22 |
23 | dependencies:
24 | flutter:
25 | sdk: flutter
26 |
27 | dio: ^2.1.0
28 | extended_image: ^0.2.2
29 | flutter_easyrefresh: ^1.2.7
30 | # fluttertoast: ^3.0.3
31 | common_utils: ^1.1.1
32 | zoomable_image: ^1.3.1
33 | photo_view: ^0.2.2
34 | # photo: ^0.3.3 # 相册选择
35 | xs_progress_hud: ^1.0.2
36 |
37 | # The following adds the Cupertino Icons font to your application.
38 | # Use with the CupertinoIcons class for iOS style icons.
39 | cupertino_icons: ^0.1.2
40 |
41 | dev_dependencies:
42 | flutter_test:
43 | sdk: flutter
44 |
45 | # For information on the generic Dart part of this file, see the
46 | # following page: https://www.dartlang.org/tools/pub/pubspec
47 |
48 | flutter:
49 | # The following line ensures that the Material Icons font is
50 | # included with your application, so that you can use the icons in
51 | # the material Icons class.
52 | uses-material-design: true
53 |
54 | # To add Flutter specific assets to your application, add an assets section,
55 | # like this:
56 | # assets:
57 | # - images/a_dot_burr.jpeg
58 | # - images/a_dot_ham.jpeg
59 |
60 | # An image asset can refer to one or more resolution-specific "variants", see
61 | # https://flutter.io/assets-and-images/#resolution-aware.
62 |
63 | # For details regarding adding assets from package dependencies, see
64 | # https://flutter.io/assets-and-images/#from-packages
65 |
66 | # To add Flutter specific custom fonts to your application, add a fonts
67 | # section here, in this "flutter" section. Each entry in this list should
68 | # have a "family" key with the font family name, and a "fonts" key with a
69 | # list giving the asset and other descriptors for the font. For
70 | # example:
71 | # fonts:
72 | # - family: Schyler
73 | # fonts:
74 | # - asset: fonts/Schyler-Regular.ttf
75 | # - asset: fonts/Schyler-Italic.ttf
76 | # style: italic
77 | # - family: Trajan Pro
78 | # fonts:
79 | # - asset: fonts/TrajanPro.ttf
80 | # - asset: fonts/TrajanPro_Bold.ttf
81 | # weight: 700
82 | #
83 | # For details regarding fonts from package dependencies,
84 | # see https://flutter.io/custom-fonts/#from-packages
85 |
86 |
87 | # This section identifies your Flutter project as a module meant for
88 | # embedding in a native host app. These identifiers should _not_ ordinarily
89 | # be changed after generation - they are used to ensure that the tooling can
90 | # maintain consistency when adding or modifying assets and plugins.
91 | # They also do not have any bearing on your native host application's
92 | # identifiers, which may be completely independent or the same as these.
93 | module:
94 | androidPackage: com.example.flutter_hybrid
95 | iosBundleIdentifier: com.example.flutterHybrid
96 |
--------------------------------------------------------------------------------
/sources.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cleven1/Flutter_Moments/fe4133e5efb5392ed6fbf90bb63818d38efacb46/sources.gif
--------------------------------------------------------------------------------
/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_hybrid/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 |
--------------------------------------------------------------------------------