Alex
Alex

Reputation: 133

Custom DatePicker and TimePicker in Preferences do not match those in Activities

I am implementing a custom TimePicker for setting a preference in a PreferenceFragmentCompat fragment, following mainly this guide. While I have successfully made it work, the size of the Dialogs does not coincide with those of the same Dialog set in another Activity.

In the case of the TimePicker the one in the PreferenceFragmentCompat looks like this

TimePicker in PreferenceFragmentCompat

While if I put a TimePicker in any other Activity the looks is

TimePicker in Activity

For the case of DatePicker, the result in the Fragment looks like the one in this question, and the solution proposed there does not work in my case.

Is there a way of having the Pickers in the PreferenceFragment look like those in Activities? My implementation is the following:

styles.xml

<style name="Preference.App.DialogPreference" parent="Preference.Material">
    <item name="positiveButtonText">@android:string/ok</item>
    <item name="negativeButtonText">@android:string/cancel</item>
    <item name="android:dialogTitle" />
</style>

SettingsActivity.kt

class SettingsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_preferences)
        supportFragmentManager.beginTransaction().replace(R.id.container, SettingsFragment())
            .commit()
    }

    class SettingsFragment : PreferenceFragmentCompat() {
        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
            setPreferencesFromResource(R.xml.preferences, rootKey)
        }
    
    
        override fun onDisplayPreferenceDialog(preference: Preference) {
            // Try if the preference is one of our custom Preferences
            val dialogFragment: DialogFragment? = when (preference) {
                is TimePickerPreference -> {
                    // Create a new instance of TimePreferenceDialogFragment with the key of the related
                    // Preference
                    TimePickerPreferenceDialog.newInstance(preference.getKey())
                }
                else -> null
            }

            if (dialogFragment != null) {
                // The dialog was created (it was one of our custom Preferences), show the dialog for it
                dialogFragment.setTargetFragment(this, 0)
                dialogFragment.show(
                    parentFragmentManager,
                    "androidx.preference.PreferenceFragmentCompat.DIALOG"
                )
            } else {
                // Dialog creation could not be handled here. Try with the super method.
                super.onDisplayPreferenceDialog(preference)
            }
        }
    }
}

TimePickerPreference.kt

class TimePickerPreference @JvmOverloads constructor(
    context: Context?,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = R.attr.preferenceStyle,
    defStyleRes: Int = defStyleAttr
) : DialogPreference(context, attrs, defStyleAttr, defStyleRes) {

    companion object {
        private const val DEFAULT_VALUE = 0
    }

    var time: Int = DEFAULT_VALUE
        set(value) {
            field = value
            // Save to SharedPreferences
            persistInt(value)
        }

    override fun onGetDefaultValue(a: TypedArray?, index: Int): Any? {
        // Default value from attribute. Fallback value is set to 0.
        return a?.getInt(index, DEFAULT_VALUE) ?: DEFAULT_VALUE
    }

    override fun onSetInitialValue(defaultValue: Any?) {
        // Read the value. Use the default value if it is not possible.
        time = getPersistedInt((defaultValue as? Int) ?: DEFAULT_VALUE)
        val hour = time / 60
        val minute = time % 60
        summary = "$hour:${if (minute < 10) "0$minute" else "$minute"}"
    }
}

TimePickerPreferenceDialog.kt

class TimePickerPreferenceDialog : PreferenceDialogFragmentCompat() {

    private lateinit var timePicker: TimePicker

    override fun onCreateDialogView(context: Context?): View {
        timePicker = TimePicker(context)
        return timePicker
    }

    override fun onBindDialogView(view: View?) {
        super.onBindDialogView(view)

        // Get the time from the related Preference
        val minutesAfterMidnight: Int = preference.time

        // Set the time to the TimePicker
        val hours = minutesAfterMidnight / 60
        val minutes = minutesAfterMidnight % 60
        val is24hour = DateFormat.is24HourFormat(context)

        timePicker.apply {
            setIs24HourView(is24hour)
            if (Build.VERSION.SDK_INT >= 23) {
                hour = hours
                minute = minutes
            } else {
                @Suppress("DEPRECATION")
                currentHour = hours
                @Suppress("DEPRECATION")
                currentMinute = minutes
            }
        }
    }

    override fun onDialogClosed(positiveResult: Boolean) {
        if (positiveResult) {
            val hours: Int
            val minutes: Int
            // Get the current values from the TimePicker
            if (Build.VERSION.SDK_INT >= 23) {
                hours = timePicker.hour
                minutes = timePicker.minute
            } else {
                @Suppress("DEPRECATION")
                hours = timePicker.currentHour
                @Suppress("DEPRECATION")
                minutes = timePicker.currentMinute
            }

            // Generate value to save
            val minutesAfterMidnight = hours * 60 + minutes

            // Save the value
            preference.apply {
                // This allows the client to ignore the user value.
                if (callChangeListener(minutesAfterMidnight)) {
                    // Save the value
                    time = minutesAfterMidnight
                }
            }
            preference.summary = "$hours:${if (minutes < 10) "0$minutes" else "$minutes"}"
        }
    }

    override fun getPreference(): TimePickerPreference {
        return super.getPreference() as? TimePickerPreference
            ?: error("Preference is not a TimePreference")
    }

    companion object {
        fun newInstance(key: String?) = TimePickerPreferenceDialog().apply {
            arguments = Bundle(1).apply {
                putString(ARG_KEY, key)
            }
        }
    }
}

Thank you very much

Upvotes: 2

Views: 492

Answers (0)

Related Questions