From bb060d6cf89500778bba27d1da5925e2623c7a99 Mon Sep 17 00:00:00 2001 From: luancurti Date: Fri, 17 May 2019 02:49:26 -0700 Subject: [PATCH] Fix DatePickerAndroid with mode spinner on Android Nougat(7.0) (#24739) Summary: When mode is spinner in Android Nougat the DatePicker shows calendar I got it from https://gist.github.com/jeffdgr8/6bc5f990bf0c13a7334ce385d482af9f and did some adjustments in order to work with `DatePicker`. Workaround for this bug: https://code.google.com/p/android/issues/detail?id=222208. In Android 7.0 Nougat, spinner mode for the DatePicker in DatePickerDialog is incorrectly displayed as calendar. ## Changelog [Android][Fixed] Fix date picker with mode spinner on Android Nougat (7.0) Pull Request resolved: https://github.com/facebook/react-native/pull/24739 Differential Revision: D15391354 Pulled By: cpojer fbshipit-source-id: 09f45367250aa14857a9c68846c7f2ce7c49ee3b --- .../DismissableDatePickerDialog.java | 73 ++++++++++++++++++- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/DismissableDatePickerDialog.java b/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/DismissableDatePickerDialog.java index 25017c2916dbc6..e73a0ac4343f09 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/DismissableDatePickerDialog.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/DismissableDatePickerDialog.java @@ -7,12 +7,16 @@ package com.facebook.react.modules.datepicker; -import android.app.DatePickerDialog; -import javax.annotation.Nullable; - import android.app.DatePickerDialog; import android.content.Context; import android.os.Build; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.DatePicker; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; /** *

@@ -33,6 +37,7 @@ public DismissableDatePickerDialog( int monthOfYear, int dayOfMonth) { super(context, callback, year, monthOfYear, dayOfMonth); + fixSpinner(context, year, monthOfYear, dayOfMonth); } public DismissableDatePickerDialog( @@ -43,6 +48,7 @@ public DismissableDatePickerDialog( int monthOfYear, int dayOfMonth) { super(context, theme, callback, year, monthOfYear, dayOfMonth); + fixSpinner(context, year, monthOfYear, dayOfMonth); } @Override @@ -53,4 +59,65 @@ protected void onStop() { super.onStop(); } } + + private void fixSpinner(Context context, int year, int month, int dayOfMonth) { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N) { + try { + // Get the theme's android:datePickerMode + final int MODE_SPINNER = 2; + Class styleableClass = Class.forName("com.android.internal.R$styleable"); + Field datePickerStyleableField = styleableClass.getField("DatePicker"); + int[] datePickerStyleable = (int[]) datePickerStyleableField.get(null); + + final TypedArray a = context.obtainStyledAttributes(null, datePickerStyleable, android.R.attr.datePickerStyle, 0); + Field datePickerModeStyleableField = styleableClass.getField("DatePicker_datePickerMode"); + int datePickerModeStyleable = datePickerModeStyleableField.getInt(null); + final int mode = a.getInt(datePickerModeStyleable, MODE_SPINNER); + a.recycle(); + + if (mode == MODE_SPINNER) { + DatePicker datePicker = (DatePicker) findField(DatePickerDialog.class, DatePicker.class, "mDatePicker").get(this); + Class delegateClass = Class.forName("android.widget.DatePickerSpinnerDelegate"); + Field delegateField = findField(DatePicker.class, delegateClass, "mDelegate"); + Object delegate = delegateField.get(datePicker); + Class spinnerDelegateClass; + spinnerDelegateClass = Class.forName("android.widget.DatePickerSpinnerDelegate"); + + // In 7.0 Nougat for some reason the datePickerMode is ignored and the delegate is DatePickerClockDelegate + if (delegate.getClass() != spinnerDelegateClass) { + delegateField.set(datePicker, null); // throw out the DatePickerClockDelegate! + datePicker.removeAllViews(); // remove the DatePickerClockDelegate views + Method createSpinnerUIDelegate = DatePicker.class.getDeclaredMethod("createSpinnerUIDelegate", Context.class, AttributeSet.class, int.class, int.class); + createSpinnerUIDelegate.setAccessible(true); + + // Instantiate a DatePickerSpinnerDelegate throughout createSpinnerUIDelegate method + delegate = createSpinnerUIDelegate.invoke(datePicker, context, null, android.R.attr.datePickerStyle, 0); + delegateField.set(datePicker, delegate); // set the DatePicker.mDelegate to the spinner delegate + datePicker.setCalendarViewShown(false); + // Initialize the date for the DatePicker delegate again + datePicker.init(year, month, dayOfMonth, this); + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + private static Field findField(Class objectClass, Class fieldClass, String expectedName) { + try { + Field field = objectClass.getDeclaredField(expectedName); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException e) { + } // ignore + // search for it if it wasn't found under the expected ivar name + for (Field searchField : objectClass.getDeclaredFields()) { + if (searchField.getType() == fieldClass) { + searchField.setAccessible(true); + return searchField; + } + } + return null; + } }