material-components / material-components-android

Modular and customizable Material Design UI components for Android
Apache License 2.0
16.37k stars 3.07k forks source link

[MaterialTimePicker] Buttons at the bottom are getting cut off #3584

Open mck182 opened 1 year ago

mck182 commented 1 year ago

Description: MaterialTimePicker is getting its buttons cut off at the bottom in the 1.11.x releases, this looks correct in 1.10.0-beta01 so it may be a regression.

I suspect this might improve if those two buttons are also constrained to parent at their bottom edge.

Expected behavior: The expected behaviour is that those buttons would not be clipped. Here are two screenshots - one from 1.10 and one from 1.11, notice how the dialog's height is less than it was in 1.10 and the buttons are clipped:

1.10 1.11
Screenshot_1694795666 Screenshot_1694795666 copy

Source code: material_timepicker_dialog.xml; This may be due to commit d10201dc88f919421b3bc4d8d7aa432de685c892 which updated the layout

Minimal sample app repro: n/a

Android API version: 33

Material Library version: 1.11.0-alpha02

Device: Pixel 6 emulator

To help us triage faster, please check to make sure you are using the latest version of the library.

We also happily accept pull requests.

mateuszkwiecinski commented 10 months ago

I confirm this bug made into "stable" 1.11.0 release and the buttons are now drawn outside dialog bounds

image

In my case it got caught with espresso test:

androidx.test.espresso.base.AssertionErrorHandler$AssertionFailedWithCauseError: '(view has effective visibility <VISIBLE> and view.getGlobalVisibleRect() covers at least <100> percent of the view's area)' doesn't match the selected view.
Expected: (view has effective visibility <VISIBLE> and view.getGlobalVisibleRect() covers at least <100> percent of the view's area)
Got: view was <95> percent visible to the user
View Details: MaterialButton{id=2131297131, res-name=material_timepicker_ok_button, visibility=VISIBLE, width=224, height=168, has-focus=false, has-focusable=true, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=false, is-selected=false, layout-params=androidx.constraintlayout.widget.ConstraintLayout$LayoutParams@YYYYYY, tag=null, root-is-layout-requested=false, has-input-connection=false, x=896.0, y=1575.0, text=OK, input-type=0, ime-target=false, has-links=false, is-checked=false}

at dalvik.system.VMStack.getThreadStackTrace(Native Method)
at java.lang.Thread.getStackTrace(Thread.java:1538)
at androidx.test.espresso.base.AssertionErrorHandler.handleSafely(AssertionErrorHandler.java:3)
at androidx.test.espresso.base.AssertionErrorHandler.handleSafely(AssertionErrorHandler.java:1)
at androidx.test.espresso.base.DefaultFailureHandler$TypedFailureHandler.handle(DefaultFailureHandler.java:4)
at androidx.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:5)
at androidx.test.espresso.ViewInteraction.waitForAndHandleInteractionResults(ViewInteraction.java:5)
at androidx.test.espresso.ViewInteraction.check(ViewInteraction.java:12)
...
nikclayton commented 3 weeks ago

Until #3588 is merged and there's a release with the fix, this hacks around the problem:

// Necessary imports
import androidx.constraintlayout.widget.ConstraintLayout
import com.google.android.material.R as MaterialR

// ...

    private fun openPickTimeDialog() {
        val picker = MaterialTimePicker.Builder()
            // call builder methods, then.
            .build()

        // Work around https://github.com/material-components/material-components-android/issues/3584
        // where the buttons get cut off because of incorrect constraints. Force the
        // constraints when the dialog resumes.
        picker.lifecycle.addObserver(
            object : DefaultLifecycleObserver {
                fun Button.constrainToBottomOfParent() {
                    (layoutParams as? LayoutParams)?.let { lp ->
                        lp.bottomToBottom = LayoutParams.PARENT_ID
                        layoutParams = lp
                    }
                }

                override fun onResume(owner: LifecycleOwner) {
                    picker.dialog
                        ?.findViewById<Button>(MaterialR.id.material_timepicker_cancel_button)
                        ?.constrainToBottomOfParent()
                    picker.dialog
                        ?.findViewById<Button>(MaterialR.id.material_timepicker_ok_button)
                        ?.constrainToBottomOfParent()
                }
            },
        )

        // Add click listeners, call picker.show(), etc...
    }