/** * Copyright (c) 2013 Nokia Corporation. All rights reserved. * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation. * Oracle and Java are trademarks or registered trademarks of Oracle and/or its * affiliates. Other product and company names mentioned herein may be trademarks * or trade names of their respective owners. * See license text file delivered with this project for more information. */ package com.nokia.example.statusshout.ui; import java.io.IOException; import javax.microedition.content.Invocation; import javax.microedition.lcdui.Alert; import javax.microedition.lcdui.AlertType; import javax.microedition.lcdui.Canvas; import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Display; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.Gauge; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.TextField; import javax.microedition.midlet.MIDlet; import com.nokia.mid.ui.CategoryBar; import com.nokia.mid.ui.ElementListener; import com.nokia.mid.ui.FileSelect; import com.nokia.mid.ui.FileSelectDetail; import com.nokia.mid.ui.IconCommand; import com.nokia.mid.ui.KeyboardVisibilityListener; import com.nokia.mid.ui.TextEditor; import com.nokia.mid.ui.TextEditorListener; import com.nokia.mid.ui.VirtualKeyboard; import com.nokia.example.statusshout.StatusShout; import com.nokia.example.statusshout.animations.AnimationListener; import com.nokia.example.statusshout.animations.IntAnimation; import com.nokia.example.statusshout.engine.FacebookService; import com.nokia.example.statusshout.engine.OAuthService; import com.nokia.example.statusshout.engine.ShareApiManager; import com.nokia.example.statusshout.engine.ShareListener; import com.nokia.example.statusshout.engine.StatusShoutData; /** * The main view of the application. */ public class MainView extends Canvas implements CommandListener, ElementListener, TextEditorListener, KeyboardVisibilityListener, Button.Listener, AnimationListener, ShareListener { // Constants private static final String TAG = "MainView."; private static final String PHOTOS_FOLDER = System.getProperty("fileconn.dir.photos"); private static final String BACKGROUND_IMAGE_URI = "/background.png"; private static final String IMAGE_PLACEHOLDER_URI = "/image-placeholder.png"; private static final String TEXT_BOX_PATTERN_IMAGE_URI = "/text-box-pattern.png"; private static final String DELETE_BUTTON_IMAGE_URI = "/delete-button.png"; private static final String FACEBOOK_ICON_URI = "/f-logo.png"; private static final String SHARE_API_ICON_URI = "/share-icon.png"; private static final String DISCARD_FACEBOOK_TOKEN_TEXT = "Discard Facebook token"; private static final String HINT_TEXT = "Type your message here"; private static final int BACKGROUND_COLOR = 0x000000; private static final int TEXT_COLOR = 0x464646; private static final int HINT_TEXT_COLOR = 0x717171; private static final int MARGIN = 5; private static final int IMAGE_WIDTH = 190; private static final int IMAGE_HEIGHT = 140; private static final int TEXT_FIELD_MAX_SIZE = 140; private static final int BUTTON_INDEX_ADD_IMAGE = 0; private static final int BUTTON_INDEX_DISCARD_IMAGE = 1; private static final int BUTTON_INDEX_LAST = 2; // Members private final Command exitAndBackCommand = new Command("Exit", Command.EXIT, 0x01); private final Button[] buttons = new Button[BUTTON_INDEX_LAST]; private MIDlet midlet; private StatusShoutData appData; private FacebookService facebookService; private ShareApiManager shareApiManager; private AboutView aboutView; private CategoryBar categoryBar; private TextEditor textEditor; private IntAnimation animation; private IconCommand shareViaFacebookCommand; private IconCommand shareUsingShareApiCommand; private Command discardFacebookTokenCommand; private Command aboutCommand; private Image backgroundImage; private Image textBoxPatternImage; private Image selectedImage; private int yOffset; // For scrolling private int textAreaY; /** * Constructor. * @param midlet The application MIDlet instance. * @throws NullPointerException if MIDlet instance is null. */ public MainView(MIDlet midlet) throws NullPointerException { super(); if (midlet == null) { throw new NullPointerException("The MIDlet instance is null!"); } this.midlet = midlet; appData = StatusShoutData.getInstance(); facebookService = new FacebookService(midlet, this); shareApiManager = ShareApiManager.getInstance(midlet); shareApiManager.setListener(this); createImagesAndButtons(); discardFacebookTokenCommand = new Command(DISCARD_FACEBOOK_TOKEN_TEXT, Command.ITEM, 0x04); aboutCommand = new Command("About", Command.ITEM, 0x06); addCommand(exitAndBackCommand); setCommandListener(this); // Create the text editor textEditor = TextEditor.createTextEditor("", TEXT_FIELD_MAX_SIZE, TextField.NON_PREDICTIVE, getWidth() - MARGIN * 2, 100); textEditor.setMultiline(true); textEditor.insert(HINT_TEXT, 0); textEditor.setForegroundColor(HINT_TEXT_COLOR); textEditor.setBackgroundColor(0x00000000); textEditor.setParent(this); // Canvas to draw on textEditor.setTouchEnabled(true); textEditor.setTextEditorListener(this); VirtualKeyboard.setVisibilityListener(this); animation = new IntAnimation(); animation.setListener(this); restoreData(); } /** * Creates the category bar and other commands which are placed in the menu. * If the category bar is already created, it is recreated if the intent is * to hide the share API icon command. * * @param showShareApi If false, will hide the sharing option via share API. */ public void setCategoryBar(boolean showShareApi) { if (categoryBar != null) { // Already created if (!showShareApi) { // Recreate without the share API icon command IconCommand[] iconCommands = new IconCommand[] { shareViaFacebookCommand }; categoryBar = new CategoryBar(iconCommands, true); categoryBar.setMode(CategoryBar.ELEMENT_MODE_RELEASE_SELECTED); categoryBar.setVisibility(true); categoryBar.setElementListener(this); } return; } Image fbIcon = null; Image shareApiIcon = null; try { fbIcon = Image.createImage(FACEBOOK_ICON_URI); if (showShareApi) { shareApiIcon = Image.createImage(SHARE_API_ICON_URI); } } catch (IOException e) { } shareViaFacebookCommand = new IconCommand("Share via Facebook", fbIcon, null, Command.ITEM, 0x01); IconCommand[] iconCommands = null; if (showShareApi) { shareUsingShareApiCommand = new IconCommand("Other sharing options", shareApiIcon, null, Command.ITEM, 0x03); iconCommands = new IconCommand[] { shareViaFacebookCommand, shareUsingShareApiCommand }; } else { iconCommands = new IconCommand[] { shareViaFacebookCommand }; } categoryBar = new CategoryBar(iconCommands, true); categoryBar.setMode(CategoryBar.ELEMENT_MODE_RELEASE_SELECTED); categoryBar.setVisibility(true); categoryBar.setElementListener(this); } /** * @see javax.microedition.lcdui.CommandListener#commandAction( * javax.microedition.lcdui.Command, javax.microedition.lcdui.Displayable) */ public void commandAction(Command command, Displayable displayable) { if (command == exitAndBackCommand) { if (displayable == this) { ((StatusShout)midlet).quit(); } else { ((StatusShout)midlet).getDisplay().setCurrent(this); categoryBar.setVisibility(true); aboutView = null; } } else if (command == discardFacebookTokenCommand) { appData.setFacebookToken(null); populateMenu(); } else if (command == aboutCommand) { aboutView = new AboutView(midlet, backgroundImage); aboutView.addCommand(exitAndBackCommand); aboutView.setCommandListener(this); categoryBar.setVisibility(false); ((StatusShout)midlet).getDisplay().setCurrent(aboutView); } } /** * @see com.nokia.mid.ui.ElementListener#notifyElementSelected(com.nokia.mid.ui.CategoryBar, int) */ public void notifyElementSelected(CategoryBar bar, int selectedIndex) { System.out.println(TAG + "notifyElementSelected(): " + selectedIndex); // Get the message String message = appData.getMessage(); if ((message != null && message.equals(HINT_TEXT)) || (message != null && message.length() == 0)) { message = null; } // Get the image final FileSelectDetail imageDetails = appData.getSelectedImageDetails(); String imageUrl = null; if (imageDetails != null && imageDetails.url != null && imageDetails.url.length() > 0) { imageUrl = imageDetails.url; } // Verify that we have something to share if (message == null && imageUrl == null) { showMessage("Add something to share.", AlertType.INFO); return; } switch (selectedIndex) { case 0: // Facebook facebookService.share(message, imageUrl); break; case 1: // Share API // Share API only supports sharing one item at a time i.e. you // cannot share both image and text at once. Thus, an image is // prioritised meaning that if an image is selected it is shared // and not the message even if one exists. if (imageUrl != null) { shareApiManager.shareImage(imageDetails.url, imageDetails.mimeType); } else { shareApiManager.shareText(message); } break; } } /** * @see com.nokia.mid.ui.KeyboardVisibilityListener#hideNotify(int) */ public void hideNotify(int keyboardCategory) { if (textEditor.getContent().length() == 0) { textEditor.setForegroundColor(HINT_TEXT_COLOR); textEditor.setContent(HINT_TEXT); } textEditor.setFocus(false); animation.start(yOffset, 0, 400, IntAnimation.EASING_CURVE_INOUTQUAD); } /** * @see com.nokia.mid.ui.KeyboardVisibilityListener#showNotify(int) */ public void showNotify(int keyboardCategory) { if (textEditor.getContent().equals(HINT_TEXT)) { textEditor.setContent(""); textEditor.setCaret(0); textEditor.setForegroundColor(TEXT_COLOR); } animation.start(yOffset, 30 - textAreaY, 400, IntAnimation.EASING_CURVE_INOUTQUAD); } /** * @see com.nokia.mid.ui.TextEditorListener#inputAction(com.nokia.mid.ui.TextEditor, int) */ public void inputAction(TextEditor textEditor, int actions) { appData.setMessage(textEditor.getContent()); } /** * @see com.nokia.example.statusshout.ui.Button.Listener#onPressedChanged( * com.nokia.example.statusshout.ui.Button, boolean) */ public void onPressedChanged(Button button, boolean pressed) { repaint(); } /** * @see com.nokia.example.statusshout.ui.Button.Listener#onTapped( * com.nokia.example.statusshout.ui.Button) */ public void onTapped(Button button) { System.out.println(TAG + "onTapped(): " + button); if (button == buttons[BUTTON_INDEX_ADD_IMAGE]) { selectImage(); } else if (button == buttons[BUTTON_INDEX_DISCARD_IMAGE]) { appData.setSelectedImageDetails(null); selectedImage = null; buttons[BUTTON_INDEX_ADD_IMAGE].setIsDisabled(false); buttons[BUTTON_INDEX_DISCARD_IMAGE].setIsDisabled(true); repaint(); } } /** * @see com.nokia.example.statusshout.animations.AnimationListener#onAnimatedValueChanged(int) */ public void onAnimatedValueChanged(int value) { yOffset = value; repaint(); } /** * @see com.nokia.example.statusshout.animations.AnimationListener#onAnimationStateChanged(int) */ public void onAnimationStateChanged(int state) { if (state == IntAnimation.STATE_FINISHED) { if (textEditor.hasFocus()) { yOffset = 30 - textAreaY; } else { yOffset = 0; } repaint(); } } /** * @see com.nokia.example.statusshout.engine.ShareListener#onSending() */ public void onSending() { if (Display.getDisplay(midlet).getCurrent() != this) { Display.getDisplay(midlet).setCurrent(this); } Alert alert = new Alert("Sending", "Please wait...", null, AlertType.INFO); alert.setIndicator(new Gauge(null, false, Gauge.INDEFINITE, Gauge.CONTINUOUS_RUNNING)); alert.setTimeout(Alert.FOREVER); alert.addCommand(new Command("OK", Command.OK, 0x01)); try { Display.getDisplay(midlet).setCurrent(alert); } catch (Exception e) { } } /** * @see com.nokia.example.statusshout.engine.ShareListener#onSuccess(java.lang.String) */ public void onSuccess(String message) { showMessage(message, AlertType.INFO); if (shareApiManager.getWasLaunchedAsSharingDestination()) { /* * This app was launched as a sharing destination, which means that * we can finish the invocation. Finishing the invocation with value * Invocation.OK will show up in the Fastlane UI. */ shareApiManager.finishInvocation(Invocation.OK); } } /** * @see com.nokia.example.statusshout.engine.ShareListener#onError(java.lang.String) */ public void onError(String errorMessage) { showMessage(errorMessage, AlertType.ERROR); } /** * @see com.nokia.example.statusshout.engine.ShareListener#onAuthenticated( * com.nokia.example.statusshout.engine.OAuthService) */ public void onAuthenticated(OAuthService service) { populateMenu(); if (Display.getDisplay(midlet).getCurrent() != this) { Display.getDisplay(midlet).setCurrent(this); } } /** * @see com.nokia.example.statusshout.engine.ShareListener#onLaunchedWithInvocation( * javax.microedition.content.Invocation) */ public void onLaunchedWithInvocation(Invocation invocation) { if (categoryBar != null) { // Hide the Share API icon command setCategoryBar(false); } } /** * @see javax.microedition.lcdui.Canvas#pointerPressed(int, int) */ protected void pointerPressed(int x, int y) { for (int i = 0; i < BUTTON_INDEX_LAST; ++i) { if (buttons[i].pointerPressed(x, y)) { return; } } if (textBoxPatternImage != null && !textEditor.hasFocus() && y > yOffset + textAreaY && y < yOffset + textAreaY + textBoxPatternImage.getHeight()) { textEditor.setFocus(true); } } /** * @see javax.microedition.lcdui.Canvas#pointerReleased(int, int) */ protected void pointerReleased(int x, int y) { for (int i = 0; i < BUTTON_INDEX_LAST; ++i) { if (buttons[i].pointerReleased(x, y)) { return; } } } /** * Sets the image. * @param imageUri The image URI. */ public void setImage(final String imageUri) { System.out.println(TAG + "setImage(): " + imageUri); FileSelectDetail detail = new FileSelectDetail(); detail.url = imageUri; appData.setSelectedImageDetails(detail); createImageThumbnail(); } /** * @see javax.microedition.lcdui.Canvas#paint(javax.microedition.lcdui.Graphics) */ protected void paint(Graphics graphics) { final int width = getWidth(); final int height = getHeight(); graphics.setColor(BACKGROUND_COLOR); graphics.fillRect(0, 0, width, height); if (backgroundImage != null) { graphics.drawImage(backgroundImage, 0, 0, Graphics.TOP | Graphics.LEFT); } int y = MARGIN * 2; if (selectedImage == null) { final Button button = buttons[BUTTON_INDEX_ADD_IMAGE]; button.paint(graphics, button.getPositionX(), yOffset + button.getPositionY()); } else { final int imageWidth = selectedImage.getWidth(); final int frameWidth = imageWidth + MARGIN * 2; final int frameHeight = selectedImage.getHeight() + MARGIN * 2; graphics.setColor(0xf4f4f4); graphics.fillRect((width - frameWidth) / 2, yOffset + y, frameWidth, frameHeight); graphics.drawImage(selectedImage, (width - imageWidth) / 2, yOffset + y + MARGIN, Graphics.TOP | Graphics.LEFT); // Draw delete button final Button button = buttons[BUTTON_INDEX_DISCARD_IMAGE]; button.paint(graphics, (width - imageWidth) / 2 + imageWidth - button.getWidth() / 2, button.getPositionY()); } y += IMAGE_HEIGHT + MARGIN * 2; if (textBoxPatternImage != null) { textAreaY = y; graphics.drawImage(textBoxPatternImage, 0, yOffset + y, Graphics.TOP | Graphics.LEFT); textEditor.setPosition(MARGIN, yOffset + y + MARGIN); } if (!textEditor.isVisible()) { textEditor.setVisible(true); } } /** * Restores the app state and settings. */ private void restoreData() { final MainView mainView = this; new Thread() { public void run() { appData.load(); createImageThumbnail(); final String message = appData.getMessage(); if (message != null) { textEditor.setForegroundColor(TEXT_COLOR); mainView.textEditor.setContent(message); } populateMenu(); repaint(); } }.start(); } /** * Creates the larger image assets of the view. To be on the safe side this * is done in a separate thread so that we don't block the UI. */ private void createImagesAndButtons() { final MainView mainView = this; new Thread() { public void run() { Image placeholderImageUnpressed = null; Image placeholderImagePressed = null; Image discardImageButtonImage = null; try { backgroundImage = Image.createImage(BACKGROUND_IMAGE_URI); placeholderImageUnpressed = Image.createImage(IMAGE_PLACEHOLDER_URI); placeholderImageUnpressed = ImageUtils.pixelMixingScale(placeholderImageUnpressed, IMAGE_WIDTH, IMAGE_HEIGHT); // Highlight color is 0x29a7cc (RGB: 41, 167, 204) placeholderImagePressed = ImageUtils.substractRgb(placeholderImageUnpressed, 214, 88, 51); textBoxPatternImage = ImageUtils.setAlpha(Image.createImage(TEXT_BOX_PATTERN_IMAGE_URI), 220); discardImageButtonImage = Image.createImage(DELETE_BUTTON_IMAGE_URI); } catch (IOException e) { } Button addImageButton = new Button(placeholderImageUnpressed, placeholderImagePressed, mainView); addImageButton.setPosition((getWidth() - placeholderImageUnpressed.getWidth()) / 2, MARGIN * 2); buttons[0] = addImageButton; Button discardImageButton = new Button(discardImageButtonImage, null, mainView); discardImageButton.setPosition(0, MARGIN * 2 - discardImageButtonImage.getHeight() / 2); buttons[1] = discardImageButton; repaint(); } }.start(); } /** * Populates the menu items. If no tokens are stored, the corresponding * discard menu items are not included in the menu. */ private void populateMenu() { try { removeCommand(discardFacebookTokenCommand); removeCommand(aboutCommand); } catch (Exception e) { } if (appData.getFacebookToken() != null) { addCommand(discardFacebookTokenCommand); } addCommand(aboutCommand); } /** * Launches FileSelect UI for image selection. Creates a thumbnail of the * selected image if a file was selected. */ private void selectImage() { new Thread() { public void run() { FileSelectDetail[] fileSelectDetails = null; try { fileSelectDetails = FileSelect.launch(PHOTOS_FOLDER, FileSelect.MEDIA_TYPE_PICTURE, false); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { return; } catch (IOException e) { e.printStackTrace(); } if (fileSelectDetails == null || fileSelectDetails[0] == null) { // No file selected return; } FileSelectDetail imageDetails = fileSelectDetails[0]; appData.setSelectedImageDetails(imageDetails); System.out.println(TAG + "selectImage(): Image file details: " + imageDetails.mimeType + ", " + imageDetails.displayName + ", " + imageDetails.url + ", " + imageDetails.size); createImageThumbnail(); } }.start(); } /** * Creates and displays an image thumbnail of the selected image. */ private void createImageThumbnail() { FileSelectDetail imageDetails = appData.getSelectedImageDetails(); if (imageDetails == null || imageDetails.url == null) { return; } try { selectedImage = ImageUtils.loadImageFromPhone(imageDetails.url); } catch (SecurityException e) { appData.setSelectedImageDetails(null); return; } if (selectedImage == null) { System.out.println(TAG + "createImageThumbnail(): Failed to load the image!"); return; } final int width = selectedImage.getWidth(); final int height = selectedImage.getHeight(); float ratio = 0; if (selectedImage.getWidth() > selectedImage.getHeight()) { // Landscape image ratio = (float)(IMAGE_WIDTH - MARGIN * 2) / width; } else { // Portrait image ratio = (float)(IMAGE_HEIGHT - MARGIN * 2) / height; } int newWidth = (int)(width * ratio); int newHeight = (int)(height * ratio); selectedImage = ImageUtils.pixelMixingScale(selectedImage, newWidth, newHeight); buttons[BUTTON_INDEX_ADD_IMAGE].setIsDisabled(true); final Button discardImageButton = buttons[BUTTON_INDEX_DISCARD_IMAGE]; discardImageButton.setIsDisabled(false); discardImageButton.setPosition( (getWidth() - newWidth) / 2 + newWidth - discardImageButton.getWidth() / 2, discardImageButton.getPositionY()); repaint(); } /** * Shows an alert dialog with the given message. * @param message The message to show. * @param alertType The alert type. */ private void showMessage(String message, AlertType alertType) { if (Display.getDisplay(midlet).getCurrent() != this) { Display.getDisplay(midlet).setCurrent(this); } Alert alert = new Alert(alertType == AlertType.ERROR ? "Error" : ""); alert.setString(message); alert.addCommand(new Command("OK", Command.OK, 0x01)); if (alertType == AlertType.ERROR) { alert.setTimeout(Alert.FOREVER); } else { alert.setTimeout(5000); } Display.getDisplay(midlet).setCurrent(alert); } }