0
0
mirror of https://github.com/ankidroid/Anki-Android.git synced 2024-09-20 12:02:16 +02:00
AnkiDroid: Anki flashcards on Android. Your secret trick to achieve superhuman information retention.
Go to file
oakkitten 0fad2a384c Fix snackbar appearing on the very bottom of the screen
Snackbars are dismissible by swiping and thus should appear above the
bottom gesture area. Some of the snackbars were appearing on the bottom
of the screen even though they were using CoordinatorLayout as the root,
this fixes it. Also, this fixes excessive relayout/remeasure while
snackbars are showing.

This does not fix snackbars not using CoordinatorLayout as the root.

An attentive reader might ask, what on Earth does this change have to do
with Snackbars? Well, let me tell you a story.

I noticed that in Full screen navigation drawer mode, which I was using,
snackbars are appearing too high. (This is another issue.) When I almost
made a commit to make them appear on the very bottom of the screen,
by setting bottomMarginGestureInsetRunnable to true, I realized that
while this does make snackbars look consistent, they shouldn't be at
the bottom of the screen in the first place. So in one case they were
too high, and in another case too low, and, if dismissed via swiping,
they left the fab too high. (This is yet another issue.)

After minute investigation, that totally didn't take several hours,
I found what I think is the issue. The timing is the problem.
Animating snackbars in should work like this:

  * Ask the framework for insets
  * Post a callback, bottomMarginGestureInsetRunnable,
    that adjusts margins taking insets into account
    * This removes any callbacks posted previously!
  * Post an animation

Looks okay? Well, turns out that if Full screen drawer mode is not on,
there's *a lot* of remeasuring going on. Remeasuring results in the
above callback getting reposted again and again, and it ends up running
*after* animation.

Here's a debug log in case of Full screen drawer mode:

  showing snackbar
  posting callback
  posting callback
  callback run
  animating

And here's one in regular mode:

  showing snackbar
  posting callback ¹
  posting callback ²
  posting callback †
  posting callback †
  posting callback †
  animating
  posting callback †
  callback run ‡
  posting callback
  callback run ‡
  posting callback
  callback run ‡
    (many more lines)
  posting callback
  callback run ‡

I guess you see the issue. What happens here:

  * Callback gets posted “normally” twice (¹, ²).
  * DrawerLayout.onMeasure sees if its child, CoordinatorLayout,
    fits system windows. Since it does, it calls its method
    dispatchApplyWindowInsets, which propagates to
    BaseTransientBottomBar's inset listener, which reposts
    bottomMarginGestureInsetRunnable (†).
  * The callback ends up running too late ... and when it is run, its
    parent has already ended its own layout. This is why, I think,
    its call to requestLayout propagates to the root, which begins to
    remeasure things again (‡).
  * The last two steps are repeated on and on in a loop.

Why does this happen? Well, I think that:

  * The positioning problem is an issue with Snackbar itself.
    It does not make sure that animation is run after inset listener
    has been called.
  * The loop problem is likely one of those weird Android issues that
    come out of nowhere. Maybe it's this particular combination of
    views? Or some funny flag somewhere deep in the code? Who knows.

How this solves it? Well, I looked at why this is not an issue if Full
screen navigation drawer setting is on. As mentioned above,
DrawerLayout.onMeasure checks if its child fits system windows, and then
calls dispatchApplyWindowInsets. In this case, CoordinatorLayout is
wrapped in FullDraggableContainer, which does not have the fit system
windows flag set. Removing this flag in CoordinatorLayout makes the
behavior the same.

***

For reference, here's tracebacks for ¹ and ²:

  Breakpoint reached
    at com.google.android.material.snackbar.BaseTransientBottomBar.updateMargins(BaseTransientBottomBar.java:438)
    at com.google.android.material.snackbar.BaseTransientBottomBar.onAttachedToWindow(BaseTransientBottomBar.java:737)
    at com.google.android.material.snackbar.BaseTransientBottomBar$SnackbarBaseLayout.onAttachedToWindow(BaseTransientBottomBar.java:1221)
    at android.view.View.dispatchAttachedToWindow(View.java:20479)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3489)
    at android.view.ViewGroup.addViewInner(ViewGroup.java:5278)
    at android.view.ViewGroup.addView(ViewGroup.java:5064)
    at android.view.ViewGroup.addView(ViewGroup.java:5004)
    at android.view.ViewGroup.addView(ViewGroup.java:4976)
    at com.google.android.material.snackbar.BaseTransientBottomBar$SnackbarBaseLayout.addToTargetParent(BaseTransientBottomBar.java:1275)
    at com.google.android.material.snackbar.BaseTransientBottomBar.showView(BaseTransientBottomBar.java:715)
    at com.google.android.material.snackbar.BaseTransientBottomBar$1.handleMessage(BaseTransientBottomBar.java:244)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    ...
  Breakpoint reached
    at com.google.android.material.snackbar.BaseTransientBottomBar.updateMargins(BaseTransientBottomBar.java:438)
    at com.google.android.material.snackbar.BaseTransientBottomBar.recalculateAndUpdateMargins(BaseTransientBottomBar.java:840)
    at com.google.android.material.snackbar.BaseTransientBottomBar.showView(BaseTransientBottomBar.java:716)
    at com.google.android.material.snackbar.BaseTransientBottomBar$1.handleMessage(BaseTransientBottomBar.java:244)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    ...

And here's traceback for †

  Breakpoint reached
    at com.google.android.material.snackbar.BaseTransientBottomBar.updateMargins(BaseTransientBottomBar.java:438)
    at com.google.android.material.snackbar.BaseTransientBottomBar.access$900(BaseTransientBottomBar.java:96)
    at com.google.android.material.snackbar.BaseTransientBottomBar$3.onApplyWindowInsets(BaseTransientBottomBar.java:383)
    at androidx.core.view.ViewCompat$Api21Impl$1.onApplyWindowInsets(ViewCompat.java:4858)
    at android.view.View.dispatchApplyWindowInsets(View.java:11309)
    at android.view.ViewGroup.dispatchApplyWindowInsets(ViewGroup.java:7320)
    at android.view.ViewGroup.brokenDispatchApplyWindowInsets(ViewGroup.java:7334)
    at android.view.ViewGroup.dispatchApplyWindowInsets(ViewGroup.java:7325)
    at android.view.ViewGroup.brokenDispatchApplyWindowInsets(ViewGroup.java:7334)
    at android.view.ViewGroup.dispatchApplyWindowInsets(ViewGroup.java:7325)
    at androidx.drawerlayout.widget.DrawerLayout.onMeasure(DrawerLayout.java:1128)
    at android.view.View.measure(View.java:25466)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
    at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:145)
    at android.view.View.measure(View.java:25466)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
    at android.view.View.measure(View.java:25466)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
    at android.view.View.measure(View.java:25466)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
    at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
    at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
    at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
    at android.view.View.measure(View.java:25466)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
    at com.android.internal.policy.DecorView.onMeasure(DecorView.java:747)
    at android.view.View.measure(View.java:25466)
    at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3402)
    at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:2246)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2544)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1948)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8177)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:972)
    at android.view.Choreographer.doCallbacks(Choreographer.java:796)
    at android.view.Choreographer.doFrame(Choreographer.java:731)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
2022-08-18 13:36:36 -05:00
.github chore: Set permissions for GitHub actions 2022-06-28 13:01:28 +05:30
.idea Fix crash during deck filtering (#11880) 2022-07-21 12:36:37 +01:00
AnkiDroid Fix snackbar appearing on the very bottom of the screen 2022-08-18 13:36:36 -05:00
annotations Allow JetBrains @Contract annotations 2020-05-14 20:48:36 -05:00
api Remove splitFieldsShouldReturnNullWhenStringIsNull 2022-07-24 14:40:34 -05:00
docs Updated strings from Crowdin 2022-07-04 12:48:46 -05:00
gradle/wrapper Update Gradle Wrapper from 7.5 to 7.5.1. 2022-08-05 19:15:45 -05:00
lint-rules lint: add rule to avoid hardcoded keys on preferences 2022-08-18 07:15:18 +03:00
tools Add script to test all commits in a branch 2022-08-16 09:48:12 -05:00
.codecov.yml coverage: disable noisy annotations on PRs 2021-03-27 09:45:48 -05:00
.gitattributes chore: Mark user dictionaries as non-generated 2022-04-13 10:42:47 -05:00
.gitignore lint: Add user dictionaries to reduce typos 2022-04-05 17:11:56 -05:00
.prettierignore fix(prettier): Ignore 'build' folder 2022-03-16 11:48:12 +05:30
.prettierrc Change .prettierrc endOfLine to auto 2022-03-17 11:01:04 -05:00
build.gradle Dependency updates 20220816 (#12036) 2022-08-16 10:23:02 -05:00
CONTRIBUTING.md Created CONTRIBUTING.md (#8222) 2021-03-15 07:53:28 -05:00
COPYING Add readme and license file back after accidentally deleting 2014-11-10 02:23:41 +09:00
gradle.properties Use IN_PROCESS compiler strategy only on CI builds 2022-06-11 13:12:10 -05:00
gradlew Update Gradle Wrapper from 7.4.2 to 7.5. 2022-07-14 21:06:24 -05:00
gradlew.bat Update Gradle Wrapper from 7.4.2 to 7.5. 2022-07-14 21:06:24 -05:00
lint-release.xml lint: enable "CheckResult" 2022-07-06 10:57:01 +01:00
lint.gradle lint: enable linting unit tests 2021-10-20 22:58:42 -05:00
pre-commit Make possible to commit hunks again on Kotlin 2022-08-13 15:45:00 +01:00
README.md Add a comment stating not to rename/remove credits 2022-08-04 13:13:40 +03:00
settings.gradle Add lint checks for new time api 2020-08-25 10:04:26 +02:00

release build Open Collective backers and sponsors commit-activity forks stars contributors license

AnkiDroid

A semi-official port of the open source Anki spaced repetition flashcard system to Android. Memorize anything with AnkiDroid!

Features

  • night mode
  • whiteboard
  • progress widget
  • detailed statistics
  • syncing with AnkiWeb
  • write answers (optional)
  • text-to-speech integration
  • more than 6000 premade decks
  • spaced repetition (supermemo 2 algorithm)
  • supported contents: text, images, sounds, MathJax
  • add cards by intent from other applications like dictionaries

Install

Wiki

View Wiki

Help

Check the user manual and the wiki for usage instructions. See the help page for how to submit a bug report or contact a project member, etc.

Contribute

You can contribute to AnkiDroid by beta testing, translating, or submitting code. See the contribution wiki page for more info.

Join Us On

Credits

Code Contributors

Thanks to these awesome code contributors who keep this project going

Sponsors

Backers

Thank you to all our backers! 🙏

Translators

Thanks to our 1400 translators, for allowing us to be available, partially or totally, in 99 languages as of July 2022.

License