PublishTweetPage.dart 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'package:async/async.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter_osc/model/api.dart';
  7. import 'package:flutter_osc/util/DataUtils.dart';
  8. import 'package:http/http.dart' as http;
  9. import 'package:image_picker/image_picker.dart';
  10. class PublishTweetPage extends StatefulWidget {
  11. @override
  12. State<StatefulWidget> createState() {
  13. return PublishTweetPageState();
  14. }
  15. }
  16. class PublishTweetPageState extends State<PublishTweetPage> {
  17. TextEditingController _controller = TextEditingController();
  18. List<File> fileList = [];
  19. Future<XFile?>? _imageFile;
  20. bool isLoading = false;
  21. String msg = "";
  22. Widget getBody() {
  23. var textField = TextField(
  24. decoration: InputDecoration(
  25. hintText: "说点什么吧~",
  26. hintStyle: TextStyle(color: const Color(0xFF808080)),
  27. border: OutlineInputBorder(
  28. borderRadius:
  29. const BorderRadius.all(const Radius.circular(10.0)))),
  30. maxLines: 6,
  31. maxLength: 150,
  32. controller: _controller,
  33. );
  34. var gridView = Builder(
  35. builder: (ctx) {
  36. return GridView.count(
  37. crossAxisCount: 4,
  38. children: List.generate(fileList.length + 1, (index) {
  39. var content;
  40. if (index == 0) {
  41. // 添加图片按钮
  42. var addCell = Center(
  43. child: Image.asset(
  44. './images/ic_add_pics.png',
  45. width: 80.0,
  46. height: 80.0,
  47. ));
  48. content = GestureDetector(
  49. onTap: () {
  50. // 添加图片
  51. pickImage(ctx);
  52. },
  53. child: addCell,
  54. );
  55. } else {
  56. // 被选中的图片
  57. content = Center(
  58. child: Image.file(
  59. fileList[index - 1],
  60. width: 80.0,
  61. height: 80.0,
  62. fit: BoxFit.cover,
  63. ));
  64. }
  65. return Container(
  66. margin: const EdgeInsets.all(2.0),
  67. width: 80.0,
  68. height: 80.0,
  69. color: const Color(0xFFECECEC),
  70. child: content,
  71. );
  72. }),
  73. );
  74. },
  75. );
  76. var children = [
  77. Text(
  78. "提示:由于OSC的openapi限制,发布动弹的接口只支持上传一张图片,本项目可添加最多9张图片,但OSC只会接收最后一张图片。",
  79. style: TextStyle(fontSize: 12.0),
  80. ),
  81. textField,
  82. Container(
  83. margin: const EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0),
  84. height: 200.0,
  85. child: gridView)
  86. ];
  87. if (isLoading) {
  88. children.add(Container(
  89. margin: const EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0),
  90. child: Center(
  91. child: CircularProgressIndicator(),
  92. ),
  93. ));
  94. } else {
  95. children.add(Container(
  96. margin: const EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0),
  97. child: Center(
  98. child: Text(msg),
  99. )));
  100. }
  101. return Container(
  102. padding: const EdgeInsets.all(5.0),
  103. child: Column(
  104. children: children,
  105. ),
  106. );
  107. }
  108. // 相机拍照或者从图库选择图片
  109. pickImage(ctx) {
  110. // 如果已添加了9张图片,则提示不允许添加更多
  111. num size = fileList.length;
  112. if (size >= 9) {
  113. ScaffoldMessenger.of(ctx).showSnackBar(SnackBar(
  114. content: Text("最多只能添加9张图片!"),
  115. ));
  116. return;
  117. }
  118. showModalBottomSheet<void>(context: context, builder: _bottomSheetBuilder);
  119. }
  120. Widget _bottomSheetBuilder(BuildContext context) {
  121. return Container(
  122. height: 182.0,
  123. child: Padding(
  124. padding: const EdgeInsets.fromLTRB(0.0, 30.0, 0.0, 30.0),
  125. child: Column(
  126. children: [
  127. _renderBottomMenuItem("相机拍照", ImageSource.camera),
  128. Divider(
  129. height: 2.0,
  130. ),
  131. _renderBottomMenuItem("图库选择照片", ImageSource.gallery)
  132. ],
  133. ),
  134. ));
  135. }
  136. _renderBottomMenuItem(title, ImageSource source) {
  137. var item = Container(
  138. height: 60.0,
  139. child: Center(child: Text(title)),
  140. );
  141. return InkWell(
  142. child: item,
  143. onTap: () {
  144. Navigator.of(context).pop();
  145. setState(() {
  146. _imageFile = ImagePicker().pickImage(source: source);
  147. });
  148. },
  149. );
  150. }
  151. sendTweet(ctx, token) async {
  152. if (token == null) {
  153. ScaffoldMessenger.of(ctx).showSnackBar(SnackBar(
  154. content: Text("未登录!"),
  155. ));
  156. return;
  157. }
  158. String content = _controller.text;
  159. if (content == null || content.length == 0 || content.trim().length == 0) {
  160. ScaffoldMessenger.of(ctx).showSnackBar(SnackBar(
  161. content: Text("请输入动弹内容!"),
  162. ));
  163. }
  164. try {
  165. Map<String, String> params = Map();
  166. params['msg'] = content;
  167. params['access_token'] = token;
  168. var request = http.MultipartRequest('POST', Uri.parse(Api.PUB_TWEET));
  169. request.fields.addAll(params);
  170. if (fileList != null && fileList.length > 0) {
  171. for (File f in fileList) {
  172. var stream = http.ByteStream(DelegatingStream.typed(f.openRead()));
  173. var length = await f.length();
  174. var filename = f.path.substring(f.path.lastIndexOf("/") + 1);
  175. request.files.add(
  176. http.MultipartFile('img', stream, length, filename: filename));
  177. }
  178. }
  179. setState(() {
  180. isLoading = true;
  181. });
  182. var response = await request.send();
  183. response.stream.transform(utf8.decoder).listen((value) {
  184. print(value);
  185. if (value != null) {
  186. var obj = json.decode(value);
  187. var error = obj['error'];
  188. setState(() {
  189. if (error != null && error == '200') {
  190. // 成功
  191. setState(() {
  192. isLoading = false;
  193. msg = "发布成功";
  194. fileList.clear();
  195. });
  196. _controller.clear();
  197. } else {
  198. setState(() {
  199. isLoading = false;
  200. msg = "发布失败:$error";
  201. });
  202. }
  203. });
  204. }
  205. });
  206. } catch (exception) {
  207. print(exception);
  208. }
  209. }
  210. @override
  211. Widget build(BuildContext context) {
  212. return Scaffold(
  213. appBar: AppBar(
  214. title: Text("发布动弹", style: TextStyle(color: Colors.white)),
  215. iconTheme: IconThemeData(color: Colors.white),
  216. actions: <Widget>[
  217. Builder(
  218. builder: (ctx) {
  219. return IconButton(
  220. icon: Icon(Icons.send),
  221. onPressed: () {
  222. // 发送动弹
  223. DataUtils.isLogin().then((isLogin) {
  224. if (isLogin) {
  225. return DataUtils.getAccessToken();
  226. } else {
  227. return null;
  228. }
  229. }).then((token) {
  230. sendTweet(ctx, token);
  231. });
  232. });
  233. },
  234. )
  235. ],
  236. ),
  237. body: FutureBuilder(
  238. future: _imageFile,
  239. builder: (BuildContext context, AsyncSnapshot<XFile?> snapshot) {
  240. if (snapshot.connectionState == ConnectionState.done &&
  241. snapshot.data != null &&
  242. _imageFile != null) {
  243. fileList.add(File(snapshot.data!.path));
  244. _imageFile = null;
  245. }
  246. return getBody();
  247. },
  248. ),
  249. );
  250. }
  251. }