2019-12-31 18:19:35 +01:00
/ *
* Copyright ( C ) 2011 The Android Open Source Project
2023-10-17 13:44:01 +02:00
* modified
* SPDX - License - Identifier : Apache - 2 . 0 AND GPL - 3 . 0 - only
2019-12-31 18:19:35 +01:00
* /
2024-01-31 18:32:43 +01:00
package helium314.keyboard.latin.suggestions ;
2019-12-31 18:19:35 +01:00
2024-01-31 18:32:43 +01:00
import static helium314.keyboard.latin.utils.ToolbarUtilsKt.* ;
2023-12-21 21:57:53 +01:00
2023-09-16 21:30:12 +02:00
import android.annotation.SuppressLint ;
2023-11-27 19:19:33 +01:00
import android.app.KeyguardManager ;
2019-12-31 18:19:35 +01:00
import android.content.Context ;
2023-12-29 12:08:32 +01:00
import android.content.SharedPreferences ;
2019-12-31 18:19:35 +01:00
import android.content.res.Resources ;
import android.content.res.TypedArray ;
import android.graphics.Color ;
2023-09-24 21:31:37 +02:00
import android.graphics.PorterDuff ;
2019-12-31 18:19:35 +01:00
import android.graphics.drawable.Drawable ;
2023-09-24 21:31:37 +02:00
import android.graphics.drawable.GradientDrawable ;
import android.graphics.drawable.ShapeDrawable ;
import android.graphics.drawable.shapes.OvalShape ;
2023-11-27 19:19:33 +01:00
import android.os.Build ;
2023-09-13 18:09:53 +02:00
import android.text.TextUtils ;
2019-12-31 18:19:35 +01:00
import android.util.AttributeSet ;
import android.util.TypedValue ;
import android.view.GestureDetector ;
import android.view.LayoutInflater ;
2023-08-27 18:41:13 +02:00
import android.view.Menu ;
2019-12-31 18:19:35 +01:00
import android.view.MotionEvent ;
import android.view.View ;
import android.view.View.OnClickListener ;
import android.view.View.OnLongClickListener ;
import android.view.ViewGroup ;
import android.view.ViewParent ;
import android.view.accessibility.AccessibilityEvent ;
import android.widget.ImageButton ;
2023-12-20 08:44:47 +01:00
import android.widget.LinearLayout ;
2019-12-31 18:19:35 +01:00
import android.widget.RelativeLayout ;
import android.widget.TextView ;
2024-01-31 18:32:43 +01:00
import helium314.keyboard.accessibility.AccessibilityUtils ;
import helium314.keyboard.keyboard.Keyboard ;
import helium314.keyboard.keyboard.MainKeyboardView ;
2024-02-05 09:33:06 +01:00
import helium314.keyboard.keyboard.PopupKeysPanel ;
2024-03-02 21:02:48 +01:00
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode ;
2024-01-31 18:32:43 +01:00
import helium314.keyboard.latin.AudioAndHapticFeedbackManager ;
import helium314.keyboard.latin.Dictionary ;
import helium314.keyboard.latin.R ;
import helium314.keyboard.latin.SuggestedWords ;
import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo ;
import helium314.keyboard.latin.common.ColorType ;
import helium314.keyboard.latin.common.Colors ;
import helium314.keyboard.latin.common.Constants ;
import helium314.keyboard.latin.define.DebugFlags ;
import helium314.keyboard.latin.settings.DebugSettings ;
import helium314.keyboard.latin.settings.Settings ;
import helium314.keyboard.latin.settings.SettingsValues ;
2024-02-05 09:33:06 +01:00
import helium314.keyboard.latin.suggestions.PopupSuggestionsView.MoreSuggestionsListener ;
2024-01-31 18:32:43 +01:00
import helium314.keyboard.latin.utils.DeviceProtectedUtils ;
import helium314.keyboard.latin.utils.DialogUtilsKt ;
import helium314.keyboard.latin.utils.Log ;
import helium314.keyboard.latin.utils.ToolbarKey ;
import helium314.keyboard.latin.utils.ToolbarUtilsKt ;
2019-12-31 18:19:35 +01:00
import java.util.ArrayList ;
2023-09-13 18:09:53 +02:00
import java.util.concurrent.atomic.AtomicBoolean ;
2019-12-31 18:19:35 +01:00
2024-01-07 12:39:51 +04:00
import androidx.annotation.NonNull ;
2024-05-15 22:47:24 +02:00
import androidx.annotation.Nullable ;
2023-09-01 10:19:09 +02:00
import androidx.appcompat.widget.PopupMenu ;
2020-01-21 20:30:29 +01:00
2019-12-31 18:19:35 +01:00
public final class SuggestionStripView extends RelativeLayout implements OnClickListener ,
OnLongClickListener {
public interface Listener {
2020-01-26 23:19:47 +01:00
void pickSuggestionManually ( SuggestedWordInfo word ) ;
void onCodeInput ( int primaryCode , int x , int y , boolean isKeyRepeat ) ;
2023-06-28 20:40:35 +02:00
void removeSuggestion ( final String word ) ;
2019-12-31 18:19:35 +01:00
}
2023-11-22 23:22:38 +01:00
public static boolean DEBUG_SUGGESTIONS ;
2023-11-30 14:57:38 +01:00
private static final float DEBUG_INFO_TEXT_SIZE_IN_DIP = 6 . 5f ;
2024-01-16 21:06:42 +01:00
private static final String TAG = SuggestionStripView . class . getSimpleName ( ) ;
2019-12-31 18:19:35 +01:00
private final ViewGroup mSuggestionsStrip ;
2023-12-28 20:32:29 +01:00
private final ImageButton mToolbarExpandKey ;
2023-09-24 21:31:37 +02:00
private final Drawable mIncognitoIcon ;
private final Drawable mToolbarArrowIcon ;
2023-11-03 16:41:49 +01:00
private final Drawable mBinIcon ;
2023-09-24 21:31:37 +02:00
private final ViewGroup mToolbar ;
2023-09-25 19:35:51 +02:00
private final View mToolbarContainer ;
2023-09-24 21:31:37 +02:00
private final ViewGroup mPinnedKeys ;
private final GradientDrawable mEnabledToolKeyBackground = new GradientDrawable ( ) ;
private final Drawable mDefaultBackground ;
2019-12-31 18:19:35 +01:00
MainKeyboardView mMainKeyboardView ;
private final View mMoreSuggestionsContainer ;
2024-02-05 09:33:06 +01:00
private final PopupSuggestionsView mMoreSuggestionsView ;
2019-12-31 18:19:35 +01:00
private final MoreSuggestions . Builder mMoreSuggestionsBuilder ;
private final ArrayList < TextView > mWordViews = new ArrayList < > ( ) ;
private final ArrayList < TextView > mDebugInfoViews = new ArrayList < > ( ) ;
private final ArrayList < View > mDividerViews = new ArrayList < > ( ) ;
Listener mListener ;
private SuggestedWords mSuggestedWords = SuggestedWords . getEmptyInstance ( ) ;
private int mStartIndexOfMoreSuggestions ;
2024-01-08 12:11:04 +01:00
private int mRtl = 1 ; // 1 if LTR, -1 if RTL
2019-12-31 18:19:35 +01:00
private final SuggestionStripLayoutHelper mLayoutHelper ;
private final StripVisibilityGroup mStripVisibilityGroup ;
2024-02-07 23:49:38 +04:00
private boolean isInlineAutofillSuggestionsVisible = false ; // Required to disable the more suggestions if inline autofill suggestions are visible
2019-12-31 18:19:35 +01:00
private static class StripVisibilityGroup {
private final View mSuggestionStripView ;
private final View mSuggestionsStrip ;
public StripVisibilityGroup ( final View suggestionStripView ,
2020-09-28 11:40:23 +02:00
final ViewGroup suggestionsStrip ) {
2019-12-31 18:19:35 +01:00
mSuggestionStripView = suggestionStripView ;
mSuggestionsStrip = suggestionsStrip ;
showSuggestionsStrip ( ) ;
}
2024-03-27 07:04:59 +02:00
public void setLayoutDirection ( final int layoutDirection ) {
2024-05-15 22:47:24 +02:00
mSuggestionStripView . setLayoutDirection ( layoutDirection ) ;
mSuggestionsStrip . setLayoutDirection ( layoutDirection ) ;
2019-12-31 18:19:35 +01:00
}
public void showSuggestionsStrip ( ) {
mSuggestionsStrip . setVisibility ( VISIBLE ) ;
}
}
/ * *
* Construct a { @link SuggestionStripView } for showing suggestions to be picked by the user .
* /
public SuggestionStripView ( final Context context , final AttributeSet attrs ) {
this ( context , attrs , R . attr . suggestionStripViewStyle ) ;
}
2024-05-15 22:47:24 +02:00
@SuppressLint ( " InflateParams " ) // does not seem suitable here
2023-12-21 21:57:53 +01:00
public SuggestionStripView ( final Context context , final AttributeSet attrs , final int defStyle ) {
2019-12-31 18:19:35 +01:00
super ( context , attrs , defStyle ) ;
2023-09-04 09:35:01 +02:00
final Colors colors = Settings . getInstance ( ) . getCurrent ( ) . mColors ;
2023-12-29 12:08:32 +01:00
final SharedPreferences prefs = DeviceProtectedUtils . getSharedPreferences ( context ) ;
DEBUG_SUGGESTIONS = prefs . getBoolean ( DebugSettings . PREF_SHOW_SUGGESTION_INFOS , false ) ;
2019-12-31 18:19:35 +01:00
final LayoutInflater inflater = LayoutInflater . from ( context ) ;
inflater . inflate ( R . layout . suggestions_strip , this ) ;
2020-01-26 23:19:47 +01:00
mSuggestionsStrip = findViewById ( R . id . suggestions_strip ) ;
2023-12-28 20:32:29 +01:00
mToolbarExpandKey = findViewById ( R . id . suggestions_strip_toolbar_key ) ;
2020-09-28 11:40:23 +02:00
mStripVisibilityGroup = new StripVisibilityGroup ( this , mSuggestionsStrip ) ;
2023-09-24 21:31:37 +02:00
mPinnedKeys = findViewById ( R . id . pinned_keys ) ;
mToolbar = findViewById ( R . id . toolbar ) ;
2023-09-25 19:35:51 +02:00
mToolbarContainer = findViewById ( R . id . toolbar_container ) ;
2019-12-31 18:19:35 +01:00
for ( int pos = 0 ; pos < SuggestedWords . MAX_SUGGESTIONS ; pos + + ) {
final TextView word = new TextView ( context , null , R . attr . suggestionWordStyle ) ;
word . setContentDescription ( getResources ( ) . getString ( R . string . spoken_empty_suggestion ) ) ;
word . setOnClickListener ( this ) ;
word . setOnLongClickListener ( this ) ;
2024-01-26 09:01:39 +01:00
colors . setBackground ( word , ColorType . STRIP_BACKGROUND ) ;
2019-12-31 18:19:35 +01:00
mWordViews . add ( word ) ;
final View divider = inflater . inflate ( R . layout . suggestion_divider , null ) ;
mDividerViews . add ( divider ) ;
final TextView info = new TextView ( context , null , R . attr . suggestionWordStyle ) ;
2023-12-19 07:48:10 +01:00
info . setTextColor ( colors . get ( ColorType . KEY_TEXT ) ) ;
2019-12-31 18:19:35 +01:00
info . setTextSize ( TypedValue . COMPLEX_UNIT_DIP , DEBUG_INFO_TEXT_SIZE_IN_DIP ) ;
mDebugInfoViews . add ( info ) ;
}
2023-11-22 23:22:38 +01:00
mLayoutHelper = new SuggestionStripLayoutHelper ( context , attrs , defStyle , mWordViews , mDividerViews , mDebugInfoViews ) ;
2019-12-31 18:19:35 +01:00
mMoreSuggestionsContainer = inflater . inflate ( R . layout . more_suggestions , null ) ;
2023-11-22 23:22:38 +01:00
mMoreSuggestionsView = mMoreSuggestionsContainer . findViewById ( R . id . more_suggestions_view ) ;
2019-12-31 18:19:35 +01:00
mMoreSuggestionsBuilder = new MoreSuggestions . Builder ( context , mMoreSuggestionsView ) ;
final Resources res = context . getResources ( ) ;
mMoreSuggestionsModalTolerance = res . getDimensionPixelOffset (
R . dimen . config_more_suggestions_modal_tolerance ) ;
2023-09-16 21:30:12 +02:00
mMoreSuggestionsSlidingDetector = new GestureDetector ( context , mMoreSuggestionsSlidingListener ) ;
2019-12-31 18:19:35 +01:00
2024-05-15 22:47:24 +02:00
@SuppressLint ( " CustomViewStyleable " )
2023-09-24 21:31:37 +02:00
final TypedArray keyboardAttr = context . obtainStyledAttributes ( attrs , R . styleable . Keyboard , defStyle , R . style . SuggestionStripView ) ;
mIncognitoIcon = keyboardAttr . getDrawable ( R . styleable . Keyboard_iconIncognitoKey ) ;
2023-11-03 16:41:49 +01:00
mToolbarArrowIcon = keyboardAttr . getDrawable ( R . styleable . Keyboard_iconToolbarKey ) ;
mBinIcon = keyboardAttr . getDrawable ( R . styleable . Keyboard_iconBin ) ;
2023-12-20 08:44:47 +01:00
final LinearLayout . LayoutParams toolbarKeyLayoutParams = new LinearLayout . LayoutParams (
getResources ( ) . getDimensionPixelSize ( R . dimen . config_suggestions_strip_edge_key_width ) ,
LinearLayout . LayoutParams . MATCH_PARENT
) ;
2023-12-29 12:08:32 +01:00
for ( final ToolbarKey key : ToolbarUtilsKt . getEnabledToolbarKeys ( prefs ) ) {
2023-12-28 20:32:29 +01:00
final ImageButton button = createToolbarKey ( context , keyboardAttr , key ) ;
2023-12-20 08:44:47 +01:00
button . setLayoutParams ( toolbarKeyLayoutParams ) ;
2023-12-28 20:32:29 +01:00
setupKey ( button , colors ) ;
2023-12-20 08:44:47 +01:00
mToolbar . addView ( button ) ;
}
2020-09-27 14:34:15 +02:00
2023-12-28 20:32:29 +01:00
final int toolbarHeight = Math . min ( mToolbarExpandKey . getLayoutParams ( ) . height , ( int ) getResources ( ) . getDimension ( R . dimen . config_suggestions_strip_height ) ) ;
mToolbarExpandKey . getLayoutParams ( ) . height = toolbarHeight ;
mToolbarExpandKey . getLayoutParams ( ) . width = toolbarHeight ; // we want it square
2024-01-26 09:01:39 +01:00
colors . setBackground ( mToolbarExpandKey , ColorType . STRIP_BACKGROUND ) ;
2023-12-28 20:32:29 +01:00
mDefaultBackground = mToolbarExpandKey . getBackground ( ) ;
2023-12-19 07:48:10 +01:00
mEnabledToolKeyBackground . setColors ( new int [ ] { colors . get ( ColorType . TOOL_BAR_KEY_ENABLED_BACKGROUND ) | 0xFF000000 , Color . TRANSPARENT } ) ; // ignore alpha on accent color
2023-09-24 21:31:37 +02:00
mEnabledToolKeyBackground . setGradientType ( GradientDrawable . RADIAL_GRADIENT ) ;
2023-12-28 20:32:29 +01:00
mEnabledToolKeyBackground . setGradientRadius ( mToolbarExpandKey . getLayoutParams ( ) . height / 2f ) ; // nothing else has a usable height at this state
2023-09-24 21:31:37 +02:00
2023-12-28 20:32:29 +01:00
mToolbarExpandKey . setOnClickListener ( this ) ;
mToolbarExpandKey . setImageDrawable ( Settings . getInstance ( ) . getCurrent ( ) . mIncognitoModeEnabled ? mIncognitoIcon : mToolbarArrowIcon ) ;
colors . setColor ( mToolbarExpandKey , ColorType . TOOL_BAR_EXPAND_KEY ) ;
mToolbarExpandKey . setBackground ( new ShapeDrawable ( new OvalShape ( ) ) ) ; // ShapeDrawable color is black, need src_atop filter
mToolbarExpandKey . getBackground ( ) . setColorFilter ( colors . get ( ColorType . TOOL_BAR_EXPAND_KEY_BACKGROUND ) , PorterDuff . Mode . SRC_ATOP ) ;
mToolbarExpandKey . getLayoutParams ( ) . height * = 0 . 82 ; // shrink the whole key a little (drawable not affected)
mToolbarExpandKey . getLayoutParams ( ) . width * = 0 . 82 ;
2023-09-24 21:31:37 +02:00
2023-12-29 12:08:32 +01:00
for ( final ToolbarKey pinnedKey : Settings . readPinnedKeys ( prefs ) ) {
2024-01-01 14:07:53 +01:00
final ImageButton button = createToolbarKey ( context , keyboardAttr , pinnedKey ) ;
button . setLayoutParams ( toolbarKeyLayoutParams ) ;
setupKey ( button , colors ) ;
mPinnedKeys . addView ( button ) ;
2023-12-28 22:47:31 +01:00
final View pinnedKeyInToolbar = mToolbar . findViewWithTag ( pinnedKey ) ;
2024-01-01 14:07:53 +01:00
if ( pinnedKeyInToolbar ! = null )
pinnedKeyInToolbar . setBackground ( mEnabledToolKeyBackground ) ;
2023-09-24 21:31:37 +02:00
}
2023-06-28 18:54:23 +02:00
2024-01-26 09:01:39 +01:00
colors . setBackground ( this , ColorType . STRIP_BACKGROUND ) ;
2024-01-01 14:07:53 +01:00
keyboardAttr . recycle ( ) ;
2019-12-31 18:19:35 +01:00
}
/ * *
* A connection back to the input method .
* /
public void setListener ( final Listener listener , final View inputView ) {
mListener = listener ;
2020-01-26 23:19:47 +01:00
mMainKeyboardView = inputView . findViewById ( R . id . keyboard_view ) ;
2019-12-31 18:19:35 +01:00
}
2023-12-28 23:00:17 +01:00
private void updateKeys ( ) {
2019-12-31 18:19:35 +01:00
final SettingsValues currentSettingsValues = Settings . getInstance ( ) . getCurrent ( ) ;
2023-12-28 22:47:31 +01:00
final View toolbarVoiceKey = mToolbar . findViewWithTag ( ToolbarKey . VOICE ) ;
if ( toolbarVoiceKey ! = null )
toolbarVoiceKey . setVisibility ( currentSettingsValues . mShowsVoiceInputKey ? VISIBLE : GONE ) ;
2023-12-28 20:32:29 +01:00
final View pinnedVoiceKey = mPinnedKeys . findViewWithTag ( ToolbarKey . VOICE ) ;
2023-09-24 21:31:37 +02:00
if ( pinnedVoiceKey ! = null )
pinnedVoiceKey . setVisibility ( currentSettingsValues . mShowsVoiceInputKey ? VISIBLE : GONE ) ;
2023-12-28 20:32:29 +01:00
mToolbarExpandKey . setImageDrawable ( currentSettingsValues . mIncognitoModeEnabled ? mIncognitoIcon : mToolbarArrowIcon ) ;
2024-01-08 12:11:04 +01:00
mToolbarExpandKey . setScaleX ( ( mToolbarContainer . getVisibility ( ) ! = VISIBLE ? 1f : - 1f ) * mRtl ) ;
2023-11-27 19:19:33 +01:00
2023-12-28 23:00:17 +01:00
// hide pinned keys if device is locked, and avoid expanding toolbar
2023-11-27 19:19:33 +01:00
final KeyguardManager km = ( KeyguardManager ) getContext ( ) . getSystemService ( Context . KEYGUARD_SERVICE ) ;
2023-12-28 23:00:17 +01:00
final boolean hideToolbarKeys = Build . VERSION . SDK_INT > = Build . VERSION_CODES . LOLLIPOP_MR1
2023-11-27 19:19:33 +01:00
? km . isDeviceLocked ( )
: km . isKeyguardLocked ( ) ;
2023-12-28 23:00:17 +01:00
mToolbarExpandKey . setOnClickListener ( hideToolbarKeys ? null : this ) ;
mPinnedKeys . setVisibility ( hideToolbarKeys ? GONE : VISIBLE ) ;
2024-02-07 23:49:38 +04:00
isInlineAutofillSuggestionsVisible = false ;
2019-12-31 18:19:35 +01:00
}
2023-10-30 22:15:37 +01:00
public void setRtl ( final boolean isRtlLanguage ) {
2024-03-27 07:04:59 +02:00
final int layoutDirection ;
if ( ! Settings . getInstance ( ) . getCurrent ( ) . mVarToolbarDirection )
2024-05-15 22:47:24 +02:00
layoutDirection = View . LAYOUT_DIRECTION_LOCALE ;
2024-03-27 07:04:59 +02:00
else {
2024-05-15 22:47:24 +02:00
layoutDirection = isRtlLanguage ? View . LAYOUT_DIRECTION_RTL : View . LAYOUT_DIRECTION_LTR ;
2024-03-27 07:04:59 +02:00
mRtl = isRtlLanguage ? - 1 : 1 ;
}
mStripVisibilityGroup . setLayoutDirection ( layoutDirection ) ;
2023-10-30 22:15:37 +01:00
}
2019-12-31 18:19:35 +01:00
public void setSuggestions ( final SuggestedWords suggestedWords , final boolean isRtlLanguage ) {
clear ( ) ;
2023-10-30 22:15:37 +01:00
setRtl ( isRtlLanguage ) ;
2023-12-28 23:00:17 +01:00
updateKeys ( ) ;
2019-12-31 18:19:35 +01:00
mSuggestedWords = suggestedWords ;
mStartIndexOfMoreSuggestions = mLayoutHelper . layoutAndReturnStartIndexOfMoreSuggestions (
getContext ( ) , mSuggestedWords , mSuggestionsStrip , this ) ;
}
2024-01-05 08:02:11 +01:00
public void setInlineSuggestionsView ( final View view ) {
clear ( ) ;
2024-02-07 23:49:38 +04:00
isInlineAutofillSuggestionsVisible = true ;
2024-01-05 08:02:11 +01:00
mSuggestionsStrip . addView ( view ) ;
2023-12-29 12:51:46 +04:00
}
2024-01-07 12:39:51 +04:00
@Override
public void onVisibilityChanged ( @NonNull final View view , final int visibility ) {
super . onVisibilityChanged ( view , visibility ) ;
if ( view = = this )
2024-02-14 09:22:30 +01:00
// workaround for a bug with inline suggestions views that just keep showing up otherwise, https://github.com/Helium314/HeliBoard/pull/386
2024-01-07 12:39:51 +04:00
mSuggestionsStrip . setVisibility ( visibility ) ;
}
2019-12-31 18:19:35 +01:00
public void setMoreSuggestionsHeight ( final int remainingHeight ) {
mLayoutHelper . setMoreSuggestionsHeight ( remainingHeight ) ;
}
2023-09-16 21:30:12 +02:00
@SuppressLint ( " ClickableViewAccessibility " ) // why would "null" need to call View#performClick?
2024-01-05 08:02:11 +01:00
private void clear ( ) {
2019-12-31 18:19:35 +01:00
mSuggestionsStrip . removeAllViews ( ) ;
2023-12-29 10:03:22 +01:00
if ( DEBUG_SUGGESTIONS )
removeAllDebugInfoViews ( ) ;
2023-09-25 19:35:51 +02:00
if ( mToolbarContainer . getVisibility ( ) ! = VISIBLE )
2023-09-24 21:31:37 +02:00
mStripVisibilityGroup . showSuggestionsStrip ( ) ;
2019-12-31 18:19:35 +01:00
dismissMoreSuggestionsPanel ( ) ;
2023-09-16 20:29:39 +02:00
for ( final TextView word : mWordViews ) {
word . setOnTouchListener ( null ) ;
}
2019-12-31 18:19:35 +01:00
}
private void removeAllDebugInfoViews ( ) {
// The debug info views may be placed as children views of this {@link SuggestionStripView}.
for ( final View debugInfoView : mDebugInfoViews ) {
final ViewParent parent = debugInfoView . getParent ( ) ;
if ( parent instanceof ViewGroup ) {
( ( ViewGroup ) parent ) . removeView ( debugInfoView ) ;
}
}
}
private final MoreSuggestionsListener mMoreSuggestionsListener = new MoreSuggestionsListener ( ) {
@Override
public void onSuggestionSelected ( final SuggestedWordInfo wordInfo ) {
mListener . pickSuggestionManually ( wordInfo ) ;
dismissMoreSuggestionsPanel ( ) ;
}
@Override
public void onCancelInput ( ) {
dismissMoreSuggestionsPanel ( ) ;
}
} ;
2024-02-05 09:33:06 +01:00
private final PopupKeysPanel . Controller mMoreSuggestionsController =
new PopupKeysPanel . Controller ( ) {
2019-12-31 18:19:35 +01:00
@Override
2024-02-05 09:33:06 +01:00
public void onDismissPopupKeysPanel ( ) {
mMainKeyboardView . onDismissPopupKeysPanel ( ) ;
2019-12-31 18:19:35 +01:00
}
@Override
2024-02-05 09:33:06 +01:00
public void onShowPopupKeysPanel ( final PopupKeysPanel panel ) {
mMainKeyboardView . onShowPopupKeysPanel ( panel ) ;
2019-12-31 18:19:35 +01:00
}
@Override
2024-02-05 09:33:06 +01:00
public void onCancelPopupKeysPanel ( ) {
2019-12-31 18:19:35 +01:00
dismissMoreSuggestionsPanel ( ) ;
}
} ;
public boolean isShowingMoreSuggestionPanel ( ) {
return mMoreSuggestionsView . isShowingInParent ( ) ;
}
public void dismissMoreSuggestionsPanel ( ) {
2024-02-05 09:33:06 +01:00
mMoreSuggestionsView . dismissPopupKeysPanel ( ) ;
2019-12-31 18:19:35 +01:00
}
@Override
public boolean onLongClick ( final View view ) {
2023-09-24 21:31:37 +02:00
AudioAndHapticFeedbackManager . getInstance ( ) . performHapticAndAudioFeedback ( Constants . NOT_A_CODE , this ) ;
2024-05-13 19:34:59 +02:00
if ( view . getTag ( ) instanceof ToolbarKey ) {
2023-09-24 21:31:37 +02:00
onLongClickToolKey ( view ) ;
2021-11-21 22:04:09 -05:00
return true ;
}
2023-09-13 18:09:53 +02:00
if ( view instanceof TextView & & mWordViews . contains ( view ) ) {
2023-09-24 21:31:37 +02:00
return onLongClickSuggestion ( ( TextView ) view ) ;
} else return showMoreSuggestions ( ) ;
}
private void onLongClickToolKey ( final View view ) {
2024-05-01 23:44:51 +03:00
if ( ! ( view . getTag ( ) instanceof ToolbarKey tag ) ) return ;
if ( view . getParent ( ) = = mPinnedKeys ) {
final int longClickCode = getCodeForToolbarKeyLongClick ( tag ) ;
2024-05-13 19:34:59 +02:00
if ( longClickCode ! = KeyCode . UNSPECIFIED ) {
2024-05-01 23:44:51 +03:00
mListener . onCodeInput ( longClickCode , Constants . SUGGESTION_STRIP_COORDINATE , Constants . SUGGESTION_STRIP_COORDINATE , false ) ;
2024-05-13 19:34:59 +02:00
}
2023-09-24 21:31:37 +02:00
} else if ( view . getParent ( ) = = mToolbar ) {
final View pinnedKeyView = mPinnedKeys . findViewWithTag ( tag ) ;
if ( pinnedKeyView = = null ) {
2023-12-20 08:44:47 +01:00
addKeyToPinnedKeys ( tag ) ;
2023-09-24 21:31:37 +02:00
mToolbar . findViewWithTag ( tag ) . setBackground ( mEnabledToolKeyBackground ) ;
Settings . addPinnedKey ( DeviceProtectedUtils . getSharedPreferences ( getContext ( ) ) , tag ) ;
} else {
Settings . removePinnedKey ( DeviceProtectedUtils . getSharedPreferences ( getContext ( ) ) , tag ) ;
2023-10-20 18:15:04 +02:00
mToolbar . findViewWithTag ( tag ) . setBackground ( mDefaultBackground . getConstantState ( ) . newDrawable ( getResources ( ) ) ) ;
2023-09-24 21:31:37 +02:00
mPinnedKeys . removeView ( pinnedKeyView ) ;
}
}
}
@SuppressLint ( " ClickableViewAccessibility " ) // no need for View#performClick, we return false mostly anyway
private boolean onLongClickSuggestion ( final TextView wordView ) {
2024-01-28 20:01:34 +01:00
boolean showIcon = true ;
if ( wordView . getTag ( ) instanceof Integer ) {
final int index = ( int ) wordView . getTag ( ) ;
if ( index < mSuggestedWords . size ( ) & & mSuggestedWords . getInfo ( index ) . mSourceDict = = Dictionary . DICTIONARY_USER_TYPED )
showIcon = false ;
}
if ( showIcon ) {
final Drawable icon = mBinIcon ;
Settings . getInstance ( ) . getCurrent ( ) . mColors . setColor ( icon , ColorType . REMOVE_SUGGESTION_ICON ) ;
int w = icon . getIntrinsicWidth ( ) ;
int h = icon . getIntrinsicWidth ( ) ;
wordView . setCompoundDrawablesWithIntrinsicBounds ( icon , null , null , null ) ;
wordView . setEllipsize ( TextUtils . TruncateAt . END ) ;
AtomicBoolean downOk = new AtomicBoolean ( false ) ;
wordView . setOnTouchListener ( ( view1 , motionEvent ) - > {
if ( motionEvent . getAction ( ) = = MotionEvent . ACTION_UP ) {
final float x = motionEvent . getX ( ) ;
final float y = motionEvent . getY ( ) ;
if ( 0 < x & & x < w & & 0 < y & & y < h ) {
removeSuggestion ( wordView ) ;
wordView . cancelLongPress ( ) ;
wordView . setPressed ( false ) ;
return true ;
}
} else if ( motionEvent . getAction ( ) = = MotionEvent . ACTION_DOWN ) {
final float x = motionEvent . getX ( ) ;
final float y = motionEvent . getY ( ) ;
if ( 0 < x & & x < w & & 0 < y & & y < h ) {
downOk . set ( true ) ;
}
2023-06-28 20:40:35 +02:00
}
2024-01-28 20:01:34 +01:00
return false ;
} ) ;
}
2023-11-22 23:22:38 +01:00
if ( DebugFlags . DEBUG_ENABLED & & ( isShowingMoreSuggestionPanel ( ) | | ! showMoreSuggestions ( ) ) ) {
2023-09-24 21:31:37 +02:00
showSourceDict ( wordView ) ;
return true ;
2023-09-13 18:09:53 +02:00
} else return showMoreSuggestions ( ) ;
2023-06-28 20:40:35 +02:00
}
2023-09-13 18:09:53 +02:00
private void showSourceDict ( final TextView wordView ) {
2023-06-28 20:40:35 +02:00
final String word = wordView . getText ( ) . toString ( ) ;
2023-09-13 18:09:53 +02:00
final int index ;
if ( wordView . getTag ( ) instanceof Integer ) {
index = ( int ) wordView . getTag ( ) ;
} else return ;
if ( index > = mSuggestedWords . size ( ) ) return ;
final SuggestedWordInfo info = mSuggestedWords . getInfo ( index ) ;
if ( ! info . getWord ( ) . equals ( word ) ) return ;
final String text = info . mSourceDict . mDictType + " : " + info . mSourceDict . mLocale ;
// apparently toast is not working on some Android versions, probably
// Android 13 with the notification permission
// Toast.makeText(getContext(), text, Toast.LENGTH_LONG).show();
2024-01-01 19:20:28 +01:00
final PopupMenu uglyWorkaround = new PopupMenu ( DialogUtilsKt . getPlatformDialogThemeContext ( getContext ( ) ) , wordView ) ;
2023-09-13 18:09:53 +02:00
uglyWorkaround . getMenu ( ) . add ( Menu . NONE , 1 , Menu . NONE , text ) ;
uglyWorkaround . show ( ) ;
}
2023-06-28 20:40:35 +02:00
2023-09-13 18:09:53 +02:00
private void removeSuggestion ( TextView wordView ) {
final String word = wordView . getText ( ) . toString ( ) ;
mListener . removeSuggestion ( word ) ;
2024-02-05 09:33:06 +01:00
mMoreSuggestionsView . dismissPopupKeysPanel ( ) ;
2023-09-13 18:09:53 +02:00
// show suggestions, but without the removed word
final ArrayList < SuggestedWordInfo > sw = new ArrayList < > ( ) ;
for ( int i = 0 ; i < mSuggestedWords . size ( ) ; i + + ) {
final SuggestedWordInfo info = mSuggestedWords . getInfo ( i ) ;
if ( ! info . getWord ( ) . equals ( word ) )
sw . add ( info ) ;
}
ArrayList < SuggestedWordInfo > rs = null ;
if ( mSuggestedWords . mRawSuggestions ! = null ) {
rs = mSuggestedWords . mRawSuggestions ;
for ( int i = 0 ; i < rs . size ( ) ; i + + ) {
if ( rs . get ( i ) . getWord ( ) . equals ( word ) ) {
rs . remove ( i ) ;
break ;
2023-08-27 18:41:13 +02:00
}
}
2023-09-13 18:09:53 +02:00
}
// copied code from setSuggestions, but without the Rtl part
clear ( ) ;
mSuggestedWords = new SuggestedWords ( sw , rs , mSuggestedWords . getTypedWordInfo ( ) ,
mSuggestedWords . mTypedWordValid , mSuggestedWords . mWillAutoCorrect ,
mSuggestedWords . mIsObsoleteSuggestions , mSuggestedWords . mInputStyle ,
mSuggestedWords . mSequenceNumber ) ;
mStartIndexOfMoreSuggestions = mLayoutHelper . layoutAndReturnStartIndexOfMoreSuggestions (
getContext ( ) , mSuggestedWords , mSuggestionsStrip , SuggestionStripView . this ) ;
mStripVisibilityGroup . showSuggestionsStrip ( ) ;
2019-12-31 18:19:35 +01:00
}
boolean showMoreSuggestions ( ) {
final Keyboard parentKeyboard = mMainKeyboardView . getKeyboard ( ) ;
if ( parentKeyboard = = null ) {
return false ;
}
final SuggestionStripLayoutHelper layoutHelper = mLayoutHelper ;
if ( mSuggestedWords . size ( ) < = mStartIndexOfMoreSuggestions ) {
return false ;
}
final int stripWidth = getWidth ( ) ;
final View container = mMoreSuggestionsContainer ;
final int maxWidth = stripWidth - container . getPaddingLeft ( ) - container . getPaddingRight ( ) ;
final MoreSuggestions . Builder builder = mMoreSuggestionsBuilder ;
builder . layout ( mSuggestedWords , mStartIndexOfMoreSuggestions , maxWidth ,
( int ) ( maxWidth * layoutHelper . mMinMoreSuggestionsWidth ) ,
layoutHelper . getMaxMoreSuggestionsRow ( ) , parentKeyboard ) ;
mMoreSuggestionsView . setKeyboard ( builder . build ( ) ) ;
container . measure ( ViewGroup . LayoutParams . WRAP_CONTENT , ViewGroup . LayoutParams . WRAP_CONTENT ) ;
final int pointX = stripWidth / 2 ;
final int pointY = - layoutHelper . mMoreSuggestionsBottomGap ;
2024-02-05 09:33:06 +01:00
mMoreSuggestionsView . showPopupKeysPanel ( this , mMoreSuggestionsController , pointX , pointY ,
2019-12-31 18:19:35 +01:00
mMoreSuggestionsListener ) ;
mOriginX = mLastX ;
mOriginY = mLastY ;
for ( int i = 0 ; i < mStartIndexOfMoreSuggestions ; i + + ) {
mWordViews . get ( i ) . setPressed ( false ) ;
}
return true ;
}
// Working variables for {@link onInterceptTouchEvent(MotionEvent)} and
// {@link onTouchEvent(MotionEvent)}.
private int mLastX ;
private int mLastY ;
private int mOriginX ;
private int mOriginY ;
private final int mMoreSuggestionsModalTolerance ;
private boolean mNeedsToTransformTouchEventToHoverEvent ;
private boolean mIsDispatchingHoverEventToMoreSuggestions ;
private final GestureDetector mMoreSuggestionsSlidingDetector ;
private final GestureDetector . OnGestureListener mMoreSuggestionsSlidingListener =
new GestureDetector . SimpleOnGestureListener ( ) {
@Override
2024-05-15 22:47:24 +02:00
public boolean onScroll ( @Nullable MotionEvent down , @NonNull MotionEvent me , float deltaX , float deltaY ) {
if ( down = = null ) return false ;
2019-12-31 18:19:35 +01:00
final float dy = me . getY ( ) - down . getY ( ) ;
2023-09-25 19:35:51 +02:00
if ( mToolbarContainer . getVisibility ( ) ! = VISIBLE & & deltaY > 0 & & dy < 0 ) {
2019-12-31 18:19:35 +01:00
return showMoreSuggestions ( ) ;
}
return false ;
}
} ;
@Override
public boolean onInterceptTouchEvent ( final MotionEvent me ) {
2024-01-02 12:05:31 +04:00
// Disable More Suggestions if inline autofill suggestions is visible
2024-02-07 23:49:38 +04:00
if ( isInlineAutofillSuggestionsVisible ) {
2024-01-02 12:05:31 +04:00
return false ;
}
2019-12-31 18:19:35 +01:00
// Detecting sliding up finger to show {@link MoreSuggestionsView}.
if ( ! mMoreSuggestionsView . isShowingInParent ( ) ) {
mLastX = ( int ) me . getX ( ) ;
mLastY = ( int ) me . getY ( ) ;
return mMoreSuggestionsSlidingDetector . onTouchEvent ( me ) ;
}
if ( mMoreSuggestionsView . isInModalMode ( ) ) {
return false ;
}
final int action = me . getAction ( ) ;
final int index = me . getActionIndex ( ) ;
final int x = ( int ) me . getX ( index ) ;
final int y = ( int ) me . getY ( index ) ;
if ( Math . abs ( x - mOriginX ) > = mMoreSuggestionsModalTolerance
| | mOriginY - y > = mMoreSuggestionsModalTolerance ) {
// Decided to be in the sliding suggestion mode only when the touch point has been moved
// upward. Further {@link MotionEvent}s will be delivered to
// {@link #onTouchEvent(MotionEvent)}.
mNeedsToTransformTouchEventToHoverEvent =
2020-01-21 18:16:01 +01:00
AccessibilityUtils . Companion . getInstance ( ) . isTouchExplorationEnabled ( ) ;
2019-12-31 18:19:35 +01:00
mIsDispatchingHoverEventToMoreSuggestions = false ;
return true ;
}
if ( action = = MotionEvent . ACTION_UP | | action = = MotionEvent . ACTION_POINTER_UP ) {
// Decided to be in the modal input mode.
mMoreSuggestionsView . setModalMode ( ) ;
}
return false ;
}
@Override
public boolean dispatchPopulateAccessibilityEvent ( final AccessibilityEvent event ) {
// Don't populate accessibility event with suggested words and voice key.
return true ;
}
@Override
2023-09-16 21:30:12 +02:00
@SuppressLint ( " ClickableViewAccessibility " ) // ok, perform click again, but why?
2019-12-31 18:19:35 +01:00
public boolean onTouchEvent ( final MotionEvent me ) {
if ( ! mMoreSuggestionsView . isShowingInParent ( ) ) {
// Ignore any touch event while more suggestions panel hasn't been shown.
// Detecting sliding up is done at {@link #onInterceptTouchEvent}.
return true ;
}
// In the sliding input mode. {@link MotionEvent} should be forwarded to
// {@link MoreSuggestionsView}.
final int index = me . getActionIndex ( ) ;
final int x = mMoreSuggestionsView . translateX ( ( int ) me . getX ( index ) ) ;
final int y = mMoreSuggestionsView . translateY ( ( int ) me . getY ( index ) ) ;
me . setLocation ( x , y ) ;
if ( ! mNeedsToTransformTouchEventToHoverEvent ) {
mMoreSuggestionsView . onTouchEvent ( me ) ;
return true ;
}
// In sliding suggestion mode with accessibility mode on, a touch event should be
// transformed to a hover event.
final int width = mMoreSuggestionsView . getWidth ( ) ;
final int height = mMoreSuggestionsView . getHeight ( ) ;
final boolean onMoreSuggestions = ( x > = 0 & & x < width & & y > = 0 & & y < height ) ;
if ( ! onMoreSuggestions & & ! mIsDispatchingHoverEventToMoreSuggestions ) {
// Just drop this touch event because dispatching hover event isn't started yet and
// the touch event isn't on {@link MoreSuggestionsView}.
return true ;
}
final int hoverAction ;
if ( onMoreSuggestions & & ! mIsDispatchingHoverEventToMoreSuggestions ) {
// Transform this touch event to a hover enter event and start dispatching a hover
// event to {@link MoreSuggestionsView}.
mIsDispatchingHoverEventToMoreSuggestions = true ;
hoverAction = MotionEvent . ACTION_HOVER_ENTER ;
} else if ( me . getActionMasked ( ) = = MotionEvent . ACTION_UP ) {
// Transform this touch event to a hover exit event and stop dispatching a hover event
// after this.
mIsDispatchingHoverEventToMoreSuggestions = false ;
mNeedsToTransformTouchEventToHoverEvent = false ;
hoverAction = MotionEvent . ACTION_HOVER_EXIT ;
} else {
// Transform this touch event to a hover move event.
hoverAction = MotionEvent . ACTION_HOVER_MOVE ;
}
me . setAction ( hoverAction ) ;
mMoreSuggestionsView . onHoverEvent ( me ) ;
return true ;
}
@Override
public void onClick ( final View view ) {
2024-03-02 21:02:48 +01:00
AudioAndHapticFeedbackManager . getInstance ( ) . performHapticAndAudioFeedback ( KeyCode . NOT_SPECIFIED , this ) ;
2023-09-24 21:31:37 +02:00
final Object tag = view . getTag ( ) ;
2023-12-28 20:32:29 +01:00
if ( tag instanceof ToolbarKey ) {
2024-05-15 22:47:24 +02:00
final int code = getCodeForToolbarKey ( ( ToolbarKey ) tag ) ;
2024-05-01 22:59:16 +02:00
if ( code ! = KeyCode . UNSPECIFIED ) {
2024-01-16 21:06:42 +01:00
Log . d ( TAG , " click toolbar key " + tag ) ;
2023-12-21 21:57:53 +01:00
mListener . onCodeInput ( code , Constants . SUGGESTION_STRIP_COORDINATE , Constants . SUGGESTION_STRIP_COORDINATE , false ) ;
2024-01-01 13:16:49 +01:00
if ( tag = = ToolbarKey . INCOGNITO | | tag = = ToolbarKey . AUTOCORRECT | | tag = = ToolbarKey . ONE_HANDED ) {
if ( tag = = ToolbarKey . INCOGNITO )
updateKeys ( ) ; // update icon
view . setActivated ( ! view . isActivated ( ) ) ;
}
2023-12-21 21:57:53 +01:00
return ;
2023-09-24 21:31:37 +02:00
}
2019-12-31 18:19:35 +01:00
}
2023-12-28 20:32:29 +01:00
if ( view = = mToolbarExpandKey ) {
2023-11-20 14:27:40 +01:00
setToolbarVisibility ( mToolbarContainer . getVisibility ( ) ! = VISIBLE ) ;
2021-11-21 14:56:25 -05:00
}
2019-12-31 18:19:35 +01:00
2023-09-24 21:31:37 +02:00
2019-12-31 18:19:35 +01:00
// {@link Integer} tag is set at
// {@link SuggestionStripLayoutHelper#setupWordViewsTextAndColor(SuggestedWords,int)} and
// {@link SuggestionStripLayoutHelper#layoutPunctuationSuggestions(SuggestedWords,ViewGroup}
if ( tag instanceof Integer ) {
final int index = ( Integer ) tag ;
if ( index > = mSuggestedWords . size ( ) ) {
return ;
}
final SuggestedWordInfo wordInfo = mSuggestedWords . getInfo ( index ) ;
mListener . pickSuggestionManually ( wordInfo ) ;
}
}
@Override
protected void onDetachedFromWindow ( ) {
super . onDetachedFromWindow ( ) ;
dismissMoreSuggestionsPanel ( ) ;
}
@Override
protected void onSizeChanged ( final int w , final int h , final int oldw , final int oldh ) {
// Called by the framework when the size is known. Show the important notice if applicable.
// This may be overriden by showing suggestions later, if applicable.
}
2023-09-24 21:31:37 +02:00
2023-11-20 14:27:40 +01:00
public void setToolbarVisibility ( final boolean visible ) {
if ( visible ) {
mPinnedKeys . setVisibility ( GONE ) ;
mSuggestionsStrip . setVisibility ( GONE ) ;
mToolbarContainer . setVisibility ( VISIBLE ) ;
} else {
mToolbarContainer . setVisibility ( GONE ) ;
mSuggestionsStrip . setVisibility ( VISIBLE ) ;
mPinnedKeys . setVisibility ( VISIBLE ) ;
}
2024-01-08 12:11:04 +01:00
mToolbarExpandKey . setScaleX ( ( visible ? - 1f : 1f ) * mRtl ) ;
2023-11-20 14:27:40 +01:00
}
2023-12-28 20:32:29 +01:00
private void addKeyToPinnedKeys ( final ToolbarKey pinnedKey ) {
final ImageButton original = mToolbar . findViewWithTag ( pinnedKey ) ;
2023-12-20 08:44:47 +01:00
if ( original = = null ) return ;
final ImageButton copy = new ImageButton ( getContext ( ) , null , R . attr . suggestionWordStyle ) ;
copy . setTag ( pinnedKey ) ;
copy . setScaleType ( original . getScaleType ( ) ) ;
copy . setScaleX ( original . getScaleX ( ) ) ;
copy . setScaleY ( original . getScaleY ( ) ) ;
2023-12-28 22:47:31 +01:00
copy . setContentDescription ( original . getContentDescription ( ) ) ;
2023-12-20 08:44:47 +01:00
copy . setImageDrawable ( original . getDrawable ( ) ) ;
copy . setLayoutParams ( original . getLayoutParams ( ) ) ;
2024-01-01 13:16:49 +01:00
copy . setActivated ( original . isActivated ( ) ) ;
2023-12-20 08:44:47 +01:00
setupKey ( copy , Settings . getInstance ( ) . getCurrent ( ) . mColors ) ;
mPinnedKeys . addView ( copy ) ;
2023-09-24 21:31:37 +02:00
}
private void setupKey ( final ImageButton view , final Colors colors ) {
view . setOnClickListener ( this ) ;
view . setOnLongClickListener ( this ) ;
2023-12-19 07:48:10 +01:00
colors . setColor ( view , ColorType . TOOL_BAR_KEY ) ;
2024-01-26 09:01:39 +01:00
colors . setBackground ( view , ColorType . STRIP_BACKGROUND ) ;
2023-09-24 21:31:37 +02:00
}
2019-12-31 18:19:35 +01:00
}