×

MainView.java

/**
 * 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);
    }
}

Last updated 15 October 2013

Back to top

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×