Unverified Commit 9ff06faf authored by dunandmuri's avatar dunandmuri Committed by GitHub

Implements visual accessibility features for designer and companion. (#2472)

parent e35a5b42
......@@ -8,6 +8,7 @@ package com.google.appinventor.client.editor.simple.components;
import static com.google.appinventor.client.Ode.MESSAGES;
import com.google.appinventor.client.editor.simple.SimpleEditor;
import com.google.appinventor.client.editor.youngandroid.YaFormEditor;
import com.google.appinventor.client.output.OdeLog;
import com.google.gwt.event.dom.client.ErrorEvent;
import com.google.gwt.event.dom.client.ErrorHandler;
......@@ -24,7 +25,7 @@ import com.google.gwt.user.client.ui.Image;
*
* @author lizlooney@google.com (Liz Looney)
*/
abstract class MockButtonBase extends MockVisibleComponent {
abstract class MockButtonBase extends MockVisibleComponent implements FormChangeListener {
// Property names
private static final String PROPERTY_NAME_IMAGE = "Image";
......@@ -75,6 +76,19 @@ abstract class MockButtonBase extends MockVisibleComponent {
deckPanel.add(image);
deckPanel.showWidget(0);
initComponent(deckPanel);
}
@Override
protected void onAttach() {
super.onAttach();
((YaFormEditor) editor).getForm().addFormChangeListener(this);
}
@Override
protected void onDetach() {
super.onDetach();
((YaFormEditor) editor).getForm().removeFormChangeListener(this);
}
/**
......@@ -159,7 +173,12 @@ abstract class MockButtonBase extends MockVisibleComponent {
return;
}
if (MockComponentsUtil.isDefaultColor(text)) {
MockForm form = ((YaFormEditor) editor).getForm();
if (form != null && form.getPropertyValue("HighContrast").equals("True")) {
MockComponentsUtil.setWidgetBackgroundColor(buttonWidget, "&HFF000000");
} else {
MockComponentsUtil.resetWidgetBackgroundColor(buttonWidget);
}
} else {
MockComponentsUtil.setWidgetBackgroundColor(buttonWidget, text);
}
......@@ -192,8 +211,17 @@ abstract class MockButtonBase extends MockVisibleComponent {
* Sets the button's FontSize property to a new value.
*/
private void setFontSizeProperty(String text) {
float convertedText = Float.parseFloat(text);
if (convertedText == 14.0 || convertedText == 24.0) {
MockForm form = ((YaFormEditor) editor).getForm();
if (form != null && form.getPropertyValue("BigDefaultText").equals("True")) {
MockComponentsUtil.setWidgetFontSize(buttonWidget, "24");
} else {
MockComponentsUtil.setWidgetFontSize(buttonWidget, "14");
}
} else {
MockComponentsUtil.setWidgetFontSize(buttonWidget, text);
updatePreferredSizeOfButton();
}
}
/*
......@@ -242,7 +270,12 @@ abstract class MockButtonBase extends MockVisibleComponent {
*/
private void setTextColorProperty(String text) {
if (MockComponentsUtil.isDefaultColor(text)) {
MockForm form = ((YaFormEditor) editor).getForm();
if (form != null && form.getPropertyValue("HighContrast").equals("True")) {
MockComponentsUtil.setWidgetTextColor(buttonWidget, "&HFFFFFFFF");
} else {
MockComponentsUtil.resetWidgetTextColor(buttonWidget);
}
} else {
MockComponentsUtil.setWidgetTextColor(buttonWidget, text);
}
......@@ -320,6 +353,43 @@ abstract class MockButtonBase extends MockVisibleComponent {
setTextColorProperty(newValue);
} else if (propertyName.equals(PROPERTY_NAME_BUTTONSHAPE)){
setShapeProperty(newValue);
}
}
@Override
public void onComponentPropertyChanged(MockComponent component, String propertyName, String propertyValue) {
if (component.getType().equals(MockForm.TYPE) && propertyName.equals("HighContrast")) {
setBackgroundColorProperty(getPropertyValue(PROPERTY_NAME_BACKGROUNDCOLOR));
setTextColorProperty(getPropertyValue(PROPERTY_NAME_TEXTCOLOR));
updatePreferredSizeOfButton();
refreshForm();
}
else if (component.getType().equals(MockForm.TYPE) && propertyName.equals("BigDefaultText")) {
setFontSizeProperty(getPropertyValue(PROPERTY_NAME_FONTSIZE));
updatePreferredSizeOfButton();
refreshForm();
}
}
@Override
public void onComponentRemoved(MockComponent component, boolean permanentlyDeleted) {
}
@Override
public void onComponentAdded(MockComponent component) {
}
@Override
public void onComponentRenamed(MockComponent component, String oldName) {
}
@Override
public void onComponentSelectionChange(MockComponent component, boolean selected) {
}
}
......@@ -8,6 +8,7 @@ package com.google.appinventor.client.editor.simple.components;
import static com.google.appinventor.client.Ode.MESSAGES;
import com.google.appinventor.client.editor.simple.SimpleEditor;
import com.google.appinventor.client.editor.youngandroid.YaFormEditor;
import com.google.gwt.safehtml.shared.SimpleHtmlSanitizer;
import com.google.gwt.user.client.ui.InlineHTML;
......@@ -15,7 +16,7 @@ import com.google.gwt.user.client.ui.InlineHTML;
* Mock Label component.
*
*/
public final class MockLabel extends MockVisibleComponent {
public final class MockLabel extends MockVisibleComponent implements FormChangeListener{
/**
* Component type name.
......@@ -41,8 +42,22 @@ public final class MockLabel extends MockVisibleComponent {
labelWidget = new InlineHTML();
labelWidget.setStylePrimaryName("ode-SimpleMockComponent");
initComponent(labelWidget);
}
@Override
protected void onAttach() {
super.onAttach();
((YaFormEditor) editor).getForm().addFormChangeListener(this);
}
@Override
protected void onDetach() {
super.onDetach();
((YaFormEditor) editor).getForm().removeFormChangeListener(this);
}
@Override
public void onCreateFromPalette() {
// Change label text to component name
......@@ -84,8 +99,18 @@ public final class MockLabel extends MockVisibleComponent {
* Sets the label's FontSize property to a new value.
*/
private void setFontSizeProperty(String text) {
float convertedText = Float.parseFloat(text);
if (convertedText == 14.0 || convertedText == 24.0) {
MockForm form = ((YaFormEditor) editor).getForm();
if (form != null && form.getPropertyValue("BigDefaultText").equals("True")) {
MockComponentsUtil.setWidgetFontSize(labelWidget, "24");
} else {
MockComponentsUtil.setWidgetFontSize(labelWidget, "14");
}
} else {
MockComponentsUtil.setWidgetFontSize(labelWidget, text);
}
}
/*
* Sets the label's FontTypeface property to a new value.
......@@ -157,4 +182,33 @@ public final class MockLabel extends MockVisibleComponent {
refreshForm();
}
}
@Override
public void onComponentPropertyChanged(MockComponent component, String propertyName, String propertyValue) {
if (component.getType().equals(MockForm.TYPE) && propertyName.equals("BigDefaultText")) {
setFontSizeProperty(getPropertyValue(PROPERTY_NAME_FONTSIZE));
refreshForm();
}
}
@Override
public void onComponentRemoved(MockComponent component, boolean permanentlyDeleted) {
}
@Override
public void onComponentAdded(MockComponent component) {
}
@Override
public void onComponentRenamed(MockComponent component, String oldName) {
}
@Override
public void onComponentSelectionChange(MockComponent component, boolean selected) {
}
}
......@@ -11,13 +11,15 @@ import com.google.appinventor.components.common.ComponentConstants;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.Widget;
import com.google.appinventor.client.editor.youngandroid.YaFormEditor;
/**
* Mock PasswordTextBox component.
*
* @author lizlooney@google.com (Liz Looney)
*/
public final class MockPasswordTextBox extends MockWrapper {
public final class MockPasswordTextBox extends MockWrapper implements FormChangeListener{
/**
* Component type name.
......@@ -41,7 +43,16 @@ public final class MockPasswordTextBox extends MockWrapper {
passwordTextBoxWidget.setText("**********");
initWrapper(passwordTextBoxWidget);
}
@Override
protected void onAttach() {
super.onAttach();
((YaFormEditor) editor).getForm().addFormChangeListener(this);
}
@Override
protected void onDetach() {
super.onDetach();
((YaFormEditor) editor).getForm().removeFormChangeListener(this);
}
/**
* Class that extends PasswordTextBox so we can use a protected constructor.
*
......@@ -81,8 +92,13 @@ public final class MockPasswordTextBox extends MockWrapper {
*/
private void setBackgroundColorProperty(String text) {
if (MockComponentsUtil.isDefaultColor(text)) {
MockForm form = ((YaFormEditor) editor).getForm();
if (form != null && form.getPropertyValue("HighContrast").equals("True")) {
text = "&HFF000000"; // black
} else {
text = "&HFFFFFFFF"; // white
}
}
MockComponentsUtil.setWidgetBackgroundColor(passwordTextBoxWidget, text);
}
......@@ -137,7 +153,13 @@ public final class MockPasswordTextBox extends MockWrapper {
*/
private void setTextColorProperty(String text) {
if (MockComponentsUtil.isDefaultColor(text)) {
text = "&HFF000000"; // black
MockForm form = ((YaFormEditor) editor).getForm();
if (form != null && form.getPropertyValue("HighContrast").equals("True")) {
text = "&HFFFFFFFF"; // white
}
else {
text = "&HFF000000"; //black
}
}
MockComponentsUtil.setWidgetTextColor(passwordTextBoxWidget, text);
}
......@@ -173,4 +195,41 @@ public final class MockPasswordTextBox extends MockWrapper {
setTextColorProperty(newValue);
}
}
@Override
public void onComponentPropertyChanged(MockComponent component, String propertyName, String propertyValue) {
if (component.getType().equals(MockForm.TYPE) && propertyName.equals("HighContrast")) {
setBackgroundColorProperty(getPropertyValue(PROPERTY_NAME_BACKGROUNDCOLOR));
setTextColorProperty(getPropertyValue(PROPERTY_NAME_TEXTCOLOR));
if (propertyValue.equals("True")){
setFontSizeProperty("24");
refreshForm();
}
else {
setFontSizeProperty("14");
refreshForm();
}
}
}
@Override
public void onComponentRemoved(MockComponent component, boolean permanentlyDeleted) {
}
@Override
public void onComponentAdded(MockComponent component) {
}
@Override
public void onComponentRenamed(MockComponent component, String oldName) {
}
@Override
public void onComponentSelectionChange(MockComponent component, boolean selected) {
}
}
......@@ -8,19 +8,21 @@ package com.google.appinventor.client.editor.simple.components;
import static com.google.appinventor.client.Ode.MESSAGES;
import com.google.appinventor.client.editor.simple.SimpleEditor;
import com.google.appinventor.client.editor.youngandroid.YaFormEditor;
import com.google.appinventor.components.common.ComponentConstants;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
/**
* Abstract superclass for textbox based mock components.
*
* @author sharon@google.com (Sharon Perl)
* @author lizlooney@google.com (Liz Looney)
*/
abstract class MockTextBoxBase extends MockWrapper {
abstract class MockTextBoxBase extends MockWrapper implements FormChangeListener{
// GWT widget used to mock a Simple TextBox
private final TextBox textBoxWidget;
......@@ -38,6 +40,16 @@ abstract class MockTextBoxBase extends MockWrapper {
initWrapper(textBoxWidget);
}
@Override
protected void onAttach() {
super.onAttach();
((YaFormEditor) editor).getForm().addFormChangeListener(this);
}
@Override
protected void onDetach() {
super.onDetach();
((YaFormEditor) editor).getForm().removeFormChangeListener(this);
}
/**
* Class that extends TextBox so we can use a protected constructor.
*
......@@ -84,8 +96,13 @@ abstract class MockTextBoxBase extends MockWrapper {
*/
private void setBackgroundColorProperty(String text) {
if (MockComponentsUtil.isDefaultColor(text)) {
MockForm form = ((YaFormEditor) editor).getForm();
if (form != null && form.getPropertyValue("HighContrast").equals("True")) {
text = "&HFF000000"; //black
} else {
text = "&HFFFFFFFF"; // white
}
}
MockComponentsUtil.setWidgetBackgroundColor(textBoxWidget, text);
}
......@@ -116,7 +133,17 @@ abstract class MockTextBoxBase extends MockWrapper {
* Sets the textbox's FontSize property to a new value.
*/
private void setFontSizeProperty(String text) {
float convertedText = Float.parseFloat(text);
if (convertedText == 14.0 || convertedText == 24.0) {
MockForm form = ((YaFormEditor) editor).getForm();
if (form != null && form.getPropertyValue("BigDefaultText").equals("True")) {
MockComponentsUtil.setWidgetFontSize(textBoxWidget, "24");
} else {
MockComponentsUtil.setWidgetFontSize(textBoxWidget, "14");
}
} else {
MockComponentsUtil.setWidgetFontSize(textBoxWidget, text);
}
updatePreferredSize();
}
......@@ -148,13 +175,23 @@ abstract class MockTextBoxBase extends MockWrapper {
*/
private void setTextColorProperty(String text) {
if (MockComponentsUtil.isDefaultColor(text)) {
text = "&HFF000000"; // black
MockForm form = ((YaFormEditor) editor).getForm();
if (form != null && form.getPropertyValue("HighContrast").equals("True")) {
text = "&HFFFFFFFF"; // white
} else {
text = "&HFF000000"; //black
}
}
MockComponentsUtil.setWidgetTextColor(textBoxWidget, text);
}
// PropertyChangeListener implementation
@Override
public void onPropertyChange(String propertyName, String newValue) {
super.onPropertyChange(propertyName, newValue);
......@@ -187,4 +224,40 @@ abstract class MockTextBoxBase extends MockWrapper {
setTextColorProperty(newValue);
}
}
@Override
public void onComponentPropertyChanged(MockComponent component, String propertyName, String propertyValue) {
if (component.getType().equals(MockForm.TYPE) && propertyName.equals("HighContrast")) {
setBackgroundColorProperty(getPropertyValue(PROPERTY_NAME_BACKGROUNDCOLOR));
setTextColorProperty(getPropertyValue(PROPERTY_NAME_TEXTCOLOR));
//setFontSizeProperty(getPropertyValue(PROPERTY_NAME_FONTSIZE));
updatePreferredSize();
refreshForm();
}
else if (component.getType().equals(MockForm.TYPE) && propertyName.equals("BigDefaultText")) {
setFontSizeProperty(getPropertyValue(PROPERTY_NAME_FONTSIZE));
updatePreferredSize();
refreshForm();
}
}
@Override
public void onComponentRemoved(MockComponent component, boolean permanentlyDeleted) {
}
@Override
public void onComponentAdded(MockComponent component) {
}
@Override
public void onComponentRenamed(MockComponent component, String oldName) {
}
@Override
public void onComponentSelectionChange(MockComponent component, boolean selected) {
}
}
......@@ -6,10 +6,13 @@
package com.google.appinventor.client.editor.simple.components;
import com.google.appinventor.client.editor.simple.SimpleEditor;
import com.google.appinventor.client.editor.youngandroid.YaFormEditor;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Widget;
import java.text.Normalizer;
import static com.google.appinventor.client.Ode.MESSAGES;
/**
......@@ -17,7 +20,7 @@ import static com.google.appinventor.client.Ode.MESSAGES;
*
* @author srlane@mit.edu (Susan Rati Lane)
*/
abstract class MockToggleBase<T extends Widget> extends MockWrapper {
abstract class MockToggleBase<T extends Widget> extends MockWrapper implements FormChangeListener {
// Set toggle widget in child classes
protected T toggleWidget;
......@@ -85,8 +88,19 @@ abstract class MockToggleBase<T extends Widget> extends MockWrapper {
* Sets the toggle's FontSize property to a new value.
*/
protected void setFontSizeProperty(String text) {
float convertedText = Float.parseFloat(text);
if (convertedText == 14.0 || convertedText == 24.0) {
MockForm form = ((YaFormEditor) editor).getForm();
if (form != null && form.getPropertyValue("BigDefaultText").equals("True")) {
MockComponentsUtil.setWidgetFontSize(toggleWidget, "24");
} else {
MockComponentsUtil.setWidgetFontSize(toggleWidget, "14");
}
} else {
MockComponentsUtil.setWidgetFontSize(toggleWidget, text);
}
updatePreferredSize();
}
/*
......@@ -145,4 +159,32 @@ abstract class MockToggleBase<T extends Widget> extends MockWrapper {
preferredSize = MockComponentsUtil
.getPreferredSizeOfElement(DOM.clone(toggleWidget.getElement(), true));
}
@Override
public void onComponentPropertyChanged(MockComponent component, String propertyName, String propertyValue) {
if (component.getType().equals(MockForm.TYPE) && propertyName.equals("BigDefaultText")) {
setFontSizeProperty(getPropertyValue(PROPERTY_NAME_FONTSIZE));
refreshForm();
}
}
@Override
public void onComponentRemoved(MockComponent component, boolean permanentlyDeleted) {
}
@Override
public void onComponentAdded(MockComponent component) {
}
@Override
public void onComponentRenamed(MockComponent component, String oldName) {
}
@Override
public void onComponentSelectionChange(MockComponent component, boolean selected) {
}
}
......@@ -1051,6 +1051,11 @@ public final class YoungAndroidFormUpgrader {
srcCompVersion = 27;
}
if (srcCompVersion < 28) {
// HighContrast and BigDefaultText properties were added.
srcCompVersion = 28;
}
return srcCompVersion;
}
......@@ -1114,6 +1119,10 @@ public final class YoungAndroidFormUpgrader {
// The Clickable property was added.
srcCompVersion = 4;
}
if (srcCompVersion < 5) {
// The AlternateText property was added.
srcCompVersion = 5;
}
return srcCompVersion;
}
......
......@@ -1606,7 +1606,10 @@ Blockly.Versioning.AllUpgradeMaps =
// Click event was added
// The Clickable property was added.
4: "noUpgrade"
4: "noUpgrade",
// AlternateText property was added.
5: "noUpgrade"
}, // End Image upgraders
......@@ -2366,7 +2369,11 @@ Blockly.Versioning.AllUpgradeMaps =
// For FORM_COMPONENT_VERSION 27:
// - Platform and PlatformVersion read-only blocks were added
27: "noUpgrade"
27: "noUpgrade",
// For FORM_COMPONENT_VERSION 28:
// - HighContrast and BigDefaultText properties were added
28: "noUpgrade"
}, // End Screen
......
......@@ -898,7 +898,10 @@ public class YaVersion {
// - Updated the default value of ShowListsAsJson from false -> true
// For FORM_COMPONENT_VERSION 27:
// - Added the Platform and PlatformVersion read-only blocks
public static final int FORM_COMPONENT_VERSION = 27;
// For FORM_COMPONENT_VERSION 28:
// - Added the AlternateText property
// - Added the BigDefaultText property
public static final int FORM_COMPONENT_VERSION = 28;
// For FUSIONTABLESCONTROL_COMPONENT_VERSION 2:
// - The Fusiontables API was migrated from SQL to V1
......@@ -931,7 +934,9 @@ public class YaVersion {
// For IMAGE_COMPONENT_VERSION 4:
// - The Click event was added.
// - The Clickable property was added.
public static final int IMAGE_COMPONENT_VERSION = 4;
// For IMAGE_COMPONENT_VERSION 5:
// - The AlternateText property was added.
public static final int IMAGE_COMPONENT_VERSION = 5;
// For IMAGEPICKER_COMPONENT_VERSION 2:
// - The Alignment property was renamed to TextAlignment.
......
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2020-2021 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package com.google.appinventor.components.runtime;
public interface AccessibleComponent {
//Sets the high contrast field of a component to be either True or False
void setHighContrast(boolean isHighContrast);
//Returns whether a component is in high contrast mode or not
boolean getHighContrast();
//Sets the large font field of a component to be either True or False
void setLargeFont(boolean isLargeFont);
//Returns whether a component is in large font mode or not
boolean getLargeFont();
}
......@@ -45,7 +45,7 @@ import java.io.IOException;
@SimpleObject
@UsesPermissions(permissionNames = "android.permission.INTERNET")
public abstract class ButtonBase extends AndroidViewComponent
implements OnClickListener, OnFocusChangeListener, OnLongClickListener, View.OnTouchListener {
implements OnClickListener, OnFocusChangeListener, OnLongClickListener, View.OnTouchListener, AccessibleComponent {
private static final String LOG_TAG = "ButtonBase";
......@@ -103,6 +103,12 @@ public abstract class ButtonBase extends AndroidViewComponent
// could not be loaded, this is null.
private Drawable backgroundImageDrawable;
//Whether or not the button is in high contrast mode
private boolean isHighContrast = false;
//Whether or not the button is in big text mode
private boolean isBigText = false;
/**
* The minimum width of a button for the current theme.
*
......@@ -158,6 +164,9 @@ public abstract class ButtonBase extends AndroidViewComponent
Shape(Component.BUTTON_SHAPE_DEFAULT);
}
public void Initialize(){
updateAppearance();
}
/**
* If a custom background images is specified for the button, then it will lose the pressed
* and disabled image effects; no visual feedback.
......@@ -343,7 +352,6 @@ public abstract class ButtonBase extends AndroidViewComponent
// Clear the prior background image.
backgroundImageDrawable = null;
// Load image from file.
if (imagePath.length() > 0) {
try {
......@@ -402,7 +410,15 @@ public abstract class ButtonBase extends AndroidViewComponent
if (backgroundColor == Component.COLOR_DEFAULT) {
// If there is no background image and color is default,
// restore original 3D bevel appearance.
if (isHighContrast || container.$form().HighContrast()) {
ViewUtil.setBackgroundDrawable(view, null);
ViewUtil.setBackgroundDrawable(view, getSafeBackgroundDrawable());
view.getBackground().setColorFilter(Component.COLOR_BLACK, PorterDuff.Mode.SRC_ATOP);
}
else {
ViewUtil.setBackgroundDrawable(view, defaultButtonDrawable);
}
} else if (backgroundColor == Component.COLOR_NONE) {
// Clear the background image.
ViewUtil.setBackgroundDrawable(view, null);
......@@ -490,8 +506,12 @@ public abstract class ButtonBase extends AndroidViewComponent
view.getBackground().setColorFilter(backgroundColor, PorterDuff.Mode.CLEAR);
}
else if (backgroundColor == Component.COLOR_DEFAULT) {
if (isHighContrast || container.$form().HighContrast()) {
view.getBackground().setColorFilter(Component.COLOR_BLACK, PorterDuff.Mode.SRC_ATOP);
} else {
view.getBackground().setColorFilter(SHAPED_DEFAULT_BACKGROUND_COLOR, PorterDuff.Mode.SRC_ATOP);
}
}
else {
view.getBackground().setColorFilter(backgroundColor, PorterDuff.Mode.SRC_ATOP);
}
......@@ -638,8 +658,18 @@ public abstract class ButtonBase extends AndroidViewComponent
@SimpleProperty(
category = PropertyCategory.APPEARANCE)
public void FontSize(float size) {
if (Math.abs(size-Component.FONT_DEFAULT_SIZE)<.01 || Math.abs(size-24)<.01) {
if (container.$form().BigDefaultText()) {
TextViewUtil.setFontSize(view, 24);
}
else {
TextViewUtil.setFontSize(view, Component.FONT_DEFAULT_SIZE);
}
}
else {
TextViewUtil.setFontSize(view, size);
}
}
/**
* The text font face of the `%type%`. Valid values are `0` (default), `1` (serif), `2` (sans
......@@ -729,9 +759,14 @@ public abstract class ButtonBase extends AndroidViewComponent
if (argb != Component.COLOR_DEFAULT) {
TextViewUtil.setTextColor(view, argb);
} else {
if (isHighContrast || container.$form().HighContrast()){
TextViewUtil.setTextColor(view, Color.WHITE);
}
else {
TextViewUtil.setTextColors(view, defaultColorStateList);
}
}
}
public abstract void click();
......@@ -768,4 +803,48 @@ public abstract class ButtonBase extends AndroidViewComponent
return longClick();
}
@Override
public void setHighContrast(boolean isHighContrast) {
//background of button
if (backgroundImageDrawable == null && shape == Component.BUTTON_SHAPE_DEFAULT && backgroundColor == Component.COLOR_DEFAULT) {
if (isHighContrast) {
ViewUtil.setBackgroundDrawable(view, null);
ViewUtil.setBackgroundDrawable(view, getSafeBackgroundDrawable());
view.getBackground().setColorFilter(Component.COLOR_BLACK, PorterDuff.Mode.SRC_ATOP);
} else {
ViewUtil.setBackgroundDrawable(view, defaultButtonDrawable);
}
}
//color of text
if (textColor == Component.COLOR_DEFAULT) {
if (isHighContrast) {
TextViewUtil.setTextColor(view, Color.WHITE);
} else {
TextViewUtil.setTextColors(view, defaultColorStateList);
}
}
}
@Override
public boolean getHighContrast() {
return isHighContrast;
}
@Override
public void setLargeFont(boolean isLargeFont) {
if (TextViewUtil.getFontSize(view, container.$context()) == 24.0 || TextViewUtil.getFontSize(view, container.$context()) == Component.FONT_DEFAULT_SIZE) {
if (isLargeFont) {
TextViewUtil.setFontSize(view, 24);
} else {
TextViewUtil.setFontSize(view, Component.FONT_DEFAULT_SIZE);
}
}
}
@Override
public boolean getLargeFont() {
return isBigText;
}
}
......@@ -886,6 +886,11 @@ public final class Canvas extends AndroidViewComponent implements ComponentConta
throw new UnsupportedOperationException("Canvas.$add() called");
}
@Override
public List<? extends Component> getChildren(){
return sprites;
}
@Override
public void setChildWidth(AndroidViewComponent component, int width) {
throw new UnsupportedOperationException("Canvas.setChildWidth() called");
......
......@@ -8,6 +8,8 @@ package com.google.appinventor.components.runtime;
import android.app.Activity;
import java.util.List;
/**
* Components that can contain other components need to implement this
* interface.
......@@ -44,6 +46,8 @@ public interface ComponentContainer {
void setChildHeight(AndroidViewComponent component, int height);
List<? extends Component> getChildren();
int Width();
int Height();
......
......@@ -142,6 +142,8 @@ public class Form extends AppInventorCompatActivity
private static final int DEFAULT_ACCENT_COLOR =
hexStringToInt(ComponentConstants.DEFAULT_ACCENT_COLOR);
private List<Component> allChildren = new ArrayList<>();
// Keep track of the current form object.
// activeForm always holds the Form that is currently handling event dispatching so runtime.scm
// can lookup symbols in the correct environment.
......@@ -203,6 +205,9 @@ public class Form extends AppInventorCompatActivity
private FrameLayout frameLayout;
private boolean scrollable;
private boolean highContrast;
private boolean bigDefaultText;
private ScaledFrameLayout scaleLayout;
private static boolean sCompatibilityMode;
......@@ -261,6 +266,8 @@ public class Form extends AppInventorCompatActivity
// FragmentActivity is added in future.
public static final int MAX_PERMISSION_NONCE = 100000;
public static class PercentStorageRecord {
public enum Dim {
HEIGHT, WIDTH };
......@@ -446,6 +453,8 @@ public class Form extends AppInventorCompatActivity
ActionBar(themeHelper.hasActionBar());
}
Scrollable(false); // frameLayout is created in Scrollable()
HighContrast(false);
BigDefaultText(false);
Sizing("Responsive"); // Note: Only the Screen1 value is used as this is per-project
AboutScreen("");
BackgroundImage("");
......@@ -1118,6 +1127,83 @@ public class Form extends AppInventorCompatActivity
});
}
/**
* HighContrast property getter method.
*
* @return true if we want high constrast mode
*/
@SimpleProperty(category = PropertyCategory.APPEARANCE,
description = "When checked, we will use high contrast mode")
public boolean HighContrast() {
return highContrast;
}
/**
* When checked, there will be high contrast mode turned on.
*
* @param highContrast true if the high contrast mode is on
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
defaultValue = "False")
@SimpleProperty
public void HighContrast(boolean highContrast) {
//this.scrollable = scrollable;
this.highContrast=highContrast;
setHighContrastRecursive(this, highContrast);
recomputeLayout();
}
private static void setHighContrastRecursive(ComponentContainer container, boolean enabled) {
for (Component child : container.getChildren()) {
if (child instanceof ComponentContainer) {
setHighContrastRecursive((ComponentContainer) child, enabled);
} else if (child instanceof AccessibleComponent) {
((AccessibleComponent) child).setHighContrast(enabled);
}
}
}
/**
* BigDefaultText property getter method.
*
* @return true if we are in the big text mode
*/
@SimpleProperty(category = PropertyCategory.APPEARANCE,
description = "When checked, we will use high contrast mode")
public boolean BigDefaultText() {
return bigDefaultText;
}
/**
* When checked, all default size text will be increased in size.
*
* @param bigDefaultText true if the big text mode is on
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
defaultValue = "False")
@SimpleProperty
public void BigDefaultText(boolean bigDefaultText) {
//this.scrollable = scrollable;
this.bigDefaultText=bigDefaultText;
setBigDefaultTextRecursive(this, bigDefaultText);
recomputeLayout();
}
private static void setBigDefaultTextRecursive(ComponentContainer container, boolean enabled) {
for (Component child : container.getChildren()) {
if (child instanceof ComponentContainer) {
setBigDefaultTextRecursive((ComponentContainer) child, enabled);
} else if (child instanceof AccessibleComponent) {
((AccessibleComponent) child).setLargeFont(enabled);
}
}
}
/**
* Scrollable property getter method.
*
......@@ -2113,9 +2199,18 @@ public class Form extends AppInventorCompatActivity
@Override
public void $add(AndroidViewComponent component) {
viewLayout.add(component);
allChildren.add(component);
}
@Override
public List<? extends Component> getChildren(){
return allChildren;
}
public float deviceDensity(){
return this.deviceDensity;
}
......
......@@ -35,6 +35,8 @@ import com.google.appinventor.components.runtime.util.MediaUtil;
import com.google.appinventor.components.runtime.util.ViewUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* A container for components that arranges them linearly, either
......@@ -67,6 +69,9 @@ public class HVArrangement extends AndroidViewComponent implements Component, Co
// Image path
private String imagePath = "";
//list of component children
private List<Component> allChildren = new ArrayList<>();
private Drawable defaultButtonDrawable;
private final Handler androidUIHandler = new Handler();
......@@ -144,6 +149,12 @@ public class HVArrangement extends AndroidViewComponent implements Component, Co
@Override
public void $add(AndroidViewComponent component) {
viewLayout.add(component);
allChildren.add(component);
}
@Override
public List<? extends Component> getChildren() {
return allChildren;
}
@Override
......
......@@ -88,6 +88,12 @@ public final class Image extends AndroidViewComponent {
return view;
}
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = "")
@SimpleProperty(description = "A written description of what the image looks like.")
public void AlternateText(String description){
view.setContentDescription(description);
}
@SimpleEvent(description = "An event that occurs when an image is clicked.")
public void Click() {
EventDispatcher.dispatchEvent(this, "Click");
......
......@@ -37,7 +37,7 @@ import android.widget.TextView;
"the appearance and placement of the text.",
category = ComponentCategory.USERINTERFACE)
@SimpleObject
public final class Label extends AndroidViewComponent {
public final class Label extends AndroidViewComponent implements AccessibleComponent{
// default margin around a label in DPs
// note that the spacing between adjacent labels will be twice this value
......@@ -79,6 +79,9 @@ public final class Label extends AndroidViewComponent {
// HTML content of the label
private String htmlContent;
//Whether or not the text should be big
private boolean isBigText = false;
/**
* Creates a new Label component.
*
......@@ -312,8 +315,19 @@ private void setLabelMargins(boolean hasMargins) {
defaultValue = Component.FONT_DEFAULT_SIZE + "")
@SimpleProperty
public void FontSize(float size) {
if (Math.abs(size-Component.FONT_DEFAULT_SIZE)<.01 || Math.abs(size-24)<.01) {
if (isBigText || container.$form().BigDefaultText()) {
TextViewUtil.setFontSize(view, 24);
}
else {
TextViewUtil.setFontSize(view, Component.FONT_DEFAULT_SIZE);
}
}
else {
TextViewUtil.setFontSize(view, size);
}
}
/**
* Returns the label's text's font face as default, serif, sans
......@@ -457,4 +471,30 @@ private void setLabelMargins(boolean hasMargins) {
TextViewUtil.setTextColor(view, container.$form().isDarkTheme() ? Component.COLOR_WHITE : Component.COLOR_BLACK);
}
}
@Override
public void setHighContrast(boolean isHighContrast) {
}
@Override
public boolean getHighContrast() {
return false;
}
@Override
public void setLargeFont(boolean isLargeFont) {
if (TextViewUtil.getFontSize(view, container.$context()) == 24.0 || TextViewUtil.getFontSize(view, container.$context()) == Component.FONT_DEFAULT_SIZE) {
if (isLargeFont) {
TextViewUtil.setFontSize(view, 24);
} else {
TextViewUtil.setFontSize(view, Component.FONT_DEFAULT_SIZE);
}
}
}
@Override
public boolean getLargeFont() {
return isBigText;
}
}
......@@ -328,6 +328,11 @@ public abstract class MapFeatureContainerBase extends AndroidViewComponent imple
throw new UnsupportedOperationException("Map.$add() called");
}
@Override
public List<? extends Component> getChildren(){
return features;
}
@Override
public void setChildWidth(AndroidViewComponent component, int width) {
throw new UnsupportedOperationException("Map.setChildWidth called");
......
......@@ -18,6 +18,9 @@ import com.google.appinventor.components.runtime.util.ViewUtil;
import android.app.Activity;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/**
* Use a table arrangement component to display a group of components in a tabular fashion.
*
......@@ -54,6 +57,8 @@ public class TableArrangement extends AndroidViewComponent
// Layout
private final TableLayout viewLayout;
private List<Component> allChildren;
/**
* Creates a new TableArrangement component.
*
......@@ -127,6 +132,12 @@ public class TableArrangement extends AndroidViewComponent
@Override
public void $add(AndroidViewComponent component) {
viewLayout.add(component);
allChildren.add(component);
}
@Override
public List<? extends Component> getChildren(){
return allChildren = new ArrayList<>();
}
@Override
......
......@@ -6,6 +6,8 @@
package com.google.appinventor.components.runtime;
import android.graphics.Color;
import android.graphics.PorterDuff;
import com.google.appinventor.components.annotations.DesignerProperty;
import com.google.appinventor.components.annotations.IsColor;
import com.google.appinventor.components.annotations.PropertyCategory;
......@@ -35,7 +37,7 @@ import android.widget.EditText;
@SimpleObject
public abstract class TextBoxBase extends AndroidViewComponent
implements OnFocusChangeListener {
implements OnFocusChangeListener, AccessibleComponent {
protected final EditText view;
......@@ -63,6 +65,15 @@ public abstract class TextBoxBase extends AndroidViewComponent
// This is our handle on Android's nice 3-d default textbox.
private Drawable defaultTextBoxDrawable;
//Whether or not the button is in high contrast mode
private boolean isHighContrast = false;
//Whether or not the button is in big text mode
private boolean isBigText = false;
//The default text color of the textbox hint, according to theme
private int hintColorDefault;
/**
* Creates a new TextBoxBase component
*
......@@ -72,6 +83,7 @@ public abstract class TextBoxBase extends AndroidViewComponent
public TextBoxBase(ComponentContainer container, EditText textview) {
super(container);
view = textview;
hintColorDefault = view.getCurrentHintTextColor();
// There appears to be an issue where, by default, Android 7+
// wants to provide suggestions in text boxes. However, we do not
// compile the necessary layouts for this to work correctly, which
......@@ -104,14 +116,21 @@ public abstract class TextBoxBase extends AndroidViewComponent
// only). Maybe we need another color value which would be 'SYSTEM_DEFAULT' which
// will not attempt to explicitly initialize with any of the properties with any
// particular value.
// BackgroundColor(Component.COLOR_NONE);
Enabled(true);
fontTypeface = Component.TYPEFACE_DEFAULT;
TextViewUtil.setFontTypeface(view, fontTypeface, bold, italic);
FontSize(Component.FONT_DEFAULT_SIZE);
Hint("");
if (isHighContrast || container.$form().HighContrast()) {
view.setHintTextColor(COLOR_YELLOW);
}
else {
view.setHintTextColor(hintColorDefault);
}
Text("");
TextColor(Component.COLOR_DEFAULT);
BackgroundColor(Component.COLOR_DEFAULT);
}
@Override
......@@ -219,10 +238,14 @@ public abstract class TextBoxBase extends AndroidViewComponent
backgroundColor = argb;
if (argb != Component.COLOR_DEFAULT) {
TextViewUtil.setBackgroundColor(view, argb);
} else {
if (isHighContrast || container.$form().$form().HighContrast()) {
TextViewUtil.setBackgroundColor(view, Component.COLOR_BLACK);
} else {
ViewUtil.setBackgroundDrawable(view, defaultTextBoxDrawable);
}
}
}
/**
* Returns true if the %type% is active and useable.
......@@ -335,8 +358,18 @@ public abstract class TextBoxBase extends AndroidViewComponent
defaultValue = Component.FONT_DEFAULT_SIZE + "")
@SimpleProperty
public void FontSize(float size) {
if (Math.abs(size-Component.FONT_DEFAULT_SIZE)<.01 || Math.abs(size-24)<.01) {
if (container.$form().BigDefaultText()) {
TextViewUtil.setFontSize(view, 24);
}
else {
TextViewUtil.setFontSize(view, Component.FONT_DEFAULT_SIZE);
}
}
else {
TextViewUtil.setFontSize(view, size);
}
}
/**
* Returns the text font face of the %type% as default, serif, sans
......@@ -456,16 +489,21 @@ public abstract class TextBoxBase extends AndroidViewComponent
* @param argb text RGB color with alpha
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_COLOR,
defaultValue = Component.DEFAULT_VALUE_COLOR_BLACK)
defaultValue = Component.DEFAULT_VALUE_COLOR_DEFAULT)
@SimpleProperty
public void TextColor(int argb) {
textColor = argb;
if (argb != Component.COLOR_DEFAULT) {
TextViewUtil.setTextColor(view, argb);
} else {
if (isHighContrast || container.$form().HighContrast()) {
TextViewUtil.setTextColor(view, COLOR_WHITE);
}
else {
TextViewUtil.setTextColor(view, container.$form().isDarkTheme() ? COLOR_WHITE : Component.COLOR_BLACK);
}
}
}
/**
* Request focus to current `%type%`.
......@@ -491,4 +529,52 @@ public abstract class TextBoxBase extends AndroidViewComponent
LostFocus();
}
}
@Override
public void setHighContrast(boolean isHighContrast) {
//background of button
if (backgroundColor == Component.COLOR_DEFAULT) {
if (isHighContrast) {
TextViewUtil.setBackgroundColor(view, Component.COLOR_BLACK);
}
else {
ViewUtil.setBackgroundDrawable(view, defaultTextBoxDrawable);
}
}
//color of text
if (textColor == Component.COLOR_DEFAULT) {
if (isHighContrast) {
TextViewUtil.setTextColor(view, COLOR_WHITE);
view.setHintTextColor(COLOR_YELLOW);
}
else {
TextViewUtil.setTextColor(view, container.$form().isDarkTheme() ? COLOR_WHITE : Component.COLOR_BLACK);
view.setHintTextColor(hintColorDefault);
}
}
}
@Override
public boolean getHighContrast() {
return isHighContrast;
}
@Override
public void setLargeFont(boolean isLargeFont) {
if (TextViewUtil.getFontSize(view, container.$context()) == 24.0 || TextViewUtil.getFontSize(view, container.$context()) == Component.FONT_DEFAULT_SIZE) {
if (isLargeFont) {
TextViewUtil.setFontSize(view, 24);
} else {
TextViewUtil.setFontSize(view, Component.FONT_DEFAULT_SIZE);
}
}
}
@Override
public boolean getLargeFont() {
return isBigText;
}
}
......@@ -27,7 +27,7 @@ import android.widget.CompoundButton.OnCheckedChangeListener;
*/
@SimpleObject
public abstract class ToggleBase<T extends CompoundButton> extends AndroidViewComponent
implements OnCheckedChangeListener, OnFocusChangeListener {
implements OnCheckedChangeListener, OnFocusChangeListener, AccessibleComponent {
protected T view;
......@@ -46,6 +46,9 @@ public abstract class ToggleBase<T extends CompoundButton> extends AndroidViewCo
// Backing for text color
private int textColor;
// Whether the text is big or not
private boolean isBigText = false;
/**
* Creates a new ToggleBase component.
*
......@@ -79,6 +82,33 @@ public abstract class ToggleBase<T extends CompoundButton> extends AndroidViewCo
return view;
}
@Override
public void setHighContrast(boolean isHighContrast) {
}
@Override
public boolean getHighContrast() {
return false;
}
@Override
public void setLargeFont(boolean isLargeFont) {
if (TextViewUtil.getFontSize(view, container.$context()) == 24.0 || TextViewUtil.getFontSize(view, container.$context()) == Component.FONT_DEFAULT_SIZE) {
if (isLargeFont) {
TextViewUtil.setFontSize(view, 24);
} else {
TextViewUtil.setFontSize(view, Component.FONT_DEFAULT_SIZE);
}
}
}
@Override
public boolean getLargeFont() {
return isBigText;
}
/**
* User tapped and released the `%type%`.
*/
......@@ -231,8 +261,18 @@ public abstract class ToggleBase<T extends CompoundButton> extends AndroidViewCo
@SimpleProperty(description = "Specifies the text font size of the %type% in scale-independent "
+ "pixels.")
public void FontSize(float size) {
if (Math.abs(size-Component.FONT_DEFAULT_SIZE)<.01 || Math.abs(size-24)<.01) {
if (isBigText || container.$form().BigDefaultText()) {
TextViewUtil.setFontSize(view, 24);
}
else {
TextViewUtil.setFontSize(view, Component.FONT_DEFAULT_SIZE);
}
}
else {
TextViewUtil.setFontSize(view, size);
}
}
/**
* Returns the text font size of the `%type%`, measured in sp(scale-independent pixels).
......
......@@ -410,6 +410,8 @@ Valid values for the month field are 1-12 and 1-31 for the day field.</dd>
<h3 id="Image-Properties">Properties</h3>
<dl class="properties">
<dt id="Image.AlternateText" class="text wo"><em>AlternateText</em></dt>
<dd>A written description of what the image looks like.</dd>
<dt id="Image.Animation" class="text wo bo"><em>Animation</em></dt>
<dd>This is a limited form of animation that can attach a small number of motion types to images.
The allowable motions are <code class="highlighter-rouge">ScrollRightSlow</code>, <code class="highlighter-rouge">ScrollRight</code>, <code class="highlighter-rouge">ScrollRightFast</code>,
......@@ -887,6 +889,8 @@ Valid values for the month field are 1-12 and 1-31 for the day field.</dd>
<dt id="Screen.BackgroundImage" class="text"><em>BackgroundImage</em></dt>
<dd>Specifies the path of the <code class="highlighter-rouge">Screen</code>’s background image. If there is both an <code class="highlighter-rouge">BackgroundImage</code>
and a <a href="#Screen.BackgroundColor"><code class="highlighter-rouge">BackgroundColor</code></a> specified, only the <code class="highlighter-rouge">BackgroundImage</code> will be visible.</dd>
<dt id="Screen.BigDefaultText" class="boolean"><em>BigDefaultText</em></dt>
<dd>When checked, all default size text will be increased in size.</dd>
<dt id="Screen.BlocksToolkit" class="text wo do"><em>BlocksToolkit</em></dt>
<dd>A JSON string representing the subset for the screen. Authors of template apps can use this to control what components, designer properties, and blocks are available in the project.</dd>
<dt id="Screen.CloseScreenAnimation" class="text"><em>CloseScreenAnimation</em></dt>
......@@ -894,6 +898,8 @@ Valid values for the month field are 1-12 and 1-31 for the day field.</dd>
to a form behind it in the activity stack.</dd>
<dt id="Screen.Height" class="number ro bo"><em>Height</em></dt>
<dd>Returns the Screen height in pixels (y-size).</dd>
<dt id="Screen.HighContrast" class="boolean"><em>HighContrast</em></dt>
<dd>When checked, there will be high contrast mode turned on.</dd>
<dt id="Screen.Icon" class="text wo do"><em>Icon</em></dt>
<dd>The image used for your App’s display icon should be a square png or jpeg image with dimensions
up to 1024x1024 pixels. Larger images may cause compiling or installing the app to fail.
......
......@@ -362,6 +362,9 @@ Component for displaying images and basic animations.
{:.properties}
{:id="Image.AlternateText" .text .wo} *AlternateText*
: A written description of what the image looks like.
{:id="Image.Animation" .text .wo .bo} *Animation*
: This is a limited form of animation that can attach a small number of motion types to images.
The allowable motions are `ScrollRightSlow`, `ScrollRight`, `ScrollRightFast`,
......@@ -963,6 +966,9 @@ Top-level component containing all other components in the program.
: Specifies the path of the `Screen`'s background image. If there is both an `BackgroundImage`
and a [`BackgroundColor`](#Screen.BackgroundColor) specified, only the `BackgroundImage` will be visible.
{:id="Screen.BigDefaultText" .boolean} *BigDefaultText*
: When checked, all default size text will be increased in size.
{:id="Screen.BlocksToolkit" .text .wo .do} *BlocksToolkit*
: A JSON string representing the subset for the screen. Authors of template apps can use this to control what components, designer properties, and blocks are available in the project.
......@@ -973,6 +979,9 @@ Top-level component containing all other components in the program.
{:id="Screen.Height" .number .ro .bo} *Height*
: Returns the Screen height in pixels (y-size).
{:id="Screen.HighContrast" .boolean} *HighContrast*
: When checked, there will be high contrast mode turned on.
{:id="Screen.Icon" .text .wo .do} *Icon*
: The image used for your App's display icon should be a square png or jpeg image with dimensions
up to 1024x1024 pixels. Larger images may cause compiling or installing the app to fail.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment