From 007f3334361688bac583164765ab93c9abcd7794 Mon Sep 17 00:00:00 2001 From: Joshua Quick Date: Fri, 23 Apr 2021 21:50:37 -0700 Subject: [PATCH] refactor(android): ti.ui.picker --- .../titanium_ui_date_picker_calendar.xml | 7 + .../titanium_ui_date_picker_spinner.xml | 6 +- .../ui/res/layout/titanium_ui_spinner.xml | 5 - .../wheel/widget/NumericWheelAdapter.java | 131 --- .../kankan/wheel/widget/WheelAdapter.java | 41 - .../java/kankan/wheel/widget/WheelView.java | 809 ------------------ .../modules/titanium/ui/DatePickerProxy.java | 29 - .../titanium/ui/PickerColumnProxy.java | 289 +++---- .../ti/modules/titanium/ui/PickerProxy.java | 567 ++++++------ .../modules/titanium/ui/PickerRowProxy.java | 90 +- .../java/ti/modules/titanium/ui/UIModule.java | 9 + .../ui/widget/picker/CustomDatePicker.java | 52 -- .../picker/FormatNumericWheelAdapter.java | 56 -- .../ui/widget/picker/TextWheelAdapter.java | 77 -- .../ui/widget/picker/TiUIDatePicker.java | 362 +++++--- .../ui/widget/picker/TiUIDateSpinner.java | 514 ----------- .../ui/widget/picker/TiUINativePicker.java | 332 ------- .../titanium/ui/widget/picker/TiUIPicker.java | 73 -- .../ui/widget/picker/TiUIPickerColumn.java | 26 - .../picker/TiUIPlainDropDownPicker.java | 296 +++++++ .../ui/widget/picker/TiUIPlainPicker.java | 29 + .../widget/picker/TiUIPlainSpinnerPicker.java | 197 +++++ .../ui/widget/picker/TiUISpinner.java | 167 ---- .../ui/widget/picker/TiUISpinnerColumn.java | 225 ----- .../ui/widget/picker/TiUISpinnerRow.java | 24 - .../ui/widget/picker/TiUITimePicker.java | 428 ++++++--- .../ui/widget/picker/TiUITimeSpinner.java | 296 ------- .../picker/TiUITimeSpinnerNumberPicker.java | 230 ----- .../java/org/appcelerator/titanium/TiC.java | 3 +- apidoc/Titanium/UI/Picker.yml | 124 ++- apidoc/Titanium/UI/UI.yml | 37 + apidoc/Titanium/UI/iOS/iOS.yml | 12 + iphone/Classes/UIModule.m | 44 + tests/Resources/ti.ui.picker.test.js | 330 ++++++- 34 files changed, 2052 insertions(+), 3865 deletions(-) create mode 100644 android/modules/ui/res/layout/titanium_ui_date_picker_calendar.xml delete mode 100644 android/modules/ui/res/layout/titanium_ui_spinner.xml delete mode 100644 android/modules/ui/src/java/kankan/wheel/widget/NumericWheelAdapter.java delete mode 100644 android/modules/ui/src/java/kankan/wheel/widget/WheelAdapter.java delete mode 100644 android/modules/ui/src/java/kankan/wheel/widget/WheelView.java delete mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/DatePickerProxy.java delete mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/CustomDatePicker.java delete mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/FormatNumericWheelAdapter.java delete mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TextWheelAdapter.java delete mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIDateSpinner.java delete mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUINativePicker.java delete mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPicker.java delete mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPickerColumn.java create mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPlainDropDownPicker.java create mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPlainPicker.java create mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPlainSpinnerPicker.java delete mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUISpinner.java delete mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUISpinnerColumn.java delete mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUISpinnerRow.java delete mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUITimeSpinner.java delete mode 100644 android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUITimeSpinnerNumberPicker.java diff --git a/android/modules/ui/res/layout/titanium_ui_date_picker_calendar.xml b/android/modules/ui/res/layout/titanium_ui_date_picker_calendar.xml new file mode 100644 index 00000000000..2a538bee087 --- /dev/null +++ b/android/modules/ui/res/layout/titanium_ui_date_picker_calendar.xml @@ -0,0 +1,7 @@ + + diff --git a/android/modules/ui/res/layout/titanium_ui_date_picker_spinner.xml b/android/modules/ui/res/layout/titanium_ui_date_picker_spinner.xml index b96dd5f32a9..b7b098eb158 100644 --- a/android/modules/ui/res/layout/titanium_ui_date_picker_spinner.xml +++ b/android/modules/ui/res/layout/titanium_ui_date_picker_spinner.xml @@ -1,5 +1,7 @@ - + android:datePickerMode ="spinner" + android:calendarViewShown="false" + android:spinnersShown="true"/> diff --git a/android/modules/ui/res/layout/titanium_ui_spinner.xml b/android/modules/ui/res/layout/titanium_ui_spinner.xml deleted file mode 100644 index 38681f86b94..00000000000 --- a/android/modules/ui/res/layout/titanium_ui_spinner.xml +++ /dev/null @@ -1,5 +0,0 @@ - - \ No newline at end of file diff --git a/android/modules/ui/src/java/kankan/wheel/widget/NumericWheelAdapter.java b/android/modules/ui/src/java/kankan/wheel/widget/NumericWheelAdapter.java deleted file mode 100644 index 634ec005687..00000000000 --- a/android/modules/ui/src/java/kankan/wheel/widget/NumericWheelAdapter.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2010 Yuri Kanivets - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * MODIFICATIONS: - * Appcelerator Titanium Mobile - * Copyright (c) 2009-2011 by Appcelerator, Inc. All Rights Reserved. - * Licensed under the terms of the Apache Public License - * Please see the LICENSE included with this distribution for details. - */ -package kankan.wheel.widget; - -/** - * Numeric Wheel adapter. - */ -public class NumericWheelAdapter implements WheelAdapter -{ - - /** The default min value */ - public static final int DEFAULT_MAX_VALUE = 9; - - /** The default max value */ - private static final int DEFAULT_MIN_VALUE = 0; - - /** The default max value */ - private static final int DEFAULT_STEP_VALUE = 1; - - // Values - private int minValue; - private int maxValue; - private int stepValue; - - /** - * Default constructor - */ - public NumericWheelAdapter() - { - this(DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE, DEFAULT_STEP_VALUE); - } - - /** - * Constructor - * @param minValue the wheel min value - * @param maxValue the wheel maz value - */ - public NumericWheelAdapter(int minValue, int maxValue) - { - this(minValue, maxValue, DEFAULT_STEP_VALUE); - } - - /** - * Constructor - * @param minValue the wheel min value - * @param maxValue the wheel maz value - * @param stepValue the numeric step value - */ - public NumericWheelAdapter(int minValue, int maxValue, int stepValue) - { - this.minValue = minValue; - this.maxValue = maxValue; - this.stepValue = stepValue; - } - - @Override - public String getItem(int index) - { - if (index >= 0 && index < getItemsCount()) { - int actualValue = minValue + index * stepValue; - return Integer.toString(actualValue); - } - return null; - } - - @Override - public int getItemsCount() - { - int itemCount = ((maxValue - minValue) / stepValue) + 1; - return itemCount; - } - - @Override - public int getMaximumLength() - { - int max = Math.max(Math.abs(maxValue), Math.abs(minValue)); - int maxLen = Integer.toString(max).length(); - if (minValue < 0) { - maxLen++; - } - return maxLen; - } - - public int getMinValue() - { - return minValue; - } - - public int getMaxValue() - { - return maxValue; - } - - public int getValue(int index) - { - int tmpValue = (minValue + index * stepValue); - if (tmpValue > maxValue) - return maxValue; - else - return tmpValue; - } - - public int getIndex(int value) - { - return (value - minValue) / stepValue; - } - public void setStepValue(int value) - { - this.stepValue = value; - } -} diff --git a/android/modules/ui/src/java/kankan/wheel/widget/WheelAdapter.java b/android/modules/ui/src/java/kankan/wheel/widget/WheelAdapter.java deleted file mode 100644 index 3d386a5067e..00000000000 --- a/android/modules/ui/src/java/kankan/wheel/widget/WheelAdapter.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2010 Yuri Kanivets - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package kankan.wheel.widget; - -public interface WheelAdapter { - /** - * Gets items count - * @return the count of wheel items - */ - int getItemsCount(); - - /** - * Gets a wheel item by index. - * - * @param index the item index - * @return the wheel item text or null - */ - String getItem(int index); - - /** - * Gets maximum item length. It is used to determine the wheel width. - * If -1 is returned there will be used the default wheel width. - * - * @return the maximum item length or -1 - */ - int getMaximumLength(); -} diff --git a/android/modules/ui/src/java/kankan/wheel/widget/WheelView.java b/android/modules/ui/src/java/kankan/wheel/widget/WheelView.java deleted file mode 100644 index d6a26a51614..00000000000 --- a/android/modules/ui/src/java/kankan/wheel/widget/WheelView.java +++ /dev/null @@ -1,809 +0,0 @@ -/* - * Android Wheel Control. - * http://android-devblog.blogspot.com/2010/05/wheel-ui-contol.html - * - * Copyright 2010 Yuri Kanivets - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * MODIFICATIONS: - * Appcelerator Titanium Mobile - * Copyright (c) 2009-2013 by Appcelerator, Inc. All Rights Reserved. - * Licensed under the terms of the Apache Public License - * Please see the LICENSE included with this distribution for details. - */ - -package kankan.wheel.widget; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.GradientDrawable.Orientation; -import android.graphics.drawable.LayerDrawable; -import android.text.Layout; -import android.text.StaticLayout; -import android.text.TextPaint; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.MotionEvent; -import android.view.View; - -/** - * Numeric wheel view. - * - * @author Yuri Kanivets - */ -@SuppressWarnings("deprecation") -public class WheelView extends View -{ - private static final int NOVAL = -1; - - /** Current value & label text color */ - private static final int VALUE_TEXT_COLOR = 0xE0000000; - - /** Items text color */ - private static final int ITEMS_TEXT_COLOR = 0xFF000000; - - /** Top and bottom shadows colors */ - private static final int[] SHADOWS_COLORS = new int[] { 0xFF111111, 0x00AAAAAA, 0x00AAAAAA }; - - /** Additional items height (is added to standard text item height) */ - //private static final int ADDITIONAL_ITEM_HEIGHT = 15; - - /** Text size */ - //private static final int TEXT_SIZE = 24; - private int textSize = 24; // default - - /** Top and bottom items offset (to hide that) */ - //private static final int ITEM_OFFSET = TEXT_SIZE / 5; - - /** Additional width for items layout */ - private static final int ADDITIONAL_ITEMS_SPACE = 5; - - /** Label offset */ - private static final int LABEL_OFFSET = 8; - - /** Left and right padding value */ - private static final int PADDING = 10; - - /** Default count of visible items */ - private static final int DEF_VISIBLE_ITEMS = 5; - - // Wheel Values - private WheelAdapter adapter = null; - private int currentItem = 0; - - // Widths - private int itemsWidth = 0; - private int labelWidth = 0; - - // Count of visible items - private int visibleItems = DEF_VISIBLE_ITEMS; - - // Text paints - private TextPaint itemsPaint; - private TextPaint valuePaint; - - // Layouts - private StaticLayout itemsLayout; - private StaticLayout labelLayout; - private StaticLayout valueLayout; - - // Label & background - private String label; - private Drawable centerDrawable; - - // Shadows drawables - private GradientDrawable topShadow; - private GradientDrawable bottomShadow; - - // Last touch Y position - private float lastYTouch; - - private WheelView.OnItemSelectedListener itemSelectedListener; - - private int textColor = NOVAL; - private Typeface typeface = Typeface.DEFAULT; - private int typefaceWeight = Typeface.NORMAL; - private boolean showSelectionIndicator = true; - - /** - * Constructor - */ - public WheelView(Context context, AttributeSet attrs, int defStyle) - { - super(context, attrs, defStyle); - } - - /** - * Constructor - */ - public WheelView(Context context, AttributeSet attrs) - { - super(context, attrs); - } - - /** - * Constructor - */ - public WheelView(Context context) - { - super(context); - } - - /** - * Gets wheel adapter - * @return the adapter - */ - public WheelAdapter getAdapter() - { - return adapter; - } - - /** - * Sets wheel adapter - * @param adapter the new wheel adapter - */ - public void setAdapter(WheelAdapter adapter) - { - this.adapter = adapter; - itemsLayout = null; - valueLayout = null; - invalidate(); - } - - /** - * Gets count of visible items - * - * @return the count of visible items - */ - public int getVisibleItems() - { - return visibleItems; - } - - /** - * Sets count of visible items - * - * @param count - * the new count - */ - public void setVisibleItems(int count) - { - visibleItems = count; - } - - /** - * Gets label - * - * @return the label - */ - public String getLabel() - { - return label; - } - - /** - * Sets label - * - * @param newLabel - * the label to set - */ - public void setLabel(String newLabel) - { - label = newLabel; - labelLayout = null; - invalidate(); - } - - /** - * Gets current value - * - * @return the current value - */ - public int getCurrentItem() - { - return currentItem; - } - - /** - * Sets the current item - * - * @param index the item index - */ - public void setCurrentItem(int index) - { - if (index != currentItem) { - itemsLayout = null; - valueLayout = null; - currentItem = index; - invalidate(); - if (this.itemSelectedListener != null) { - itemSelectedListener.onItemSelected(this, index); - } - } - } - - private void resetTextPainters() - { - TextPaint[] painters = new TextPaint[] { itemsPaint, valuePaint }; - for (int i = 0; i < painters.length; i++) { - TextPaint painter = painters[i]; - if (painter != null) { - int flags = Paint.ANTI_ALIAS_FLAG; - if (typefaceWeight == Typeface.BOLD) { - flags = flags | Paint.FAKE_BOLD_TEXT_FLAG; - } - if (i == 1) { - flags = flags | Paint.DITHER_FLAG; - } - painter.setFlags(flags); - painter.setColor((textColor == NOVAL) ? ITEMS_TEXT_COLOR : textColor); - painter.setTypeface(typeface); - painter.setTextSize(textSize); - } - } - } - - /** - * Initializes resources - */ - private void initResourcesIfNecessary() - { - if (itemsPaint == null) { - if (typefaceWeight == Typeface.BOLD) { - itemsPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.FAKE_BOLD_TEXT_FLAG); - } else { - itemsPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - } - //itemsPaint.density = getResources().getDisplayMetrics().density; - itemsPaint.setTextSize(textSize); - itemsPaint.setTypeface(typeface); - itemsPaint.setColor((textColor == NOVAL) ? ITEMS_TEXT_COLOR : textColor); - } - - if (valuePaint == null) { - if (typefaceWeight == Typeface.BOLD) { - valuePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.FAKE_BOLD_TEXT_FLAG | Paint.DITHER_FLAG); - } else { - valuePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); - } - //valuePaint.density = getResources().getDisplayMetrics().density; - valuePaint.setTextSize(textSize); - valuePaint.setShadowLayer(0.5f, 0, 0.5f, 0xFFFFFFFF); - valuePaint.setTypeface(typeface); - valuePaint.setColor((textColor == NOVAL) ? ITEMS_TEXT_COLOR : textColor); - } - - if (centerDrawable == null) { - centerDrawable = getWheelValDrawable(); //getContext().getResources().getDrawable(R.drawable.wheel_val); - } - - if (topShadow == null) { - topShadow = new GradientDrawable(Orientation.TOP_BOTTOM, SHADOWS_COLORS); - } - - if (bottomShadow == null) { - bottomShadow = new GradientDrawable(Orientation.BOTTOM_TOP, SHADOWS_COLORS); - } - - //setBackgroundResource(R.drawable.wheel_bg); - setBackgroundDrawable(getWheelBackground()); - } - - /** - * Direct programmatic creation of drawables (instead of R & res files) - */ - private int dipToInt(float dips) - { - return Math.round( - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dips, getResources().getDisplayMetrics())); - } - private GradientDrawable makeGradientDrawable(Orientation orientation, int startColor, int centerColor, - int endColor, float strokeDips, int strokeColor) - { - GradientDrawable gd = makeGradientDrawable(orientation, startColor, centerColor, endColor); - gd.setStroke(dipToInt(strokeDips), strokeColor); - return gd; - } - - private GradientDrawable makeGradientDrawable(Orientation orientation, int startColor, int centerColor, - int endColor) - { - int[] colors = new int[] { startColor, centerColor, endColor }; - GradientDrawable gd = new GradientDrawable(orientation, colors); - return gd; - } - - private Drawable getWheelValDrawable() - { - return makeGradientDrawable(Orientation.BOTTOM_TOP, Color.parseColor("#70222222"), - Color.parseColor("#70222222"), Color.parseColor("#70EEEEEE"), 1f, - Color.parseColor("#70333333")); - } - - private Drawable getWheelBackground() - { - Drawable item0 = - makeGradientDrawable(Orientation.BOTTOM_TOP, Color.parseColor("#333333"), Color.parseColor("#DDDDDD"), - Color.parseColor("#333333"), 1f, Color.parseColor("#FF333333")); - Drawable item1 = makeGradientDrawable(Orientation.BOTTOM_TOP, Color.parseColor("#AAAAAA"), - Color.parseColor("#FFFFFF"), Color.parseColor("#AAAAAA")); - LayerDrawable ld = new LayerDrawable(new Drawable[] { item0, item1 }); - ld.setLayerInset(1, dipToInt(4f), dipToInt(1f), dipToInt(4f), dipToInt(1f)); - return ld; - } - - /** - * Calculates desired height for layout - * - * @param layout - * the source layout - * @return the desired layout height - */ - // APPCELERATOR TITANIUM CUSTOMIZATION: - private int getDesiredHeight(Layout layout) - { - // Validate argument. - if (layout == null) { - return 0; - } - - // Calculate height of spinner based on lines rendered in given layout. - // Note: Subtracting offset from height clips the top-most and bottom-most lines in spinner - // on purpose so that end-user knows that there are more items to scroll to. - int desired = layout.getHeight(); - desired -= getItemOffset() * 2; - - // Do not allow desired height to be larger than assigned minimum. - desired = Math.max(desired, getSuggestedMinimumHeight()); - return desired; - } - - /** - * Builds text depending on current value - * - * @return the text - */ - // APPCELERATOR TITANIUM CUSTOMIZATION: - // Must build ellipsized string here because the layout in this class only support one-line items. (TIMOB-14654) - private String buildText(int widthItems) - { - // Empty lines on Android 5.0 and higher have a different line/spacing height compared to non-empty lines. - // Work-Around: Add a zero-width space char to end of every line to ensure they all have same line height. - final String ZERO_WIDTH_SPACE = "\u200B"; - final String LINE_ENDING = ZERO_WIDTH_SPACE + "\n"; - - // Create the text lines above the selection. - WheelAdapter adapter = getAdapter(); - StringBuilder itemsText = new StringBuilder(); - int addItems = visibleItems / 2; - for (int i = currentItem - addItems; i < currentItem; i++) { - if (i >= 0 && adapter != null) { - String text = adapter.getItem(i); - if (text != null) { - // TITANIUM - text = (String) TextUtils.ellipsize(text, itemsPaint, widthItems, TextUtils.TruncateAt.END); - itemsText.append(text); - } - } - itemsText.append(LINE_ENDING); - } - - // Create an empty line where the selected item will be. - itemsText.append(LINE_ENDING); - - // Create the text lines below the selection. - for (int i = currentItem + 1; i <= currentItem + addItems; i++) { - if (adapter != null && i < adapter.getItemsCount()) { - String text = adapter.getItem(i); - if (text != null) { - // TITANIUM - text = (String) TextUtils.ellipsize(text, itemsPaint, widthItems, TextUtils.TruncateAt.END); - itemsText.append(text); - } - } - if (i < currentItem + addItems) { - itemsText.append(LINE_ENDING); - } - } - itemsText.append(ZERO_WIDTH_SPACE); - - // Return the multiline text to be displayed by the spinner. - return itemsText.toString(); - } - - /** - * Returns the max item length that can be present - * @return the max length - */ - private int getMaxTextLength() - { - WheelAdapter adapter = getAdapter(); - if (adapter == null) { - return 0; - } - - int adapterLength = adapter.getMaximumLength(); - if (adapterLength > 0) { - return adapterLength; - } - - String maxText = null; - int addItems = visibleItems / 2; - for (int i = Math.max(currentItem - addItems, 0); - i < Math.min(currentItem + visibleItems, adapter.getItemsCount()); i++) { - String text = adapter.getItem(i); - if (text != null && (maxText == null || maxText.length() < text.length())) { - maxText = text; - } - } - - return maxText != null ? maxText.length() : 0; - } - - /** - * Calculates control width and creates text layouts - * @param widthSize the input layout width - * @param mode the layout mode - * @return the calculated control width - */ - private int calculateLayoutWidth(int widthSize, int mode) - { - initResourcesIfNecessary(); - - int width = widthSize; - - int maxLength = getMaxTextLength(); - if (maxLength > 0) { - float textWidth = (float) Math.ceil(Layout.getDesiredWidth("0", itemsPaint)); - itemsWidth = (int) (maxLength * textWidth); - } else { - itemsWidth = 0; - } - itemsWidth += ADDITIONAL_ITEMS_SPACE; // make it some more - - labelWidth = 0; - if (label != null && label.length() > 0) { - labelWidth = (int) Math.ceil(Layout.getDesiredWidth(label, valuePaint)); - } - - boolean recalculate = false; - if (mode == MeasureSpec.EXACTLY) { - width = widthSize; - recalculate = true; - } else { - width = itemsWidth + labelWidth + 2 * PADDING; - if (labelWidth > 0) { - width += LABEL_OFFSET; - } - - // Check against our minimum width - width = Math.max(width, getSuggestedMinimumWidth()); - - if (mode == MeasureSpec.AT_MOST && widthSize < width) { - width = widthSize; - recalculate = true; - } - } - - if (recalculate) { - // recalculate width - int pureWidth = width - LABEL_OFFSET - 2 * PADDING; - if (pureWidth <= 0) { - itemsWidth = labelWidth = 0; - } - if (labelWidth > 0) { - double newWidthItems = (double) itemsWidth * pureWidth / (itemsWidth + labelWidth); - itemsWidth = (int) newWidthItems; - labelWidth = pureWidth - itemsWidth; - } else { - itemsWidth = pureWidth + LABEL_OFFSET; // no label - } - } - - if (itemsWidth > 0) { - createLayouts(itemsWidth, labelWidth); - } - - return width; - } - - /** - * Creates layouts - * @param widthItems width of items layout - * @param widthLabel width of label layout - */ - private void createLayouts(int widthItems, int widthLabel) - { - if (itemsLayout == null || itemsLayout.getWidth() > widthItems) { - // APPCELERATOR TITANIUM CUSTOMIZATION: - // Must build ellipsized string here because the layout in this class only support one-line items. (TIMOB-14654) - String text = buildText(widthItems); - if (text == null) { - text = ""; - } - itemsLayout = - new StaticLayout(text, 0, text.length(), itemsPaint, widthItems, - widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER, 1, - getAdditionalItemHeight(), false, TextUtils.TruncateAt.END, widthItems); - } else { - itemsLayout.increaseWidthTo(widthItems); - } - - if (valueLayout == null || valueLayout.getWidth() > widthItems) { - String text = getAdapter() != null ? getAdapter().getItem(currentItem) : null; - // APPCELERATOR TITANIUM CUSTOMIZATION: - // Must build ellipsized string here because the layout in this class only support one-line items. (TIMOB-14654) - text = text != null ? (String) TextUtils.ellipsize(text, valuePaint, widthItems, TextUtils.TruncateAt.END) - : null; - valueLayout = - new StaticLayout(text != null ? text : "", 0, text != null ? text.length() : 0, valuePaint, widthItems, - widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER, 1, - getAdditionalItemHeight(), false, TextUtils.TruncateAt.END, widthItems); - } else { - valueLayout.increaseWidthTo(widthItems); - } - - if (widthLabel > 0) { - if (labelLayout == null || labelLayout.getWidth() > widthLabel) { - labelLayout = new StaticLayout(label, valuePaint, widthLabel, Layout.Alignment.ALIGN_NORMAL, 1, - getAdditionalItemHeight(), false); - } else { - labelLayout.increaseWidthTo(widthLabel); - } - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) - { - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int widthSize = MeasureSpec.getSize(widthMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - - int width = calculateLayoutWidth(widthSize, widthMode); - - int height; - if (heightMode == MeasureSpec.EXACTLY) { - height = heightSize; - } else { - height = getDesiredHeight(itemsLayout); - - if (heightMode == MeasureSpec.AT_MOST) { - height = Math.min(height, heightSize); - } - } - - setMeasuredDimension(width, height); - } - - @Override - protected void onDraw(Canvas canvas) - { - super.onDraw(canvas); - - if (itemsLayout == null) { - if (itemsWidth == 0) { - calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY); - } else { - createLayouts(itemsWidth, labelWidth); - } - } - - drawCenterRect(canvas); - - if (itemsWidth > 0) { - canvas.save(); - canvas.translate(PADDING, -getItemOffset()); - drawItems(canvas); - drawValue(canvas); - canvas.restore(); - } - - drawShadows(canvas); - } - - /** - * Draws shadows on top and bottom of control - * @param canvas the canvas for drawing - */ - private void drawShadows(Canvas canvas) - { - topShadow.setBounds(0, 0, getWidth(), getHeight() / visibleItems); - topShadow.draw(canvas); - - bottomShadow.setBounds(0, getHeight() - getHeight() / visibleItems, getWidth(), getHeight()); - bottomShadow.draw(canvas); - } - - /** - * Draws value and label layout - * @param canvas the canvas for drawing - */ - private void drawValue(Canvas canvas) - { - valuePaint.setColor((textColor == NOVAL) ? VALUE_TEXT_COLOR : textColor); - valuePaint.drawableState = getDrawableState(); - - Rect bounds = new Rect(); - itemsLayout.getLineBounds(visibleItems / 2, bounds); - - // draw label - if (labelLayout != null) { - canvas.save(); - canvas.translate(itemsLayout.getWidth() + LABEL_OFFSET, bounds.top); - labelLayout.draw(canvas); - canvas.restore(); - } - - // draw current value - canvas.save(); - canvas.translate(0, bounds.top); - valueLayout.draw(canvas); - canvas.restore(); - } - - /** - * Draws items - * @param canvas the canvas for drawing - */ - private void drawItems(Canvas canvas) - { - itemsPaint.drawableState = getDrawableState(); - itemsLayout.draw(canvas); - } - - /** - * Draws rect for current value - * @param canvas the canvas for drawing - */ - private void drawCenterRect(Canvas canvas) - { - if (!showSelectionIndicator) { - return; - } - int center = getHeight() / 2; - int offset = getHeight() / visibleItems / 2; - centerDrawable.setBounds(0, center - offset, getWidth(), center + offset); - centerDrawable.draw(canvas); - } - - @Override - public boolean onTouchEvent(MotionEvent event) - { - WheelAdapter adapter = getAdapter(); - if (adapter == null) { - return true; - } - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - lastYTouch = event.getY(); - break; - - case MotionEvent.ACTION_MOVE: - float delta = event.getY() - lastYTouch; - //A nasty hack for timob-8470 b/c touch events seem to not register in area above the first row when put in a scrollview. - int count = (int) (visibleItems * delta * 3 / getHeight()); - int pos = currentItem - count; - pos = Math.max(pos, 0); - pos = Math.min(pos, adapter.getItemsCount() - 1); - if (pos != currentItem) { - lastYTouch = event.getY(); - setCurrentItem(pos); - } - break; - case MotionEvent.ACTION_UP: - break; - } - return true; - } - - public interface OnItemSelectedListener { - void onItemSelected(WheelView view, int index); - } - - public void setItemSelectedListener(OnItemSelectedListener listener) - { - this.itemSelectedListener = listener; - } - - private int getAdditionalItemHeight() - { - return (int) (textSize * 0.625); - } - - private int getItemOffset() - { - return (int) (textSize / 5); - } - - public void fullLayoutReset() - { - itemsLayout = null; - valueLayout = null; - requestLayout(); - } - - public void setTextSize(int size) - { - int orig = textSize; - textSize = size; - if (orig != textSize) { - resetTextPainters(); - } - } - - public int getTextSize() - { - return textSize; - } - - public void setTextColor(int color) - { - this.textColor = color; - resetTextPainters(); - invalidate(); - } - - public void setTypeface(Typeface tf) - { - Typeface old = this.typeface; - this.typeface = tf; - if (!old.equals(tf)) { - resetTextPainters(); - } - } - - public Typeface getTypeface() - { - return this.typeface; - } - - public void setTypefaceWeight(int weight) - { - int old = this.typefaceWeight; - this.typefaceWeight = weight; - if (old != weight) { - resetTextPainters(); - } - } - - public int getTypefaceWeight() - { - return this.typefaceWeight; - } - - public void setShowSelectionIndicator(boolean show) - { - boolean oldval = showSelectionIndicator; - showSelectionIndicator = show; - if (oldval != show) { - invalidate(); - } - } - - public boolean getShowSelectionIndicator() - { - return showSelectionIndicator; - } -} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/DatePickerProxy.java b/android/modules/ui/src/java/ti/modules/titanium/ui/DatePickerProxy.java deleted file mode 100644 index ffa987734d0..00000000000 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/DatePickerProxy.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Appcelerator Titanium Mobile - * Copyright (c) 2009-2016 by Appcelerator, 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 org.appcelerator.kroll.annotations.Kroll; -import org.appcelerator.titanium.proxy.TiViewProxy; -import org.appcelerator.titanium.view.TiUIView; - -import ti.modules.titanium.ui.widget.picker.TiUIDatePicker; -import android.app.Activity; - -@Kroll.proxy(creatableInModule = UIModule.class) -public class DatePickerProxy extends TiViewProxy -{ - public DatePickerProxy() - { - super(); - } - - @Override - public TiUIView createView(Activity activity) - { - return new TiUIDatePicker(this); - } -} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/PickerColumnProxy.java b/android/modules/ui/src/java/ti/modules/titanium/ui/PickerColumnProxy.java index 78bf7d3537a..08c76ba866f 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/PickerColumnProxy.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/PickerColumnProxy.java @@ -1,229 +1,220 @@ /** * Appcelerator Titanium Mobile - * Copyright (c) 2009-2016 by Appcelerator, Inc. All Rights Reserved. + * Copyright (c) 2009-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.util.Log; +import java.util.ArrayList; import org.appcelerator.kroll.KrollDict; +import org.appcelerator.kroll.KrollProxy; import org.appcelerator.kroll.annotations.Kroll; -import org.appcelerator.titanium.proxy.TiViewProxy; -import org.appcelerator.titanium.view.TiUIView; - -import ti.modules.titanium.ui.PickerRowProxy.PickerRowListener; -import ti.modules.titanium.ui.widget.picker.TiUIPickerColumn; -import ti.modules.titanium.ui.widget.picker.TiUISpinnerColumn; -import android.app.Activity; -import android.util.Log; +import org.appcelerator.titanium.TiC; -@Kroll.proxy(creatableInModule = UIModule.class) -public class PickerColumnProxy extends TiViewProxy implements PickerRowListener +@Kroll.proxy(creatableInModule = UIModule.class, + propertyAccessors = { + TiC.PROPERTY_WIDTH, +}) +public class PickerColumnProxy extends KrollProxy implements PickerRowProxy.OnChangedListener { - private static final String TAG = "PickerColumnProxy"; - - private PickerColumnListener columnListener = null; - private boolean useSpinner = false; - private boolean suppressListenerEvents = false; + public interface OnChangedListener { + void onChanged(PickerColumnProxy proxy); + } - // Indicate whether this picker column is not created by users. - // Users can directly add picker rows to the picker. In this case, we create a picker column for them and this is - // the only column in the picker. - private boolean createIfMissing = false; + private static final String TAG = "PickerColumnProxy"; + private final ArrayList rowList = new ArrayList<>(); + private final ArrayList listeners = new ArrayList<>(); + private boolean canInvokeListeners = true; - public PickerColumnProxy() + @Override + public void release() { - super(); + super.release(); + + for (PickerRowProxy rowProxy : this.rowList) { + rowProxy.removeListener(this); + } + this.rowList.clear(); } - public void setColumnListener(PickerColumnListener listener) + public void addListener(PickerColumnProxy.OnChangedListener listener) { - columnListener = listener; + if ((listener != null) && !this.listeners.contains(listener)) { + this.listeners.add(listener); + } } - public void setUseSpinner(boolean value) + public void removeListener(PickerColumnProxy.OnChangedListener listener) { - useSpinner = value; + this.listeners.remove(listener); } @Override - public void handleCreationDict(KrollDict dict) - { - super.handleCreationDict(dict); - if (dict.containsKey("rows")) { - Object rowsAtCreation = dict.get("rows"); - if (rowsAtCreation.getClass().isArray()) { - Object[] rowsArray = (Object[]) rowsAtCreation; - addRows(rowsArray); + public void handleCreationDict(KrollDict options) + { + super.handleCreationDict(options); + + if (options.containsKey("rows")) { + Object value = options.get("rows"); + if ((value != null) && value.getClass().isArray()) { + setRows((Object[]) value); + } else { + setRows(null); } } } - @Override - public void add(Object args) + @Kroll.setProperty + public void setFont(KrollDict value) { - handleAddRow((TiViewProxy) args); + setPropertyAndFire(TiC.PROPERTY_FONT, value); + onColumnChanged(); } - private void handleAddRow(TiViewProxy o) - { - if (o == null) - return; - if (o instanceof PickerRowProxy) { - ((PickerRowProxy) o).setRowListener(this); - super.add((PickerRowProxy) o); - if (columnListener != null && !suppressListenerEvents) { - int index = children.indexOf(o); - columnListener.rowAdded(this, index); + @Kroll.method + public void add(Object value) + { + if (value instanceof PickerRowProxy) { + // Add single row. + addRow((PickerRowProxy) value); + } else if ((value != null) && value.getClass().isArray()) { + // Add array of rows. + int rowCount = this.rowList.size(); + boolean wasEnabled = this.canInvokeListeners; + this.canInvokeListeners = true; + for (Object nextObject : (Object[]) value) { + add(nextObject); } - } else { - Log.w(TAG, "add() unsupported argument type: " + o.getClass().getSimpleName()); - } - } - - @Override - public void remove(TiViewProxy o) - { - if (o instanceof PickerRowProxy) { - int index = children.indexOf(o); - super.remove((PickerRowProxy) o); - if (columnListener != null && !suppressListenerEvents) { - columnListener.rowRemoved(this, index); + this.canInvokeListeners = wasEnabled; + if (rowCount != this.rowList.size()) { + onColumnChanged(); } - } else if (o != null) { - Log.w(TAG, "remove() unsupported argment type: " + o.getClass().getSimpleName()); + } else { + Log.w(TAG, "Unable to add row to PickerColumn. Must be of type: Ti.UI.PickerRow"); } } @Kroll.method - public void addRow(Object row) + public void addRow(PickerRowProxy rowProxy) { - if (row instanceof PickerRowProxy) { - this.add((PickerRowProxy) row); - } else { - Log.w(TAG, "Unable to add the row. Invalid type for row."); + // Validate. + if (rowProxy == null) { + return; } - } - protected void addRows(Object[] rows) - { - for (Object row : rows) { - addRow(row); + // Do not continue if already added. + if (this.rowList.contains(rowProxy)) { + return; } + + // Add row to collection. + this.rowList.add(rowProxy); + rowProxy.addListener(this); + + // Notify listeners that row has been added. + onColumnChanged(); } @Kroll.method - public void removeRow(Object row) + public void remove(Object row) { - if (row instanceof PickerRowProxy) { - this.remove((PickerRowProxy) row); - } else { - Log.w(TAG, "Unable to remove the row. Invalid type for row."); - } + removeRow(row); } - @Kroll.getProperty - public PickerRowProxy[] getRows() + @Kroll.method + public void removeAllChildren() { - if (children == null || children.size() == 0) { - return null; - } - return children.toArray(new PickerRowProxy[0]); + setRows(null); } - @Kroll.setProperty - public void setRows(Object[] rows) + @Kroll.method + public void removeRow(Object value) { - try { - suppressListenerEvents = true; - if (children != null && children.size() > 0) { - int count = children.size(); - for (int i = (count - 1); i >= 0; i--) { - remove(children.get(i)); - } - } - addRows(rows); - } finally { - suppressListenerEvents = false; + // Validate argument. + if (!(value instanceof PickerRowProxy)) { + Log.w(TAG, "Unable to remove given row. Must be of type: Ti.UI.PickerRow"); + return; } - if (columnListener != null) { - columnListener.rowsReplaced(this); + PickerRowProxy rowProxy = (PickerRowProxy) value; + + // Fetch index of given row by reference. + int index = this.rowList.indexOf(rowProxy); + if (index < 0) { + return; } + + // Remove given row. + this.rowList.remove(index); + rowProxy.removeListener(this); + + // Notify listeners that row was removed. + onColumnChanged(); } @Kroll.getProperty - public int getRowCount() + public PickerRowProxy[] getRows() { - return children.size(); + return this.rowList.toArray(new PickerRowProxy[0]); } - @Override - public TiUIView createView(Activity activity) + @Kroll.setProperty(retain = false) + public void setRows(Object[] rows) { - if (useSpinner) { - return new TiUISpinnerColumn(this); - } else { - return new TiUIPickerColumn(this); - } - } - - public interface PickerColumnListener { - void rowAdded(PickerColumnProxy column, int rowIndex); - void rowRemoved(PickerColumnProxy column, int oldRowIndex); - void rowChanged(PickerColumnProxy column, int rowIndex); - void rowSelected(PickerColumnProxy column, int rowIndex); - void rowsReplaced(PickerColumnProxy column); // wholesale replace of rows - } + // Temporarily disable listeners. + boolean wasEnabled = this.canInvokeListeners; + this.canInvokeListeners = false; - @Override - public void rowChanged(PickerRowProxy row) - { - if (columnListener != null && !suppressListenerEvents) { - int index = children.indexOf(row); - columnListener.rowChanged(this, index); + // Remove all rows. + while (!this.rowList.isEmpty()) { + removeRow(this.rowList.get(this.rowList.size() - 1)); } - } - public void onItemSelected(int rowIndex) - { - if (columnListener != null && !suppressListenerEvents) { - columnListener.rowSelected(this, rowIndex); + // Add given rows. + if (rows != null) { + for (Object nextRow : rows) { + add(nextRow); + } } + + // Notify listeners that column has changed. + this.canInvokeListeners = wasEnabled; + onColumnChanged(); } - public PickerRowProxy getSelectedRow() + public PickerRowProxy getRowByIndex(int index) { - if (!(peekView() instanceof TiUISpinnerColumn)) { - return null; - } - int rowIndex = ((TiUISpinnerColumn) peekView()).getSelectedRowIndex(); - if (rowIndex < 0) { - return null; - } else { - return (PickerRowProxy) children.get(rowIndex); + if ((index >= 0) && (index < this.rowList.size())) { + return this.rowList.get(index); } + return null; } - public int getThisColumnIndex() + @Kroll.getProperty + public int getRowCount() { - return ((PickerProxy) getParent()).getColumnIndex(this); + return this.rowList.size(); } - public void parentShouldRequestLayout() + @Override + public void onChanged(PickerRowProxy row) { - if (getParent() instanceof PickerProxy) { - ((PickerProxy) getParent()).forceRequestLayout(); - } + onColumnChanged(); } - public void setCreateIfMissing(boolean flag) + private void onColumnChanged() { - createIfMissing = flag; - } + if (!canInvokeListeners) { + return; + } - public boolean getCreateIfMissing() - { - return createIfMissing; + ArrayList clonedListeners = new ArrayList<>(this.listeners); + for (OnChangedListener listener : clonedListeners) { + if (this.listeners.contains(listener)) { + listener.onChanged(this); + } + } } @Override diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/PickerProxy.java b/android/modules/ui/src/java/ti/modules/titanium/ui/PickerProxy.java index cd9daad499b..e1590841504 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/PickerProxy.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/PickerProxy.java @@ -1,6 +1,6 @@ /** * Appcelerator Titanium Mobile - * Copyright (c) 2009-2016 by Appcelerator, Inc. All Rights Reserved. + * Copyright (c) 2009-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. */ @@ -16,70 +16,66 @@ import org.appcelerator.kroll.KrollDict; import org.appcelerator.kroll.KrollFunction; import org.appcelerator.kroll.annotations.Kroll; +import org.appcelerator.titanium.R; import org.appcelerator.titanium.TiApplication; import org.appcelerator.titanium.TiC; +import org.appcelerator.titanium.TiDimension; import org.appcelerator.titanium.proxy.TiViewProxy; import org.appcelerator.titanium.util.TiConvert; +import org.appcelerator.titanium.util.TiUIHelper; import org.appcelerator.titanium.view.TiUIView; -import ti.modules.titanium.ui.PickerColumnProxy.PickerColumnListener; import ti.modules.titanium.ui.widget.picker.TiUIDatePicker; -import ti.modules.titanium.ui.widget.picker.TiUIDateSpinner; -import ti.modules.titanium.ui.widget.picker.TiUINativePicker; -import ti.modules.titanium.ui.widget.picker.TiUIPicker; -import ti.modules.titanium.ui.widget.picker.TiUISpinner; +import ti.modules.titanium.ui.widget.picker.TiUIPlainDropDownPicker; +import ti.modules.titanium.ui.widget.picker.TiUIPlainPicker; +import ti.modules.titanium.ui.widget.picker.TiUIPlainSpinnerPicker; import ti.modules.titanium.ui.widget.picker.TiUITimePicker; -import ti.modules.titanium.ui.widget.picker.TiUITimeSpinner; -import ti.modules.titanium.ui.widget.picker.TiUITimeSpinnerNumberPicker; import android.annotation.SuppressLint; import android.app.Activity; +import android.graphics.Color; import android.util.Log; +import android.view.Gravity; + +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.view.ContextThemeWrapper; import com.google.android.material.datepicker.CalendarConstraints; import com.google.android.material.datepicker.CompositeDateValidator; import com.google.android.material.datepicker.DateValidatorPointBackward; import com.google.android.material.datepicker.DateValidatorPointForward; import com.google.android.material.datepicker.MaterialDatePicker; +import com.google.android.material.textfield.MaterialAutoCompleteTextView; +import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.timepicker.MaterialTimePicker; import com.google.android.material.timepicker.TimeFormat; @Kroll.proxy(creatableInModule = UIModule.class, propertyAccessors = { - TiC.PROPERTY_LOCALE, - TiC.PROPERTY_SELECTION_OPENS, - TiC.PROPERTY_VISIBLE_ITEMS, - TiC.PROPERTY_VALUE, TiC.PROPERTY_CALENDAR_VIEW_SHOWN, + TiC.PROPERTY_DATE_PICKER_STYLE, TiC.PROPERTY_FONT, + TiC.PROPERTY_LOCALE, TiC.PROPERTY_MIN_DATE, - TiC.PROPERTY_MAX_DATE + TiC.PROPERTY_MAX_DATE, + TiC.PROPERTY_SELECTION_OPENS, + TiC.PROPERTY_VALUE }) -public class PickerProxy extends TiViewProxy implements PickerColumnListener +public class PickerProxy extends TiViewProxy implements PickerColumnProxy.OnChangedListener { - private int type = UIModule.PICKER_TYPE_PLAIN; - private ArrayList preselectedRows = new ArrayList<>(); private static final String TAG = "PickerProxy"; - public static final int DEFAULT_VISIBLE_ITEMS_COUNT = 5; + private int type = UIModule.PICKER_TYPE_PLAIN; + private final ArrayList columnList = new ArrayList<>(); + private final ArrayList selectedRows = new ArrayList<>(); private boolean useSpinner = false; - private boolean nativeSpinner = false; - private int lastSelectedIndex = -1; + private boolean canFireColumnEvents = true; public PickerProxy() { super(); defaultValues.put(TiC.PROPERTY_CALENDAR_VIEW_SHOWN, false); - } - - public void setLastSelectedIndex(int index) - { - this.lastSelectedIndex = index; - } - - public int getLastSelectedIndex() - { - return lastSelectedIndex; + defaultValues.put(TiC.PROPERTY_DATE_PICKER_STYLE, UIModule.DATE_PICKER_STYLE_AUTOMATIC); } @Override @@ -87,11 +83,10 @@ public void handleCreationDict(KrollDict dict) { super.handleCreationDict(dict); if (dict.containsKey(TiC.PROPERTY_USE_SPINNER)) { - useSpinner = TiConvert.toBoolean(dict, TiC.PROPERTY_USE_SPINNER); - Log.w(TAG, "The useSpinner property is deprecated. Please refer to the documentation for more information"); + this.useSpinner = TiConvert.toBoolean(dict, TiC.PROPERTY_USE_SPINNER, this.useSpinner); } if (dict.containsKey(TiC.PROPERTY_NATIVE_SPINNER)) { - nativeSpinner = TiConvert.toBoolean(dict, TiC.PROPERTY_NATIVE_SPINNER); + this.useSpinner = TiConvert.toBoolean(dict, TiC.PROPERTY_NATIVE_SPINNER, this.useSpinner); } if (hasProperty("type")) { type = TiConvert.toInt(getProperty("type")); @@ -111,88 +106,110 @@ public TiUIView createView(Activity activity) Log.w(TAG, "Date+Time timer not supported in Titanium for Android"); return null; } else if (type == UIModule.PICKER_TYPE_PLAIN) { - return createPlainPicker(activity, useSpinner); - } else if (type == UIModule.PICKER_TYPE_DATE) { - if (useSpinner) { - return createDateSpinner(activity); + if (this.useSpinner) { + return new TiUIPlainSpinnerPicker(this); } else { - return createDatePicker(activity); + return new TiUIPlainDropDownPicker(this); } + } else if (type == UIModule.PICKER_TYPE_DATE) { + return new TiUIDatePicker(this); } else if (type == UIModule.PICKER_TYPE_TIME) { - if (nativeSpinner) { - return createTimeSpinnerNumberPicker(activity); - } - if (useSpinner) { - return createTimeSpinner(activity); - } else { - return createTimePicker(activity); - } + return new TiUITimePicker(this); } else { Log.w(TAG, "Unknown picker type"); return null; } } - private TiUIView createPlainPicker(Activity activity, boolean useSpinner) - { - TiUIPicker picker = useSpinner ? new TiUISpinner(this, activity) : new TiUINativePicker(this, activity); - return picker; - } - - private TiUIView createDatePicker(Activity activity) - { - return new TiUIDatePicker(this, activity); - } - - private TiUIView createTimePicker(Activity activity) - { - return new TiUITimePicker(this, activity); - } - - private TiUIView createTimeSpinnerNumberPicker(Activity activity) - { - return new TiUITimeSpinnerNumberPicker(this, activity); - } - - private TiUIView createTimeSpinner(Activity activity) + public TextInputLayout createTextInputLayout() { - return new TiUITimeSpinner(this, activity); - } - - private TiUIView createDateSpinner(Activity activity) - { - return new TiUIDateSpinner(this, activity); - } - - @Kroll.getProperty - public boolean getUseSpinner() - { - Log.w(TAG, "The useSpinner property is deprecated. Please refer to the documentation for more information"); - return useSpinner; - } + // Fetch proxy's activity. + Activity activity = getActivity(); + if (activity == null) { + activity = TiApplication.getAppRootOrCurrentActivity(); + if (activity == null) { + return null; + } + } - @Kroll.setProperty - public void setUseSpinner(boolean value) - { - Log.w(TAG, "The useSpinner property is deprecated. Please refer to the documentation for more information"); - if (peekView() != null) { - Log.w(TAG, "Attempt to change useSpinner property after view has already been created. Ignoring."); - } else { - useSpinner = value; - if (children != null && children.size() > 0) { - for (TiViewProxy child : children) { - if (child instanceof PickerColumnProxy) { - ((PickerColumnProxy) child).setUseSpinner(value); - } + // Create the TextInputLayout with drop-down arrow and configured border. + TextInputLayout textInputLayout = null; + int borderStyle = UIModule.INPUT_BORDERSTYLE_FILLED; + borderStyle = TiConvert.toInt(getProperty(TiC.PROPERTY_BORDER_STYLE), borderStyle); + switch (borderStyle) { + case UIModule.INPUT_BORDERSTYLE_BEZEL: + case UIModule.INPUT_BORDERSTYLE_LINE: + case UIModule.INPUT_BORDERSTYLE_ROUNDED: + textInputLayout = new TextInputLayout(new ContextThemeWrapper( + activity, R.style.Widget_MaterialComponents_TextInputLayout_OutlinedBox_ExposedDropdownMenu)); + textInputLayout.setBoxBackgroundMode(TextInputLayout.BOX_BACKGROUND_OUTLINE); + textInputLayout.setBoxBackgroundColor(Color.TRANSPARENT); + if (borderStyle == UIModule.INPUT_BORDERSTYLE_ROUNDED) { + float radius = (new TiDimension("5dp", TiDimension.TYPE_LEFT)).getAsPixels(textInputLayout); + textInputLayout.setBoxCornerRadii(radius, radius, radius, radius); + } else { + textInputLayout.setBoxCornerRadii(0, 0, 0, 0); } + break; + case UIModule.INPUT_BORDERSTYLE_NONE: + case UIModule.INPUT_BORDERSTYLE_UNDERLINED: + textInputLayout = new TextInputLayout(new ContextThemeWrapper( + activity, R.style.Widget_MaterialComponents_TextInputLayout_OutlinedBox_ExposedDropdownMenu)); + textInputLayout.setBoxBackgroundMode(TextInputLayout.BOX_BACKGROUND_NONE); + break; + case UIModule.INPUT_BORDERSTYLE_FILLED: + default: + textInputLayout = new TextInputLayout(new ContextThemeWrapper( + activity, R.style.Widget_MaterialComponents_TextInputLayout_FilledBox_ExposedDropdownMenu)); + textInputLayout.setBoxBackgroundMode(TextInputLayout.BOX_BACKGROUND_FILLED); + break; + } + textInputLayout.setHintEnabled(hasProperty(TiC.PROPERTY_HINT_TEXT)); + if (textInputLayout.isHintEnabled()) { + textInputLayout.setHint(TiConvert.toString(getProperty(TiC.PROPERTY_HINT_TEXT), "")); + } + + // Add a read-only EditText to the layout. + MaterialAutoCompleteTextView editText = null; + if (textInputLayout.getBoxBackgroundMode() != TextInputLayout.BOX_BACKGROUND_NONE) { + editText = new MaterialAutoCompleteTextView(textInputLayout.getContext()); + } else { + editText = new MaterialAutoCompleteTextView(activity); + if (borderStyle == UIModule.INPUT_BORDERSTYLE_NONE) { + editText.setBackground(null); } } + editText.setSingleLine(); + editText.setMaxLines(1); + editText.setGravity(Gravity.CENTER_VERTICAL | Gravity.START); + editText.setInputType(0); + if (textInputLayout.isHintEnabled() == false) { + // Remove extra padding from top since hint text is disabled. + editText.setPadding( + editText.getPaddingLeft(), + editText.getPaddingBottom(), + editText.getPaddingRight(), + editText.getPaddingBottom()); + } + String[] fontProperties = TiUIHelper.getFontProperties(getProperties()); + if ((fontProperties != null) && (fontProperties.length > 0)) { + TiUIHelper.styleText( + editText, + fontProperties[TiUIHelper.FONT_FAMILY_POSITION], + fontProperties[TiUIHelper.FONT_SIZE_POSITION], + fontProperties[TiUIHelper.FONT_WEIGHT_POSITION], + fontProperties[TiUIHelper.FONT_STYLE_POSITION]); + } + textInputLayout.addView(editText, new TextInputLayout.LayoutParams( + TextInputLayout.LayoutParams.MATCH_PARENT, TextInputLayout.LayoutParams.MATCH_PARENT)); + + return textInputLayout; } @Kroll.getProperty public int getType() { - return type; + return this.type; } @Kroll.setProperty @@ -211,94 +228,140 @@ private boolean isPlainPicker() } @Override - public void remove(TiViewProxy child) + public void add(Object child) { - int index = -1; - if (children.contains(child)) { - index = children.indexOf(child); - } - super.remove(child); - if (peekView() instanceof TiUIPicker) { - ((TiUIPicker) peekView()).onColumnRemoved(index); + // If given a view proxy, then let base class remove. + if (child instanceof TiViewProxy) { + remove((TiViewProxy) child); + return; } - } - @Override - public void add(Object child) - { + // Do not continue if not a plain picker. if (!isPlainPicker()) { Log.w(TAG, "Attempt to add to date/time or countdown picker ignored."); return; } if (child instanceof PickerColumnProxy) { - PickerColumnProxy column = (PickerColumnProxy) child; - addColumn(column); + addColumn((PickerColumnProxy) child); } else if (child instanceof PickerRowProxy) { - getFirstColumn(true).add((PickerRowProxy) child); - } else if (child.getClass().isArray()) { - Object[] obj = (Object[]) child; - Object firstObj = obj[0]; - if (firstObj instanceof PickerRowProxy) { - getFirstColumn(true).addRows(obj); - } else if (firstObj instanceof PickerColumnProxy) { - addColumns(obj); + getOrCreateFirstColumn().add(child); + } else if ((child != null) && child.getClass().isArray()) { + Object[] childArray = (Object[]) child; + Object firstObject = childArray[0]; + if (firstObject instanceof PickerRowProxy) { + getOrCreateFirstColumn().add(child); + } else if (firstObject instanceof PickerColumnProxy) { + int columnCount = this.columnList.size(); + boolean wasEnabled = this.canFireColumnEvents; + this.canFireColumnEvents = false; + for (Object nextObject : childArray) { + if (nextObject instanceof PickerColumnProxy) { + addColumn((PickerColumnProxy) nextObject); + } else { + Log.w(TAG, "add() was given an invalid object. Must be of type: "); + } + } + this.canFireColumnEvents = wasEnabled; + if (columnCount != this.columnList.size()) { + onColumnListChanged(); + } } } else { - Log.w(TAG, "Unexpected type not added to picker: " + child.getClass().getName()); + String errorMessage + = "add() method was given an invalid object." + + " Must be given an array of type 'Ti.UI.PickerColumn' or 'Ti.UI.PickerRow"; + Log.w(TAG, errorMessage); } } - private void addColumns(Object[] columns) + private void addColumn(@NonNull PickerColumnProxy column) { - for (Object obj : columns) { - if (obj instanceof PickerColumnProxy) { - addColumn((PickerColumnProxy) obj); - } else { - Log.w(TAG, "Unexpected type not added to picker: " + obj.getClass().getName()); - } + if (this.columnList.contains(column)) { + return; } + + this.columnList.add(column); + this.selectedRows.add(0); + column.addListener(this); + onColumnListChanged(); } - private void addColumn(PickerColumnProxy column) + @Kroll.method + public void remove(Object child) { - prepareColumn(column); - super.add(column); - if (peekView() instanceof TiUIPicker) { - ((TiUIPicker) peekView()).onColumnAdded(children.indexOf(column)); + // If given a view proxy, then let base class remove. + if (child instanceof TiViewProxy) { + remove((TiViewProxy) child); + return; + } + + // Do not continue if not a column. + if (!(child instanceof PickerColumnProxy)) { + Log.w(TAG, "Unable to remove given column. Must be of type: Ti.UI.PickerColumn"); + return; + } + + // Check exists in picker's collection. + int index = this.columnList.indexOf(child); + if (index < 0) { + return; + } + + // Remove column from collection and UI. + this.columnList.remove(index); + if (index < this.selectedRows.size()) { + this.selectedRows.remove(index); } + ((PickerColumnProxy) child).removeListener(this); + onColumnListChanged(); } - private void prepareColumn(PickerColumnProxy column) - { - column.setUseSpinner(useSpinner); - column.setColumnListener(this); + @Override + public void removeAllChildren() + { + // Remove all columns from picker. + this.selectedRows.clear(); + if (!this.columnList.isEmpty()) { + boolean wasEnabled = this.canFireColumnEvents; + this.canFireColumnEvents = false; + while (!this.columnList.isEmpty()) { + remove(this.columnList.get(this.columnList.size() - 1)); + } + this.canFireColumnEvents = wasEnabled; + onColumnListChanged(); + } + + // Remove all view proxies. + super.removeAllChildren(); } @Kroll.method - public void setSelectedRow(int column, int row, @Kroll.argument(optional = true) boolean animated) + public void setSelectedRow(int columnIndex, int rowIndex, @Kroll.argument(optional = true) boolean animated) { + // Do not continue if not a plain picker. if (!isPlainPicker()) { Log.w(TAG, "Selecting row in date/time or countdown picker is not supported."); return; } - TiUIView view = peekView(); - if (view == null) { - // assign it to be selected after view creation - if (preselectedRows == null) { - preselectedRows = new ArrayList<>(); - } - while (preselectedRows.size() < (column + 1)) { - preselectedRows.add(null); - } - if (preselectedRows.size() >= (column + 1)) { - preselectedRows.remove(column); - } - preselectedRows.add(column, row); + + // Add given selection to collection. + // Needs to be stored in case view doesn't exist yet or it is recreated after a dark/light theme change. + if ((columnIndex >= 0) && (columnIndex < this.selectedRows.size())) { + this.selectedRows.set(columnIndex, rowIndex); } else { - ((TiUIPicker) view).selectRow(column, row, animated); + Log.w(TAG, "setSelectedRow() column index is out of range. Given: " + columnIndex); + return; + } + + // Set selection in view if available. + TiUIView view = peekView(); + if (view instanceof TiUIPlainPicker) { + ((TiUIPlainPicker) view).selectRow(columnIndex, rowIndex, animated); if (TiConvert.toBoolean(getProperty(TiC.PROPERTY_SELECTION_OPENS), false)) { - ((TiUIPicker) view).openPicker(); + if (view instanceof TiUIPlainDropDownPicker) { + ((TiUIPlainDropDownPicker) view).openPicker(); + } } } } @@ -306,134 +369,89 @@ public void setSelectedRow(int column, int row, @Kroll.argument(optional = true) @Kroll.method public PickerRowProxy getSelectedRow(int columnIndex) { + int rowIndex = getSelectedRowIndex(columnIndex); + return (rowIndex >= 0) ? getRow(columnIndex, rowIndex) : null; + } + + public int getSelectedRowIndex(int columnIndex) + { + // Do not continue if not a plain picker. if (!isPlainPicker()) { Log.w(TAG, "Cannot get selected row in date/time or countdown picker."); - return null; - } - if (!(peekView() instanceof TiUIPicker)) { - return null; + return -1; } - PickerRowProxy row = null; - if (peekView() instanceof TiUIPicker) { - int rowIndex = ((TiUIPicker) peekView()).getSelectedRowIndex(columnIndex); - if (rowIndex >= 0) { - row = getRow(columnIndex, rowIndex); - } + + // Validate column index. + if ((columnIndex < 0) || (columnIndex >= this.selectedRows.size())) { + return -1; } - return row; + + // Fetch currently selected row index for given column. + return this.selectedRows.get(columnIndex); } + @NonNull @Kroll.getProperty public PickerColumnProxy[] getColumns() { if (!isPlainPicker()) { Log.w(TAG, "Cannot get columns from date/time or countdown picker."); - return null; - } - if (children == null) { - return new PickerColumnProxy[] {}; - } else { - return children.toArray(new PickerColumnProxy[0]); + return new PickerColumnProxy[0]; } + + return this.columnList.toArray(new PickerColumnProxy[0]); } @Kroll.setProperty - public void setColumns(Object passedColumns) + public void setColumns(Object value) { + // Do not continue if not a plain picker. if (!isPlainPicker()) { Log.w(TAG, "Cannot set columns in date/time or countdown picker."); return; } - boolean dirty = false; - try { - if (peekView() instanceof TiUIPicker) { - ((TiUIPicker) peekView()).batchModelChange = true; - } - if (children != null && children.size() > 0) { - int count = children.size(); - for (int i = (count - 1); i >= 0; i--) { - remove(children.get(i)); - dirty = true; - } - } - Object[] columns = null; - if (passedColumns.getClass().isArray()) { - columns = (Object[]) passedColumns; - } else { - columns = new Object[] { passedColumns }; - } - if (!(columns[0] instanceof PickerColumnProxy)) { - Log.w(TAG, "Unexpected object type ignored for setColumns"); - } else { - for (Object o : columns) { - if (o instanceof PickerColumnProxy) { - add((PickerColumnProxy) o); - dirty = true; - } - } - } - } finally { - if (peekView() instanceof TiUIPicker) { - ((TiUIPicker) peekView()).batchModelChange = false; - } - } - if (dirty) { - TiUIPicker pickerView = (TiUIPicker) peekView(); - if (pickerView != null) { - pickerView.onModelReplaced(); - } - } - } - - public int getColumnCount() - { - TiViewProxy[] columns = getColumns(); - if (columns == null) { - return 0; - } else { - return columns.length; - } + // Remove all previous columns and add given columns. + boolean wasEnabled = this.canFireColumnEvents; + this.canFireColumnEvents = false; + removeAllChildren(); + add(value); + this.canFireColumnEvents = wasEnabled; + onColumnListChanged(); } public PickerColumnProxy getColumn(int index) { - if (children == null || index >= children.size() || (!(children.get(index) instanceof PickerColumnProxy))) { - return null; - } else { - return (PickerColumnProxy) children.get(index); + if ((index >= 0) && (index < this.columnList.size())) { + return this.columnList.get(index); } + return null; } - public int getColumnIndex(PickerColumnProxy column) + public int getColumnIndexOf(PickerColumnProxy columnProxy) { - if (children != null && children.size() > 0) { - return children.indexOf(column); - } else { - return -1; - } + return this.columnList.indexOf(columnProxy); } public PickerRowProxy getRow(int columnIndex, int rowIndex) { PickerColumnProxy column = getColumn(columnIndex); - if (column == null) { - return null; - } - TiViewProxy[] rowArray = column.getChildren(); - if (rowArray == null || rowIndex >= rowArray.length || (!(rowArray[rowIndex] instanceof PickerRowProxy))) { - return null; - } else { - return (PickerRowProxy) rowArray[rowIndex]; + if (column != null) { + return column.getRowByIndex(rowIndex); } + return null; + } + + public PickerColumnProxy getFirstColumn() + { + return getColumn(0); } - public PickerColumnProxy getFirstColumn(boolean createIfMissing) + public PickerColumnProxy getOrCreateFirstColumn() { PickerColumnProxy column = getColumn(0); - if (column == null && createIfMissing) { + if (column == null) { column = new PickerColumnProxy(); - column.setCreateIfMissing(true); add(column); } return column; @@ -720,30 +738,20 @@ public void showTimePickerDialog(Object[] args) picker.show(appCompatActivity.getSupportFragmentManager(), picker.toString()); } - private void fireColumnModelChange(int columnIndex) - { - if (peekView() instanceof TiUIPicker) { - ((TiUIPicker) peekView()).onColumnModelChanged(columnIndex); - } - } - - private void fireRowChange(int columnIndex, int rowIndex) + public void fireSelectionChange(int columnIndex, int rowIndex) { - if (peekView() instanceof TiUIPicker) { - ((TiUIPicker) peekView()).onRowChanged(columnIndex, rowIndex); + // Update selection collection. + if ((columnIndex >= 0) && (columnIndex < this.selectedRows.size())) { + this.selectedRows.set(columnIndex, rowIndex); } - } - public void fireSelectionChange(int columnIndex, int rowIndex) - { + // Fire a "change" event with given selection. KrollDict d = new KrollDict(); d.put("columnIndex", columnIndex); d.put("rowIndex", rowIndex); - PickerColumnProxy column = getColumn(columnIndex); - PickerRowProxy row = getRow(columnIndex, rowIndex); - d.put("column", column); - d.put("row", row); - int columnCount = getColumnCount(); + d.put("column", getColumn(columnIndex)); + d.put("row", getRow(columnIndex, rowIndex)); + int columnCount = this.columnList.size(); ArrayList selectedValues = new ArrayList<>(columnCount); for (int i = 0; i < columnCount; i++) { PickerRowProxy rowInColumn = getSelectedRow(i); @@ -754,49 +762,30 @@ public void fireSelectionChange(int columnIndex, int rowIndex) } } d.put("selectedValue", selectedValues.toArray()); - fireEvent("change", d); - } - - @Override - public void rowAdded(PickerColumnProxy column, int rowIndex) - { - fireColumnModelChange(children.indexOf(column)); + fireEvent(TiC.EVENT_CHANGE, d); } - @Override - public void rowRemoved(PickerColumnProxy column, int oldRowIndex) - { - fireColumnModelChange(children.indexOf(column)); - } - - @Override - public void rowsReplaced(PickerColumnProxy column) + public ArrayList getSelectedRows() { - fireColumnModelChange(children.indexOf(column)); + return new ArrayList<>(this.selectedRows); } @Override - public void rowChanged(PickerColumnProxy column, int rowIndex) + public void onChanged(PickerColumnProxy proxy) { - fireRowChange(children.indexOf(column), rowIndex); - } - - @Override - public void rowSelected(PickerColumnProxy column, int rowIndex) - { - int columnIndex = children.indexOf(column); - fireSelectionChange(columnIndex, rowIndex); - } - - public ArrayList getPreselectedRows() - { - return preselectedRows; + TiUIView uiView = peekView(); + if (uiView instanceof TiUIPlainPicker) { + ((TiUIPlainPicker) uiView).onColumnChanged(proxy); + } } - public void forceRequestLayout() + private void onColumnListChanged() { - if (peekView() instanceof TiUISpinner) { - ((TiUISpinner) view).forceRequestLayout(); + if (this.canFireColumnEvents) { + TiUIView uiView = peekView(); + if (uiView instanceof TiUIPlainPicker) { + ((TiUIPlainPicker) uiView).onColumnListChanged(); + } } } diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/PickerRowProxy.java b/android/modules/ui/src/java/ti/modules/titanium/ui/PickerRowProxy.java index 6886d9cf0a9..e49aeb75d7d 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/PickerRowProxy.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/PickerRowProxy.java @@ -1,32 +1,49 @@ /** * Appcelerator Titanium Mobile - * Copyright (c) 2009-2016 by Appcelerator, Inc. All Rights Reserved. + * Copyright (c) 2009-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 java.util.ArrayList; import org.appcelerator.kroll.KrollDict; +import org.appcelerator.kroll.KrollProxy; import org.appcelerator.kroll.annotations.Kroll; -import org.appcelerator.kroll.common.Log; import org.appcelerator.titanium.TiC; -import org.appcelerator.titanium.proxy.TiViewProxy; import org.appcelerator.titanium.util.TiConvert; -import org.appcelerator.titanium.view.TiUIView; - -import ti.modules.titanium.ui.widget.picker.TiUISpinnerRow; -import android.app.Activity; @Kroll.proxy(creatableInModule = UIModule.class) -public class PickerRowProxy extends TiViewProxy +public class PickerRowProxy extends KrollProxy { + public interface OnChangedListener { + void onChanged(PickerRowProxy proxy); + } + private static final String TAG = "PickerRowProxy"; + private final ArrayList listeners = new ArrayList<>(); private String title = "[PickerRow]"; - private PickerRowListener rowListener = null; - public PickerRowProxy() + public void addListener(PickerRowProxy.OnChangedListener listener) + { + if ((listener != null) && !this.listeners.contains(listener)) { + this.listeners.add(listener); + } + } + + public void removeListener(PickerRowProxy.OnChangedListener listener) + { + this.listeners.remove(listener); + } + + @Override + public void handleCreationDict(KrollDict options) { - super(); + super.handleCreationDict(options); + + if (options.containsKey("title")) { + this.title = TiConvert.toString(options, "title"); + } } @Kroll.getProperty @@ -39,9 +56,7 @@ public String getColor() public void setColor(String color) { setPropertyAndFire(TiC.PROPERTY_COLOR, color); - if (rowListener != null) { - rowListener.rowChanged(this); - } + onRowChanged(); } @Kroll.getProperty @@ -53,54 +68,27 @@ public String getTitle() @Kroll.setProperty public void setTitle(String value) { - title = value; - if (rowListener != null) { - rowListener.rowChanged(this); - } + this.title = value; + setPropertyAndFire(TiC.PROPERTY_TITLE, this.title); + onRowChanged(); } @Override public String toString() { - return title; + return this.title; } - public void setRowListener(PickerRowListener listener) + private void onRowChanged() { - rowListener = listener; - } - - @Override - public void add(Object args) - { - Log.w(TAG, "PickerRow does not support child controls"); - } - - @Override - public void remove(TiViewProxy child) - { - Log.w(TAG, "PickerRow does not support child controls"); - } - - @Override - public TiUIView createView(Activity activity) - { - return new TiUISpinnerRow(this); - } - - @Override - public void handleCreationDict(KrollDict options) - { - super.handleCreationDict(options); - if (options.containsKey("title")) { - title = TiConvert.toString(options, "title"); + ArrayList clonedListeners = new ArrayList<>(this.listeners); + for (PickerRowProxy.OnChangedListener listener : clonedListeners) { + if (this.listeners.contains(listener)) { + listener.onChanged(this); + } } } - public interface PickerRowListener { - void rowChanged(PickerRowProxy row); - } - @Override public String getApiName() { diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/UIModule.java b/android/modules/ui/src/java/ti/modules/titanium/ui/UIModule.java index bfc8da941a9..2d95e50f41c 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/UIModule.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/UIModule.java @@ -208,6 +208,15 @@ public class UIModule extends KrollModule @Kroll.constant public static final int BUTTON_STYLE_OPTION_NEUTRAL = 6; + @Kroll.constant + public static final int DATE_PICKER_STYLE_AUTOMATIC = 1; + @Kroll.constant + public static final int DATE_PICKER_STYLE_COMPACT = 2; + @Kroll.constant + public static final int DATE_PICKER_STYLE_INLINE = 3; + @Kroll.constant + public static final int DATE_PICKER_STYLE_WHEELS = 4; + @Kroll.constant public static final int INPUT_BORDERSTYLE_NONE = 0; @Kroll.constant diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/CustomDatePicker.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/CustomDatePicker.java deleted file mode 100644 index 83ee98520af..00000000000 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/CustomDatePicker.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Appcelerator Titanium Mobile - * Copyright (c) 2009-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.picker; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.DatePicker; - -import org.appcelerator.kroll.common.Log; -import org.appcelerator.titanium.proxy.TiViewProxy; -import org.appcelerator.titanium.util.TiUIHelper; - -public class CustomDatePicker extends DatePicker -{ - - private TiViewProxy proxy = null; - - public void setProxy(TiViewProxy value) - { - this.proxy = value; - } - - public CustomDatePicker(Context context) - { - super(context); - } - - public CustomDatePicker(Context context, AttributeSet attrs) - { - super(context, attrs); - } - - public CustomDatePicker(Context context, AttributeSet attrs, int defStyleAttr) - { - super(context, attrs, defStyleAttr); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) - { - super.onLayout(changed, left, top, right, bottom); - if (this.proxy != null && changed) { - TiUIHelper.firePostLayoutEvent(this.proxy); - } else { - Log.w("Picker", "Proxy not assigned to a native view!"); - } - } -} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/FormatNumericWheelAdapter.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/FormatNumericWheelAdapter.java deleted file mode 100644 index ba626614d0e..00000000000 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/FormatNumericWheelAdapter.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Appcelerator Titanium Mobile - * Copyright (c) 2009-2011 by Appcelerator, 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.picker; - -import java.text.NumberFormat; - -import kankan.wheel.widget.NumericWheelAdapter; - -public class FormatNumericWheelAdapter extends NumericWheelAdapter -{ - private NumberFormat formatter; - private int maxCharacterLength = 2; - - public FormatNumericWheelAdapter(int minValue, int maxValue, NumberFormat formatter, int maxCharLength) - { - this(minValue, maxValue, formatter, maxCharLength, 1); - } - - public FormatNumericWheelAdapter(int minValue, int maxValue, NumberFormat formatter, int maxCharLength, - int stepValue) - { - super(minValue, maxValue, stepValue); - this.formatter = formatter; - this.maxCharacterLength = maxCharLength; - } - - public void setFormatter(NumberFormat formatter) - { - this.formatter = formatter; - } - @Override - public String getItem(int index) - { - int actualValue = getValue(index); - if (formatter == null) { - return Integer.toString(actualValue); - } else { - return formatter.format(actualValue); - } - } - @Override - public int getMaximumLength() - { - return maxCharacterLength; - } - - public void setMaximumLength(int value) - { - maxCharacterLength = value; - } -} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TextWheelAdapter.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TextWheelAdapter.java deleted file mode 100644 index 41e70254b73..00000000000 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TextWheelAdapter.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Appcelerator Titanium Mobile - * Copyright (c) 2009-2011 by Appcelerator, 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.picker; - -import java.util.ArrayList; -import java.util.Arrays; - -import kankan.wheel.widget.WheelAdapter; - -public class TextWheelAdapter implements WheelAdapter -{ - private ArrayList values = null; - private int maxLength; - - public TextWheelAdapter(ArrayList values) - { - setValues(values); - } - - public TextWheelAdapter(Object[] values) - { - this(new ArrayList<>(Arrays.asList(values))); - } - - @Override - public String getItem(int index) - { - if (values == null || index < values.size()) { - return values.get(index).toString(); - } else { - throw new ArrayIndexOutOfBoundsException(index); - } - } - - @Override - public int getMaximumLength() - { - return maxLength; - } - - private int calcMaxLength() - { - if (values == null) { - return 0; // TODO really? - } else { - int max = 0; - for (Object o : values) { - max = Math.max(max, o.toString().length()); - } - return max; - } - } - - public void setValues(Object[] newValues) - { - setValues(new ArrayList<>(Arrays.asList(newValues))); - } - - public void setValues(ArrayList newValues) - { - if (values != null) - values.clear(); - this.values = newValues; - this.maxLength = calcMaxLength(); - } - - @Override - public int getItemsCount() - { - return (values == null) ? 0 : values.size(); - } -} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIDatePicker.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIDatePicker.java index a8d67c3eed8..4e03ef419c9 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIDatePicker.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIDatePicker.java @@ -1,62 +1,130 @@ /** * Appcelerator Titanium Mobile - * Copyright (c) 2009-2012 by Appcelerator, Inc. All Rights Reserved. + * Copyright (c) 2009-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.picker; +import android.os.Build; +import android.view.View; +import android.widget.DatePicker; +import android.widget.DatePicker.OnDateChangedListener; +import android.widget.EditText; +import androidx.annotation.NonNull; +import com.google.android.material.textfield.TextInputLayout; +import java.text.DateFormat; import java.util.Calendar; import java.util.Date; - +import java.util.HashMap; import org.appcelerator.kroll.KrollDict; +import org.appcelerator.kroll.KrollFunction; +import org.appcelerator.kroll.KrollObject; import org.appcelerator.kroll.KrollProxy; import org.appcelerator.kroll.common.Log; +import org.appcelerator.titanium.R; import org.appcelerator.titanium.TiC; -import org.appcelerator.titanium.proxy.TiViewProxy; import org.appcelerator.titanium.util.TiConvert; -import org.appcelerator.titanium.util.TiRHelper; -import org.appcelerator.titanium.util.TiRHelper.ResourceNotFoundException; +import org.appcelerator.titanium.util.TiUIHelper; import org.appcelerator.titanium.view.TiUIView; - -import android.app.Activity; -import android.widget.DatePicker; -import android.widget.DatePicker.OnDateChangedListener; +import ti.modules.titanium.ui.PickerProxy; +import ti.modules.titanium.ui.UIModule; public class TiUIDatePicker extends TiUIView implements OnDateChangedListener { - private boolean suppressChangeEvent = false; private static final String TAG = "TiUIDatePicker"; + private TiUIDatePicker.DialogCallback dialogCallback; + private boolean suppressChangeEvent; + private Date minDate; + private Date maxDate; - protected Date minDate; - protected Date maxDate; - protected int minuteInterval; - - public TiUIDatePicker(TiViewProxy proxy) + public TiUIDatePicker(@NonNull PickerProxy proxy) { super(proxy); - } - public TiUIDatePicker(final TiViewProxy proxy, Activity activity) - { - this(proxy); Log.d(TAG, "Creating a date picker", Log.DEBUG_MODE); - // A bug where PickerCalendarDelegate does not send events to the - // listener on API Level 21 (Android 5.0) for TIMOB-19192 - // https://code.google.com/p/android/issues/detail?id=147657 - // Work around is to use spinner view instead of calendar view - int datePickerSpinner; - try { - datePickerSpinner = TiRHelper.getResource("layout.titanium_ui_date_picker_spinner"); - } catch (ResourceNotFoundException e) { - if (Log.isDebugModeEnabled()) { - Log.e(TAG, "XML resources could not be found!!!"); + // Determine if we should use a spinner, calendar view, or text field. + boolean useTextField = false; + boolean useSpinner = false; + if (proxy.hasProperty(TiC.PROPERTY_DATE_PICKER_STYLE)) { + switch (TiConvert.toInt(proxy.getProperty(TiC.PROPERTY_DATE_PICKER_STYLE))) { + case UIModule.DATE_PICKER_STYLE_AUTOMATIC: + case UIModule.DATE_PICKER_STYLE_COMPACT: + useTextField = true; + break; + case UIModule.DATE_PICKER_STYLE_INLINE: + useSpinner = false; // Use calendar view. + break; + case UIModule.DATE_PICKER_STYLE_WHEELS: + useSpinner = true; + break; + default: + break; } - return; } - CustomDatePicker picker = (CustomDatePicker) activity.getLayoutInflater().inflate(datePickerSpinner, null); - picker.setProxy(getProxy()); - setNativeView(picker); + if (proxy.hasPropertyAndNotNull(TiC.PROPERTY_USE_SPINNER)) { + useSpinner = TiConvert.toBoolean(proxy.getProperty(TiC.PROPERTY_USE_SPINNER), useSpinner); + if (useSpinner) { + useTextField = false; + } + } + if (proxy.hasPropertyAndNotNull(TiC.PROPERTY_NATIVE_SPINNER)) { + useSpinner = TiConvert.toBoolean(proxy.getProperty(TiC.PROPERTY_NATIVE_SPINNER), useSpinner); + if (useSpinner) { + useTextField = false; + } + } + if (TiConvert.toBoolean(proxy.getProperty(TiC.PROPERTY_CALENDAR_VIEW_SHOWN), false)) { + useSpinner = false; + useTextField = false; + } + if (!useSpinner && (Build.VERSION.SDK_INT == 21)) { + // Android 5.0 fails to call onDateChanged() for calendar view. (Android 5.1+ is okay.) + // See: https://code.google.com/p/android/issues/detail?id=147657 + Log.w(TAG, "Ti.UI.Picker cannot show an inlined calendar view on Android 5.0. Using spinner instead."); + useSpinner = true; + } + + // Create the date picker view. + View view = null; + if (useTextField) { + // Attempt to create a text field which will show a date selection dialog when tapped on. + TextInputLayout textInputLayout = proxy.createTextInputLayout(); + if ((textInputLayout != null) && (textInputLayout.getEditText() != null)) { + this.dialogCallback = new DialogCallback(this); + View.OnClickListener clickListener = (View v) -> { + textInputLayout.requestFocus(); + HashMap options = new HashMap<>(); + options.put(TiC.PROPERTY_CALLBACK, this.dialogCallback); + proxy.showDatePickerDialog(new Object[] { options }); + }; + textInputLayout.getEditText().setOnClickListener(clickListener); + textInputLayout.setEndIconOnClickListener(clickListener); + view = textInputLayout; + } + } + if (view == null) { + // Create a spinner or calendar view. + int layoutId; + if (useSpinner) { + layoutId = R.layout.titanium_ui_date_picker_spinner; + } else { + layoutId = R.layout.titanium_ui_date_picker_calendar; + } + DatePicker datePicker = (DatePicker) proxy.getActivity().getLayoutInflater().inflate(layoutId, null); + view = datePicker; + } + view.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()); + } + }); + + setNativeView(view); } @Override @@ -64,70 +132,79 @@ public void processProperties(KrollDict d) { super.processProperties(d); - boolean valueExistsInProxy = false; - Calendar calendar = Calendar.getInstance(); - DatePicker picker = (DatePicker) getNativeView(); + DatePicker datePicker = getDatePicker(); - if (d.containsKey(TiC.PROPERTY_VALUE)) { - calendar.setTime((Date) d.get(TiC.PROPERTY_VALUE)); - valueExistsInProxy = true; - } + // Apply min/max properties. if (d.containsKey(TiC.PROPERTY_MIN_DATE)) { this.minDate = createDateWithoutTimeFrom((Date) d.get(TiC.PROPERTY_MIN_DATE)); - picker.setMinDate(this.minDate.getTime()); - } - if (d.containsKey(TiC.PROPERTY_CALENDAR_VIEW_SHOWN)) { - setCalendarView(TiConvert.toBoolean(d, TiC.PROPERTY_CALENDAR_VIEW_SHOWN)); } if (d.containsKey(TiC.PROPERTY_MAX_DATE)) { this.maxDate = createDateWithoutTimeFrom((Date) d.get(TiC.PROPERTY_MAX_DATE)); - picker.setMaxDate(this.maxDate.getTime()); } - if (d.containsKey(TiC.PROPERTY_MINUTE_INTERVAL)) { - int mi = d.getInt(TiC.PROPERTY_MINUTE_INTERVAL); - if (mi >= 1 && mi <= 30 && mi % 60 == 0) { - this.minuteInterval = mi; + if ((this.minDate != null) && (this.maxDate != null) && (this.maxDate.compareTo(this.minDate) <= 0)) { + Log.w(TAG, "maxDate is less or equal minDate, ignoring both settings."); + this.minDate = null; + this.maxDate = null; + } + if (datePicker != null) { + if (this.minDate != null) { + datePicker.setMinDate(this.minDate.getTime()); + } + if (this.maxDate != null) { + datePicker.setMaxDate(this.maxDate.getTime()); } } - suppressChangeEvent = true; - picker.init(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), - this); - suppressChangeEvent = false; - if (!valueExistsInProxy) { - proxy.setProperty(TiC.PROPERTY_VALUE, calendar.getTime()); + // Fetch date value and display it in the view. + Calendar calendar = Calendar.getInstance(); + if (d.containsKeyAndNotNull(TiC.PROPERTY_VALUE)) { + calendar.setTime(TiConvert.toDate(d.get(TiC.PROPERTY_VALUE))); + } + if (datePicker != null) { + this.suppressChangeEvent = true; + datePicker.init( + calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), this); + this.suppressChangeEvent = false; + } else { + setValue(calendar.getTime(), true); } - //iPhone ignores both values if max <= min - if (minDate != null && maxDate != null) { - if (maxDate.compareTo(minDate) <= 0) { - Log.w(TAG, "maxDate is less or equal minDate, ignoring both settings."); - minDate = null; - maxDate = null; - } + // Update proxy's "value" property with above time if not assigned. + if (proxy.getProperty(TiC.PROPERTY_VALUE) == null) { + proxy.setProperty(TiC.PROPERTY_VALUE, calendar.getTime()); } } @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_VALUE)) { - Date date = (Date) newValue; - setValue(date.getTime()); - } else if (key.equals(TiC.PROPERTY_CALENDAR_VIEW_SHOWN)) { - setCalendarView(TiConvert.toBoolean(newValue)); + setValue((Date) newValue); } else if (TiC.PROPERTY_MIN_DATE.equals(key)) { this.minDate = createDateWithoutTimeFrom((Date) newValue); - ((DatePicker) getNativeView()).setMinDate(this.minDate.getTime()); + if (getDatePicker() != null) { + getDatePicker().setMinDate(this.minDate.getTime()); + } } else if (TiC.PROPERTY_MAX_DATE.equals(key)) { this.maxDate = createDateWithoutTimeFrom((Date) newValue); - ((DatePicker) getNativeView()).setMaxDate(this.maxDate.getTime()); + if (getDatePicker() != null) { + getDatePicker().setMaxDate(this.maxDate.getTime()); + } + } else { + super.propertyChanged(key, oldValue, newValue, proxy); } - super.propertyChanged(key, oldValue, newValue, proxy); } + @Override public void onDateChanged(DatePicker picker, int year, int monthOfYear, int dayOfMonth) { + // Store given date without time of day component set to zero. Calendar targetCalendar = Calendar.getInstance(); targetCalendar.set(Calendar.YEAR, year); targetCalendar.set(Calendar.MONTH, monthOfYear); @@ -137,58 +214,70 @@ public void onDateChanged(DatePicker picker, int year, int monthOfYear, int dayO targetCalendar.set(Calendar.SECOND, 0); targetCalendar.set(Calendar.MILLISECOND, 0); - if ((null != this.minDate) && (targetCalendar.getTime().before(this.minDate))) { + // Apply min/max bounds to selected date. (Should never happen, but better safe than sorry.) + if ((null != this.minDate) && targetCalendar.getTime().before(this.minDate)) { targetCalendar.setTime(this.minDate); - setValue(this.minDate.getTime(), true); - } - if ((null != this.maxDate) && (targetCalendar.getTime().after(this.maxDate))) { + setValue(this.minDate, true); + } else if ((null != this.maxDate) && targetCalendar.getTime().after(this.maxDate)) { targetCalendar.setTime(this.maxDate); - setValue(this.maxDate.getTime(), true); + setValue(this.maxDate, true); } - Date newTime = targetCalendar.getTime(); - Object oTime = proxy.getProperty(TiC.PROPERTY_VALUE); - Date oldTime = null; - - if (oTime instanceof Date) { - oldTime = (Date) oTime; + // Do not continue if proxy was released. + if (this.proxy == null) { + return; } - // Due to a native Android bug in 4.x, this callback is called twice, so here - // we check if the dates are identical, we don't fire "change" event or reset value. - if (oldTime != null && oldTime.equals(newTime)) { + // Do not continue if date hasn't changed. (We should only fire event if changed.) + Date newTime = targetCalendar.getTime(); + Date oldTime = TiConvert.toDate(this.proxy.getProperty(TiC.PROPERTY_VALUE)); + if ((oldTime != null) && oldTime.equals(newTime)) { return; } - if (!suppressChangeEvent) { + // Update "value" property with selected date. + this.proxy.setProperty(TiC.PROPERTY_VALUE, newTime); + + // Fire a "change" event. + if (!this.suppressChangeEvent) { KrollDict data = new KrollDict(); data.put(TiC.PROPERTY_VALUE, newTime); fireEvent(TiC.EVENT_CHANGE, data); } - - proxy.setProperty(TiC.PROPERTY_VALUE, newTime); } - public void setValue(long value) + public void setValue(Date value) { setValue(value, false); } - public void setValue(long value, boolean suppressEvent) + public void setValue(Date value, boolean suppressEvent) { - DatePicker picker = (DatePicker) getNativeView(); - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(value); - suppressChangeEvent = suppressEvent; - picker.updateDate(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), - calendar.get(Calendar.DAY_OF_MONTH)); - suppressChangeEvent = false; - } + // Update DatePicker view if used. + DatePicker datePicker = getDatePicker(); + if (datePicker != null) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(value.getTime()); + this.suppressChangeEvent = suppressEvent; + datePicker.updateDate( + calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); + this.suppressChangeEvent = false; + return; + } - public void setCalendarView(boolean value) - { - DatePicker picker = (DatePicker) getNativeView(); - picker.setCalendarViewShown(value); + // We're likely using a text field instead. Format date to localized string. + EditText editText = null; + View view = getNativeView(); + if (view instanceof TextInputLayout) { + editText = ((TextInputLayout) view).getEditText(); + } else if (view instanceof EditText) { + editText = (EditText) view; + } + if (editText != null) { + DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT); + editText.setText(dateFormat.format(value)); + editText.requestLayout(); + } } private Date createDateWithoutTimeFrom(Date value) @@ -205,4 +294,77 @@ private Date createDateWithoutTimeFrom(Date value) calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); } + + private DatePicker getDatePicker() + { + View view = getNativeView(); + if (view instanceof DatePicker) { + return (DatePicker) view; + } + return null; + } + + /** Callback to be passed to PickerProxy.showDatePickerDialog() method to acquire selected date. */ + private static class DialogCallback implements KrollFunction + { + private TiUIDatePicker picker; + + public DialogCallback(@NonNull TiUIDatePicker picker) + { + this.picker = picker; + } + + @Override + public Object call(KrollObject krollObject, HashMap args) + { + callAsync(krollObject, args); + return null; + } + + @Override + public Object call(KrollObject krollObject, Object[] args) + { + callAsync(krollObject, args); + return null; + } + + @Override + public void callAsync(KrollObject krollObject, HashMap args) + { + // Validate. + if (args == null) { + return; + } + + // Fetch selected date "value". Property won't be defined if user canceled out. + Object objectValue = args.get(TiC.PROPERTY_VALUE); + if (!(objectValue instanceof Date)) { + return; + } + Date dateValue = (Date) objectValue; + + // Make sure selected date does not exceed min/max bounds. (Should never happen.) + if ((this.picker.minDate != null) && dateValue.before(this.picker.minDate)) { + dateValue = this.picker.minDate; + } else if ((this.picker.maxDate != null) && dateValue.after(this.picker.maxDate)) { + dateValue = this.picker.maxDate; + } + + // Show selected date in text field. + this.picker.setValue(dateValue); + + // Fire a "change" event. + KrollDict data = new KrollDict(); + data.put(TiC.PROPERTY_VALUE, dateValue); + this.picker.fireEvent(TiC.EVENT_CHANGE, data); + } + + @Override + public void callAsync(KrollObject krollObject, Object[] args) + { + if ((args != null) && (args.length > 0) && (args[0] instanceof HashMap)) { + callAsync(krollObject, (HashMap) args[0]); + } + } + } } diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIDateSpinner.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIDateSpinner.java deleted file mode 100644 index 7d7ac359a2b..00000000000 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIDateSpinner.java +++ /dev/null @@ -1,514 +0,0 @@ -/** - * Appcelerator Titanium Mobile - * Copyright (c) 2009-2012 by Appcelerator, 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.picker; - -import java.text.DateFormatSymbols; -import java.text.DecimalFormat; -import java.text.FieldPosition; -import java.text.NumberFormat; -import java.text.ParsePosition; -import java.util.Calendar; -import java.util.Date; -import java.util.Locale; - -import kankan.wheel.widget.WheelView; - -import org.appcelerator.kroll.KrollDict; -import org.appcelerator.kroll.KrollProxy; -import org.appcelerator.kroll.common.Log; -import org.appcelerator.titanium.TiC; -import org.appcelerator.titanium.proxy.TiViewProxy; -import org.appcelerator.titanium.util.TiConvert; -import org.appcelerator.titanium.util.TiUIHelper; -import org.appcelerator.titanium.view.TiUIView; - -import android.app.Activity; -import android.graphics.Typeface; -import android.view.ViewGroup.LayoutParams; -import android.widget.LinearLayout; - -public class TiUIDateSpinner extends TiUIView implements WheelView.OnItemSelectedListener -{ - private static final String TAG = "TiUIDateSpinner"; - private WheelView monthWheel; - private WheelView dayWheel; - private WheelView yearWheel; - - private FormatNumericWheelAdapter monthAdapter; - private FormatNumericWheelAdapter dayAdapter; - private FormatNumericWheelAdapter yearAdapter; - - private boolean suppressChangeEvent = false; - private boolean ignoreItemSelection = false; - - private final Calendar maxDate = Calendar.getInstance(); - private final Calendar minDate = Calendar.getInstance(); - private Locale locale = Locale.getDefault(); - private boolean dayBeforeMonth = false; - private boolean numericMonths = false; - private final Calendar calendar = Calendar.getInstance(); - - public TiUIDateSpinner(TiViewProxy proxy) - { - super(proxy); - } - public TiUIDateSpinner(TiViewProxy proxy, Activity activity) - { - this(proxy); - createNativeView(activity); - } - - private void createNativeView(Activity activity) - { - // defaults - maxDate.set(calendar.get(Calendar.YEAR) + 100, 11, 31); - minDate.set(calendar.get(Calendar.YEAR) - 100, 0, 1); - - monthWheel = new WheelView(activity); - dayWheel = new WheelView(activity); - yearWheel = new WheelView(activity); - - monthWheel.setTextSize(20); - dayWheel.setTextSize(monthWheel.getTextSize()); - yearWheel.setTextSize(monthWheel.getTextSize()); - - monthWheel.setItemSelectedListener(this); - dayWheel.setItemSelectedListener(this); - yearWheel.setItemSelectedListener(this); - - LinearLayout layout = new LinearLayout(activity) { - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) - { - super.onLayout(changed, left, top, right, bottom); - TiUIHelper.firePostLayoutEvent(proxy); - } - }; - layout.setOrientation(LinearLayout.HORIZONTAL); - - if (proxy.hasProperty(TiC.PROPERTY_DAY_BEFORE_MONTH)) { - // TODO dayBeforeMonth = TiConvert.toBoolean(proxy.getProperties(), "dayBeforeMonth"); - } - - if (dayBeforeMonth) { - addViewToPicker(dayWheel, layout); - addViewToPicker(monthWheel, layout); - } else { - addViewToPicker(monthWheel, layout); - addViewToPicker(dayWheel, layout); - } - - addViewToPicker(yearWheel, layout); - setNativeView(layout); - } - - private void addViewToPicker(WheelView v, LinearLayout layout) - { - layout.addView( - v, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, (float) .33)); - } - - @Override - public void processProperties(KrollDict d) - { - super.processProperties(d); - - boolean valueExistsInProxy = false; - - if (d.containsKey(TiC.PROPERTY_VALUE)) { - calendar.setTime((Date) d.get(TiC.PROPERTY_VALUE)); - valueExistsInProxy = true; - } - - if (d.containsKey(TiC.PROPERTY_MIN_DATE)) { - Calendar c = Calendar.getInstance(); - minDate.setTime(TiConvert.toDate(d, TiC.PROPERTY_MIN_DATE)); - c.setTime(minDate.getTime()); - } - - if (d.containsKey(TiC.PROPERTY_MAX_DATE)) { - Calendar c = Calendar.getInstance(); - maxDate.setTime(TiConvert.toDate(d, TiC.PROPERTY_MAX_DATE)); - c.setTime(maxDate.getTime()); - } - - if (d.containsKey(TiC.PROPERTY_LOCALE)) { - setLocale(TiConvert.toString(d, TiC.PROPERTY_LOCALE)); - } - - if (d.containsKey(TiC.PROPERTY_DAY_BEFORE_MONTH)) { - dayBeforeMonth = TiConvert.toBoolean(d, TiC.PROPERTY_DAY_BEFORE_MONTH); - } - - if (d.containsKey(TiC.PROPERTY_NUMERIC_MONTHS)) { - numericMonths = TiConvert.toBoolean(d, TiC.PROPERTY_NUMERIC_MONTHS); - } - - if (d.containsKey(TiC.PROPERTY_FONT)) { - setFontProperties(); - } - - if (maxDate.before(minDate)) { - maxDate.setTime(minDate.getTime()); - } - - // If initial value is out-of-bounds, set date to nearest bound - if (calendar.after(maxDate)) { - calendar.setTime(maxDate.getTime()); - } else if (calendar.before(minDate)) { - calendar.setTime(minDate.getTime()); - } - - setValue(calendar.getTimeInMillis(), true); - - if (!valueExistsInProxy) { - proxy.setProperty(TiC.PROPERTY_VALUE, calendar.getTime()); - } - } - - @Override - public void propertyChanged(String key, Object oldValue, Object newValue, KrollProxy proxy) - { - if (TiC.PROPERTY_FONT.equals(key)) { - setFontProperties(); - - } else if (TiC.PROPERTY_VALUE.equals(key)) { - Date date = (Date) newValue; - setValue(date.getTime()); - } else if (TiC.PROPERTY_LOCALE.equals(key)) { - setLocale(TiConvert.toString(newValue)); - } else if (TiC.PROPERTY_MIN_DATE.equals(key)) { - minDate.setTime(TiConvert.toDate(newValue)); - setAdapters(); - } else if (TiC.PROPERTY_MAX_DATE.equals(key)) { - maxDate.setTime(TiConvert.toDate(newValue)); - setAdapters(); - } - super.propertyChanged(key, oldValue, newValue, proxy); - } - - private void setFontProperties() - { - Float fontSize = null; - Typeface typeface = null; - String[] fontProperties = TiUIHelper.getFontProperties(proxy.getProperties()); - - if (fontProperties[TiUIHelper.FONT_SIZE_POSITION] != null) { - fontSize = Float.valueOf(TiUIHelper.getSize(fontProperties[TiUIHelper.FONT_SIZE_POSITION])); - } - - if (fontProperties[TiUIHelper.FONT_FAMILY_POSITION] != null) { - typeface = TiUIHelper.toTypeface(fontProperties[TiUIHelper.FONT_FAMILY_POSITION]); - } - Integer typefaceWeight = null; - if (fontProperties[TiUIHelper.FONT_WEIGHT_POSITION] != null) { - typefaceWeight = Integer.valueOf(TiUIHelper.toTypefaceStyle(fontProperties[TiUIHelper.FONT_WEIGHT_POSITION], - fontProperties[TiUIHelper.FONT_SIZE_POSITION])); - } - - if (typeface != null) { - dayWheel.setTypeface(typeface); - monthWheel.setTypeface(typeface); - yearWheel.setTypeface(typeface); - } - if (typefaceWeight != null) { - dayWheel.setTypefaceWeight(typefaceWeight); - monthWheel.setTypefaceWeight(typefaceWeight); - yearWheel.setTypefaceWeight(typefaceWeight); - } - if (fontSize != null) { - dayWheel.setTextSize(fontSize.intValue()); - monthWheel.setTextSize(fontSize.intValue()); - yearWheel.setTextSize(fontSize.intValue()); - } - dayWheel.invalidate(); - monthWheel.invalidate(); - yearWheel.invalidate(); - } - - private void setAdapters() - { - setYearAdapter(); - setMonthAdapter(); - setDayAdapter(); - } - - private void setYearAdapter() - { - int minYear = minDate.get(Calendar.YEAR); - int maxYear = maxDate.get(Calendar.YEAR); - if (yearAdapter != null && yearAdapter.getMinValue() == minYear && yearAdapter.getMaxValue() == maxYear) { - return; - } - yearAdapter = new FormatNumericWheelAdapter(minYear, maxYear, new DecimalFormat("0000"), 4); - - ignoreItemSelection = true; - yearWheel.setAdapter(yearAdapter); - ignoreItemSelection = false; - } - - private void setMonthAdapter() - { - setMonthAdapter(false); - } - - private void setMonthAdapter(boolean forceUpdate) - { - int setMinMonth = 1; - int setMaxMonth = 12; - - int currentMin = -1, currentMax = -1; - if (monthAdapter != null) { - currentMin = monthAdapter.getMinValue(); - currentMax = monthAdapter.getMaxValue(); - } - - int maxYear = maxDate.get(Calendar.YEAR); - int minYear = minDate.get(Calendar.YEAR); - int selYear = getSelectedYear(); - - if (selYear == maxYear) { - setMaxMonth = maxDate.get(Calendar.MONTH) + 1; - } - - if (selYear == minYear) { - setMinMonth = minDate.get(Calendar.MONTH) + 1; - } - - if (currentMin != setMinMonth || currentMax != setMaxMonth || forceUpdate) { - NumberFormat format; - int width = 4; - if (numericMonths) { - format = new DecimalFormat("00"); - } else { - format = new MonthFormat(this.locale); - width = ((MonthFormat) format).getLongestMonthName(); - } - monthAdapter = new FormatNumericWheelAdapter(setMinMonth, setMaxMonth, format, width); - ignoreItemSelection = true; - monthWheel.setAdapter(monthAdapter); - ignoreItemSelection = false; - } - } - - private void setDayAdapter() - { - int setMinDay = 1; - int setMaxDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); - - int currentMin = -1, currentMax = -1; - if (dayAdapter != null) { - currentMin = dayAdapter.getMinValue(); - currentMax = dayAdapter.getMaxValue(); - } - - int maxYear = maxDate.get(Calendar.YEAR); - int minYear = minDate.get(Calendar.YEAR); - int selYear = getSelectedYear(); - int maxMonth = maxDate.get(Calendar.MONTH) + 1; - int minMonth = minDate.get(Calendar.MONTH) + 1; - int selMonth = getSelectedMonth(); - - if (selYear == maxYear && selMonth == maxMonth) { - setMaxDay = maxDate.get(Calendar.DAY_OF_MONTH); - } - - if (selYear == minYear && selMonth == minMonth) { - setMinDay = minDate.get(Calendar.DAY_OF_MONTH); - } - - if (currentMin != setMinDay || currentMax != setMaxDay) { - dayAdapter = new FormatNumericWheelAdapter(setMinDay, setMaxDay, new DecimalFormat("00"), 4); - ignoreItemSelection = true; - dayWheel.setAdapter(dayAdapter); - ignoreItemSelection = false; - } - } - - private void syncWheels() - { - ignoreItemSelection = true; - yearWheel.setCurrentItem(yearAdapter.getIndex(calendar.get(Calendar.YEAR))); - monthWheel.setCurrentItem(monthAdapter.getIndex(calendar.get(Calendar.MONTH) + 1)); - dayWheel.setCurrentItem(dayAdapter.getIndex(calendar.get(Calendar.DAY_OF_MONTH))); - ignoreItemSelection = false; - } - - public void setValue(long value) - { - setValue(value, false); - } - - public void setValue(long value, boolean suppressEvent) - { - Date oldVal, newVal; - oldVal = calendar.getTime(); - - setCalendar(value); - newVal = calendar.getTime(); - if (newVal.after(maxDate.getTime())) { - newVal = maxDate.getTime(); - setCalendar(newVal); - } else if (newVal.before(minDate.getTime())) { - newVal = minDate.getTime(); - setCalendar(newVal); - } - - boolean isChanged = (!newVal.equals(oldVal)); - - setAdapters(); - - syncWheels(); - proxy.setProperty(TiC.PROPERTY_VALUE, newVal); - - if (isChanged && !suppressEvent) { - if (!suppressChangeEvent) { - KrollDict data = new KrollDict(); - data.put(TiC.PROPERTY_VALUE, newVal); - fireEvent(TiC.EVENT_CHANGE, data); - } - } - } - - public void setValue(Date value, boolean suppressEvent) - { - long millis = value.getTime(); - setValue(millis, suppressEvent); - } - - public void setValue(Date value) - { - setValue(value, false); - } - - public void setValue() - { - setValue(getSelectedDate()); - } - - private void setLocale(String localeString) - { - Locale locale = Locale.getDefault(); - if (localeString != null && localeString.length() > 1) { - String stripped = localeString.replaceAll("-", "").replaceAll("_", ""); - if (stripped.length() == 2) { - locale = new Locale(stripped); - } else if (stripped.length() >= 4) { - String language = stripped.substring(0, 2); - String country = stripped.substring(2, 4); - if (stripped.length() > 4) { - locale = new Locale(language, country, stripped.substring(4)); - } else { - locale = new Locale(language, country); - } - } else { - Log.w(TAG, "Locale string '" + localeString + "' not understood. Using default locale."); - } - } - - if (!this.locale.equals(locale)) { - this.locale = locale; - setMonthAdapter(true); - syncWheels(); - } - } - - private void setCalendar(long millis) - { - calendar.setTimeInMillis(millis); - } - - private void setCalendar(Date date) - { - calendar.setTime(date); - } - - private int getSelectedYear() - { - return yearAdapter.getValue(yearWheel.getCurrentItem()); - } - - private int getSelectedMonth() - { - return monthAdapter.getValue(monthWheel.getCurrentItem()); - } - - private int getSelectedDay() - { - return dayAdapter.getValue(dayWheel.getCurrentItem()); - } - - private Date getSelectedDate() - { - int year = getSelectedYear(); - int month = getSelectedMonth() - 1; - int day = getSelectedDay(); - Calendar c = Calendar.getInstance(); - c.set(year, month, day); - return c.getTime(); - } - - @Override - public void onItemSelected(WheelView view, int index) - { - if (ignoreItemSelection) { - return; - } - setValue(); - } - - class MonthFormat extends NumberFormat - { - private static final long serialVersionUID = 1L; - private DateFormatSymbols symbols = new DateFormatSymbols(Locale.getDefault()); - - public MonthFormat(Locale locale) - { - super(); - setLocale(locale); - } - - @Override - public StringBuffer format(double value, StringBuffer buffer, FieldPosition position) - { - return format((long) value, buffer, position); - } - - @Override - public StringBuffer format(long value, StringBuffer buffer, FieldPosition position) - { - buffer.append(symbols.getMonths()[((int) value) - 1]); - return buffer; - } - - @Override - public Number parse(String value, ParsePosition position) - { - String[] months = symbols.getMonths(); - for (int i = 0; i < months.length; i++) { - if (months[i].equals(value)) { - return new Long(i + 1); - } - } - return null; - } - - public void setLocale(Locale locale) - { - symbols = new DateFormatSymbols(locale); - } - - public int getLongestMonthName() - { - int max = 0; - for (String month : symbols.getMonths()) { - max = (month.length() > max) ? month.length() : max; - } - return max; - } - } -} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUINativePicker.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUINativePicker.java deleted file mode 100644 index 23ce5f9e2c5..00000000000 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUINativePicker.java +++ /dev/null @@ -1,332 +0,0 @@ -/** - * Appcelerator Titanium Mobile - * Copyright (c) 2009-2015 by Appcelerator, 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.picker; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.appcelerator.kroll.KrollDict; -import org.appcelerator.kroll.KrollProxy; -import org.appcelerator.kroll.common.Log; -import org.appcelerator.titanium.TiC; -import org.appcelerator.titanium.proxy.TiViewProxy; -import org.appcelerator.titanium.util.TiConvert; -import org.appcelerator.titanium.util.TiRHelper; -import org.appcelerator.titanium.util.TiRHelper.ResourceNotFoundException; -import org.appcelerator.titanium.util.TiUIHelper; -import org.appcelerator.titanium.view.TiUIView; - -import ti.modules.titanium.ui.PickerColumnProxy; -import ti.modules.titanium.ui.PickerProxy; -import android.app.Activity; -import android.content.Context; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.ArrayAdapter; -import android.widget.Spinner; -import android.widget.TextView; - -public class TiUINativePicker extends TiUIPicker -{ - private static final String TAG = "TiUINativePicker"; - private boolean nativeViewDrawn = false; - - public static class TiSpinnerAdapter extends ArrayAdapter - { - String[] fontProperties; - private int defaultTextColor; - private boolean hasLoadedDefaultTextColor; - - public TiSpinnerAdapter(Context context, int textViewResourceId, List objects) - { - super(context, textViewResourceId, objects); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) - { - TextView tv = (TextView) super.getView(position, convertView, parent); - styleTextView(position, tv); - return tv; - } - - @Override - public View getDropDownView(int position, View convertView, ViewGroup parent) - { - TextView tv = (TextView) super.getDropDownView(position, convertView, parent); - styleTextView(position, tv); - return tv; - } - - public void setFontProperties(KrollDict d) - { - fontProperties = TiUIHelper.getFontProperties(d); - } - - private void styleTextView(int position, TextView tv) - { - // Fetch text view's default text color if not done already. - if (!this.hasLoadedDefaultTextColor) { - this.defaultTextColor = tv.getCurrentTextColor(); - this.hasLoadedDefaultTextColor = true; - } - - // Update text view's font if configured. - if (fontProperties != null) { - TiUIHelper.styleText( - tv, fontProperties[TiUIHelper.FONT_FAMILY_POSITION], fontProperties[TiUIHelper.FONT_SIZE_POSITION], - fontProperties[TiUIHelper.FONT_WEIGHT_POSITION], fontProperties[TiUIHelper.FONT_STYLE_POSITION]); - } - - // Update text color if configured. - TiViewProxy rowProxy = (TiViewProxy) this.getItem(position); - if (rowProxy.hasProperty(TiC.PROPERTY_COLOR)) { - String colorString = TiConvert.toString(rowProxy.getProperty(TiC.PROPERTY_COLOR)); - int color = (colorString != null) ? TiConvert.toColor(colorString) : this.defaultTextColor; - tv.setTextColor(color); - } - } - } - - public TiUINativePicker(TiViewProxy proxy) - { - super(proxy); - } - public TiUINativePicker(final TiViewProxy proxy, Activity activity) - { - this(proxy); - int spinnerId; - try { - spinnerId = TiRHelper.getResource("layout.titanium_ui_spinner"); - } catch (ResourceNotFoundException e) { - if (Log.isDebugModeEnabled()) { - Log.e(TAG, "XML resources could not be found!!!"); - } - return; - } - final Spinner spinner = (Spinner) activity.getLayoutInflater().inflate(spinnerId, null); - - spinner.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(proxy); - // Set the flag showing if the native view has been drawn - nativeViewDrawn = true; - // Attach the listener for the first time after - // all the setting up has finished. - spinner.setOnItemSelectedListener(onItemSelectedListener); - } - }); - - spinner.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) - { - if (event.getAction() == MotionEvent.ACTION_UP) { - KrollDict data = new KrollDict(); - data.put(TiC.PROPERTY_X, event.getX()); - data.put(TiC.PROPERTY_Y, event.getY()); - fireEvent(TiC.EVENT_CLICK, data); - } - return false; - } - }); - - setNativeView(spinner); - refreshNativeView(); - preselectRows(); - } - - private void preselectRows() - { - Spinner spinner = (Spinner) nativeView; - ArrayList preselectedRows = getPickerProxy().getPreselectedRows(); - if (preselectedRows == null || preselectedRows.size() == 0) { - return; - } - if (spinner == null) - return; - for (int i = 0; i < preselectedRows.size(); i++) { - Integer rowIndex = preselectedRows.get(i); - if (rowIndex == 0 || rowIndex.intValue() < 0) { - continue; - } - selectRow(i, rowIndex, false); - } - } - - @Override - public void selectRow(int columnIndex, int rowIndex, boolean animated) - { - // At the moment we only support one column. - if (columnIndex != 0) { - Log.w(TAG, "Only one column is supported. Ignoring request to set selected row of column " + columnIndex); - return; - } - Spinner view = (Spinner) nativeView; - int rowCount = view.getAdapter().getCount(); - if (rowIndex < 0 || rowIndex >= rowCount) { - Log.w(TAG, "Ignoring request to select out-of-bounds row index " + rowIndex); - return; - } - view.setSelection(rowIndex, animated); - } - - @Override - public void openPicker() - { - Spinner view = (Spinner) nativeView; - view.performClick(); - } - - @Override - public int getSelectedRowIndex(int columnIndex) - { - if (columnIndex != 0) { - Log.w(TAG, "Ignoring request to get selected row from out-of-bounds columnIndex " + columnIndex); - return -1; - } - return ((Spinner) getNativeView()).getSelectedItemPosition(); - } - - @Override - protected void refreshNativeView() - { - // Don't allow change events here - Spinner spinner = (Spinner) nativeView; - if (spinner == null) { - return; - } - try { - if (nativeViewDrawn) { - // If we have drawn the spinner it has a selected item listener. - // Detach while the native view is refreshed to prevent - // unnecessary event triggers. - spinner.setOnItemSelectedListener(null); - } - int rememberSelectedRow = getPickerProxy().getLastSelectedIndex(); - // Just one column - the first column - for now. - // Maybe someday we'll support multiple columns. - PickerColumnProxy column = getPickerProxy().getFirstColumn(false); - if (column == null) { - return; - } - TiViewProxy[] rowArray = column.getChildren(); - if (rowArray == null || rowArray.length == 0) { - return; - } - ArrayList rows = new ArrayList<>(Arrays.asList(rowArray)); - // At the moment we're using the simple spinner layouts provided - // in android because we're only supporting a piece of text, which - // is fetched via PickerRowProxy.toString(). If we allow - // anything beyond a string, we'll have to implement our own - // layouts (maybe our own Adapter too.) - TiSpinnerAdapter adapter = - new TiSpinnerAdapter<>(spinner.getContext(), android.R.layout.simple_spinner_item, rows); - adapter.setFontProperties(proxy.getProperties()); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinner.setAdapter(adapter); - if (rememberSelectedRow >= 0) { - selectRow(0, rememberSelectedRow, false); - } - // The new adapter has been set. - // If the Spinner has been drawn reattach the onItemSelected listener here. - // If it has not been drawn yet, the listener will be attached in the - // onLayout lifecycle event. - if (nativeViewDrawn) { - spinner.setOnItemSelectedListener(onItemSelectedListener); - } - } catch (Throwable t) { - Log.e(TAG, "Unable to refresh native spinner control: " + t.getMessage(), t); - } - } - - private final OnItemSelectedListener onItemSelectedListener = new OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long itemId) - { - // if the user selects a new item form the picker - // it should overwrite the values stored in preselected rows - getPickerProxy().getPreselectedRows().clear(); - getPickerProxy().setLastSelectedIndex(position); - fireSelectionChange(0, position); - - // Invalidate the parent view after the item is selected (TIMOB-13540). - ViewParent p = nativeView.getParent(); - if (p instanceof View) { - ((View) p).invalidate(); - } - } - - @Override - public void onNothingSelected(AdapterView parent) - { - } - }; - - public void add(TiUIView child) - { - // Don't do anything. We don't add/remove views to the native picker (the Android "Spinner"). - } - @Override - public void remove(TiUIView child) - { - // Don't do anything. We don't add/remove views to the native picker (the Android "Spinner"). - } - @Override - public void onColumnAdded(int columnIndex) - { - if (!batchModelChange) { - refreshNativeView(); - } - } - @Override - public void onColumnRemoved(int oldColumnIndex) - { - if (!batchModelChange) { - refreshNativeView(); - } - } - @Override - public void onColumnModelChanged(int columnIndex) - { - if (!batchModelChange) { - refreshNativeView(); - } - } - @Override - public void onRowChanged(int columnIndex, int rowIndex) - { - if (!batchModelChange) { - refreshNativeView(); - } - } - protected void fireSelectionChange(int columnIndex, int rowIndex) - { - ((PickerProxy) proxy).fireSelectionChange(columnIndex, rowIndex); - } - - @Override - public void propertyChanged(String key, Object oldValue, Object newValue, KrollProxy proxy) - { - if (key.equals(TiC.PROPERTY_FONT)) { - Spinner spinner = (Spinner) nativeView; - TiSpinnerAdapter adapter = (TiSpinnerAdapter) spinner.getAdapter(); - adapter.setFontProperties(proxy.getProperties()); - adapter.notifyDataSetChanged(); - } else { - super.propertyChanged(key, oldValue, newValue, proxy); - } - } -} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPicker.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPicker.java deleted file mode 100644 index e48ead429a5..00000000000 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPicker.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Appcelerator Titanium Mobile - * Copyright (c) 2009-2012 by Appcelerator, 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.picker; - -import java.util.ArrayList; - -import org.appcelerator.titanium.proxy.TiViewProxy; -import org.appcelerator.titanium.view.TiUIView; - -import ti.modules.titanium.ui.PickerProxy; - -public abstract class TiUIPicker extends TiUIView -{ - protected boolean suppressChangeEvent = false; - public boolean batchModelChange = - false; // Set by proxy to indicate that several model changes are occurring and therefore view can wait to refresh - - public TiUIPicker(TiViewProxy proxy) - { - super(proxy); - } - - public abstract void selectRow(int columnIndex, int rowIndex, boolean animated); - public abstract int getSelectedRowIndex(int columnIndex); - protected abstract void refreshNativeView(); - - public void openPicker() {} - - // When the whole set of columns has been changed out. - public void onModelReplaced() - { - if (!batchModelChange) { - refreshNativeView(); - } - } - - // When a column has been added. - public void onColumnAdded(int columnIndex) - { - } - // When a column has been removed. - public void onColumnRemoved(int oldColumnIndex) - { - } - // When a row has been added to / removed from a column - public void onColumnModelChanged(int columnIndex) - { - } - // When a row value has been changed. - public void onRowChanged(int columnIndex, int rowIndex) - { - } - - protected PickerProxy getPickerProxy() - { - return (PickerProxy) proxy; - } - - public void selectRows(ArrayList selectionIndexes) - { - if (selectionIndexes == null || selectionIndexes.size() == 0) { - return; - } - for (int colnum = 0; colnum < selectionIndexes.size(); colnum++) { - int rownum = selectionIndexes.get(colnum).intValue(); - selectRow(colnum, rownum, false); - } - } -} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPickerColumn.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPickerColumn.java deleted file mode 100644 index 790597b3683..00000000000 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPickerColumn.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Appcelerator Titanium Mobile - * Copyright (c) 2009-2011 by Appcelerator, Inc. All Rights Reserved. - * Licensed under the terms of the Apache Public License - * Please see the LICENSE included with this distribution for details. - */ - -/** - * This class does not do anything, because it's used as the "view" for the - * columns in the android native picker (our view for which is the TiUINativePicker - * class), and we don't really put custom views into that. But to keep the - * picker completely in our view meme, a class was required. - */ - -package ti.modules.titanium.ui.widget.picker; - -import org.appcelerator.titanium.proxy.TiViewProxy; -import org.appcelerator.titanium.view.TiUIView; - -public class TiUIPickerColumn extends TiUIView -{ - public TiUIPickerColumn(TiViewProxy proxy) - { - super(proxy); - } -} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPlainDropDownPicker.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPlainDropDownPicker.java new file mode 100644 index 00000000000..a062b25b601 --- /dev/null +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPlainDropDownPicker.java @@ -0,0 +1,296 @@ +/** + * Appcelerator Titanium Mobile + * Copyright (c) 2009-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.picker; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.TextView; +import androidx.annotation.NonNull; +import com.google.android.material.textfield.TextInputLayout; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.appcelerator.kroll.KrollProxy; +import org.appcelerator.kroll.common.Log; +import org.appcelerator.titanium.TiC; +import org.appcelerator.titanium.util.TiConvert; +import org.appcelerator.titanium.util.TiUIHelper; +import ti.modules.titanium.ui.PickerColumnProxy; +import ti.modules.titanium.ui.PickerProxy; +import ti.modules.titanium.ui.PickerRowProxy; + +public class TiUIPlainDropDownPicker extends TiUIPlainPicker +{ + private static final String TAG = "TiUINativePicker"; + + public TiUIPlainDropDownPicker(@NonNull PickerProxy proxy) + { + super(proxy); + + // Create the text field. + TextInputLayout textInputLayout = proxy.createTextInputLayout(); + textInputLayout.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()); + } + }); + setNativeView(textInputLayout); + + // Set up a selection listener. + AutoCompleteTextView textView = (AutoCompleteTextView) textInputLayout.getEditText(); + textView.setOnItemClickListener((AdapterView parent, View view, int position, long id) -> { + if (proxy.getSelectedRowIndex(0) != position) { + TiPickerAdapter adapter = (TiPickerAdapter) textView.getAdapter(); + if (adapter != null) { + adapter.setSelectedIndex(position); + } + fireSelectionChange(0, position); + } + }); + + // Add all rows to view. Must be done after calling setNativeView() method. + updateAdapterList(); + } + + @Override + public void propertyChanged(String key, Object oldValue, Object newValue, KrollProxy proxy) + { + if (key.equals(TiC.PROPERTY_FONT)) { + AutoCompleteTextView textView = getAutoCompleteTextView(); + if (textView != null) { + TiPickerAdapter adapter = (TiPickerAdapter) textView.getAdapter(); + adapter.setFontProperties(getFontProperties()); + adapter.notifyDataSetChanged(); + } + } else { + super.propertyChanged(key, oldValue, newValue, proxy); + } + } + + @Override + public void selectRow(int columnIndex, int rowIndex, boolean animated) + { + // Drop-down picker only supports 1 column. + if (columnIndex != 0) { + Log.w(TAG, "Ti.UI.Picker drop-down only supports 1 column. Cannot select row for column: " + columnIndex); + return; + } + + // Fetch text view. + AutoCompleteTextView textView = getAutoCompleteTextView(); + TiPickerAdapter adapter = (textView != null) ? (TiPickerAdapter) textView.getAdapter() : null; + if (adapter == null) { + return; + } + + // Select the given row in the view. + String text = ""; + if (adapter.getCount() > 0) { + rowIndex = Math.max(rowIndex, 0); + rowIndex = Math.min(rowIndex, adapter.getCount() - 1); + text = adapter.getItem(rowIndex).toString(); + } + int lastRowIndex = adapter.getSelectedIndex(); + adapter.setSelectedIndex(rowIndex); + textView.setText(text, false); + + // Fire a "change" event if the selected row changed. + // Note: Do not fire the event if list was empty or currently empty. + if ((rowIndex >= 0) && (lastRowIndex >= 0) && (rowIndex != lastRowIndex)) { + fireSelectionChange(0, rowIndex); + } + } + + public void openPicker() + { + AutoCompleteTextView textView = getAutoCompleteTextView(); + if (textView != null) { + textView.showDropDown();; + } + } + + private void updateAdapterList() + { + // Fetch the picker's proxy. + PickerProxy pickerProxy = getPickerProxy(); + if (pickerProxy == null) { + return; + } + + // Fetch the text field to update the drop-down list on. + AutoCompleteTextView textView = getAutoCompleteTextView(); + if (textView == null) { + return; + } + + // Fetch all rows from the 1st column. (We do not support more than 1 column.) + List rowList = null; + PickerColumnProxy columnProxy = pickerProxy.getFirstColumn(); + if (columnProxy != null) { + PickerRowProxy[] rowProxyArray = columnProxy.getRows(); + if ((rowProxyArray != null) && (rowProxyArray.length > 0)) { + rowList = new ArrayList<>(Arrays.asList(rowProxyArray)); + } + } + if (rowList == null) { + rowList = new ArrayList<>(); + } + + // Update view's drop-down list. + TiPickerAdapter adapter = (TiPickerAdapter) textView.getAdapter(); + int lastSelectedIndex = (adapter != null) ? adapter.getSelectedIndex() : -1; + adapter = new TiPickerAdapter(textView.getContext(), android.R.layout.simple_spinner_dropdown_item, rowList); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + adapter.setFontProperties(getFontProperties()); + adapter.setSelectedIndex(lastSelectedIndex); + textView.setAdapter(adapter); + + // Select a row. + if (rowList.isEmpty()) { + textView.clearListSelection(); + } else { + selectRow(0, pickerProxy.getSelectedRowIndex(0), false); + } + } + + @Override + public void onColumnChanged(PickerColumnProxy proxy) + { + updateAdapterList(); + } + + @Override + public void onColumnListChanged() + { + updateAdapterList(); + } + + protected void fireSelectionChange(int columnIndex, int rowIndex) + { + PickerProxy pickerProxy = getPickerProxy(); + if (pickerProxy != null) { + pickerProxy.fireSelectionChange(columnIndex, rowIndex); + } + } + + private AutoCompleteTextView getAutoCompleteTextView() + { + View view = getNativeView(); + if (view instanceof TextInputLayout) { + view = ((TextInputLayout) view).getEditText(); + if (view instanceof AutoCompleteTextView) { + return (AutoCompleteTextView) view; + } + } + return null; + } + + private String[] getFontProperties() + { + // Fetch font properties from proxy and return it as an array. + String[] fontProperties = null; + PickerProxy pickerProxy = getPickerProxy(); + if (pickerProxy != null) { + PickerColumnProxy columnProxy = pickerProxy.getColumn(0); + if (columnProxy != null) { + // Fetch font properties from column proxy. + fontProperties = TiUIHelper.getFontProperties(columnProxy.getProperties()); + } + if ((fontProperties == null) || (fontProperties.length <= 0)) { + // Fallback to fetching font properties from picker proxy. + fontProperties = TiUIHelper.getFontProperties(pickerProxy.getProperties()); + } + } + return fontProperties; + } + + private static class TiPickerAdapter extends ArrayAdapter + { + private String[] fontProperties; + private int selectedIndex = -1; + private int defaultTextColor; + private boolean hasLoadedDefaultTextColor; + + public TiPickerAdapter(Context context, int textViewResourceId, List objects) + { + super(context, textViewResourceId, objects); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + TextView textView = (TextView) super.getView(position, convertView, parent); + styleTextView(position, textView); + return textView; + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) + { + TextView textView = (TextView) super.getDropDownView(position, convertView, parent); + styleTextView(position, textView); + return textView; + } + + public int getSelectedIndex() + { + return this.selectedIndex; + } + + public void setSelectedIndex(int value) + { + value = Math.max(value, -1); + value = Math.min(value, getCount() - 1); + this.selectedIndex = value; + } + + public String[] getFontProperties() + { + return this.fontProperties; + } + + public void setFontProperties(String[] fontProperties) + { + this.fontProperties = fontProperties; + } + + private void styleTextView(int position, TextView textView) + { + // Fetch text view's default text color if not done already. + if (!this.hasLoadedDefaultTextColor) { + this.defaultTextColor = textView.getCurrentTextColor(); + this.hasLoadedDefaultTextColor = true; + } + + // Update text view's font if configured. + if ((this.fontProperties != null) && (this.fontProperties.length > 0)) { + TiUIHelper.styleText( + textView, + this.fontProperties[TiUIHelper.FONT_FAMILY_POSITION], + this.fontProperties[TiUIHelper.FONT_SIZE_POSITION], + this.fontProperties[TiUIHelper.FONT_WEIGHT_POSITION], + this.fontProperties[TiUIHelper.FONT_STYLE_POSITION]); + } + + // Update text color if configured. + PickerRowProxy rowProxy = this.getItem(position); + if (rowProxy.hasProperty(TiC.PROPERTY_COLOR)) { + String colorString = TiConvert.toString(rowProxy.getProperty(TiC.PROPERTY_COLOR)); + int color = (colorString != null) ? TiConvert.toColor(colorString) : this.defaultTextColor; + textView.setTextColor(color); + } + } + } +} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPlainPicker.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPlainPicker.java new file mode 100644 index 00000000000..8309c4a6cbe --- /dev/null +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPlainPicker.java @@ -0,0 +1,29 @@ +/** + * Appcelerator Titanium Mobile + * Copyright (c) 2009-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.picker; + +import androidx.annotation.NonNull; +import org.appcelerator.titanium.view.TiUIView; +import ti.modules.titanium.ui.PickerProxy; +import ti.modules.titanium.ui.PickerColumnProxy; + +public abstract class TiUIPlainPicker extends TiUIView +{ + public TiUIPlainPicker(@NonNull PickerProxy proxy) + { + super(proxy); + } + + protected PickerProxy getPickerProxy() + { + return (this.proxy instanceof PickerProxy) ? (PickerProxy) proxy : null; + } + + public abstract void selectRow(int columnIndex, int rowIndex, boolean animated); + public abstract void onColumnChanged(PickerColumnProxy proxy); + public abstract void onColumnListChanged(); +} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPlainSpinnerPicker.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPlainSpinnerPicker.java new file mode 100644 index 00000000000..b307b98db12 --- /dev/null +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUIPlainSpinnerPicker.java @@ -0,0 +1,197 @@ +/** + * Appcelerator Titanium Mobile + * Copyright (c) 2009-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.picker; + +import android.view.View; +import android.widget.NumberPicker; +import androidx.annotation.NonNull; +import org.appcelerator.titanium.TiC; +import org.appcelerator.titanium.TiDimension; +import org.appcelerator.titanium.util.TiConvert; +import org.appcelerator.titanium.view.TiCompositeLayout; +import ti.modules.titanium.ui.PickerColumnProxy; +import ti.modules.titanium.ui.PickerProxy; +import ti.modules.titanium.ui.PickerRowProxy; + +public class TiUIPlainSpinnerPicker extends TiUIPlainPicker implements NumberPicker.OnValueChangeListener +{ + private static final String TAG = "TiUISpinner"; + + public TiUIPlainSpinnerPicker(@NonNull PickerProxy proxy) + { + super(proxy); + + TiCompositeLayout layout = new TiCompositeLayout( + proxy.getActivity(), TiCompositeLayout.LayoutArrangement.HORIZONTAL, proxy); + layout.setEnableHorizontalWrap(true); + setNativeView(layout); + + // Add all picker columns to the native view. Must be done last. + recreateAllColumns(); + } + + @Override + public void selectRow(int columnIndex, int rowIndex, boolean animated) + { + NumberPicker pickerView = getNumberPickerForIndex(columnIndex); + if (pickerView != null) { + rowIndex = Math.max(rowIndex, 0); + rowIndex = Math.min(rowIndex, pickerView.getMaxValue()); + pickerView.setValue(rowIndex); + } + } + + @Override + public void onColumnChanged(PickerColumnProxy columnProxy) + { + // Fetch the picker proxy. + PickerProxy pickerProxy = getPickerProxy(); + if (pickerProxy == null) { + return; + } + + // Update the column's NumberPicker view. + NumberPicker pickerView = getNumberPickerForIndex(pickerProxy.getColumnIndexOf(columnProxy)); + if (pickerView != null) { + update(pickerView, columnProxy); + pickerView.requestLayout(); + } + } + + @Override + public void onColumnListChanged() + { + recreateAllColumns(); + } + + @Override + public void onValueChange(NumberPicker picker, int oldValue, int newValue) + { + // Fetch proxy and view group. + PickerProxy pickerProxy = getPickerProxy(); + TiCompositeLayout layout = (TiCompositeLayout) getNativeView(); + if ((pickerProxy == null) || (layout == null)) { + return; + } + + // Fire a "change" event. + for (int index = 0; index < layout.getChildCount(); index++) { + if (layout.getChildAt(index) == picker) { + pickerProxy.fireSelectionChange(index, newValue); + break; + } + } + } + + private NumberPicker getNumberPickerForIndex(int columnIndex) + { + // Fetch the view group hosting all picker column child views. + TiCompositeLayout layout = (TiCompositeLayout) getNativeView(); + if (layout == null) { + return null; + } + + // Validate given column index. + if ((columnIndex < 0) || (columnIndex >= layout.getChildCount())) { + return null; + } + + // Return the column's picker view. + View childView = layout.getChildAt(columnIndex); + if (childView instanceof NumberPicker) { + return (NumberPicker) childView; + } + return null; + } + + private void recreateAllColumns() + { + // Fetch the picker proxy. + PickerProxy pickerProxy = getPickerProxy(); + if (pickerProxy == null) { + return; + } + + // Fetch the view group hosting all picker column child views. + TiCompositeLayout layout = (TiCompositeLayout) getNativeView(); + if (layout == null) { + return; + } + + // Remove all previously created column pickers. + layout.removeAllViews(); + + // Create new column picker views and add them to layout. + PickerColumnProxy[] columnProxies = pickerProxy.getColumns(); + for (int columnIndex = 0; columnIndex < columnProxies.length; columnIndex++) { + // Create the column picker. + NumberPicker pickerView = new NumberPicker(pickerProxy.getActivity()); + pickerView.setOnValueChangedListener(this); + pickerView.setWrapSelectorWheel(false); + pickerView.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS); // Disable text editor. + update(pickerView, columnProxies[columnIndex]); + + // Select a row based on proxy's last assigned index. + int rowIndex = Math.max(pickerProxy.getSelectedRowIndex(columnIndex), 0); + pickerView.setValue(Math.min(rowIndex, pickerView.getMaxValue())); + + // Add picker to layout. + layout.addView(pickerView); + } + } + + private void update(NumberPicker pickerView, PickerColumnProxy columnProxy) + { + // Validate arguments. + if ((pickerView == null) || (columnProxy == null)) { + return; + } + + // Fetch the picker proxy. + PickerProxy pickerProxy = getPickerProxy(); + if (pickerProxy == null) { + return; + } + + // Fetch the currently selected row index. To be restored after updating column. + int lastSelectedRowIndex = pickerView.getValue(); + + // Set all row titles in the picker. + PickerRowProxy[] rowProxyArray = columnProxy.getRows(); + if (rowProxyArray.length > 0) { + pickerView.setDisplayedValues(TiConvert.toStringArray(rowProxyArray)); + pickerView.setMaxValue(rowProxyArray.length - 1); + } else { + pickerView.setDisplayedValues(new String[] { " " }); + pickerView.setMaxValue(0); + } + + // Set the picker's width if configured. + if (columnProxy.hasProperty(TiC.PROPERTY_WIDTH)) { + TiCompositeLayout.LayoutParams layoutParams = new TiCompositeLayout.LayoutParams(); + Object value = columnProxy.getProperty(TiC.PROPERTY_WIDTH); + if (value != null) { + layoutParams.optionWidth = null; + layoutParams.sizeOrFillWidthEnabled = true; + if (value.equals(TiC.LAYOUT_SIZE)) { + layoutParams.autoFillsWidth = false; + } else if (value.equals(TiC.LAYOUT_FILL)) { + layoutParams.autoFillsWidth = true; + } else if (!value.equals(TiC.SIZE_AUTO)) { + layoutParams.optionWidth = + TiConvert.toTiDimension(TiConvert.toString(value), TiDimension.TYPE_WIDTH); + layoutParams.sizeOrFillWidthEnabled = false; + } + } + pickerView.setLayoutParams(layoutParams); + } + + // Restore last selected row. + pickerView.setValue(lastSelectedRowIndex); + } +} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUISpinner.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUISpinner.java deleted file mode 100644 index 8a0ab823beb..00000000000 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUISpinner.java +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Appcelerator Titanium Mobile - * Copyright (c) 2009-2016 by Appcelerator, 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.picker; - -import org.appcelerator.kroll.KrollDict; -import org.appcelerator.kroll.KrollProxy; -import org.appcelerator.kroll.common.Log; -import org.appcelerator.titanium.TiC; -import org.appcelerator.titanium.proxy.TiViewProxy; -import org.appcelerator.titanium.util.TiConvert; -import org.appcelerator.titanium.view.TiCompositeLayout; -import org.appcelerator.titanium.view.TiCompositeLayout.LayoutArrangement; -import org.appcelerator.titanium.view.TiUIView; - -import android.app.Activity; - -public class TiUISpinner extends TiUIPicker -{ - private static final String TAG = "TiUISpinner"; - - public TiUISpinner(TiViewProxy proxy) - { - super(proxy); - } - public TiUISpinner(TiViewProxy proxy, Activity activity) - { - this(proxy); - TiCompositeLayout layout = new TiCompositeLayout(activity, LayoutArrangement.HORIZONTAL, proxy); - layout.setEnableHorizontalWrap(true); - setNativeView(layout); - } - - @Override - protected void refreshNativeView() - { - if (children == null || children.size() == 0) { - return; - } - for (TiUIView child : children) { - refreshColumn((TiUISpinnerColumn) child); - } - } - - private void refreshColumn(int columnIndex) - { - if (columnIndex < 0 || children == null || children.size() == 0 || columnIndex > (children.size() + 1)) { - return; - } - refreshColumn((TiUISpinnerColumn) children.get(columnIndex)); - } - private void refreshColumn(TiUISpinnerColumn column) - { - if (column == null) { - return; - } - column.refreshNativeView(); - } - - @Override - public int getSelectedRowIndex(int columnIndex) - { - if (columnIndex < 0 || children == null || children.size() == 0 || columnIndex >= children.size()) { - Log.w(TAG, "Ignoring effort to get selected row index for out-of-bounds columnIndex " + columnIndex); - return -1; - } - TiUIView child = children.get(columnIndex); - if (child instanceof TiUISpinnerColumn) { - return ((TiUISpinnerColumn) child).getSelectedRowIndex(); - } else { - Log.w(TAG, "Could not locate column " + columnIndex - + ". Ignoring effort to get selected row index in that column."); - return -1; - } - } - @Override - public void selectRow(int columnIndex, int rowIndex, boolean animated) - { - if (children == null || columnIndex >= children.size()) { - Log.w(TAG, "Column " + columnIndex + " does not exist. Ignoring effort to select a row in that column."); - return; - } - TiUIView child = children.get(columnIndex); - if (child instanceof TiUISpinnerColumn) { - ((TiUISpinnerColumn) child).selectRow(rowIndex); - } else { - Log.w(TAG, "Could not locate column " + columnIndex + ". Ignoring effort to select a row in that column."); - } - } - - @Override - public void onColumnModelChanged(int columnIndex) - { - refreshColumn(columnIndex); - } - @Override - public void onRowChanged(int columnIndex, int rowIndex) - { - refreshColumn(columnIndex); - } - @Override - public void propertyChanged(String key, Object oldValue, Object newValue, KrollProxy proxy) - { - if (TiC.PROPERTY_VISIBLE_ITEMS.equals(key) || TiC.PROPERTY_SELECTION_INDICATOR.equals(key)) { - propagateProperty(key, newValue); - if (TiC.PROPERTY_VISIBLE_ITEMS.equals(key)) { - forceRequestLayout(); - } - } else { - super.propertyChanged(key, oldValue, newValue, proxy); - } - } - - private void propagateProperty(String key, Object value) - { - if (children != null && children.size() > 0) { - for (TiUIView child : children) { - if (child instanceof TiUISpinnerColumn) { - child.getProxy().setPropertyAndFire(key, value); - } - } - } - } - @Override - public void processProperties(KrollDict d) - { - super.processProperties(d); - if (d.containsKey(TiC.PROPERTY_VISIBLE_ITEMS)) { - propagateProperty(TiC.PROPERTY_VISIBLE_ITEMS, TiConvert.toInt(d, TiC.PROPERTY_VISIBLE_ITEMS)); - } - if (d.containsKey(TiC.PROPERTY_SELECTION_INDICATOR)) { - propagateProperty(TiC.PROPERTY_SELECTION_INDICATOR, - TiConvert.toBoolean(d, TiC.PROPERTY_SELECTION_INDICATOR)); - } - } - - @SuppressWarnings("deprecation") - @Override - public void add(TiUIView child) - { - if (proxy.hasProperty(TiC.PROPERTY_VISIBLE_ITEMS)) { - child.getProxy().setPropertyAndFire(TiC.PROPERTY_VISIBLE_ITEMS, - TiConvert.toInt(proxy.getProperty(TiC.PROPERTY_VISIBLE_ITEMS))); - } - if (proxy.hasProperty(TiC.PROPERTY_SELECTION_INDICATOR)) { - child.getProxy().setPropertyAndFire( - TiC.PROPERTY_SELECTION_INDICATOR, - TiConvert.toBoolean(proxy.getProperty(TiC.PROPERTY_SELECTION_INDICATOR))); - } - super.add(child); - } - public void forceRequestLayout() - { - if (children != null && children.size() > 0) { - for (TiUIView child : children) { - if (child instanceof TiUISpinnerColumn) { - ((TiUISpinnerColumn) child).forceRequestLayout(); - } - } - } - layoutNativeView(); - } -} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUISpinnerColumn.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUISpinnerColumn.java deleted file mode 100644 index 07a328bba0c..00000000000 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUISpinnerColumn.java +++ /dev/null @@ -1,225 +0,0 @@ -/** - * Appcelerator Titanium Mobile - * Copyright (c) 2009-2011 by Appcelerator, 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.picker; - -import java.util.ArrayList; -import java.util.HashMap; - -import kankan.wheel.widget.WheelView; - -import org.appcelerator.kroll.KrollDict; -import org.appcelerator.kroll.KrollProxy; -import org.appcelerator.kroll.common.Log; -import org.appcelerator.titanium.TiC; -import org.appcelerator.titanium.proxy.TiViewProxy; -import org.appcelerator.titanium.util.TiConvert; -import org.appcelerator.titanium.util.TiUIHelper; -import org.appcelerator.titanium.view.TiUIView; - -import ti.modules.titanium.ui.PickerColumnProxy; -import ti.modules.titanium.ui.PickerProxy; -import ti.modules.titanium.ui.PickerRowProxy; -import android.graphics.Typeface; - -public class TiUISpinnerColumn extends TiUIView implements WheelView.OnItemSelectedListener -{ - - private static final String TAG = "TiUISpinnerColumn"; - private boolean suppressItemSelected = false; - - public TiUISpinnerColumn(TiViewProxy proxy) - { - super(proxy); - if (proxy instanceof PickerColumnProxy && ((PickerColumnProxy) proxy).getCreateIfMissing()) { - layoutParams.autoFillsWidth = true; - } - refreshNativeView(); - preselectRow(); - ((WheelView) nativeView).setItemSelectedListener(this); - } - - private void preselectRow() - { - if (proxy.getParent() instanceof PickerProxy) { - ArrayList preselectedRows = ((PickerProxy) proxy.getParent()).getPreselectedRows(); - if (preselectedRows == null || preselectedRows.size() == 0) { - return; - } - int columnIndex = ((PickerColumnProxy) proxy).getThisColumnIndex(); - if (columnIndex >= 0 && columnIndex < preselectedRows.size()) { - Integer rowIndex = preselectedRows.get(columnIndex); - if (rowIndex != null && rowIndex.intValue() >= 0) { - selectRow(rowIndex); - } - } - } - } - - @Override - public void processProperties(KrollDict d) - { - super.processProperties(d); - if (d.containsKeyStartingWith("font")) { - setFontProperties(); - } - if (d.containsKey(TiC.PROPERTY_COLOR)) { - ((WheelView) nativeView).setTextColor(new Integer(TiConvert.toColor(d, TiC.PROPERTY_COLOR))); - } - if (d.containsKey(TiC.PROPERTY_VISIBLE_ITEMS)) { - ((WheelView) nativeView).setVisibleItems(TiConvert.toInt(d, TiC.PROPERTY_VISIBLE_ITEMS)); - } else { - ((WheelView) nativeView).setVisibleItems(PickerProxy.DEFAULT_VISIBLE_ITEMS_COUNT); - } - if (d.containsKey(TiC.PROPERTY_SELECTION_INDICATOR)) { - ((WheelView) nativeView) - .setShowSelectionIndicator(TiConvert.toBoolean(d, TiC.PROPERTY_SELECTION_INDICATOR)); - } - refreshNativeView(); - } - - private void setFontProperties() - { - WheelView view = (WheelView) nativeView; - String fontFamily = null; - Float fontSize = null; - String fontWeight = null; - Typeface typeface = null; - KrollDict d = proxy.getProperties(); - if (d.containsKey(TiC.PROPERTY_FONT) && d.get(TiC.PROPERTY_FONT) instanceof HashMap) { - KrollDict font = d.getKrollDict(TiC.PROPERTY_FONT); - if (font.containsKey("fontSize")) { - String sFontSize = TiConvert.toString(font, "fontSize"); - fontSize = new Float(TiUIHelper.getSize(sFontSize)); - } - if (font.containsKey("fontFamily")) { - fontFamily = TiConvert.toString(font, "fontFamily"); - } - if (font.containsKey("fontWeight")) { - fontWeight = TiConvert.toString(font, "fontWeight"); - } - } - if (d.containsKeyAndNotNull(TiC.PROPERTY_FONT_FAMILY)) { - fontFamily = TiConvert.toString(d, TiC.PROPERTY_FONT_FAMILY); - } - if (d.containsKeyAndNotNull(TiC.PROPERTY_FONT_SIZE)) { - String sFontSize = TiConvert.toString(d, TiC.PROPERTY_FONT_SIZE); - fontSize = new Float(TiUIHelper.getSize(sFontSize)); - } - if (d.containsKeyAndNotNull(TiC.PROPERTY_FONT_WEIGHT)) { - fontWeight = TiConvert.toString(d, TiC.PROPERTY_FONT_WEIGHT); - } - if (fontFamily != null) { - typeface = TiUIHelper.toTypeface(fontFamily); - } - Integer typefaceWeight = null; - if (fontWeight != null) { - typefaceWeight = new Integer(TiUIHelper.toTypefaceStyle(fontWeight, null)); - } - - boolean dirty = false; - if (typeface != null) { - dirty = dirty || !typeface.equals(view.getTypeface()); - view.setTypeface(typeface); - } - if (typefaceWeight != null) { - dirty = dirty || typefaceWeight.intValue() != view.getTypefaceWeight(); - view.setTypefaceWeight(typefaceWeight); - } - if (fontSize != null) { - int fontSizeInt = fontSize.intValue(); - dirty = dirty || fontSizeInt != view.getTextSize(); - view.setTextSize(fontSize.intValue()); - } - if (dirty) { - ((PickerColumnProxy) proxy).parentShouldRequestLayout(); - } - } - - @Override - public void propertyChanged(String key, Object oldValue, Object newValue, KrollProxy proxy) - { - if (key.startsWith("font")) { - setFontProperties(); - } else if (key.equals(TiC.PROPERTY_COLOR)) { - ((WheelView) nativeView).setTextColor(new Integer(TiConvert.toColor(TiConvert.toString(newValue)))); - } else if (key.equals(TiC.PROPERTY_VISIBLE_ITEMS)) { - ((WheelView) nativeView).setVisibleItems(TiConvert.toInt(newValue)); - } else if (key.equals(TiC.PROPERTY_SELECTION_INDICATOR)) { - ((WheelView) nativeView).setShowSelectionIndicator(TiConvert.toBoolean(newValue)); - } else { - super.propertyChanged(key, oldValue, newValue, proxy); - } - } - - public void refreshNativeView() - { - WheelView view = null; - if (nativeView instanceof WheelView) { - view = (WheelView) nativeView; - } else { - view = new WheelView(proxy.getActivity()); - Float defaultFontSize = new Float(TiUIHelper.getSize(TiUIHelper.getDefaultFontSize(proxy.getActivity()))); - view.setTextSize(defaultFontSize.intValue()); - setNativeView(view); - } - int selectedRow = view.getCurrentItem(); - PickerRowProxy[] rows = ((PickerColumnProxy) proxy).getRows(); - int rowCount = (rows == null) ? 0 : rows.length; - if (selectedRow >= rowCount) { - suppressItemSelected = true; - if (rowCount > 0) { - view.setCurrentItem(rowCount - 1); - } else { - view.setCurrentItem(0); - } - suppressItemSelected = false; - } - TextWheelAdapter adapter = null; - if (rows != null) { - adapter = new TextWheelAdapter(rows); - } - view.setAdapter(adapter); - } - - public void selectRow(int rowIndex) - { - if (nativeView instanceof WheelView) { - WheelView view = (WheelView) nativeView; - if (rowIndex < 0 || rowIndex >= view.getAdapter().getItemsCount()) { - Log.w(TAG, "Ignoring attempt to select out-of-bound row index " + rowIndex); - return; - } - view.setCurrentItem(rowIndex); - } - } - - @Override - public void onItemSelected(WheelView view, int index) - { - if (suppressItemSelected) { - return; - } - ((PickerColumnProxy) proxy).onItemSelected(index); - } - - public int getSelectedRowIndex() - { - int result = -1; - if (nativeView instanceof WheelView) { - result = ((WheelView) nativeView).getCurrentItem(); - } - return result; - } - - public void forceRequestLayout() - { - if (nativeView instanceof WheelView) { - ((WheelView) nativeView).fullLayoutReset(); - } - } -} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUISpinnerRow.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUISpinnerRow.java deleted file mode 100644 index 6360218c499..00000000000 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUISpinnerRow.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Appcelerator Titanium Mobile - * Copyright (c) 2009-2011 by Appcelerator, Inc. All Rights Reserved. - * Licensed under the terms of the Apache Public License - * Please see the LICENSE included with this distribution for details. - */ - -/** - * This class does not do anything, because for our spinner-style picker - * we support only text (the "title" property of the PickerRowProxy), not complex - * views. But to keep the spinner in our view meme, a class was required. - */ -package ti.modules.titanium.ui.widget.picker; - -import org.appcelerator.titanium.proxy.TiViewProxy; -import org.appcelerator.titanium.view.TiUIView; - -public class TiUISpinnerRow extends TiUIView -{ - public TiUISpinnerRow(TiViewProxy proxy) - { - super(proxy); - } -} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUITimePicker.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUITimePicker.java index 8afc8e16231..0b99f3332fa 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUITimePicker.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUITimePicker.java @@ -1,209 +1,357 @@ /** * Appcelerator Titanium Mobile - * Copyright (c) 2009-2012 by Appcelerator, Inc. All Rights Reserved. + * Copyright (c) 2009-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.picker; +import android.content.res.Resources; +import android.os.Build; +import android.view.View; +import android.widget.EditText; +import android.widget.TimePicker; +import androidx.annotation.NonNull; +import com.google.android.material.textfield.TextInputLayout; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; - +import java.util.HashMap; +import java.util.Locale; import org.appcelerator.kroll.KrollDict; +import org.appcelerator.kroll.KrollFunction; +import org.appcelerator.kroll.KrollObject; import org.appcelerator.kroll.KrollProxy; import org.appcelerator.kroll.common.Log; -import org.appcelerator.titanium.proxy.TiViewProxy; +import org.appcelerator.titanium.R; +import org.appcelerator.titanium.TiC; import org.appcelerator.titanium.TiApplication; import org.appcelerator.titanium.util.TiConvert; -import org.appcelerator.titanium.util.TiRHelper; import org.appcelerator.titanium.util.TiUIHelper; -import org.appcelerator.titanium.util.TiRHelper.ResourceNotFoundException; import org.appcelerator.titanium.view.TiUIView; +import ti.modules.titanium.ui.PickerProxy; +import ti.modules.titanium.ui.UIModule; -import android.app.Activity; -import android.content.res.Resources; -import android.os.Build; -import android.view.View; -import android.widget.TimePicker; -import android.widget.TimePicker.OnTimeChangedListener; - -public class TiUITimePicker extends TiUIView implements OnTimeChangedListener +public class TiUITimePicker extends TiUIView implements TimePicker.OnTimeChangedListener { private static final String TAG = "TiUITimePicker"; - private boolean suppressChangeEvent = false; - - protected Date minDate, maxDate; - protected int minuteInterval; - private static int id_am = 0; private static int id_pm = 0; + private TiUITimePicker.DialogCallback dialogCallback; + private boolean suppressChangeEvent = false; - public TiUITimePicker(TiViewProxy proxy) + public TiUITimePicker(@NonNull PickerProxy proxy) { super(proxy); - } - public TiUITimePicker(final TiViewProxy proxy, Activity activity) - { - this(proxy); Log.d(TAG, "Creating a time picker", Log.DEBUG_MODE); - final TimePicker picker; - // If it is not API Level 21 (Android 5.0), create picker normally. - // If not, it will inflate a spinner picker to address a bug. - if (Build.VERSION.SDK_INT != Build.VERSION_CODES.LOLLIPOP) { - picker = new TimePicker(activity) { - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) - { - super.onLayout(changed, left, top, right, bottom); - TiUIHelper.firePostLayoutEvent(proxy); - } - }; - - // TIMOB-8430: https://issuetracker.google.com/issues/36931448 - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP - && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { - Resources resources = TiApplication.getInstance().getResources(); - if (id_am == 0) { - id_am = resources.getIdentifier("android:id/am_label", "drawable", "android.widget.TimePicker"); - } - if (id_pm == 0) { - id_pm = resources.getIdentifier("android:id/pm_label", "drawable", "android.widget.TimePicker"); - } - View am = (View) picker.findViewById(id_am); - View pm = (View) picker.findViewById(id_pm); - View.OnClickListener listener = new View.OnClickListener() { - @Override - public void onClick(View v) - { + // Determine if we should use a spinner, clock view, or text field. + boolean useTextField = false; + boolean useSpinner = false; + if (proxy.hasProperty(TiC.PROPERTY_DATE_PICKER_STYLE)) { + switch (TiConvert.toInt(proxy.getProperty(TiC.PROPERTY_DATE_PICKER_STYLE))) { + case UIModule.DATE_PICKER_STYLE_AUTOMATIC: + case UIModule.DATE_PICKER_STYLE_COMPACT: + useTextField = true; + break; + case UIModule.DATE_PICKER_STYLE_INLINE: + useSpinner = false; // Use clock view. + break; + case UIModule.DATE_PICKER_STYLE_WHEELS: + useSpinner = true; + break; + default: + break; + } + } + if (proxy.hasPropertyAndNotNull(TiC.PROPERTY_USE_SPINNER)) { + useSpinner = TiConvert.toBoolean(proxy.getProperty(TiC.PROPERTY_USE_SPINNER), useSpinner); + if (useSpinner) { + useTextField = false; + } + } + if (proxy.hasPropertyAndNotNull(TiC.PROPERTY_NATIVE_SPINNER)) { + useSpinner = TiConvert.toBoolean(proxy.getProperty(TiC.PROPERTY_NATIVE_SPINNER), useSpinner); + if (useSpinner) { + useTextField = false; + } + } + if (!useSpinner && (Build.VERSION.SDK_INT == 21)) { + // Android 5.0 fails to call onTimeChanged() for clock view. (Android 5.1+ is okay.) + // See: https://code.google.com/p/android/issues/detail?id=147657 + Log.w(TAG, "Ti.UI.Picker cannot show an inlined calendar view on Android 5.0. Using spinner instead."); + useSpinner = true; + } + + // Create the time picker view. + View view = null; + if (useTextField) { + // Attempt to create a text field which will show a time selection dialog when tapped on. + TextInputLayout textInputLayout = proxy.createTextInputLayout(); + if ((textInputLayout != null) && (textInputLayout.getEditText() != null)) { + this.dialogCallback = new TiUITimePicker.DialogCallback(this); + View.OnClickListener clickListener = (View v) -> { + textInputLayout.requestFocus(); + HashMap options = new HashMap<>(); + options.put(TiC.PROPERTY_CALLBACK, this.dialogCallback); + proxy.showTimePickerDialog(new Object[] { options }); + }; + textInputLayout.getEditText().setOnClickListener(clickListener); + textInputLayout.setEndIconOnClickListener(clickListener); + view = textInputLayout; + } + } + if (view == null) { + // Create a spinner or clock view. + TimePicker timePicker; + if (useSpinner) { + // Create picker with spinners. + int timePickerSpinner = R.layout.titanium_ui_time_picker_spinner; + timePicker = (TimePicker) proxy.getActivity().getLayoutInflater().inflate(timePickerSpinner, null); + timePicker.setDescendantFocusability(TimePicker.FOCUS_BLOCK_DESCENDANTS); + } else { + // Create picker with a clock view. + timePicker = new TimePicker(proxy.getActivity()); + + // Work-around Google bug where onTimeChanged() is not called when tapping AM/PM buttons. + // See: https://issuetracker.google.com/issues/36931448 + if ((Build.VERSION.SDK_INT > 21) && (Build.VERSION.SDK_INT <= 23)) { + Resources resources = TiApplication.getInstance().getResources(); + if (id_am == 0) { + id_am = resources.getIdentifier("android:id/am_label", "drawable", "android.widget.TimePicker"); + } + if (id_pm == 0) { + id_pm = resources.getIdentifier("android:id/pm_label", "drawable", "android.widget.TimePicker"); + } + View amView = timePicker.findViewById(id_am); + View pmView = timePicker.findViewById(id_pm); + View.OnClickListener listener = (View v) -> { if (Build.VERSION.SDK_INT >= 23) { - picker.setHour((picker.getHour() + 12) % 24); + timePicker.setHour((timePicker.getHour() + 12) % 24); } else { - picker.setCurrentHour((picker.getCurrentHour() + 12) % 24); + timePicker.setCurrentHour((timePicker.getCurrentHour() + 12) % 24); } + }; + if (amView != null) { + amView.setOnClickListener(listener); + } + if (pmView != null) { + pmView.setOnClickListener(listener); } - }; - if (am != null) { - am.setOnClickListener(listener); - } - if (pm != null) { - pm.setOnClickListener(listener); - } - } - - } else { - // A bug where PickerCalendarDelegate does not send events to the - // listener on API Level 21 (Android 5.0) for TIMOB-19192 - // https://code.google.com/p/android/issues/detail?id=147657 - // Work around is to use spinner view instead of calendar view in - // in Android 5.0 - int timePickerSpinner; - try { - timePickerSpinner = TiRHelper.getResource("layout.titanium_ui_time_picker_spinner"); - } catch (ResourceNotFoundException e) { - if (Log.isDebugModeEnabled()) { - Log.e(TAG, "XML resources could not be found!!!"); } - return; } - picker = (TimePicker) activity.getLayoutInflater().inflate(timePickerSpinner, null); + timePicker.setIs24HourView(false); + timePicker.setOnTimeChangedListener(this); + view = timePicker; } - picker.setIs24HourView(false); - picker.setOnTimeChangedListener(this); - setNativeView(picker); + view.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()); + } + }); + setNativeView(view); } @Override - public void processProperties(KrollDict d) + public void processProperties(KrollDict properties) { - super.processProperties(d); - - boolean valueExistsInProxy = false; - Calendar calendar = Calendar.getInstance(); - - TimePicker picker = (TimePicker) getNativeView(); - if (d.containsKey("value")) { - calendar.setTime((Date) d.get("value")); - valueExistsInProxy = true; - } - if (d.containsKey("minDate")) { - this.minDate = (Date) d.get("minDate"); - } - if (d.containsKey("maxDate")) { - this.maxDate = (Date) d.get("maxDate"); - } - if (d.containsKey("minuteInterval")) { - int mi = d.getInt("minuteInterval"); - if (mi >= 1 && mi <= 30 && mi % 60 == 0) { - this.minuteInterval = mi; - } - } + super.processProperties(properties); - // Undocumented but maybe useful for Android - boolean is24HourFormat = false; - if (d.containsKey("format24")) { - is24HourFormat = d.getBoolean("format24"); + // Configure picker for 12 hour or 24 hour format. + TimePicker timePicker = getTimePicker(); + if (timePicker != null) { + timePicker.setIs24HourView(TiConvert.toBoolean(properties, TiC.PROPERTY_FORMAT_24, false)); } - picker.setIs24HourView(is24HourFormat); - setValue(calendar.getTimeInMillis(), true); - - if (!valueExistsInProxy) { - proxy.setProperty("value", calendar.getTime()); + // Fetch time value and display it in the view. + Calendar calendar = Calendar.getInstance(); + Date dateValue = TiConvert.toDate(properties.get(TiC.PROPERTY_VALUE)); + if (dateValue != null) { + calendar.setTime(dateValue); } + setValue(calendar, true); - //iPhone ignores both values if max <= min - if (minDate != null && maxDate != null) { - if (maxDate.compareTo(minDate) <= 0) { - Log.w(TAG, "maxDate is less or equal minDate, ignoring both settings."); - minDate = null; - maxDate = null; - } + // Update proxy's "value" property with above time if not assigned. + if (proxy.getProperty(TiC.PROPERTY_VALUE) == null) { + proxy.setProperty(TiC.PROPERTY_VALUE, calendar.getTime()); } } @Override public void propertyChanged(String key, Object oldValue, Object newValue, KrollProxy proxy) { - if (key.equals("value")) { - Date date = (Date) newValue; - setValue(date.getTime()); - } else if (key.equals("format24")) { - ((TimePicker) getNativeView()).setIs24HourView(TiConvert.toBoolean(newValue)); + if (key.equals(TiC.PROPERTY_VALUE)) { + setValue(TiConvert.toDate(newValue)); + } else if (key.equals(TiC.PROPERTY_FORMAT_24)) { + TimePicker timePicker = getTimePicker(); + if (timePicker != null) { + timePicker.setIs24HourView(TiConvert.toBoolean(newValue, false)); + } else if (this.proxy != null) { + setValue(TiConvert.toDate(this.proxy.getProperty(TiC.PROPERTY_VALUE))); + } + } else { + super.propertyChanged(key, oldValue, newValue, proxy); } - super.propertyChanged(key, oldValue, newValue, proxy); } - public void setValue(long value) + public void setValue(Object value) { setValue(value, false); } - public void setValue(long value, boolean suppressEvent) + public void setValue(@NonNull Calendar value) + { + setValue(value, false); + } + + public void setValue(Object value, boolean suppressEvent) { - TimePicker picker = (TimePicker) getNativeView(); Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(value); - - // This causes two events to fire. - suppressChangeEvent = true; - picker.setCurrentHour(calendar.get(Calendar.HOUR_OF_DAY)); - suppressChangeEvent = suppressEvent; - picker.setCurrentMinute(calendar.get(Calendar.MINUTE)); - suppressChangeEvent = false; + Date newDate = TiConvert.toDate(value); + if (newDate != null) { + calendar.setTime(newDate); + } + setValue(calendar, suppressEvent); + } + + public void setValue(@NonNull Calendar value, boolean suppressEvent) + { + // Update TimePicker view if used. + TimePicker timePicker = getTimePicker(); + if (timePicker != null) { + this.suppressChangeEvent = true; + timePicker.setCurrentHour(value.get(Calendar.HOUR_OF_DAY)); + this.suppressChangeEvent = suppressEvent; + timePicker.setCurrentMinute(value.get(Calendar.MINUTE)); + this.suppressChangeEvent = false; + return; + } + + // We're likely using a text field instead. Format time to localized string. + EditText editText = null; + View view = getNativeView(); + if (view instanceof TextInputLayout) { + editText = ((TextInputLayout) view).getEditText(); + } else if (view instanceof EditText) { + editText = (EditText) view; + } + if (editText != null) { + DateFormat dateFormat = null; + try { + String datePattern; + if ((proxy != null) && TiConvert.toBoolean(proxy.getProperty(TiC.PROPERTY_FORMAT_24), false)) { + datePattern = "HH:mm"; + } else { + datePattern = "hh:mm a"; + } + Locale locale = Locale.getDefault(); + datePattern = android.text.format.DateFormat.getBestDateTimePattern(locale, datePattern); + datePattern = datePattern.replace('b', 'a'); + dateFormat = new SimpleDateFormat(datePattern, locale); + } catch (Throwable ex) { + Log.e(TAG, "Failed to generate 'best' date pattern.", ex); + dateFormat = DateFormat.getTimeInstance(DateFormat.SHORT); + } + editText.setText(dateFormat.format(value.getTime())); + editText.requestLayout(); + } } @Override public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { + // Do not continue if proxy was released. + if (this.proxy == null) { + return; + } + + // Create date object from selected hour and minute values. Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, hourOfDay); calendar.set(Calendar.MINUTE, minute); - proxy.setProperty("value", calendar.getTime()); - if (!suppressChangeEvent) { + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + Date dateValue = calendar.getTime(); + + // Update "value" property with selected time. + this.proxy.setProperty(TiC.PROPERTY_VALUE, calendar.getTime()); + + // Fire a "change" event. + if (!this.suppressChangeEvent) { + KrollDict data = new KrollDict(); + data.put(TiC.PROPERTY_VALUE, calendar.getTime()); + fireEvent(TiC.EVENT_CHANGE, data); + } + } + + private TimePicker getTimePicker() + { + View view = getNativeView(); + if (view instanceof TimePicker) { + return (TimePicker) view; + } + return null; + } + + /** Callback to be passed to PickerProxy.showTimePickerDialog() method to acquire selected time. */ + private static class DialogCallback implements KrollFunction + { + private TiUITimePicker picker; + + public DialogCallback(@NonNull TiUITimePicker picker) + { + this.picker = picker; + } + + @Override + public Object call(KrollObject krollObject, HashMap args) + { + callAsync(krollObject, args); + return null; + } + + @Override + public Object call(KrollObject krollObject, Object[] args) + { + callAsync(krollObject, args); + return null; + } + + @Override + public void callAsync(KrollObject krollObject, HashMap args) + { + // Validate. + if (args == null) { + return; + } + + // Fetch selected time "value". Property will be null if user canceled out. + Object objectValue = args.get(TiC.PROPERTY_VALUE); + if (!(objectValue instanceof Date)) { + return; + } + Date dateValue = (Date) objectValue; + + // Show selected time in text field. + this.picker.setValue(dateValue); + + // Fire a "change" event. KrollDict data = new KrollDict(); - data.put("value", calendar.getTime()); - fireEvent("change", data); + data.put(TiC.PROPERTY_VALUE, dateValue); + this.picker.fireEvent(TiC.EVENT_CHANGE, data); + } + + @Override + public void callAsync(KrollObject krollObject, Object[] args) + { + if ((args != null) && (args.length > 0) && (args[0] instanceof HashMap)) { + callAsync(krollObject, (HashMap) args[0]); + } } } } diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUITimeSpinner.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUITimeSpinner.java deleted file mode 100644 index c7f499e32d3..00000000000 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUITimeSpinner.java +++ /dev/null @@ -1,296 +0,0 @@ -/** - * Appcelerator Titanium Mobile - * Copyright (c) 2009-2012 by Appcelerator, 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.picker; - -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; - -import kankan.wheel.widget.WheelView; - -import org.appcelerator.kroll.KrollDict; -import org.appcelerator.kroll.KrollProxy; -import org.appcelerator.kroll.common.Log; -import org.appcelerator.titanium.TiC; -import org.appcelerator.titanium.proxy.TiViewProxy; -import org.appcelerator.titanium.util.TiConvert; -import org.appcelerator.titanium.util.TiUIHelper; -import org.appcelerator.titanium.view.TiUIView; - -import android.app.Activity; -import android.content.Context; -import android.graphics.Typeface; -import android.widget.LinearLayout; - -public class TiUITimeSpinner extends TiUIView implements WheelView.OnItemSelectedListener -{ - private WheelView hoursWheel; - private WheelView minutesWheel; - private WheelView amPmWheel; - private boolean suppressChangeEvent = false; - private boolean ignoreItemSelection = false; - private static final String TAG = "TiUITimeSpinner"; - - private final Calendar calendar = Calendar.getInstance(); - - public TiUITimeSpinner(TiViewProxy proxy) - { - super(proxy); - } - public TiUITimeSpinner(TiViewProxy proxy, Activity activity) - { - this(proxy); - createNativeView(activity); - } - - private FormatNumericWheelAdapter makeHoursAdapter(boolean format24) - { - DecimalFormat formatter = new DecimalFormat("00"); - return new FormatNumericWheelAdapter(format24 ? 0 : 1, format24 ? 23 : 12, formatter, 6); - } - - private WheelView makeAmPmWheel(Context context, int textSize) - { - ArrayList amPmRows = new ArrayList<>(); - amPmRows.add(" am "); - amPmRows.add(" pm "); - WheelView view = new WheelView(context); - view.setAdapter(new TextWheelAdapter(amPmRows)); - view.setTextSize(textSize); - view.setItemSelectedListener(this); - return view; - } - private void createNativeView(Activity activity) - { - boolean format24 = true; - if (proxy.hasProperty("format24")) { - format24 = TiConvert.toBoolean(proxy.getProperty("format24")); - } - - int minuteInterval = 1; - if (proxy.hasProperty(TiC.PROPERTY_MINUTE_INTERVAL)) { - int dirtyMinuteInterval = TiConvert.toInt(proxy.getProperty(TiC.PROPERTY_MINUTE_INTERVAL)); - if ((dirtyMinuteInterval > 0) && (dirtyMinuteInterval <= 30) && (60 % dirtyMinuteInterval == 0)) { - minuteInterval = dirtyMinuteInterval; - } else { - Log.w(TAG, "Clearing invalid minuteInterval property value of " + dirtyMinuteInterval); - proxy.setProperty(TiC.PROPERTY_MINUTE_INTERVAL, null); - } - } - - DecimalFormat formatter = new DecimalFormat("00"); - FormatNumericWheelAdapter hours = makeHoursAdapter(format24); - FormatNumericWheelAdapter minutes = new FormatNumericWheelAdapter(0, 59, formatter, 6, minuteInterval); - hoursWheel = new WheelView(activity); - minutesWheel = new WheelView(activity); - hoursWheel.setTextSize(20); - minutesWheel.setTextSize(hoursWheel.getTextSize()); - hoursWheel.setAdapter(hours); - minutesWheel.setAdapter(minutes); - hoursWheel.setItemSelectedListener(this); - minutesWheel.setItemSelectedListener(this); - - amPmWheel = null; - - if (!format24) { - amPmWheel = makeAmPmWheel(activity, hoursWheel.getTextSize()); - } - - LinearLayout layout = new LinearLayout(activity); - layout.setOrientation(LinearLayout.HORIZONTAL); - layout.addView(hoursWheel); - layout.addView(minutesWheel); - if (!format24) { - layout.addView(amPmWheel); - } - setNativeView(layout); - } - - @Override - public void processProperties(KrollDict d) - { - super.processProperties(d); - - boolean valueExistsInProxy = false; - - if (d.containsKey(TiC.PROPERTY_FONT)) { - setFontProperties(); - } - - if (d.containsKey(TiC.PROPERTY_VALUE)) { - calendar.setTime((Date) d.get(TiC.PROPERTY_VALUE)); - valueExistsInProxy = true; - } - - setValue(calendar.getTimeInMillis(), true); - - if (!valueExistsInProxy) { - proxy.setProperty(TiC.PROPERTY_VALUE, calendar.getTime()); - } - } - - private void setFontProperties() - { - Float fontSize = null; - Typeface typeface = null; - String[] fontProperties = TiUIHelper.getFontProperties(proxy.getProperties()); - - if (fontProperties[TiUIHelper.FONT_SIZE_POSITION] != null) { - fontSize = Float.valueOf(TiUIHelper.getSize(fontProperties[TiUIHelper.FONT_SIZE_POSITION])); - } - - if (fontProperties[TiUIHelper.FONT_FAMILY_POSITION] != null) { - typeface = TiUIHelper.toTypeface(fontProperties[TiUIHelper.FONT_FAMILY_POSITION]); - } - Integer typefaceWeight = null; - if (fontProperties[TiUIHelper.FONT_WEIGHT_POSITION] != null) { - typefaceWeight = Integer.valueOf(TiUIHelper.toTypefaceStyle(fontProperties[TiUIHelper.FONT_WEIGHT_POSITION], - fontProperties[TiUIHelper.FONT_SIZE_POSITION])); - } - - if (typeface != null) { - hoursWheel.setTypeface(typeface); - minutesWheel.setTypeface(typeface); - if (amPmWheel != null) { - amPmWheel.setTypeface(typeface); - } - } - if (typefaceWeight != null) { - hoursWheel.setTypefaceWeight(typefaceWeight); - minutesWheel.setTypefaceWeight(typefaceWeight); - if (amPmWheel != null) { - amPmWheel.setTypefaceWeight(typefaceWeight); - } - } - if (fontSize != null) { - hoursWheel.setTextSize(fontSize.intValue()); - minutesWheel.setTextSize(fontSize.intValue()); - if (amPmWheel != null) { - amPmWheel.setTextSize(fontSize.intValue()); - } - } - hoursWheel.invalidate(); - minutesWheel.invalidate(); - if (amPmWheel != null) { - amPmWheel.invalidate(); - } - } - - @Override - public void propertyChanged(String key, Object oldValue, Object newValue, KrollProxy proxy) - { - if (key.equals(TiC.PROPERTY_VALUE)) { - Date date = (Date) newValue; - setValue(date.getTime()); - } else if (key.equals("format24")) { - boolean is24HourFormat = TiConvert.toBoolean(newValue); - ignoreItemSelection = true; - suppressChangeEvent = true; - hoursWheel.setAdapter(makeHoursAdapter(is24HourFormat)); - LinearLayout vg = (LinearLayout) nativeView; - if (is24HourFormat && vg.indexOfChild(amPmWheel) >= 0) { - vg.removeView(amPmWheel); - } else if (!is24HourFormat && vg.getChildCount() < 3) { - amPmWheel = makeAmPmWheel(hoursWheel.getContext(), hoursWheel.getTextSize()); - vg.addView(amPmWheel); - } - setValue(calendar.getTimeInMillis(), true); // updates the time display - ignoreItemSelection = false; - suppressChangeEvent = false; - } else if (key.equals(TiC.PROPERTY_MINUTE_INTERVAL)) { - int interval = TiConvert.toInt(newValue); - if ((interval > 0) && (interval <= 30) && (60 % interval == 0)) { - FormatNumericWheelAdapter adapter = (FormatNumericWheelAdapter) minutesWheel.getAdapter(); - adapter.setStepValue(interval); - minutesWheel.setAdapter(adapter); // forces the wheel to re-do its items listing - } else { - // Reject it - Log.w(TAG, "Ignoring illegal minuteInterval value: " + interval); - proxy.setProperty(TiC.PROPERTY_MINUTE_INTERVAL, oldValue); - } - } else if (key.equals(TiC.PROPERTY_FONT)) { - setFontProperties(); - } - super.propertyChanged(key, oldValue, newValue, proxy); - } - - public void setValue(long value) - { - setValue(value, false); - } - - public void setValue(long value, boolean suppressEvent) - { - boolean format24 = true; - if (proxy.hasProperty("format24")) { - format24 = TiConvert.toBoolean(proxy.getProperty("format24")); - } - calendar.setTimeInMillis(value); - - suppressChangeEvent = true; - ignoreItemSelection = true; - - if (!format24) { - int hour = calendar.get(Calendar.HOUR); - if (hour == 0) { - hoursWheel.setCurrentItem(11); // 12 - } else { - hoursWheel.setCurrentItem(hour - 1); // i.e., the visible "1" on the wheel is index 0. - } - if (calendar.get(Calendar.HOUR_OF_DAY) <= 11) { - amPmWheel.setCurrentItem(0); - } else { - amPmWheel.setCurrentItem(1); - } - } else { - hoursWheel.setCurrentItem(calendar.get(Calendar.HOUR_OF_DAY)); - } - - suppressChangeEvent = suppressEvent; - ignoreItemSelection = false; - minutesWheel.setCurrentItem( - ((FormatNumericWheelAdapter) minutesWheel.getAdapter()).getIndex(calendar.get(Calendar.MINUTE))); - suppressChangeEvent = false; - } - - @Override - public void onItemSelected(WheelView view, int index) - { - if (ignoreItemSelection) { - return; - } - boolean format24 = true; - if (proxy.hasProperty("format24")) { - format24 = TiConvert.toBoolean(proxy.getProperty("format24")); - } - calendar.set(Calendar.MINUTE, - ((FormatNumericWheelAdapter) minutesWheel.getAdapter()).getValue(minutesWheel.getCurrentItem())); - if (!format24) { - int hourOfDay = 0; - if (hoursWheel.getCurrentItem() == 11) { // "12" on the dial - if (amPmWheel.getCurrentItem() == 0) { // "am" - hourOfDay = 0; - } else { - hourOfDay = 12; - } - } else { - hourOfDay = 1 + (12 * amPmWheel.getCurrentItem()) + hoursWheel.getCurrentItem(); - } - calendar.set(Calendar.HOUR_OF_DAY, hourOfDay); - } else { - calendar.set(Calendar.HOUR_OF_DAY, hoursWheel.getCurrentItem()); - } - Date dateval = calendar.getTime(); - proxy.setProperty("value", dateval); - if (!suppressChangeEvent) { - KrollDict data = new KrollDict(); - data.put("value", dateval); - fireEvent("change", data); - } - } -} diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUITimeSpinnerNumberPicker.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUITimeSpinnerNumberPicker.java deleted file mode 100644 index 544dfaa5800..00000000000 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/picker/TiUITimeSpinnerNumberPicker.java +++ /dev/null @@ -1,230 +0,0 @@ -/** - * Appcelerator Titanium Mobile - * Copyright (c) 2016 by Appcelerator, 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.picker; - -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.List; - -import org.appcelerator.kroll.KrollDict; -import org.appcelerator.kroll.KrollProxy; -import org.appcelerator.kroll.common.Log; -import org.appcelerator.titanium.TiC; -import org.appcelerator.titanium.proxy.TiViewProxy; -import org.appcelerator.titanium.util.TiConvert; -import org.appcelerator.titanium.view.TiUIView; - -import android.app.Activity; -import android.content.Context; -import android.widget.LinearLayout; -import android.widget.NumberPicker; - -public class TiUITimeSpinnerNumberPicker extends TiUIView implements NumberPicker.OnValueChangeListener -{ - - private NumberPicker hoursWheel; - private String[] hoursString; - private NumberPicker minutesWheel; - private String[] minutesString; - private NumberPicker amPmWheel; - private boolean suppressChangeEvent = false; - private boolean ignoreItemSelection = false; - private static final String TAG = "TiUITimeSpinnerNumberPicker"; - private final Calendar calendar = Calendar.getInstance(); - - public TiUITimeSpinnerNumberPicker(TiViewProxy proxy) - { - super(proxy); - } - - public TiUITimeSpinnerNumberPicker(TiViewProxy proxy, Activity activity) - { - this(proxy); - createNativeView(activity); - } - - private NumberPicker makeAmPmWheel(Context context) - { - NumberPicker view = new NumberPicker(context); - String[] amPmRows = { " am ", " pm " }; - view.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS); - view.setDisplayedValues(amPmRows); - view.setMaxValue(amPmRows.length - 1); - view.setMinValue(0); - view.setOnValueChangedListener(this); - return view; - } - - private void createNativeView(Activity activity) - { - boolean format24 = true; - if (proxy.hasProperty("format24")) { - format24 = TiConvert.toBoolean(proxy.getProperty("format24")); - } - - int minuteInterval = 1; - if (proxy.hasProperty(TiC.PROPERTY_MINUTE_INTERVAL)) { - int dirtyMinuteInterval = TiConvert.toInt(proxy.getProperty(TiC.PROPERTY_MINUTE_INTERVAL)); - if ((dirtyMinuteInterval > 0) && (dirtyMinuteInterval <= 30) && (60 % dirtyMinuteInterval == 0)) { - minuteInterval = dirtyMinuteInterval; - } else { - Log.w(TAG, "Clearing invalid minuteInterval property value of " + dirtyMinuteInterval); - proxy.setProperty(TiC.PROPERTY_MINUTE_INTERVAL, null); - } - } - - DecimalFormat formatter = new DecimalFormat("00"); - hoursWheel = new NumberPicker(activity); - minutesWheel = new NumberPicker(activity); - hoursString = generateNumbers(format24 ? 0 : 1, format24 ? 23 : 12, formatter, 6, 1); - hoursWheel.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS); - hoursWheel.setDisplayedValues(hoursString); - hoursWheel.setMaxValue(hoursString.length - 1); - hoursWheel.setMinValue(0); - minutesString = generateNumbers(0, 59, formatter, 6, minuteInterval); - minutesWheel.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS); - minutesWheel.setDisplayedValues(minutesString); - minutesWheel.setMaxValue(minutesString.length - 1); - minutesWheel.setMinValue(0); - hoursWheel.setOnValueChangedListener(this); - minutesWheel.setOnValueChangedListener(this); - amPmWheel = null; - - if (!format24) { - amPmWheel = makeAmPmWheel(activity); - } - - LinearLayout layout = new LinearLayout(activity); - layout.setOrientation(LinearLayout.HORIZONTAL); - layout.addView(hoursWheel); - layout.addView(minutesWheel); - if (!format24) { - layout.addView(amPmWheel); - } - - setNativeView(layout); - } - - private String[] generateNumbers(int minValue, int maxValue, NumberFormat formatter, int maxCharLength, - int stepValue) - { - int itemCount = ((maxValue - minValue) / stepValue) + 1; - List list = new ArrayList<>(); - for (int index = 0; index < itemCount; index++) { - int actualValue = minValue + index * stepValue; - if (formatter != null) { - list.add(formatter.format(actualValue)); - } else { - list.add(Integer.toString(actualValue)); - } - } - return list.toArray(new String[0]); - } - - @Override - public void processProperties(KrollDict d) - { - super.processProperties(d); - - boolean valueExistsInProxy = false; - - if (d.containsKey(TiC.PROPERTY_VALUE)) { - calendar.setTime((Date) d.get(TiC.PROPERTY_VALUE)); - valueExistsInProxy = true; - } - - setValue(calendar.getTimeInMillis()); - - if (!valueExistsInProxy) { - proxy.setProperty(TiC.PROPERTY_VALUE, calendar.getTime()); - } - } - - @Override - public void propertyChanged(String key, Object oldValue, Object newValue, KrollProxy proxy) - { - if (key.equals(TiC.PROPERTY_VALUE)) { - Date date = (Date) newValue; - setValue(date.getTime()); - } - - super.propertyChanged(key, oldValue, newValue, proxy); - } - - public void setValue(long value) - { - boolean format24 = true; - if (proxy.hasProperty("format24")) { - format24 = TiConvert.toBoolean(proxy.getProperty("format24")); - } - calendar.setTimeInMillis(value); - - if (!format24) { - int hour = calendar.get(Calendar.HOUR); - if (hour == 0) { - hoursWheel.setValue(11); // 12 - } else { - hoursWheel.setValue(hour - 1); // i.e., the visible "1" on the wheel is index 0. - } - if (calendar.get(Calendar.HOUR_OF_DAY) <= 11) { - amPmWheel.setValue(0); - } else { - amPmWheel.setValue(1); - } - } else { - hoursWheel.setValue(calendar.get(Calendar.HOUR_OF_DAY)); - } - - int found = 0; - for (int x = 0; x < minutesString.length; x++) { - String minToFind = calendar.get(Calendar.MINUTE) + ""; - if (minutesString[x].equalsIgnoreCase(minToFind)) { - found = x; - } - } - minutesWheel.setValue(found); - } - - @Override - public void onValueChange(NumberPicker picker, int oldVal, int newVal) - { - - if (ignoreItemSelection) { - return; - } - boolean format24 = true; - if (proxy.hasProperty("format24")) { - format24 = TiConvert.toBoolean(proxy.getProperty("format24")); - } - calendar.set(Calendar.MINUTE, Integer.valueOf(minutesString[minutesWheel.getValue()])); - if (!format24) { - int hourOfDay = 0; - if (hoursWheel.getValue() == 11) { // "12" on the dial - if (amPmWheel.getValue() == 0) { // "am" - hourOfDay = 0; - } else { - hourOfDay = 12; - } - } else { - hourOfDay = 1 + (12 * amPmWheel.getValue()) + hoursWheel.getValue(); - } - calendar.set(Calendar.HOUR_OF_DAY, hourOfDay); - } else { - calendar.set(Calendar.HOUR_OF_DAY, hoursWheel.getValue()); - } - Date dateval = calendar.getTime(); - proxy.setProperty(TiC.PROPERTY_VALUE, dateval); - if (!suppressChangeEvent) { - KrollDict data = new KrollDict(); - data.put(TiC.PROPERTY_VALUE, dateval); - fireEvent(TiC.EVENT_CHANGE, data); - } - } -} diff --git a/android/titanium/src/java/org/appcelerator/titanium/TiC.java b/android/titanium/src/java/org/appcelerator/titanium/TiC.java index 9892b423d66..a6787279b71 100644 --- a/android/titanium/src/java/org/appcelerator/titanium/TiC.java +++ b/android/titanium/src/java/org/appcelerator/titanium/TiC.java @@ -364,6 +364,7 @@ public class TiC public static final String PROPERTY_CURVE = "curve"; public static final String PROPERTY_DATA = "data"; public static final String PROPERTY_DATE = "date"; + public static final String PROPERTY_DATE_PICKER_STYLE = "datePickerStyle"; public static final String PROPERTY_DAY_BEFORE_MONTH = "dayBeforeMonth"; public static final String PROPERTY_DECODE_RETRIES = "decodeRetries"; public static final String PROPERTY_DESCRIPTION = "description"; @@ -667,7 +668,6 @@ public class TiC public static final String PROPERTY_SELECTED_BACKGROUND_COLOR = "selectedBackgroundColor"; public static final String PROPERTY_SELECTED_BACKGROUND_IMAGE = "selectedBackgroundImage"; public static final String PROPERTY_SELECTED_INDEX = "selectedIndex"; - public static final String PROPERTY_SELECTION_INDICATOR = "selectionIndicator"; public static final String PROPERTY_SELECTION_OPENS = "selectionOpens"; public static final String PROPERTY_SEPARATOR_COLOR = "separatorColor"; public static final String PROPERTY_SEPARATOR_HEIGHT = "separatorHeight"; @@ -769,7 +769,6 @@ public class TiC public static final String PROPERTY_VISIBLE = "visible"; public static final String PROPERTY_VISIBILITY = "visibility"; public static final String PROPERTY_VISIBLE_ITEM_COUNT = "visibleItemCount"; - public static final String PROPERTY_VISIBLE_ITEMS = "visibleItems"; public static final String PROPERTY_VIEW = "view"; public static final String PROPERTY_VIEWS = "views"; public static final String PROPERTY_VOLUME = "volume"; diff --git a/apidoc/Titanium/UI/Picker.yml b/apidoc/Titanium/UI/Picker.yml index f3a04f3075c..c0e7550d08d 100644 --- a/apidoc/Titanium/UI/Picker.yml +++ b/apidoc/Titanium/UI/Picker.yml @@ -210,6 +210,24 @@ properties: since: { iphone: "5.2.0", ipad: "5.2.0", android: "0.9.0"} default: White (iOS), Transparent (Android) + - name: borderStyle + summary: Border style to use when picker is shown as a text field or drop-down field. + description: | + This is an Android only property which only applies to pickers of type: + + * [PICKER_TYPE_DATE](Titanium.UI.PICKER_TYPE_DATE) or [PICKER_TYPE_TIME](Titanium.UI.PICKER_TYPE_TIME) + that also have [datePickerStyle](Titanium.UI.Picker.datePickerStyle) set to + [DATE_PICKER_STYLE_COMPACT](Titanium.UI.DATE_PICKER_STYLE_COMPACT). + + * [PICKER_TYPE_PLAIN](Titanium.UI.PICKER_TYPE_PLAIN) that also have property + [useSpinner](Titanium.UI.Picker.useSpinner) set `false` to show a drop-down picker. + type: Number + constants: Titanium.UI.INPUT_BORDERSTYLE_* + default: + availability: creation + platforms: [android] + since: "10.0.1" + - name: columns summary: Columns used for this picker, as an array of objects. description: | @@ -254,8 +272,8 @@ properties: The picker type does not support text customizing as stated in the [UIKit User Interface Catalog](https://developer.apple.com/documentation/uikit/uidatepicker). - Important: On iOS 14+, you also have to set the to - in order to use this property. + Important: On iOS 14+, you also have to set the to + in order to use this property. type: [String, Titanium.UI.Color] platforms: [iphone, ipad] since: "5.2.0" @@ -265,24 +283,29 @@ properties: summary: | Determines whether the Time pickers display in 24-hour or 12-hour clock format. description: | - Applicable to and - picker types. + Only applcable to pickers of type . - When this property is enabled, a time picker is displayed with hours 0 through 23, - or with hours 1 through 12 and am/pm controls otherwise. + When this property is set `true`, a time picker is displayed with hours 0 through 23. + When set `false`, hours will be 1 through 12 with am/pm controls. + type: Boolean + default: false + platforms: [android] - For backward compatibility, the default value of this property depends on the style of - picker in use. + - name: hintText + summary: Text to be shown above date/time when picker is shown as a text field or drop-down field. + description: | + This is an Android only property which only applies to pickers of type: - For a spinner picker (when `useSpinner` is `true`), this defaults to `true` (24-hour format.) - For a native picker (when `useSpinner` is either un-set or `false`), this defaults to `false` - (12-hour format.) + * [PICKER_TYPE_DATE](Titanium.UI.PICKER_TYPE_DATE) or [PICKER_TYPE_TIME](Titanium.UI.PICKER_TYPE_TIME) + that also have [datePickerStyle](Titanium.UI.Picker.datePickerStyle) set to + [DATE_PICKER_STYLE_COMPACT](Titanium.UI.DATE_PICKER_STYLE_COMPACT). - The value of this property may be modified even after a picker is rendered and the UI will be - updated accordingly. - type: Boolean - default: true (spinner enabled), false (otherwise) + * [PICKER_TYPE_PLAIN](Titanium.UI.PICKER_TYPE_PLAIN) that also have property + [useSpinner](Titanium.UI.Picker.useSpinner) set `false` to show a drop-down picker. + type: String + default: undefined platforms: [android] + since: "10.0.1" - name: locale summary: Locale used when displaying Date and Time picker values. @@ -348,13 +371,13 @@ properties: summary: | Determines whether the visual selection indicator is shown. description: | - If `true`, selection indicator is enabled. - - Since iOS 7 and later you cannot customize the picker view's selection indicator. - The selection indicator is always shown, so setting this property to `false` has no effect. + As of Titanium 10.0.1, this property will be ignored and Android will always show an indicator. type: Boolean - default: true (Android), false (iPhone, iPad) - platforms: [android, iphone, ipad, macos] + default: true + platforms: [android] + deprecated: + since: "10.0.1" + notes: This property is ignored as of Titanium 10.0.1. - name: selectionOpens summary: | @@ -369,15 +392,19 @@ properties: since: "5.0.0" - name: datePickerStyle - summary: Request a style if the picker is a date picker (`PICKER_TYPE_DATE`). + summary: Determines how a date or time picker should appear. description: | - If the style changed, then the date picker may need to be resized and will - generate a layout pass to display correctly. + Used to display the picker has calendar/clock view, as wheel spinners, or + as a field which displays selection dialog when tapped on. + + This property is ignored if the [type](Titanium.UI.Picker.type) property is set to + . type: Number - platforms: [iphone, ipad] - since: "9.2.0" - constants: Titanium.UI.iOS.DATE_PICKER_STYLE_* - default: + availability: creation + platforms: [android, iphone, ipad] + since: {android: 10.0.1, iphone: 9.2.0, ipad: 9.2.0, macos: 9.2.0} + constants: Titanium.UI.DATE_PICKER_STYLE_* + default: osver: { ios: { min: "13.4" } } - name: type @@ -393,37 +420,44 @@ properties: default: - name: useSpinner - summary: | - Determines whether the non-native Android control, with a spinning wheel that looks and - behaves like the iOS picker, is invoked rather than the default native "dropdown" style. + summary: Determines if a multicolumn spinner or single column drop-down picker should be used. description: | - If `true`, the spinner is enabled. + This property is intended to be used by plain picker types. When set on date/time pickers, + this property will override the [datePickerStyle](Titanium.UI.Picker.datePickerStyle) property. - This property should be set either at picker creation, i.e. - `Titanium.UI.createPicker({ useSpinner:true });`, or before the picker is added to its parent. + If `true`, Android will show spinners for each column like iOS. - This property must be enabled for multi-column pickers. + If `false`, Android will display the 1st column as a drop-down list picker and all other + columns will be ignored. + + As of Titanium 10.0.1, this property shows a native Android spinner widget like how the + [nativeSpinner](Titanium.UI.Picker.nativeSpinner) property worked in older versions. + On older Titanium versions, this property shows a non-native custom spinner view. type: Boolean default: false + availability: creation platforms: [android] - deprecated: - since: "5.2.1" - notes: This property is deprecated. Please use the default native "dropdown" style. - name: nativeSpinner + summary: Determines if a multicolumn spinner or single column drop-down picker should be used. summary: | Creates a native Android control for creating a Time Spinner with Type `Ti.UI.PICKER_TYPE_TIME`. This is invoked rather than the default native "dropdown" style. description: | - If `true`, the nativeSpinner is enabled. + This property is intended to be used by time picker types. + + If `true`, Android will show hour, minute, and am/pm spinners like iOS. - This property should be set either at picker creation, i.e. - `Titanium.UI.createPicker({ nativeSpinner:true });`, or before the picker is added to its parent. + If `false`, Android will display a clock view. - Example as follows: + If `undefined` with Titanium 10.0.1 or higher, the + [datePickerStyle](Titanium.UI.Picker.datePickerStyle) property is used. + + As of Titanium 10.0.1, this property has the same effect as the + [useSpinner](Titanium.UI.Picker.useSpinner) property which also shows a native spinner. ``` js - var picker = Ti.UI.createPicker({ + const picker = Ti.UI.createPicker({ type: Ti.UI.PICKER_TYPE_TIME, nativeSpinner: true, format24: false, @@ -435,6 +469,7 @@ properties: ``` type: Boolean default: false + availability: creation platforms: [android] since: "5.4.0" @@ -461,6 +496,9 @@ properties: type: Number default: 5 platforms: [android] + deprecated: + since: "10.0.1" + notes: This property is ignored as of Titanium 10.0.1. - name: calendarViewShown summary: | diff --git a/apidoc/Titanium/UI/UI.yml b/apidoc/Titanium/UI/UI.yml index cd4a1e9e53a..fbb670cc75d 100644 --- a/apidoc/Titanium/UI/UI.yml +++ b/apidoc/Titanium/UI/UI.yml @@ -1523,6 +1523,43 @@ properties: permission: read-only since: "10.0.0" + - name: DATE_PICKER_STYLE_AUTOMATIC + summary: | + Displays a using the best visual style on the current platform for date/time selection. + description: | + On Android and iOS, a picker with this style will be shown in compatct form, + where the picker will be a read-only text field which opens a selection dialog when tapped on. + + Note: Prior to iOS 14, this property is only used on iOS Catalyst apps. + type: Number + permission: read-only + since: "10.0.1" + osver: { ios: { min: "13.4" } } + + - name: DATE_PICKER_STYLE_WHEELS + summary: Displays a as spinner wheels for date/time selection. + type: Number + permission: read-only + since: "10.0.1" + osver: { ios: { min: "13.4" } } + + - name: DATE_PICKER_STYLE_COMPACT + summary: Displays a as a read-only text field which opens a selection dialog when tapped on. + description: "Note: Prior to iOS 14, this property is only used on iOS Catalyst apps." + type: Number + permission: read-only + since: "10.0.1" + osver: { ios: { min: "13.4" } } + + - name: DATE_PICKER_STYLE_INLINE + summary: Displays a as a large calendar or clock view for date/time selection. + description: | + On iOS, an inlined "time" picker will appear as a text field with spinners for hours and minutes. + type: Number + permission: read-only + since: "10.0.1" + osver: { ios: { min: "14.0" } } + - name: HINT_TYPE_STATIC summary: | Use when creating a TextField to specify the hintType as static. diff --git a/apidoc/Titanium/UI/iOS/iOS.yml b/apidoc/Titanium/UI/iOS/iOS.yml index cec2807a084..d0d2e3fa65c 100644 --- a/apidoc/Titanium/UI/iOS/iOS.yml +++ b/apidoc/Titanium/UI/iOS/iOS.yml @@ -715,6 +715,9 @@ properties: permission: read-only since: "9.2.0" osver: { ios: { min: "13.4" } } + deprecated: + since: "10.0.1" + notes: Use [Titanium.UI.DATE_PICKER_STYLE_AUTOMATIC](Titanium.UI.DATE_PICKER_STYLE_AUTOMATIC) instead. - name: DATE_PICKER_STYLE_WHEELS summary: | @@ -723,6 +726,9 @@ properties: permission: read-only since: "9.2.0" osver: { ios: { min: "13.4" } } + deprecated: + since: "10.0.1" + notes: Use [Titanium.UI.DATE_PICKER_STYLE_WHEELS](Titanium.UI.DATE_PICKER_STYLE_WHEELS) instead. - name: DATE_PICKER_STYLE_COMPACT summary: | @@ -732,6 +738,9 @@ properties: permission: read-only since: "9.2.0" osver: { ios: { min: "13.4" } } + deprecated: + since: "10.0.1" + notes: Use [Titanium.UI.DATE_PICKER_STYLE_COMPACT](Titanium.UI.DATE_PICKER_STYLE_COMPACT) instead. - name: DATE_PICKER_STYLE_INLINE summary: | @@ -740,6 +749,9 @@ properties: permission: read-only since: "9.2.0" osver: { ios: { min: "14.0" } } + deprecated: + since: "10.0.1" + notes: Use [Titanium.UI.DATE_PICKER_STYLE_INLINE](Titanium.UI.DATE_PICKER_STYLE_INLINE) instead. - name: forceTouchSupported summary: Determines if the 3D-Touch capability "Force Touch" is supported (`true`) or not (`false`) by the device. diff --git a/iphone/Classes/UIModule.m b/iphone/Classes/UIModule.m index 106c13b8f15..81663bafbe2 100644 --- a/iphone/Classes/UIModule.m +++ b/iphone/Classes/UIModule.m @@ -316,6 +316,50 @@ - (id)createOptionBar:(id)args } #endif +#ifdef USE_TI_UIPICKER + +#if IS_SDK_IOS_13_4 +- (NSNumber *)DATE_PICKER_STYLE_AUTOMATIC +{ + if (![TiUtils isIOSVersionOrGreater:@"13.4"]) { + return @(-1); + } + + return @(UIDatePickerStyleAutomatic); +} + +- (NSNumber *)DATE_PICKER_STYLE_WHEELS +{ + if (![TiUtils isIOSVersionOrGreater:@"13.4"]) { + return @(-1); + } + + return @(UIDatePickerStyleWheels); +} + +- (NSNumber *)DATE_PICKER_STYLE_COMPACT +{ + if (![TiUtils isIOSVersionOrGreater:@"13.4"]) { + return @(-1); + } + + return @(UIDatePickerStyleCompact); +} +#endif + +#if IS_SDK_IOS_14 +- (NSNumber *)DATE_PICKER_STYLE_INLINE +{ + if (![TiUtils isIOSVersionOrGreater:@"14.0"]) { + return @(-1); + } + + return @(UIDatePickerStyleInline); +} +#endif + +#endif // USE_TI_UIPICKER + #ifdef USE_TI_UITOOLBAR - (id)createToolbar:(id)args { diff --git a/tests/Resources/ti.ui.picker.test.js b/tests/Resources/ti.ui.picker.test.js index 1f64bd7a003..502ee180989 100644 --- a/tests/Resources/ti.ui.picker.test.js +++ b/tests/Resources/ti.ui.picker.test.js @@ -54,6 +54,132 @@ describe('Titanium.UI.Picker', function () { win.open(); }); + describe('.datePickerStyle', () => { + function test(datePickerStyle, finish) { + const date = new Date(); + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_DATE, + datePickerStyle: datePickerStyle, + value: date + }); + win = Ti.UI.createWindow(); + win.add(picker); + win.addEventListener('open', () => { + try { + should(picker.datePickerStyle).be.eql(datePickerStyle); + should(picker.value).be.eql(date); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); + } + + it('Ti.UI.DATE_PICKER_STYLE_AUTOMATIC', (finish) => { + test(Ti.UI.DATE_PICKER_STYLE_AUTOMATIC, finish); + }); + + it('Ti.UI.DATE_PICKER_STYLE_COMPACT', (finish) => { + test(Ti.UI.DATE_PICKER_STYLE_COMPACT, finish); + }); + + it('Ti.UI.DATE_PICKER_STYLE_INLINE', (finish) => { + test(Ti.UI.DATE_PICKER_STYLE_INLINE, finish); + }); + + it('Ti.UI.DATE_PICKER_STYLE_WHEELS', (finish) => { + test(Ti.UI.DATE_PICKER_STYLE_WHEELS, finish); + }); + }); + + describe.android('.borderStyle', () => { + function test(borderStyle, finish) { + const date = new Date(); + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_DATE, + datePickerStyle: Ti.UI.DATE_PICKER_STYLE_COMPACT, + borderStyle: borderStyle, + value: date + }); + win = Ti.UI.createWindow(); + win.add(picker); + win.addEventListener('open', () => { + try { + should(picker.borderStyle).be.eql(borderStyle); + should(picker.value).be.eql(date); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); + } + + it('Ti.UI.INPUT_BORDERSTYLE_NONE', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_NONE, finish); + }); + + it('Ti.UI.INPUT_BORDERSTYLE_UNDERLINED', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_UNDERLINED, finish); + }); + + it('Ti.UI.INPUT_BORDERSTYLE_FILLED', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_FILLED, finish); + }); + + it('Ti.UI.INPUT_BORDERSTYLE_LINE', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_LINE, finish); + }); + + it('Ti.UI.INPUT_BORDERSTYLE_ROUNDED', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_ROUNDED, finish); + }); + }); + + it.android('.hintText', (finish) => { + const date = new Date(); + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_DATE, + datePickerStyle: Ti.UI.DATE_PICKER_STYLE_COMPACT, + hintText: 'Hint Text', + value: date + }); + win = Ti.UI.createWindow(); + win.add(picker); + win.addEventListener('open', () => { + try { + should(picker.hintText).be.eql('Hint Text'); + should(picker.value).be.eql(date); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); + }); + + it.android('.useSpinner', (finish) => { + const date = new Date(); + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_DATE, + useSpinner: true, + value: date + }); + win = Ti.UI.createWindow(); + win.add(picker); + win.addEventListener('open', () => { + try { + should(picker.useSpinner).be.true(); + should(picker.value).be.eql(date); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); + }); + it.ios('.dateTimeColor (invalid "type" - TIMOB-28181)', function (finish) { const dp = Ti.UI.createPicker({ type: Ti.UI.PICKER_TYPE_PLAIN, @@ -76,7 +202,7 @@ describe('Titanium.UI.Picker', function () { it.ios('.dateTimeColor (valid "type" + "datePickerStyle" - TIMOB-28181)', function (finish) { const dp = Ti.UI.createPicker({ type: Ti.UI.PICKER_TYPE_DATE, - datePickerStyle: Ti.UI.iOS.DATE_PICKER_STYLE_WHEELS, + datePickerStyle: Ti.UI.DATE_PICKER_STYLE_WHEELS, dateTimeColor: 'red' }); @@ -216,6 +342,136 @@ describe('Titanium.UI.Picker', function () { }); win.open(); }); + + describe('.datePickerStyle', () => { + function test(datePickerStyle, finish) { + const date = new Date(); + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_TIME, + datePickerStyle: datePickerStyle, + value: date + }); + win = Ti.UI.createWindow(); + win.add(picker); + win.addEventListener('open', () => { + try { + should(picker.datePickerStyle).be.eql(datePickerStyle); + should(picker.value.getHours()).be.eql(date.getHours()); + should(picker.value.getMinutes()).be.eql(date.getMinutes()); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); + } + + it('Ti.UI.DATE_PICKER_STYLE_AUTOMATIC', (finish) => { + test(Ti.UI.DATE_PICKER_STYLE_AUTOMATIC, finish); + }); + + it('Ti.UI.DATE_PICKER_STYLE_COMPACT', (finish) => { + test(Ti.UI.DATE_PICKER_STYLE_COMPACT, finish); + }); + + it('Ti.UI.DATE_PICKER_STYLE_INLINE', (finish) => { + test(Ti.UI.DATE_PICKER_STYLE_INLINE, finish); + }); + + it('Ti.UI.DATE_PICKER_STYLE_WHEELS', (finish) => { + test(Ti.UI.DATE_PICKER_STYLE_WHEELS, finish); + }); + }); + + describe.android('.borderStyle', () => { + function test(borderStyle, finish) { + const date = new Date(); + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_TIME, + datePickerStyle: Ti.UI.DATE_PICKER_STYLE_COMPACT, + borderStyle: borderStyle, + value: date + }); + win = Ti.UI.createWindow(); + win.add(picker); + win.addEventListener('open', () => { + try { + should(picker.borderStyle).be.eql(borderStyle); + should(picker.value.getHours()).be.eql(date.getHours()); + should(picker.value.getMinutes()).be.eql(date.getMinutes()); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); + } + + it('Ti.UI.INPUT_BORDERSTYLE_NONE', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_NONE, finish); + }); + + it('Ti.UI.INPUT_BORDERSTYLE_UNDERLINED', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_UNDERLINED, finish); + }); + + it('Ti.UI.INPUT_BORDERSTYLE_FILLED', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_FILLED, finish); + }); + + it('Ti.UI.INPUT_BORDERSTYLE_LINE', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_LINE, finish); + }); + + it('Ti.UI.INPUT_BORDERSTYLE_ROUNDED', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_ROUNDED, finish); + }); + }); + + it.android('.hintText', (finish) => { + const date = new Date(); + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_TIME, + datePickerStyle: Ti.UI.DATE_PICKER_STYLE_COMPACT, + hintText: 'Hint Text', + value: date + }); + win = Ti.UI.createWindow(); + win.add(picker); + win.addEventListener('open', () => { + try { + should(picker.hintText).be.eql('Hint Text'); + should(picker.value.getHours()).be.eql(date.getHours()); + should(picker.value.getMinutes()).be.eql(date.getMinutes()); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); + }); + + it.android('.useSpinner', (finish) => { + const date = new Date(); + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_TIME, + useSpinner: true, + value: date + }); + win = Ti.UI.createWindow(); + win.add(picker); + win.addEventListener('open', () => { + try { + should(picker.useSpinner).be.true(); + should(picker.value.getHours()).be.eql(date.getHours()); + should(picker.value.getMinutes()).be.eql(date.getMinutes()); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); + }); }); describe('.type is PICKER_TYPE_PLAIN', () => { @@ -274,7 +530,8 @@ describe('Titanium.UI.Picker', function () { it('#add(multiple PickerColumn)', function (finish) { const picker = Ti.UI.createPicker({ - type: Ti.UI.PICKER_TYPE_PLAIN + type: Ti.UI.PICKER_TYPE_PLAIN, + useSpinner: true }); win = Ti.UI.createWindow({ @@ -381,6 +638,75 @@ describe('Titanium.UI.Picker', function () { win.open(); }); + it.android('.hintText', (finish) => { + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_PLAIN, + useSpinner: false, + hintText: 'Hint Text', + columns: [ + Ti.UI.createPickerColumn({ rows: [ Ti.UI.createPickerRow({ title: 'Item 1' }) ] }) + ] + }); + win = Ti.UI.createWindow(); + win.add(picker); + win.addEventListener('open', () => { + finish(); + }); + win.open(); + }); + + describe.android('.borderStyle', () => { + function test(borderStyle, finish) { + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_PLAIN, + useSpinner: false, + borderStyle: borderStyle, + columns: [ + Ti.UI.createPickerColumn({ + rows: [ + Ti.UI.createPickerRow({ title: 'Item 1' }), + Ti.UI.createPickerRow({ title: 'Item 2' }), + Ti.UI.createPickerRow({ title: 'Item 3' }), + ] + }) + ], + }); + picker.setSelectedRow(0, 1, false); + win = Ti.UI.createWindow(); + win.add(picker); + win.addEventListener('open', () => { + try { + should(picker.borderStyle).be.eql(borderStyle); + should(picker.getSelectedRow(0).title).be.eql('Item 2'); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); + } + + it('Ti.UI.INPUT_BORDERSTYLE_NONE', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_NONE, finish); + }); + + it('Ti.UI.INPUT_BORDERSTYLE_UNDERLINED', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_UNDERLINED, finish); + }); + + it('Ti.UI.INPUT_BORDERSTYLE_FILLED', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_FILLED, finish); + }); + + it('Ti.UI.INPUT_BORDERSTYLE_LINE', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_LINE, finish); + }); + + it('Ti.UI.INPUT_BORDERSTYLE_ROUNDED', (finish) => { + test(Ti.UI.INPUT_BORDERSTYLE_ROUNDED, finish); + }); + }); + describe('events', () => { it('change', function (finish) { const pickerType = Ti.UI.createPicker();