diff --git a/lib/helpers/api_helper.dart b/lib/helpers/api_helper.dart index a1b015c..89698d3 100644 --- a/lib/helpers/api_helper.dart +++ b/lib/helpers/api_helper.dart @@ -1,11 +1,10 @@ -import 'dart:convert'; import 'dart:io'; import 'package:comunic/helpers/account_credentials_helper.dart'; import 'package:comunic/models/api_request.dart'; import 'package:comunic/models/api_response.dart'; import 'package:comunic/models/config.dart'; -import 'package:http/http.dart' as http; +import 'package:dio/dio.dart'; /// API Helper /// @@ -16,7 +15,7 @@ class APIHelper { /// /// This method should never throw but the response code of the [APIResponse] /// should be verified before accessing response content - Future exec(APIRequest request) async { + Future exec(APIRequest request, {bool multipart = false}) async { try { //Add API tokens request.addString("serviceName", config().serviceName); @@ -38,20 +37,39 @@ class APIHelper { else url = Uri.https(config().apiServerName, path); - final response = await http.post( + final data = FormData.from(request.args); + + // Process files (if required) + if (multipart) + request.files.forEach( + (k, v) => data.add(k, UploadFileInfo(v, v.path.split("/").last))); + + // Execute the request + final response = await Dio().post( url.toString(), - body: request.args, - encoding: utf8 + data: data, + options: Options( + receiveDataWhenStatusError: true, + validateStatus: (s) => true, + responseType: ResponseType.plain, + ), ); if (response.statusCode != HttpStatus.ok) return APIResponse(response.statusCode, null); - return APIResponse(response.statusCode, utf8.decode(response.bodyBytes)); + return APIResponse(response.statusCode, response.data); } on Exception catch (e) { print(e.toString()); print("Could not execute a request!"); return APIResponse(-1, null); } } + + /// Same as exec, but also allows to send files + /// + /// Warning ! Currently the response body to such requests is always null ! + Future execWithFiles(APIRequest request) async { + return await exec(request, multipart: true); + } } diff --git a/lib/helpers/conversations_helper.dart b/lib/helpers/conversations_helper.dart index 91b392b..5e7429a 100644 --- a/lib/helpers/conversations_helper.dart +++ b/lib/helpers/conversations_helper.dart @@ -4,6 +4,7 @@ import 'package:comunic/lists/conversation_messages_list.dart'; import 'package:comunic/lists/conversations_list.dart'; import 'package:comunic/lists/users_list.dart'; import 'package:comunic/models/api_request.dart'; +import 'package:comunic/models/api_response.dart'; import 'package:comunic/models/conversation.dart'; import 'package:comunic/models/conversation_message.dart'; import 'package:comunic/models/new_conversation_message.dart'; @@ -162,14 +163,25 @@ class ConversationsHelper { /// Send a new message to the server Future sendMessage(NewConversationMessage message) async { - final response = await APIRequest( + final request = APIRequest( uri: "conversations/sendMessage", needLogin: true, args: { "conversationID": message.conversationID.toString(), - "message": message.message + "message": message.hasMessage ? message.message : "" }, - ).exec(); + ); + + //Check for image + if(message.hasImage) + request.addFile("image", message.image); + + //Send the message + APIResponse response; + if(!message.hasImage) + response = await request.exec(); + else + response = await request.execWithFiles(); if(response.code == 401) return SendMessageResult.MESSAGE_REJECTED; diff --git a/lib/models/api_request.dart b/lib/models/api_request.dart index 3704574..0cc9c15 100644 --- a/lib/models/api_request.dart +++ b/lib/models/api_request.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:comunic/helpers/api_helper.dart'; import 'package:comunic/models/api_response.dart'; import 'package:meta/meta.dart'; @@ -12,6 +14,7 @@ class APIRequest { final String uri; final bool needLogin; Map args; + Map files = Map(); APIRequest({@required this.uri, this.needLogin = false, this.args}) : assert(uri != null), @@ -26,6 +29,11 @@ class APIRequest { void addBool(String name, bool value) => args[name] = value ? "true" : "false"; + void addFile(String name, File file) => files[name] = file; + /// Execute the request Future exec() async => APIHelper().exec(this); + + /// Execute the request with files + Future execWithFiles() async => APIHelper().execWithFiles(this); } diff --git a/lib/models/new_conversation_message.dart b/lib/models/new_conversation_message.dart index 8b3f04a..94d94a2 100644 --- a/lib/models/new_conversation_message.dart +++ b/lib/models/new_conversation_message.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:meta/meta.dart'; /// New conversation message model @@ -9,9 +11,15 @@ import 'package:meta/meta.dart'; class NewConversationMessage { final int conversationID; final String message; + final File image; NewConversationMessage({ @required this.conversationID, @required this.message, - }) : assert(conversationID != null); + this.image + }) : assert(conversationID != null), + assert(image != null || message != null); + + bool get hasMessage => message != null; + bool get hasImage => image != null; } diff --git a/lib/ui/screens/conversation_screen.dart b/lib/ui/screens/conversation_screen.dart index 3aaef61..0107063 100644 --- a/lib/ui/screens/conversation_screen.dart +++ b/lib/ui/screens/conversation_screen.dart @@ -86,15 +86,36 @@ class _ConversationScreenState extends State { /// Pick and send an image Future _sendImage(BuildContext context) async { final image = await pickImage(context); + + if (image == null) return null; + + _submitMessage( + context, + NewConversationMessage( + conversationID: widget.conversationID, + message: null, + image: image, + ), + ); } - /// Send a new message - Future _submitMessage(BuildContext context, String content) async { + /// Send a new text message + Future _submitTextMessage(BuildContext context, String content) async { + _submitMessage( + context, + NewConversationMessage( + conversationID: widget.conversationID, + message: content, + ), + ); + } + + /// Submit a new message + Future _submitMessage( + BuildContext context, NewConversationMessage message) async { //Send the message _setSending(true); - final result = await _conversationsHelper.sendMessage( - NewConversationMessage( - conversationID: widget.conversationID, message: content)); + final result = await _conversationsHelper.sendMessage(message); _setSending(false); //Check the result of the operation @@ -148,14 +169,14 @@ class _ConversationScreenState extends State { children: [ // Image area new Container( - margin: new EdgeInsets.symmetric(horizontal: 4.0), - child: new IconButton( - icon: new Icon( - Icons.photo_camera, - color: Theme.of(context).accentColor, - ), - onPressed: () => _sendImage(context)), - ), + margin: new EdgeInsets.symmetric(horizontal: 4.0), + child: new IconButton( + icon: new Icon( + Icons.photo_camera, + color: Theme.of(context).accentColor, + ), + onPressed: () => _sendImage(context)), + ), // Message area new Flexible( @@ -166,8 +187,9 @@ class _ConversationScreenState extends State { _isMessageValid = messageText.length > 4; }); }, - onSubmitted: - _isMessageValid ? (s) => _submitMessage(context, s) : null, + onSubmitted: _isMessageValid + ? (s) => _submitTextMessage(context, s) + : null, decoration: new InputDecoration.collapsed(hintText: tr("Send a message")), ), @@ -184,7 +206,8 @@ class _ConversationScreenState extends State { : Theme.of(context).disabledColor, ), onPressed: !_isSendingMessage && _isMessageValid - ? () => _submitMessage(context, _textEditingController.text) + ? () => + _submitTextMessage(context, _textEditingController.text) : null, ), ), diff --git a/pubspec.lock b/pubspec.lock index a7f15b2..acf32ab 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,6 +29,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" + cookie_jar: + dependency: transitive + description: + name: cookie_jar + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" cupertino_icons: dependency: "direct main" description: @@ -36,6 +43,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.2" + dio: + dependency: "direct main" + description: + name: dio + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" flutter: dependency: "direct main" description: flutter @@ -46,20 +60,6 @@ packages: description: flutter source: sdk version: "0.0.0" - http: - dependency: "direct main" - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.0+2" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.3" image_picker: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 6bce70c..9488f30 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,7 +34,7 @@ dependencies: image_picker: ^0.5.4+3 # The HTTP client is used to make requests on the Comunic API - http: ^0.12.0+2 + dio: ^2.1.2 dev_dependencies: flutter_test: