diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java index 02777942..f9e5a896 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java @@ -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 suggestions) { if (suggestionBar != null) { - suggestionBar.setSuggestions(suggestions, 0, false); + suggestionBar.setMany(suggestions, 0, false); } } public void set(ArrayList suggestions, boolean containsGenerated) { if (suggestionBar != null) { - suggestionBar.setSuggestions(suggestions, 0, containsGenerated); + suggestionBar.setMany(suggestions, 0, containsGenerated); } } public void set(ArrayList suggestions, int selectIndex, boolean containsGenerated) { if (suggestionBar != null) { - suggestionBar.setSuggestions(suggestions, selectIndex, containsGenerated); + suggestionBar.setMany(suggestions, selectIndex, containsGenerated); } } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java index 3391f166..e33530d8 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java @@ -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 diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java b/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java index 92aef08f..cc0fa849 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java @@ -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 newSuggestions, int initialSel, boolean containsGenerated) { + public void setMany(@Nullable List 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 newSuggestions, int limit) { + private void addMany(List 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 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 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(); }