From 27b3af8f05ce5a1807ebb543cf6c9865c4725743 Mon Sep 17 00:00:00 2001 From: Martin Trollip <13981100+martintrollip@users.noreply.github.com> Date: Wed, 7 Jun 2023 12:41:31 +0200 Subject: [PATCH] Support message attributes when sending and receiving messages --- .../flutter/twilio_conversations/Mapper.kt | 32 ++++- .../lib/redux/effects/twilio_middleware.dart | 6 +- example/lib/widgets/message_item.dart | 3 +- ios/Classes/Methods/MessagesMethods.swift | 6 + lib/src/attributes.dart | 6 +- test/attributes_test.dart | 133 ++++++++++++++++++ 6 files changed, 176 insertions(+), 10 deletions(-) create mode 100644 test/attributes_test.dart diff --git a/android/src/main/kotlin/twilio/flutter/twilio_conversations/Mapper.kt b/android/src/main/kotlin/twilio/flutter/twilio_conversations/Mapper.kt index 0cea174..68991a6 100644 --- a/android/src/main/kotlin/twilio/flutter/twilio_conversations/Mapper.kt +++ b/android/src/main/kotlin/twilio/flutter/twilio_conversations/Mapper.kt @@ -92,11 +92,33 @@ object Mapper { ) } - fun attributesToMap(attributes: Attributes): Map { - return mapOf( - "type" to "name.string", - "data" to "User" - ) + fun attributesToMap(attributes: Attributes): Map { + return when { + attributes.boolean != null -> mapOf( + "type" to "boolean", + "data" to attributes.boolean!!, + ) + attributes.number != null -> mapOf( + "type" to "number", + "data" to attributes.number!!, + ) + attributes.string != null -> mapOf( + "type" to "string", + "data" to attributes.string!!, + ) + attributes.jsonArray != null -> mapOf( + "type" to "array", + "data" to "${attributes.jsonArray}", + ) + attributes.jsonObject != null -> mapOf( + "type" to "object", + "data" to "${attributes.jsonObject}", + ) + else -> mapOf( + "type" to "null", + "data" to null, + ) + } } private fun channelsToMap(channels: List): Map { diff --git a/example/lib/redux/effects/twilio_middleware.dart b/example/lib/redux/effects/twilio_middleware.dart index 8f7dd05..3a7bff8 100644 --- a/example/lib/redux/effects/twilio_middleware.dart +++ b/example/lib/redux/effects/twilio_middleware.dart @@ -47,8 +47,10 @@ class TwilioMiddleware extends EpicMiddleware { ) => stream.asyncExpand((action) async* { try { - final request = await action.channel.messages - ?.sendMessage(MessageOptions()..withBody(action.text)); + final request = + await action.channel.messages?.sendMessage(MessageOptions() + ..withBody(action.text) + ..withAttributes({'customKey': 'customValue'})); if (request != null) { print( diff --git a/example/lib/widgets/message_item.dart b/example/lib/widgets/message_item.dart index 9e71fd9..c9c0784 100644 --- a/example/lib/widgets/message_item.dart +++ b/example/lib/widgets/message_item.dart @@ -31,7 +31,8 @@ class MessageItem extends StatelessWidget { padding: const EdgeInsets.only( bottom: 6, ), - child: Text('${message.author} @ ${message.dateCreated}'), + child: Text( + '${message.author} @ ${message.dateCreated} with ${message.attributes?.getJSONObject()}}'), ), Container( decoration: BoxDecoration( diff --git a/ios/Classes/Methods/MessagesMethods.swift b/ios/Classes/Methods/MessagesMethods.swift index f059cda..9354fca 100644 --- a/ios/Classes/Methods/MessagesMethods.swift +++ b/ios/Classes/Methods/MessagesMethods.swift @@ -24,6 +24,12 @@ public class MessagesMethods { if (options["body"] != nil) { messagePreparator.setBody(options["body"] as? String) } + + if (options["attributes"] as? [String: Any?] != nil) { + let attributesDict = options["attributes"] as? [String: Any?] + let attributes = TCHJsonAttributes.init(dictionary: attributesDict! as [AnyHashable: Any]) + messagePreparator.setAttributes(attributes, error: nil) + } if (options["input"] != nil && options["mimeType"] as? String != nil) { let input = options["input"] as? String diff --git a/lib/src/attributes.dart b/lib/src/attributes.dart index 6b964ed..f411970 100644 --- a/lib/src/attributes.dart +++ b/lib/src/attributes.dart @@ -13,8 +13,10 @@ class Attributes { Attributes(this._type, this._json); factory Attributes.fromMap(Map map) { - var type = AttributesType.STRING; - return Attributes(type, 'DATA'); + final type = + EnumToString.fromString(AttributesType.values, map['type'] ?? 'null') ?? + AttributesType.NULL; + return Attributes(type, map['data'] ?? ''); } Map? getJSONObject() { diff --git a/test/attributes_test.dart b/test/attributes_test.dart new file mode 100644 index 0000000..c69b963 --- /dev/null +++ b/test/attributes_test.dart @@ -0,0 +1,133 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_twilio_conversations/flutter_twilio_conversations.dart'; + +void main() { + setUpAll(() {}); + + group('.fromMap()', () { + const objectExample = { + 'type': 'object', + 'data': '{"key": "value"}', + }; + + const arrayExample = { + 'type': 'array', + 'data': '[{"key": "value"}, {"key1": "value1"}]', + }; + + const stringExample = { + 'type': 'string', + 'data': 'string', + }; + + const numberExample = { + 'type': 'number', + 'data': '123', + }; + + const booleanExample = { + 'type': 'boolean', + 'data': 'true', + }; + + const nullExample = { + 'type': 'null', + 'data': 'null', + }; + + const invalidExample = { + 'invalid': 'invalid', + }; + + const invalidObjectExample = { + 'type': 'object', + 'data': 'not a json', + }; + + test('should construct from OBJECT', () async { + final object = Attributes.fromMap(objectExample); + expect(object.type, AttributesType.OBJECT); + expect(object.getBoolean(), isNull); + expect(object.getNumber(), isNull); + expect(object.getString(), isNull); + expect(object.getJSONArray(), isNull); + expect(object.getJSONObject(), isNotNull); + expect(object.getJSONObject(), {"key": "value"}); + }); + + test('should construct from ARRAY', () async { + final array = Attributes.fromMap(arrayExample); + expect(array.type, AttributesType.ARRAY); + expect(array.getJSONObject(), isNull); + expect(array.getBoolean(), isNull); + expect(array.getNumber(), isNull); + expect(array.getString(), isNull); + expect(array.getJSONArray(), isNotNull); + expect(array.getJSONArray(), [ + {"key": "value"}, + {"key1": "value1"} + ]); + }); + + test('should construct from STRING', () async { + final string = Attributes.fromMap(stringExample); + expect(string.type, AttributesType.STRING); + expect(string.getBoolean(), isNull); + expect(string.getNumber(), isNull); + expect(string.getJSONArray(), isNull); + expect(string.getJSONObject(), isNull); + expect(string.getString(), isNotNull); + expect(string.getString(), "string"); + }); + + test('should construct from NUMBER', () async { + final number = Attributes.fromMap(numberExample); + expect(number.type, AttributesType.NUMBER); + expect(number.getBoolean(), isNull); + expect(number.getString(), isNull); + expect(number.getJSONArray(), isNull); + expect(number.getJSONObject(), isNull); + expect(number.getNumber(), isNotNull); + expect(number.getNumber(), 123); + }); + + test('should construct from BOOLEAN', () async { + final boolean = Attributes.fromMap(booleanExample); + expect(boolean.type, AttributesType.BOOLEAN); + expect(boolean.getNumber(), isNull); + expect(boolean.getString(), isNull); + expect(boolean.getJSONArray(), isNull); + expect(boolean.getJSONObject(), isNull); + expect(boolean.getBoolean(), isNotNull); + expect(boolean.getBoolean(), true); + }); + + test('should construct from NULL', () async { + final nullAttr = Attributes.fromMap(nullExample); + expect(nullAttr.type, AttributesType.NULL); + expect(nullAttr.getBoolean(), isNull); + expect(nullAttr.getNumber(), isNull); + expect(nullAttr.getString(), isNull); + expect(nullAttr.getJSONArray(), isNull); + expect(nullAttr.getJSONObject(), isNull); + }); + + test('should construct from invalid', () async { + final invalidAttr = Attributes.fromMap(invalidExample); + expect(invalidAttr.type, AttributesType.NULL); + expect(invalidAttr.getBoolean(), isNull); + expect(invalidAttr.getNumber(), isNull); + expect(invalidAttr.getString(), isNull); + expect(invalidAttr.getJSONArray(), isNull); + expect(invalidAttr.getJSONObject(), isNull); + + final invalidObject = Attributes.fromMap(invalidObjectExample); + expect(invalidObject.type, AttributesType.OBJECT); + expect(invalidObject.getBoolean(), isNull); + expect(invalidObject.getNumber(), isNull); + expect(invalidObject.getString(), isNull); + expect(invalidObject.getJSONArray(), isNull); + expect(invalidObject.getJSONObject, throwsException); + }); + }); +}