Skip to content

feat(deck-picker): edge to edge support#20964

Open
david-allison wants to merge 3 commits into
ankidroid:mainfrom
david-allison:edge-2-edge
Open

feat(deck-picker): edge to edge support#20964
david-allison wants to merge 3 commits into
ankidroid:mainfrom
david-allison:edge-2-edge

Conversation

@david-allison
Copy link
Copy Markdown
Member

@david-allison david-allison commented May 7, 2026

Note

Assisted-by: Claude Opus 4.7 - lots of discussion, wasn't too helpful here

Purpose / Description

This PR enables edge to edge support for the Deck Picker. The DeckPicker currently uses an opaque toolbar, so we only need to focus on edge-to-edge for the bottom bar & side panels (for example: if there's a camera cutout on a tablet).

Fixes

Approach

This was difficult and there was a lot of trial and error.

Deck List & "Studied X cards in Y seconds"

The deck list needs to be at the bottom of the screen.

- android:layout_above="@id/review_summary_text_view"

Due to this, the deck list appears under the review summary. Before this change, the review_summary background was effectively opaque, and now needs to handle overlap with the deck name, without obscuring the content.

Furthermore, some Android versions have a 'transparent nav', which applied a transparency filter to elements underneath it. Different Android versions did this differently, and the colors were non-public(with API <= 25 not supporting dark navigation buttons), so I rolled my own (BottomFadeFrameLayout): using a DST_OUT filter.

FAB

val fabBottomOffset = if (isRobolectric) 12.dp.toPx(this) else -12.dp.toPx(this)
floatingActionButtonBinding.root.updatePadding(bottom = bars.bottom + fabBottomOffset)

The floating action bar is surprisingly complex: it uses a ScrollView to handle smaller screens, and this means we need a margin for the shadow to display properly, rather than removing its clipping behavior.

I extracted some of the bottom padding to try and remove 'fabBottomOffset', but due to the shadow requirements, this did not seem feasible.

isRobolectric: due to an undetermined bug, the FAB was ~24dp too low on the screenshot after edge to edge was implemented. This fixes it manually

How Has This Been Tested?

tools/compare-screenshot-test.sh 8f35410 "com.ichi2.anki.Deck**ScreenshotTest" image image
  • Google Pixel 9 Pro
  • AOSP API 24 emulator [phone]
  • Pixel 6 Pro API 33 emulator
  • API 34 Tablet Emulator
  • Pixel 10 Pro API 37 Emulator
  • API 29 Medium Phone
  • API 27 Medium Phone
Details Screenshot 2026-05-07 at 01 31 49 Screenshot 2026-05-07 at 01 32 09 Screenshot 2026-05-07 at 01 32 31 Screenshot 2026-05-07 at 01 34 03 Screenshot 2026-05-07 at 01 50 02

Overlap with selected deck - same style as the standard Android bottom nav

Screenshot 2026-05-07 at 01 38 27

Learning (optional, can help others)

https://developer.android.com/develop/ui/views/layout/edge-to-edge

Checklist

  • You have a descriptive commit message with a short title (first line, max 50 chars).
  • You have commented your code, particularly in hard-to-understand areas
  • You have performed a self-review of your own code
  • UI changes: include screenshots of all affected screens (in particular showing any new or changed strings)
  • UI Changes: You have tested your change using the Google Accessibility Scanner

For activities with 'Theme_Dark.Launcher', android:windowBackground is
set, but was never removed.

On views with transparent backgrounds (such as the Card Browser column
headers), this leaked through when edge to edge was enabled

Part of issue 17334 - Edge to Edge

Assisted-by: Claude Opus 4.7 - diagnostics + fix
Prep for edge-to-edge work.

Scrolling tests were attempted, but Roborazzi did not scroll the
RecyclerView correctly

Issue 17334

Assisted-by: Claude Opus 4.7 - much of the initial implementation
In order to support edge to edge, we needed to move the RecyclerView
underneath the nav + "Studied X in Y" TextView.

The bottom nav on many Android phones will fade the content underneath
it, but different phones have different implementations, using private
colors. So we define our own 'BottomFadeFrameLayout' to handle this.

Roughly equivalent to the fade on an API 33 emulator

----

The screenshot test did not place the FAB in the correct place
vertically. Therefore adjustmnets were made behind an isRobolectric
wrapper

The last row of the RecyclerView is moved by a few pixels to be directly
 in-line with the top of the FAB

The padding cannot be lower, otherwise the FAB would obscure the
counts

I attempted to make tests in Roborazzi which scrolled to the bottom of
the list, but they were flaky (1px of padding made a huge difference in
scroll position)

Issue 17334
@david-allison david-allison added this to the 2.25 release milestone May 7, 2026
Comment on lines +105 to +108
SystemBarStyle.auto(
lightScrim = Color.TRANSPARENT,
darkScrim = Color.TRANSPARENT,
)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have { Themes.isNightTheme } to handle a user selecting a theme manually

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't understand; what's the meaning of this comment?

Copy link
Copy Markdown
Member

@ericli3690 ericli3690 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code-wise, looks fantastic! Only nits.

I tested on a Lenovo Tab M11 and it looks good. However, when I tested on my phone (Samsung S23, API 34), there were issues with overlapping icons in landscape mode on the left and right hand sides, did you forget to apply left and right insets? Note the overlapping "show tabs" and "back" icons on the left and right edges in the screenshots here:

Image Image Image

This doesn't look intended.


plugins {
id("org.jetbrains.kotlin.jvm")
/** Wrapper around the `IntArray` returned by [View.getLocationOnScreen]. */
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cute!

* A container which emulates the 'fade' effect which is applied to content when underneath the
* button navigation.
*
* Extends the button nav bar's visual effect to our components.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I notice in your PR description that you have a brief explanation of why this is necessary:

Furthermore, some Android versions have a 'transparent nav', which applied a transparency filter to elements underneath it. Different Android versions did this differently, and the colors were non-public(with API <= 25 not supporting dark navigation buttons), so I rolled my own (BottomFadeFrameLayout): using a DST_OUT filter.

Perhaps insert it inline in this docstring? In my opinion it's not immediately clear from reading this file as to why this BottomFadeFrameLayout is necessary.

Comment on lines +105 to +108
SystemBarStyle.auto(
lightScrim = Color.TRANSPARENT,
darkScrim = Color.TRANSPARENT,
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't understand; what's the meaning of this comment?

)
deckPickerBinding.reviewSummaryTextView.updatePadding(bottom = bars.bottom)

val fabBottomOffset = if (isRobolectric) 12.dp.toPx(this) else -12.dp.toPx(this)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add inline comment here explaining that this is a quick fix for Robolectric as you've explained in your PR description?

}

context(test: RobolectricTest)
fun withDeckPicker(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe move this to a shared file rather than putting it a the bottom of DeckPickerScreenshotTest, since it's also used in DeckPickerTabletScreenshotTest?

setRecyclerViewBottomPaddingAbove(floatingActionButtonBinding.fabMain)
insets
}
floatingActionButtonBinding.fabMain.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Holy moly so many underscores
Good job Google

@ericli3690 ericli3690 added Needs Author Reply Waiting for a reply from the original author and removed Needs Review labels May 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Has Conflicts Needs Author Reply Waiting for a reply from the original author

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants