package mobi.monaca.framework.nativeui.component; import java.io.IOException; import java.util.ArrayList; import java.util.Map; import mobi.monaca.framework.MonacaApplication; import mobi.monaca.framework.nativeui.ComponentEventer; import mobi.monaca.framework.nativeui.DefaultStyleJSON; import mobi.monaca.framework.nativeui.UIContext; import mobi.monaca.framework.nativeui.UIEventer; import mobi.monaca.framework.nativeui.UIGravity; import mobi.monaca.framework.nativeui.UIUtil; import mobi.monaca.framework.nativeui.UIValidator; import mobi.monaca.framework.nativeui.container.TabbarContainer; import mobi.monaca.framework.nativeui.container.ToolbarContainer; import mobi.monaca.framework.nativeui.exception.ConversionException; import mobi.monaca.framework.nativeui.exception.DuplicateIDException; import mobi.monaca.framework.nativeui.exception.InvalidValueException; import mobi.monaca.framework.nativeui.exception.MenuNameNotDefinedInAppMenuFileException; import mobi.monaca.framework.nativeui.exception.NativeUIException; import mobi.monaca.framework.nativeui.exception.NativeUIIOException; import mobi.monaca.framework.nativeui.exception.RequiredKeyNotFoundException; import mobi.monaca.framework.nativeui.menu.MenuRepresentation; import mobi.monaca.framework.util.MyLog; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Shader.TileMode; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.text.TextUtils; import android.util.FloatMath; import android.view.Gravity; import android.view.View; /** * Used for manipulating MonacaPageActivity background style * */ public class PageComponent extends Component { private static final String TAG = PageComponent.class.getSimpleName(); private LayerDrawable mLayeredBackgroundDrawable; private PageOrientation mScreenOrientation = PageOrientation.INHERIT; protected Component topComponent; protected Component bottomComponent; protected ComponentEventer mBackButtonEventer; public UIEventer eventer; public String menuName; protected static String[] validKeys = { "top", "bottom", "event", "style", "iosStyle", "androidStyle", "menu", "id" }; protected static String[] styleValidKeys = { "backgroundColor", "backgroundImage", "backgroundSize", "backgroundRepeat", "backgroundPosition", "screenOrientation" }; @Override public String[] getValidKeys() { return validKeys; } public PageComponent(UIContext uiContext, JSONObject pageJSON) throws NativeUIException, JSONException { super(uiContext, pageJSON); uiContext.setPageComponent(this); UIValidator.validateKey(uiContext, "Page's style", style, styleValidKeys); JSONObject event = getComponentJSON().optJSONObject("event"); if (event != null) { eventer = new UIEventer(uiContext, getComponentJSON().optJSONObject("event")); } menuName = getComponentJSON().optString("menu"); if (!TextUtils.isEmpty(menuName)) { MonacaApplication app = (MonacaApplication) uiContext.getPageActivity().getApplication(); MenuRepresentation menuRepresentation = app.findMenuRepresentation(menuName); if (menuRepresentation == null) { throw new MenuNameNotDefinedInAppMenuFileException(getComponentName(), menuName); } } style(); buildChildren(); } public ComponentEventer getBackButtonEventer() { return mBackButtonEventer; }; public void setBackButtonEventer(ComponentEventer mBackButtonEventer) { this.mBackButtonEventer = mBackButtonEventer; } public Map<String, Component> getComponentIDsMap(){ return uiContext.getComponentIDsMap(); } protected void addIDtoComponentIDsMap() throws DuplicateIDException { // this is the top component -> a new of object of this page should clear old ids of previous uiContext.getComponentIDsMap().clear(); super.addIDtoComponentIDsMap(); } private static final String[] TOP_CONTAINER_VALID_VALUES = { "toolbar" }; private static final String[] BOTTOM_CONTAINER_VALID_VALUES = { "toolbar, tabbar" }; private void buildChildren() throws NativeUIException, JSONException { JSONObject topJSON = getComponentJSON().optJSONObject("top"); if (topJSON != null) { String containerType = topJSON.optString("container"); if (!TextUtils.isEmpty(containerType)) { if (containerType.equalsIgnoreCase("toolbar")) { topComponent = new ToolbarContainer(uiContext, topJSON, true); } else { InvalidValueException exception = new InvalidValueException("Page top", "container", containerType, TOP_CONTAINER_VALID_VALUES); throw exception; } } else { RequiredKeyNotFoundException exception = new RequiredKeyNotFoundException("top", "container"); throw exception; } } JSONObject bottomJSON = getComponentJSON().optJSONObject("bottom"); if (bottomJSON != null) { String containerType = bottomJSON.optString("container"); if (!TextUtils.isEmpty(containerType)) { if (containerType.equalsIgnoreCase("toolbar")) { bottomComponent = new ToolbarContainer(uiContext, bottomJSON, false); } else if (containerType.equalsIgnoreCase("tabbar")) { bottomComponent = new TabbarContainer(uiContext, bottomJSON); } else { InvalidValueException exception = new InvalidValueException("Page bottom", "container", containerType, BOTTOM_CONTAINER_VALID_VALUES); UIValidator.reportException(uiContext, exception); } } else { RequiredKeyNotFoundException exception = new RequiredKeyNotFoundException("top", "container"); UIValidator.reportException(uiContext, exception); } } } @Override public View getView() { return null; } public Component getTopComponent() { return topComponent; } public Component getBottomComponent() { return bottomComponent; } public PageOrientation getScreenOrientation() { return mScreenOrientation; } public Drawable getBackgroundDrawable() { return mLayeredBackgroundDrawable; } @Override public void updateStyle(JSONObject update) throws InvalidValueException { UIUtil.updateJSONObject(style, update); style(); uiContext.getPageActivity().applyScreenOrientation(mScreenOrientation); uiContext.getPageActivity().setupBackground(mLayeredBackgroundDrawable); } private void style() throws InvalidValueException { ArrayList<Drawable> layerList = new ArrayList<Drawable>(); processScreenOrientation(); processPageStyleBackgroundColor(style, layerList); processPageStyleBackgroundImage(style, layerList); processPageStyleBackgroundRepeat(layerList); Drawable[] layers = new Drawable[layerList.size()]; mLayeredBackgroundDrawable = new LayerDrawable(layerList.toArray(layers)); } /* * "screenOrientation": "landscape,portrait" * "screenOrientation": "landscape" * "screenOrientation": "portrait" */ private static final String[] SCREEN_ORIENTATION_VALID_VALUES = {"portrait", "landscape", "inherit", "portrait, landscape"}; private void processScreenOrientation() throws InvalidValueException { String screenOrientationString = style.optString("screenOrientation").trim(); if(TextUtils.isEmpty(screenOrientationString)){ return; } if(screenOrientationString.contains(",")){ // multiple values if(screenOrientationString.contains("portrait") && screenOrientationString.contains("landscape")){ mScreenOrientation = PageOrientation.SENSOR; }else{ raiseScreenOrietationInvalidValueException(screenOrientationString); } }else{ // single value if (screenOrientationString.equalsIgnoreCase("portrait")) { mScreenOrientation = PageOrientation.PORTRAIT; } else if (screenOrientationString.equalsIgnoreCase("landscape")) { mScreenOrientation = PageOrientation.LANDSCAPE; } else if (screenOrientationString.equalsIgnoreCase("inherit")) { mScreenOrientation = PageOrientation.INHERIT; } else { raiseScreenOrietationInvalidValueException(screenOrientationString); } } } private void raiseScreenOrietationInvalidValueException(String screenOrientationString) throws InvalidValueException { InvalidValueException invalidValueException = new InvalidValueException(getComponentName() + " style", "screenOrientation", screenOrientationString, SCREEN_ORIENTATION_VALID_VALUES); throw invalidValueException; } private static final String[] validBackgroundRepeatValues = { "repeat-x", "repeat-y", "repeat" }; private void processPageStyleBackgroundRepeat(ArrayList<Drawable> layerList) { String backgroundRepeatString = style.optString("backgroundRepeat"); String backgroundImageString = style.optString("backgroundImage"); if (!backgroundRepeatString.equalsIgnoreCase("") && !backgroundImageString.equalsIgnoreCase("")) { Drawable drawable = getTopDrawable(layerList); if ((drawable instanceof BitmapDrawable) == false) { return; } BitmapDrawable bitmapDrawable = (BitmapDrawable) getTopDrawable(layerList); MyLog.e(TAG, "background repeat:" + backgroundRepeatString); if (backgroundRepeatString.equalsIgnoreCase("repeat-x")) { bitmapDrawable.setTileModeX(TileMode.REPEAT); } else if (backgroundRepeatString.equalsIgnoreCase("repeat-y")) { bitmapDrawable.setTileModeY(TileMode.REPEAT); } else if (backgroundRepeatString.equalsIgnoreCase("repeat")) { bitmapDrawable.setTileModeXY(TileMode.REPEAT, TileMode.REPEAT); } else if (backgroundRepeatString.equalsIgnoreCase("no-repeat")) { bitmapDrawable.setTileModeXY(null, null); } else { InvalidValueException exception = new InvalidValueException("Page's style", "backgroundRepeat", backgroundRepeatString, validBackgroundRepeatValues); UIValidator.reportException(uiContext, exception); } } } private Drawable getTopDrawable(ArrayList<Drawable> layerList) { return layerList.get(layerList.size() - 1); } private void processPageStyleBackgroundColor(JSONObject pageStyle, ArrayList<Drawable> layerList) { // // background color String backgroundColorString = pageStyle.optString("backgroundColor").trim(); // default int color = Color.WHITE; if (!backgroundColorString.equalsIgnoreCase("")) { // #xxxxxx format if (backgroundColorString.startsWith("#")) { try { color = Color.parseColor(backgroundColorString); } catch (NumberFormatException e) { e.printStackTrace(); ConversionException exception = new ConversionException("Page's style", "backgroundColor", backgroundColorString, "Color"); UIValidator.reportException(uiContext, exception); } } else { ConversionException exception = new ConversionException("Page's style", "backgroundColor", backgroundColorString, "Color"); UIValidator.reportException(uiContext, exception); } } ColorDrawable colorDrawable = new ColorDrawable(color); layerList.add(colorDrawable); } private void processPageStyleBackgroundImage(JSONObject pageStyle, ArrayList<Drawable> layerList) { // // background image String backgroundImageFile = pageStyle.optString("backgroundImage").trim(); boolean shouldSkipBackgroundPosition = false; if (!backgroundImageFile.equalsIgnoreCase("")) { Bitmap bitmap; try { bitmap = uiContext.readScaledBitmap(backgroundImageFile); BitmapDrawable backgroundImage = new BitmapDrawable(uiContext.getResources(), bitmap); layerList.add(backgroundImage); float bitmapWidth = bitmap.getWidth(); float bitmapHeight = bitmap.getHeight(); float deviceWidth = uiContext.getDisplayMetrics().widthPixels; float deviceHeight = uiContext.getDisplayMetrics().heightPixels; // // Background Size String backgroundSize = pageStyle.optString("backgroundSize").trim(); if (backgroundSize.equalsIgnoreCase("")) { backgroundSize = "cover"; } // cover if (backgroundSize.equalsIgnoreCase("cover")) { backgroundImage.setGravity(Gravity.FILL); shouldSkipBackgroundPosition = true; // contain } else if (backgroundSize.equalsIgnoreCase("contain")) { float widthRatio = deviceWidth / bitmapWidth; float heightRatio = deviceHeight / bitmapHeight; float safeRatio = widthRatio < heightRatio ? widthRatio : heightRatio; int scaledHeight = (int) FloatMath.ceil(bitmapHeight * safeRatio); bitmap = UIUtil.resizeBitmap(bitmap, scaledHeight); BitmapDrawable drawable = new BitmapDrawable(uiContext.getResources(), bitmap); layerList.remove(layerList.size() - 1); layerList.add(drawable); } // pixel or percentage or dip else { int width = 0; int height = 0; // both width and height specified if (backgroundSize.trim().contains(",")) { JSONArray sizes = pageStyle.optJSONArray("backgroundSize"); if (sizes != null) { // width String widthString = sizes.optString(0); if (widthString.endsWith("%")) { String percentageString = widthString.replace("%", ""); int percentage = Integer.parseInt(percentageString); width = (int) (deviceWidth * percentage / 100); } else if (widthString.endsWith("px")) { widthString = widthString.replace("px", ""); width = Integer.parseInt(widthString); } else { widthString = widthString.replace("dip", ""); width = Integer.parseInt(widthString); width = UIUtil.dip2px(uiContext, width); } // height String heightString = sizes.optString(1); if (heightString.endsWith("%")) { String percentageString = heightString.replace("%", ""); int percentage = Integer.parseInt(percentageString); height = (int) (deviceHeight * percentage / 100); } else if (widthString.endsWith("px")) { heightString = heightString.replace("px", ""); height = Integer.parseInt(heightString); } else { heightString = widthString.replace("dip", ""); height = Integer.parseInt(widthString); height = UIUtil.dip2px(uiContext, width); } } else { ConversionException exception = new ConversionException("Page style", "backgroundSize", backgroundSize, "Size"); UIValidator.reportException(uiContext, exception); } } else { // only width specified String widthString = backgroundSize; if (widthString.endsWith("%")) { String percentageString = widthString.replace("%", ""); int percentage = Integer.parseInt(percentageString); width = (int) (deviceWidth * percentage / 100); } else if (widthString.endsWith("px")) { widthString = widthString.replace("px", ""); width = Integer.parseInt(widthString); } else { widthString = widthString.replace("dip", ""); width = Integer.parseInt(widthString); width = UIUtil.dip2px(uiContext, width); } float ratio = bitmapHeight / bitmapWidth; height = (int) (width * ratio); MyLog.v(TAG, "scaled height:" + height); } // finished calculating width and height bitmap = UIUtil.resizeBitmap(bitmap, width, height); BitmapDrawable drawable = new BitmapDrawable(uiContext.getResources(), bitmap); layerList.remove(layerList.size() - 1); layerList.add(drawable); } BitmapDrawable finalDrawable = (BitmapDrawable) getTopDrawable(layerList); processBackgroundPosition(pageStyle, finalDrawable, shouldSkipBackgroundPosition); } catch (IOException e) { e.printStackTrace(); NativeUIIOException exception = new NativeUIIOException("Page style", "backgroundImage", backgroundImageFile, e.getMessage()); UIValidator.reportException(uiContext, exception); } } } private void processBackgroundPosition(JSONObject pageStyle, BitmapDrawable backgroundImage, boolean shouldSkipBackgroundPosition) { if (shouldSkipBackgroundPosition) { return; } // // position String horizontalPositionString = "center"; String verticalPositionString = "center"; String backgroundPosition = pageStyle.optString("backgroundPosition").trim(); // get horizontal and vertical Integer horizontalGravity = Gravity.CENTER; Integer verticalGravity = Gravity.CENTER; if (!backgroundPosition.equalsIgnoreCase("")) { // default horizontalPositionString = "center"; verticalPositionString = "center"; if (backgroundPosition.contains(" ")) { // user specified both horizontal and vertial MyLog.w(TAG, backgroundPosition + " contains a space!"); String[] positions = backgroundPosition.split(" "); horizontalPositionString = positions[0]; verticalPositionString = positions[1]; } else { // only horizontal position horizontalPositionString = backgroundPosition; } // check horizontal // left, center, or right if (UIGravity.hasHorizontalGravity(horizontalPositionString)) { horizontalGravity = UIGravity.getHorizontalGravity(horizontalPositionString); if (horizontalGravity == null) { InvalidValueException exception = new InvalidValueException("Page style", "backgroundPosition", horizontalPositionString, UIGravity.HORIZONTAL_POSITIONS); UIValidator.reportException(uiContext, exception); } } // check vertical // top, center, or bottom if (UIGravity.hasVerticalGravity(verticalPositionString)) { verticalGravity = UIGravity.getVerticalGravity(verticalPositionString); if (verticalGravity == null) { InvalidValueException exception = new InvalidValueException("Page style", "backgroundPosition", verticalPositionString, UIGravity.VERTICAL_POSITIONS); UIValidator.reportException(uiContext, exception); } } } backgroundImage.setGravity(horizontalGravity | verticalGravity); } @Override public String getComponentName() { return "Page"; } @Override public JSONObject getDefaultStyle() { return DefaultStyleJSON.page(); } }