1
0
Fork 0

fixed suggestion list flashing when typing fast or holding backspace (rendering is now debounced)

This commit is contained in:
sspanak 2025-03-29 13:05:24 +02:00 committed by Dimo Karaivanov
parent f8d7af669d
commit 02af8561e2
3 changed files with 60 additions and 58 deletions

View file

@ -57,7 +57,7 @@ public class SuggestionOps {
@NonNull
public String get(int index) {
return suggestionBar != null ? suggestionBar.getSuggestion(index) : "";
return suggestionBar != null ? suggestionBar.get(index) : "";
}
@ -69,20 +69,20 @@ public class SuggestionOps {
public void set(ArrayList<String> suggestions) {
if (suggestionBar != null) {
suggestionBar.setSuggestions(suggestions, 0, false);
suggestionBar.setMany(suggestions, 0, false);
}
}
public void set(ArrayList<String> suggestions, boolean containsGenerated) {
if (suggestionBar != null) {
suggestionBar.setSuggestions(suggestions, 0, containsGenerated);
suggestionBar.setMany(suggestions, 0, containsGenerated);
}
}
public void set(ArrayList<String> suggestions, int selectIndex, boolean containsGenerated) {
if (suggestionBar != null) {
suggestionBar.setSuggestions(suggestions, selectIndex, containsGenerated);
suggestionBar.setMany(suggestions, selectIndex, containsGenerated);
}
}

View file

@ -34,6 +34,8 @@ public class SettingsStore extends SettingsHotkeys {
public final static int SUGGESTIONS_MAX = 20;
public final static int SUGGESTIONS_MIN = 8;
public final static int SUGGESTIONS_POSITIONS_LIMIT = 100;
public final static int SUGGESTIONS_RENDER_DEBOUNCE_TIME = 25; // ms
public final static int SUGGESTIONS_RENDER_CLEAR_DEBOUNCE_TIME = 60; // ms
public final static int SUGGESTIONS_SELECT_ANIMATION_DURATION = 66;
public final static int SUGGESTIONS_TRANSLATE_ANIMATION_DURATION = 0;
public final static int TEXT_INPUT_DEBOUNCE_TIME = 500; // ms

View file

@ -32,7 +32,9 @@ public class SuggestionsBar {
private final String STEM_PUNCTUATION_VARIATION_PREFIX = "";
@NonNull private String stem = "";
private int backgroundColor = Color.TRANSPARENT;
private int defaultBackgroundColor = Color.TRANSPARENT;
private int backgroundColor;
private double lastClickTime = 0;
private int lastScrollIndex = 0;
private int selectedIndex = 0;
@ -47,7 +49,7 @@ public class SuggestionsBar {
private SuggestionsAdapter mSuggestionsAdapter;
private Vibration vibration;
private final Handler alternativeScrollingHandler = new Handler();
private final Handler delayedDisplayHandler = new Handler();
public SuggestionsBar(@NonNull SettingsStore settings, @NonNull ResizableMainView mainView, @NonNull Runnable onItemClick) {
@ -136,7 +138,7 @@ public class SuggestionsBar {
@NonNull
public String getSuggestion(int id) {
public String get(int id) {
if (id < 0 || id >= visibleSuggestions.size()) {
return "";
}
@ -186,17 +188,17 @@ public class SuggestionsBar {
}
public void setSuggestions(@Nullable List<String> newSuggestions, int initialSel, boolean containsGenerated) {
public void setMany(@Nullable List<String> newSuggestions, int initialSel, boolean containsGenerated) {
suggestions = newSuggestions;
ecoSetBackground(newSuggestions);
visibleSuggestions.clear();
selectedIndex = newSuggestions == null || newSuggestions.isEmpty() ? 0 : Math.max(initialSel, 0);
visibleSuggestions.clear();
setStem(newSuggestions, containsGenerated);
addManySuggestions(newSuggestions, mView != null ? SettingsStore.SUGGESTIONS_MAX : Integer.MAX_VALUE);
addMany(newSuggestions, mView != null ? SettingsStore.SUGGESTIONS_MAX : Integer.MAX_VALUE);
selectedIndex = Math.min(selectedIndex, visibleSuggestions.size() - 1);
displaySuggestions();
renderDebounced();
}
@ -235,13 +237,13 @@ public class SuggestionsBar {
* for performance reasons, hence the "limit" parameter. When they are too many, the SHOW_MORE_SUGGESTION,
* will be displayed at the end.
*/
private void addManySuggestions(List<String> newSuggestions, int limit) {
private void addMany(List<String> newSuggestions, int limit) {
if (newSuggestions == null) {
return;
}
for (int i = 0, end = Math.min(limit, newSuggestions.size()); i < end; i++) {
addSuggestion(newSuggestions.get(i));
add(newSuggestions.get(i));
}
if (newSuggestions.size() > limit) {
@ -250,14 +252,14 @@ public class SuggestionsBar {
}
private void addSuggestion(@NonNull String suggestion) {
private void add(@NonNull String suggestion) {
// shorten the stem variations
if (!stem.isEmpty() && suggestion.length() == stem.length() + 1 && suggestion.toLowerCase().startsWith(stem.toLowerCase())) {
String trimmedSuggestion = suggestion.substring(stem.length());
char firstChar = trimmedSuggestion.charAt(0);
String prefix = Character.isAlphabetic(firstChar) && !Characters.isCombiningPunctuation(firstChar) ? STEM_VARIATION_PREFIX : STEM_PUNCTUATION_VARIATION_PREFIX;
prefix = Characters.isFathatan(firstChar) ? " " : prefix; // Fix incorrect display of fathatan without a base character. It is a combining character, but since it is a letter, we must include a base character not to break it, with a "..." prefix
prefix = Characters.isFathatan(firstChar) ? " " : prefix; // Fix incorrect display of Fathatan without a base character. It is a combining character, but since it is a letter, we must include a base character not to break it, with a "..." prefix
visibleSuggestions.add(prefix + formatUnreadableSuggestion(trimmedSuggestion));
return;
}
@ -266,11 +268,24 @@ public class SuggestionsBar {
}
private void displaySuggestions() {
/**
* Reduces flashing of the suggestions bar when the suggestions are empty and saves some resources
* by reducing the calls to render().
*/
private void renderDebounced() {
final int delay = visibleSuggestions.isEmpty() ? SettingsStore.SUGGESTIONS_RENDER_CLEAR_DEBOUNCE_TIME : SettingsStore.SUGGESTIONS_RENDER_DEBOUNCE_TIME;
delayedDisplayHandler.removeCallbacksAndMessages(null);
delayedDisplayHandler.postDelayed(this::render, delay);
}
private void render() {
if (mView == null) {
return;
}
setBackground(false);
boolean smooth = settings.getSuggestionSmoothScroll() && visibleSuggestions.size() <= SettingsStore.SUGGESTIONS_MAX + 1;
mView.setItemAnimator(smooth ? animator : null);
@ -282,9 +297,10 @@ public class SuggestionsBar {
/**
* If addManySuggestions() constrained the visible suggestions, the end of the list will contain
* the SHOW_MORE_SUGGESTION. This method will display remove the SHOW_MORE_SUGGESTION and display
* all the hidden suggestions. It also scrolls correctly to the new visible suggestion.
* If addMany() constrained the visible suggestions, the end of the list will contain
* the SHOW_MORE_SUGGESTION. This method will remove the SHOW_MORE_SUGGESTION, prepare
* all hidden suggestions for displaying, and will scroll correctly to the new visible suggestion.
* After that, you must call render(), to visualize the changes.
*/
private boolean appendHiddenSuggestionsIfNeeded(boolean scrollBack) {
if (mView == null || !visibleSuggestions.get(selectedIndex).equals(SHOW_MORE_SUGGESTION)) {
@ -292,7 +308,7 @@ public class SuggestionsBar {
}
visibleSuggestions.clear();
addManySuggestions(suggestions, Integer.MAX_VALUE);
addMany(suggestions, Integer.MAX_VALUE);
selectedIndex = scrollBack || selectedIndex >= visibleSuggestions.size() ? visibleSuggestions.size() - 1 : selectedIndex;
return true;
@ -320,9 +336,9 @@ public class SuggestionsBar {
calculateScrollIndex(increment);
if (appendHiddenSuggestionsIfNeeded(increment < 0)) {
displaySuggestions();
render();
}
scrollToIndex();
scrollToSelected();
}
@ -338,7 +354,7 @@ public class SuggestionsBar {
}
private void scrollToIndex() {
private void scrollToSelected() {
if (mView == null) {
return;
}
@ -346,19 +362,19 @@ public class SuggestionsBar {
mSuggestionsAdapter.setSelection(selectedIndex);
if (settings.getSuggestionScrollingDelay() > 0) {
alternativeScrollingHandler.removeCallbacksAndMessages(null);
alternativeScrollingHandler.postDelayed(this::scrollView, settings.getSuggestionScrollingDelay());
delayedDisplayHandler.removeCallbacksAndMessages(null);
delayedDisplayHandler.postDelayed(this::renderScroll, settings.getSuggestionScrollingDelay());
} else {
scrollView();
renderScroll();
}
}
/**
* Tells the adapter to scroll. Always call scrollToIndex() first,
* Tells the adapter to scroll. Always call scrollToSelected() first,
* to set the selected index in the adapter.
*/
private void scrollView() {
private void renderScroll() {
if (mView == null) {
return;
}
@ -383,12 +399,12 @@ public class SuggestionsBar {
Context context = mView.getContext();
backgroundColor = ContextCompat.getColor(context, R.color.keyboard_background);
defaultBackgroundColor = ContextCompat.getColor(context, R.color.keyboard_background);
mSuggestionsAdapter.setColorDefault(ContextCompat.getColor(context, R.color.keyboard_text));
mSuggestionsAdapter.setColorHighlight(ContextCompat.getColor(context, R.color.suggestion_selected_text));
mSuggestionsAdapter.setBackgroundHighlight(ContextCompat.getColor(context, R.color.suggestion_selected_background));
setBackground(visibleSuggestions);
setBackground(true);
}
@ -397,36 +413,20 @@ public class SuggestionsBar {
* Makes the background transparent, when there are no suggestions and theme-colored,
* when there are suggestions.
*/
private void setBackground(List<String> newSuggestions) {
private void setBackground(boolean force) {
if (mView == null) {
return;
}
int newSuggestionsSize = newSuggestions != null ? newSuggestions.size() : 0;
if (newSuggestionsSize == 0) {
mView.setBackgroundColor(Color.TRANSPARENT);
return;
boolean mustChange = (
(backgroundColor == Color.TRANSPARENT && !visibleSuggestions.isEmpty()) ||
(backgroundColor == defaultBackgroundColor && visibleSuggestions.isEmpty())
);
if (force || mustChange) {
backgroundColor = visibleSuggestions.isEmpty() ? Color.TRANSPARENT : defaultBackgroundColor;
mView.setBackgroundColor(backgroundColor);
}
mView.setBackgroundColor(backgroundColor);
}
/**
* ecoSetBackground
* A performance-optimized version of "setBackground().
* Determines if the suggestions have changed and only then it changes the background.
*/
private void ecoSetBackground(List<String> newSuggestions) {
int newSuggestionsSize = newSuggestions != null ? newSuggestions.size() : 0;
if (
(newSuggestionsSize == 0 && visibleSuggestions.isEmpty())
|| (newSuggestionsSize > 0 && !visibleSuggestions.isEmpty())
) {
return;
}
setBackground(newSuggestions);
}
@ -438,7 +438,7 @@ public class SuggestionsBar {
vibration.vibrate();
selectedIndex = position;
if (appendHiddenSuggestionsIfNeeded(false)) {
displaySuggestions();
render();
} else {
onItemClick.run();
}