/**
*
*/
package net.frontlinesms.ui.handler.message;
// TODO Remove static imports
import static net.frontlinesms.FrontlineSMSConstants.MESSAGE_NO_CONTACT_SELECTED;
import static net.frontlinesms.ui.UiGeneratorControllerConstants.COMPONENT_LB_COST;
import static net.frontlinesms.ui.UiGeneratorControllerConstants.COMPONENT_LB_FIRST;
import static net.frontlinesms.ui.UiGeneratorControllerConstants.COMPONENT_LB_HELP;
import static net.frontlinesms.ui.UiGeneratorControllerConstants.COMPONENT_LB_MSG_NUMBER;
import static net.frontlinesms.ui.UiGeneratorControllerConstants.COMPONENT_LB_REMAINING_CHARS;
import static net.frontlinesms.ui.UiGeneratorControllerConstants.COMPONENT_LB_SECOND;
import static net.frontlinesms.ui.UiGeneratorControllerConstants.COMPONENT_LB_THIRD;
import static net.frontlinesms.ui.UiGeneratorControllerConstants.COMPONENT_LB_TOO_MANY_MESSAGES;
import static net.frontlinesms.ui.UiGeneratorControllerConstants.COMPONENT_TF_MESSAGE;
import static net.frontlinesms.ui.UiGeneratorControllerConstants.COMPONENT_TF_RECIPIENT;
import java.awt.Color;
import java.util.List;
import java.util.regex.Pattern;
import net.frontlinesms.AppProperties;
import net.frontlinesms.FrontlineSMSConstants;
import net.frontlinesms.FrontlineUtils;
import net.frontlinesms.data.domain.Contact;
import net.frontlinesms.data.domain.FrontlineMessage;
import net.frontlinesms.data.domain.Group;
import net.frontlinesms.ui.Icon;
import net.frontlinesms.ui.ThinletUiEventHandler;
import net.frontlinesms.ui.UiGeneratorController;
import net.frontlinesms.ui.UiGeneratorControllerConstants;
import net.frontlinesms.ui.handler.contacts.ContactSelecter;
import net.frontlinesms.ui.handler.contacts.GroupSelecterDialog;
import net.frontlinesms.ui.handler.contacts.SingleGroupSelecterDialogOwner;
import net.frontlinesms.ui.handler.keyword.BaseActionDialog;
import net.frontlinesms.ui.i18n.InternationalisationUtils;
import org.apache.log4j.Logger;
import org.smslib.util.GsmAlphabet;
/**
* Controller for a panel which allows sending of text SMS messages
* @author Alex
*/
public class MessagePanelHandler implements ThinletUiEventHandler, SingleGroupSelecterDialogOwner {
//> STATIC CONSTANTS
/** UI XML File Path: the panel containing the messaging controls */
private static final String UI_FILE_MESSAGE_PANEL = "/ui/core/messages/pnComposeMessage.xml";
//> THINLET COMPONENTS
/** Thinlet component name: Button to send message */
private static final String COMPONENT_BT_SEND = "btSend";
private static final String COMPONENT_LB_ICON = "lbIcon";
//> INSTANCE PROPERTIES
private final Logger LOG = FrontlineUtils.getLogger(this.getClass());
/** The {@link UiGeneratorController} that shows the tab. */
private final UiGeneratorController uiController;
/** The parent component */
private Object messagePanel;
/** The number of people the current SMS will be sent to */
private int numberToSend = 1;
/** The boolean stipulating whether the recipient field should be displayed */
private boolean shouldDisplayRecipientField;
/** The boolean stipulating whether we should check the length of the message (we don't in the auto-reply, for example) */
private boolean shouldCheckMaxMessageLength;
/** The number of recipients, used to estimate the cost of the message */
private int numberOfRecipients;
//> CONSTRUCTORS
/**
* @param uiController
*/
private MessagePanelHandler(UiGeneratorController uiController, boolean shouldDisplay, boolean shouldCheckMaxMessageLength, int numberOfRecipients) {
this.uiController = uiController;
this.shouldDisplayRecipientField = shouldDisplay;
this.shouldCheckMaxMessageLength = shouldCheckMaxMessageLength;
this.numberOfRecipients = numberOfRecipients;
}
private synchronized void init() {
assert(this.messagePanel == null) : "This has already been initialised.";
this.messagePanel = uiController.loadComponentFromFile(UI_FILE_MESSAGE_PANEL, this);
Object pnRecipient = find(UiGeneratorControllerConstants.COMPONENT_PN_MESSAGE_RECIPIENT);
uiController.setVisible(pnRecipient, shouldDisplayRecipientField);
Object lbTooManyMessages = find(UiGeneratorControllerConstants.COMPONENT_LB_TOO_MANY_MESSAGES);
if (lbTooManyMessages != null) {
uiController.setVisible(lbTooManyMessages, false);
uiController.setColor(lbTooManyMessages, "foreground", Color.RED);
}
updateMessageDetails(find(COMPONENT_TF_RECIPIENT), "");
}
private Object find(String component) {
return this.uiController.find(this.messagePanel, component);
}
//> ACCESSORS
/** @return {@link #messagePanel} */
public Object getPanel() {
return this.messagePanel;
}
private double getCostPerSms() {
return AppProperties.getInstance().getCostPerSmsSent();
}
/**
* Adds a constant substitution marker to the SMS text.
* @param currentText
* @param textArea
* @param type The constant that should be inserted
*/
public void addConstantToCommand(String currentText, Object tfMessage, String type) {
BaseActionDialog.addConstantToCommand(uiController, currentText, tfMessage, type);
updateMessageDetails(find(COMPONENT_TF_RECIPIENT), uiController.getText(tfMessage));
}
//> THINLET UI METHODS
/** Sets the method called by the send button at the bottom of the compose message panel */
public void setSendButtonMethod(ThinletUiEventHandler eventHandler, Object rootComponent, String methodCall) {
Object sendButton = find(COMPONENT_BT_SEND);
uiController.setAction(sendButton, methodCall, rootComponent, eventHandler);
}
/**
* Extract message details from the controls in the panel, and send an SMS.
*/
public void send() {
String recipient = uiController.getText(find(COMPONENT_TF_RECIPIENT));
String message = uiController.getText(find(COMPONENT_TF_MESSAGE));
if (recipient.length() == 0) {
uiController.alert(InternationalisationUtils.getI18nString(FrontlineSMSConstants.MESSAGE_BLANK_PHONE_NUMBER));
return;
}
this.uiController.getFrontlineController().sendTextMessage(recipient, message);
this.clearComponents();
}
private void clearComponents() {
// We clear the components
uiController.setText(find(COMPONENT_TF_RECIPIENT), "");
uiController.setText(find(COMPONENT_TF_MESSAGE), "");
uiController.setText(find(COMPONENT_LB_REMAINING_CHARS), String.valueOf(FrontlineMessage.SMS_LENGTH_LIMIT));
uiController.setText(find(COMPONENT_LB_MSG_NUMBER), "0");
uiController.setIcon(find(COMPONENT_LB_FIRST), Icon.SMS_DISABLED);
uiController.setIcon(find(COMPONENT_LB_SECOND), Icon.SMS_DISABLED);
uiController.setIcon(find(COMPONENT_LB_THIRD), Icon.SMS_DISABLED);
uiController.setText(find(COMPONENT_LB_COST), InternationalisationUtils.formatCurrency(0));
if (shouldCheckMaxMessageLength) // Otherwise this component doesn't exist
uiController.setVisible(find(COMPONENT_LB_TOO_MANY_MESSAGES), false);
Object sendButton = find(COMPONENT_BT_SEND);
if (sendButton != null) uiController.setEnabled(sendButton, false);
}
public void sendToGroup() {
Object attachedObject = this.uiController.getAttachedObject(find(COMPONENT_TF_RECIPIENT));
if (attachedObject != null && attachedObject instanceof Group) {
List<Contact> recipientList = this.uiController.getFrontlineController().getGroupMembershipDao().getMembers((Group) attachedObject);
for (Contact contact : recipientList) {
this.uiController.getFrontlineController().sendTextMessage(contact.getPhoneNumber(), this.uiController.getText(find(COMPONENT_TF_MESSAGE)));
}
}
this.clearComponents();
}
/**
* Event triggered when the message recipient has changed
* @param text the new text value for the message recipient
*
*/
public void recipientChanged(Object recipientField, String message) {
this.uiController.setAttachedObject(recipientField, null);
this.uiController.setIcon(find(COMPONENT_LB_ICON), Icon.USER_STATUS_ACTIVE);
this.numberToSend = 1;
this.updateMessageDetails(recipientField, message);
}
/** Method which triggers showing of the contact selecter. */
public void selectMessageRecipient() {
ContactSelecter contactSelecter = new ContactSelecter(this.uiController);
final boolean shouldHaveEmail = false;
contactSelecter.show(InternationalisationUtils.getI18nString(FrontlineSMSConstants.SENTENCE_SELECT_MESSAGE_RECIPIENT_TITLE), "setRecipientTextfield(contactSelecter_contactList, contactSelecter)", null, this, shouldHaveEmail);
}
/** Method which triggers showing of the group selecter. */
public void selectGroup() {
GroupSelecterDialog groupSelect = new GroupSelecterDialog(this.uiController, this);
groupSelect.init(this.uiController.getRootGroup());
groupSelect.show();
}
public void groupSelectionCompleted(Group group) {
this.numberToSend = this.uiController.getFrontlineController().getGroupMembershipDao().getMemberCount(group);
Object tfRecipient = find(UiGeneratorControllerConstants.COMPONENT_TF_RECIPIENT);
this.uiController.setText(tfRecipient, group.getName() + " (" + this.numberToSend + ")");
this.uiController.setAttachedObject(tfRecipient, group);
this.uiController.setIcon(find(COMPONENT_LB_ICON), Icon.GROUP);
setSendButtonMethod(this, this.messagePanel, "sendToGroup");
this.updateMessageDetails(group, this.uiController.getText(find(UiGeneratorControllerConstants.COMPONENT_TF_MESSAGE)));
}
/**
* Sets the phone number of the selected contact.
*
* This method is triggered by the contact selected, as detailed in {@link #selectMessageRecipient()}.
*
* @param contactSelecter_contactList
* @param dialog
*/
public void setRecipientTextfield(Object contactSelecter_contactList, Object dialog) {
Object tfRecipient = find(UiGeneratorControllerConstants.COMPONENT_TF_RECIPIENT),
tfMessage = find(UiGeneratorControllerConstants.COMPONENT_TF_MESSAGE);
Object selectedItem = uiController.getSelectedItem(contactSelecter_contactList);
if (selectedItem == null) {
uiController.alert(InternationalisationUtils.getI18nString(FrontlineSMSConstants.MESSAGE_NO_CONTACT_SELECTED));
return;
}
Contact selectedContact = uiController.getContact(selectedItem);
uiController.setText(tfRecipient, selectedContact.getPhoneNumber());
uiController.remove(dialog);
setSendButtonMethod(this, null, "send");
this.updateCost();
// The recipient text has changed, we check whether the send button should be enabled
this.recipientChanged(tfRecipient, uiController.getText(tfMessage));
}
/**
* @param recipients Either the recipients field's text or the group attached
* @param message the new text value for the message body
*/
public void updateMessageDetails(Object recipients, String message) {
int messageLength = message.length();
Object sendButton = find(COMPONENT_BT_SEND);
boolean areAllCharactersValidGSM = GsmAlphabet.areAllCharactersValidGSM(message);
int totalLengthAllowed = FrontlineMessage.getTotalLengthAllowed(message);
boolean shouldEnableSendButton = (messageLength > 0 && (!shouldCheckMaxMessageLength || messageLength <= totalLengthAllowed));
if (shouldDisplayRecipientField) {
if (recipients instanceof Group) {
shouldEnableSendButton &= this.numberToSend > 0;
} else if (recipients != null) {
shouldEnableSendButton &= !this.uiController.getText(recipients).isEmpty();
}
}
if (sendButton != null)
uiController.setEnabled(sendButton, shouldEnableSendButton);
int singleMessageCharacterLimit;
int multipartMessageCharacterLimit;
if(areAllCharactersValidGSM) {
singleMessageCharacterLimit = FrontlineMessage.SMS_LENGTH_LIMIT;
multipartMessageCharacterLimit = FrontlineMessage.SMS_MULTIPART_LENGTH_LIMIT;
} else {
// It appears there are some unicode-only characters here. We should therefore
// treat this message as if it will be sent as unicode.
singleMessageCharacterLimit = FrontlineMessage.SMS_LENGTH_LIMIT_UCS2;
multipartMessageCharacterLimit = FrontlineMessage.SMS_MULTIPART_LENGTH_LIMIT_UCS2;
}
Object tfMessage = find(COMPONENT_TF_MESSAGE);
Object lbTooManyMessages = find(COMPONENT_LB_TOO_MANY_MESSAGES);
final int numberOfMsgs = FrontlineMessage.getExpectedNumberOfSmsParts(message);
double costEstimate;
int remaining;
if (shouldCheckMaxMessageLength && messageLength > totalLengthAllowed) {
remaining = 0;
costEstimate = 0;
uiController.setVisible(lbTooManyMessages, true);
uiController.setColor(tfMessage, "foreground", Color.RED);
} else {
if (shouldCheckMaxMessageLength) {
uiController.setVisible(lbTooManyMessages, false);
uiController.setColor(tfMessage, "foreground", Color.BLACK);
}
if (messageLength <= singleMessageCharacterLimit) {
remaining = (messageLength % singleMessageCharacterLimit) == 0 ? 0
: singleMessageCharacterLimit - (messageLength % singleMessageCharacterLimit);
} else {
int charCount = messageLength - singleMessageCharacterLimit;
remaining = (charCount % multipartMessageCharacterLimit) == 0 ? 0
: multipartMessageCharacterLimit - ((charCount % multipartMessageCharacterLimit));
}
costEstimate = numberOfMsgs * this.getCostPerSms() * this.numberToSend;
}
// The message will actually cost {numberOfRecipients} times the calculated cost
costEstimate *= numberOfRecipients;
uiController.setText(find(COMPONENT_LB_REMAINING_CHARS), String.valueOf(remaining));
uiController.setText(find(COMPONENT_LB_COST), InternationalisationUtils.formatCurrency(costEstimate));
uiController.setVisible(find(COMPONENT_LB_HELP), false);
uiController.setText(find(COMPONENT_LB_MSG_NUMBER), String.valueOf(numberOfMsgs));
uiController.setIcon(find(COMPONENT_LB_FIRST), Icon.SMS_DISABLED);
uiController.setIcon(find(COMPONENT_LB_SECOND), Icon.SMS_DISABLED);
uiController.setIcon(find(COMPONENT_LB_THIRD), Icon.SMS_DISABLED);
if (numberOfMsgs >= 1) uiController.setIcon(find(COMPONENT_LB_FIRST), Icon.SMS);
if (numberOfMsgs >= 2) uiController.setIcon(find(COMPONENT_LB_SECOND), Icon.SMS);
if (numberOfMsgs == 3) uiController.setIcon(find(COMPONENT_LB_THIRD), Icon.SMS);
if (numberOfMsgs > 3) uiController.setIcon(find(COMPONENT_LB_THIRD), Icon.SMS_ADD);
if (Pattern.matches(".*\\$[^ ]*\\}.*", message)) {
uiController.setVisible(find(COMPONENT_LB_HELP), true);
}
}
/**
* Sets the phone number of the selected contact.
*
* @param contactSelecter_contactList
* @param dialog
*/
public void homeScreen_setRecipientTextfield(Object contactSelecter_contactList, Object dialog) {
Object tfRecipient = find(COMPONENT_TF_RECIPIENT);
Object selectedItem = uiController.getSelectedItem(contactSelecter_contactList);
if (selectedItem == null) {
uiController.alert(InternationalisationUtils.getI18nString(MESSAGE_NO_CONTACT_SELECTED));
return;
}
Contact selectedContact = uiController.getContact(selectedItem);
uiController.setText(tfRecipient, selectedContact.getPhoneNumber());
uiController.remove(dialog);
this.numberToSend = 1;
this.updateCost();
}
public void hideSendButton() {
this.uiController.setVisible(find(COMPONENT_BT_SEND), false);
}
public void updateCost() {
LOG.trace("Updating message panel cost estimate");
this.uiController.setText(find(COMPONENT_LB_MSG_NUMBER), String.valueOf(numberToSend));
this.uiController.setText(find(COMPONENT_LB_COST), InternationalisationUtils.formatCurrency(AppProperties.getInstance().getCostPerSmsSent() * numberToSend));
LOG.trace("EXIT");
}
//> INSTANCE HELPER METHODS
//> STATIC FACTORIES
/**
* Create and initialise a new {@link MessagePanelHandler}.
* @return a new, initialised instance of {@link MessagePanelHandler}
*/
public static final MessagePanelHandler create(UiGeneratorController ui, boolean shouldDisplayRecipientField, boolean checkMaxMessageLength, int numberOfRecipients) {
MessagePanelHandler handler = new MessagePanelHandler(ui, shouldDisplayRecipientField, checkMaxMessageLength, numberOfRecipients);
handler.init();
return handler;
}
//> STATIC HELPER METHODS
}