Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Ti.UI.OptionBar #12510

Merged
merged 3 commits into from
Mar 9, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Appcelerator Titanium Mobile
* Copyright (c) 2021 by Axway, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
package ti.modules.titanium.ui;

import android.app.Activity;
import org.appcelerator.kroll.annotations.Kroll;
import org.appcelerator.titanium.proxy.TiViewProxy;
import org.appcelerator.titanium.TiC;
import org.appcelerator.titanium.view.TiUIView;
import ti.modules.titanium.ui.widget.TiUIOptionBar;

@Kroll.proxy(creatableInModule = UIModule.class, propertyAccessors = {
TiC.PROPERTY_INDEX,
TiC.PROPERTY_LABELS,
})
public class OptionBarProxy extends TiViewProxy
{
public OptionBarProxy()
{
defaultValues.put(TiC.PROPERTY_INDEX, 0);
}

@Override
public TiUIView createView(Activity activity)
{
return new TiUIOptionBar(this);
}

@Override
public String getApiName()
{
return "Ti.UI.OptionBar";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
/**
* Appcelerator Titanium Mobile
* Copyright (c) 2021 by Axway, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
package ti.modules.titanium.ui.widget;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Checkable;
import androidx.appcompat.view.ContextThemeWrapper;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.button.MaterialButtonToggleGroup;
import java.util.HashMap;
import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.KrollProxy;
import org.appcelerator.titanium.TiC;
import org.appcelerator.titanium.proxy.TiViewProxy;
import org.appcelerator.titanium.R;
import org.appcelerator.titanium.util.TiConvert;
import org.appcelerator.titanium.util.TiUIHelper;
import org.appcelerator.titanium.view.TiUIView;

public class TiUIOptionBar extends TiUIView
{
private static final String TAG = "TiUIOptionBar";

/** Set true to prevent "click" events from firing. Set false to allow these events. */
private boolean isIgnoringCheckEvent;

public TiUIOptionBar(TiViewProxy proxy)
{
super(proxy);

// Determine if the options should be shown vertically or horizontally.
String layout = TiConvert.toString(proxy.getProperty(TiC.PROPERTY_LAYOUT), TiC.LAYOUT_HORIZONTAL);
boolean isHorizontal = !TiC.LAYOUT_VERTICAL.equals(layout);

// Create an outlined toggle button group. (Looks similar to iOS' old segmented control.)
MaterialButtonToggleGroup buttonGroup = new MaterialButtonToggleGroup(proxy.getActivity());
buttonGroup.setSelectionRequired(true);
buttonGroup.setSingleSelection(true);
buttonGroup.setOrientation(
isHorizontal ? MaterialButtonToggleGroup.HORIZONTAL : MaterialButtonToggleGroup.VERTICAL);

// Listen for selection changes.
buttonGroup.addOnButtonCheckedListener(this::onButtonChecked);

// Set up view to fire a "postlayout" event every time its layout has changed.
buttonGroup.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(
View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom)
{
TiUIHelper.firePostLayoutEvent(getProxy());
}
});

// Keep a reference to the above created view.
setNativeView(buttonGroup);
}

@Override
public void processProperties(KrollDict properties)
{
// Validate.
if (properties == null) {
return;
}

// Apply given properties to view.
if (properties.containsKey(TiC.PROPERTY_LABELS)) {
processLabels(properties.get(TiC.PROPERTY_LABELS));
}
if (properties.containsKey(TiC.PROPERTY_INDEX)) {
checkOption(TiConvert.toInt(properties.get(TiC.PROPERTY_INDEX)));
}

// Let base class handle all other view property settings.
super.processProperties(properties);
}

@Override
public void propertyChanged(String key, Object oldValue, Object newValue, KrollProxy proxy)
{
// Validate.
if (key == null) {
return;
}

// Handle property change.
if (key.equals(TiC.PROPERTY_INDEX)) {
checkOption(TiConvert.toInt(newValue));
} else if (key.equals(TiC.PROPERTY_LABELS)) {
processLabels(newValue);
} else {
super.propertyChanged(key, oldValue, newValue, proxy);
}
}

private void checkOption(int index)
{
View view = getNativeView();
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
if ((index >= 0) && (index < viewGroup.getChildCount())) {
view = viewGroup.getChildAt(index);
if (view instanceof Checkable) {
boolean oldValue = this.isIgnoringCheckEvent;
this.isIgnoringCheckEvent = true;
((Checkable) view).setChecked(true);
this.isIgnoringCheckEvent = oldValue;
}
}
}
}

private void processLabels(Object labels)
{
// Do not continue if proxy has been released.
if (this.proxy == null) {
return;
}

// Fetch the button group view.
MaterialButtonToggleGroup buttonGroup = getButtonGroup();
if (buttonGroup == null) {
return;
}

// Clear the previously assigned buttons.
buttonGroup.removeAllViews();

// Fetch "labels" property and validate it.
if ((labels == null) || !labels.getClass().isArray()) {
return;
}
Object[] objectArray = (Object[]) labels;
if (objectArray.length <= 0) {
return;
}

// Process the labels object.
if (objectArray[0] instanceof String) {
// We were given an array of option titles.
for (Object title : objectArray) {
addButton(TiConvert.toString(title, ""), null, null, true);
}
} else if (objectArray[0] instanceof HashMap) {
// We were given an array of Titanium "BarItemType" dictionaries.
for (Object nextObject : objectArray) {
// Make sure next element is a dictionary.
if ((nextObject instanceof HashMap) == false) {
continue;
}
HashMap hashMap = (HashMap) nextObject;

// Fetch the optional "title" property.
String title = TiConvert.toString(hashMap.get(TiC.PROPERTY_TITLE), "");

// Fetch the optional "accessibilityLabel" property.
String accessibilityLabel = TiConvert.toString(hashMap.get(TiC.PROPERTY_ACCESSIBILITY_LABEL), null);

// Fetch the optional "image" property and load it as a drawable.
Drawable imageDrawable = null;
Object imageObject = hashMap.get(TiC.PROPERTY_IMAGE);
if (imageObject != null) {
imageDrawable = TiUIHelper.getResourceDrawable(imageObject);
}

// Fetch the optional "enabled" flag.
boolean isEnabled = TiConvert.toBoolean(hashMap.get(TiC.PROPERTY_ENABLED), true);

// Add the button.
addButton(title, accessibilityLabel, imageDrawable, isEnabled);
}
}
}

private void addButton(String title, String accessibilityLabel, Drawable imageDrawable, boolean isEnabled)
{
// Fetch the button group view.
MaterialButtonToggleGroup buttonGroup = getButtonGroup();
if (buttonGroup == null) {
return;
}

// Title must be non-null.
if (title == null) {
title = "";
}

// Create a button with given settings and add it to view group.
Context context = buttonGroup.getContext();
int attributeId = R.attr.materialButtonOutlinedStyle;
if (title.isEmpty() && (imageDrawable != null)) {
context = new ContextThemeWrapper(context, R.style.Widget_Titanium_OutlinedButton_IconOnly);
attributeId = R.attr.materialButtonToggleGroupStyle;
}
MaterialButton button = new MaterialButton(context, null, attributeId);
button.setEnabled(isEnabled);
button.setText(title);
if ((accessibilityLabel != null) && !accessibilityLabel.isEmpty()) {
button.setContentDescription(accessibilityLabel);
}
if (imageDrawable != null) {
button.setIcon(imageDrawable);
}
if (buttonGroup.getOrientation() != MaterialButtonToggleGroup.HORIZONTAL) {
button.setInsetTop(0);
button.setInsetBottom(0);
}
buttonGroup.addView(button);
}

private void onButtonChecked(ViewGroup viewGroup, int viewId, boolean isChecked)
{
// Validate.
if ((this.proxy == null) || (viewGroup == null)) {
return;
}

// Find the checked/selected view matching the given ID.
for (int index = 0; index < viewGroup.getChildCount(); index++) {
View childView = viewGroup.getChildAt(index);
if ((childView instanceof Checkable) && (childView.getId() == viewId)) {
if (((Checkable) childView).isChecked()) {
// Update the proxy's "index" property.
this.proxy.setProperty(TiC.PROPERTY_INDEX, index);

// Fire a "click" event for selected option.
if (!this.isIgnoringCheckEvent) {
KrollDict data = new KrollDict();
data.put(TiC.PROPERTY_INDEX, index);
this.proxy.fireEvent(TiC.EVENT_CLICK, data);
}
return;
}
}
}
}

private MaterialButtonToggleGroup getButtonGroup()
{
View view = getNativeView();
if (view instanceof MaterialButtonToggleGroup) {
return (MaterialButtonToggleGroup) view;
}
return null;
}
}
2 changes: 1 addition & 1 deletion android/templates/build/ti.constants.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ project.ext {
tiAndroidXAppCompatLibVersion = '1.2.0'
tiAndroidXCoreLibVersion = '1.3.2'
tiAndroidXFragmentLibVersion = '1.2.5'
tiMaterialLibVersion = '1.2.1'
tiMaterialLibVersion = '1.3.0'
tiPlayServicesBaseLibVersion = '17.5.0'
tiManifestPlaceholders = [
tiActivityConfigChanges: 'density|fontScale|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode'
Expand Down