Reapply emoji picker TODO: Show picker content

This commit is contained in:
Arne 2024-10-15 13:54:23 +02:00
parent 31bc60134f
commit 6cd19ba414
14 changed files with 966 additions and 5 deletions

View file

@ -201,7 +201,7 @@
android:launchMode="singleTask"
android:minWidth="300dp"
android:minHeight="300dp"
android:windowSoftInputMode="stateHidden" />
android:windowSoftInputMode="adjustPan|stateHidden" />
<activity
android:name=".ui.ScanActivity"
android:exported="false"

View file

@ -25,7 +25,7 @@ public class AddReactionActivity extends XmppActivity {
binding.toolbar.setNavigationIcon(R.drawable.ic_clear_24dp);
binding.toolbar.setNavigationOnClickListener(v -> finish());
setTitle(R.string.add_reaction_title);
binding.emojiPicker.setOnEmojiPickedListener(
binding.reactionPicker.setOnEmojiPickedListener(
emojiViewItem -> addReaction(emojiViewItem.getEmoji()));
}

View file

@ -6,6 +6,7 @@ import static android.view.View.VISIBLE;
import static eu.siacs.conversations.ui.XmppActivity.EXTRA_ACCOUNT;
import static eu.siacs.conversations.ui.XmppActivity.REQUEST_INVITE_TO_CONVERSATION;
import static eu.siacs.conversations.ui.util.SoftKeyboardUtils.hideSoftKeyboard;
import static eu.siacs.conversations.utils.Compatibility.hasStoragePermission;
import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
import static eu.siacs.conversations.utils.PermissionUtils.audioGranted;
import static eu.siacs.conversations.utils.PermissionUtils.cameraGranted;
@ -30,6 +31,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.Typeface;
import android.icu.util.Calendar;
import android.icu.util.TimeZone;
import android.media.MediaRecorder;
@ -62,6 +64,7 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
@ -73,9 +76,11 @@ import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
@ -85,13 +90,17 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.ColorUtils;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.inputmethod.InputConnectionCompat;
import androidx.core.view.inputmethod.InputContentInfoCompat;
import androidx.databinding.DataBindingUtil;
import androidx.documentfile.provider.DocumentFile;
import androidx.emoji2.emojipicker.EmojiPickerView;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
@ -100,6 +109,9 @@ import com.bumptech.glide.Glide;
import de.monocles.chat.BobTransfer;
import de.monocles.chat.EmojiSearch;
import de.monocles.chat.EmojiSearchOld;
import de.monocles.chat.GifsAdapter;
import de.monocles.chat.KeyboardHeightProvider;
import de.monocles.chat.WebxdcPage;
import de.monocles.chat.WebxdcStore;
@ -130,6 +142,8 @@ import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.AbstractMap;
import java.util.ArrayList;
@ -151,6 +165,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
@ -307,6 +322,15 @@ public class ConversationFragment extends XmppFragment
private int identiconWidth = -1;
private File savingAsSticker = null;
private EmojiSearch emojiSearch = null;
File dirStickers = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + File.separator + "Stickers");
//Gifspaths
private File[] files;
private String[] filesPaths;
private String[] filesNames;
File dirGifs = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + File.separator + "Stickers"); //TODO: Change this to dedicated GIFs folder later
private KeyboardHeightProvider.KeyboardHeightListener keyboardHeightListener = null;
private KeyboardHeightProvider keyboardHeightProvider = null;
protected OnClickListener clickToVerify = new OnClickListener() {
@Override
@ -1493,6 +1517,7 @@ public class ConversationFragment extends XmppFragment
setHasOptionsMenu(true);
activity.getOnBackPressedDispatcher().addCallback(this, backPressedLeaveSingleThread);
activity.getOnBackPressedDispatcher().addCallback(this, backPressedLeaveVoiceRecorder);
activity.getOnBackPressedDispatcher().addCallback(this, backPressedLeaveEmojiPicker);
oldOrientation = activity.getRequestedOrientation();
}
@ -1579,6 +1604,10 @@ public class ConversationFragment extends XmppFragment
DataBindingUtil.inflate(inflater, R.layout.fragment_conversation, container, false);
binding.getRoot().setOnClickListener(null); // TODO why the fuck did we do this?
backPressedLeaveEmojiPicker.setEnabled(binding.emojisStickerLayout.getHeight() > 100);
LoadStickers();
LoadGifs();
binding.textinput.setOnEditorActionListener(mEditorActionListener);
binding.textinput.setRichContentListener(new String[] {"image/*"}, mEditorContentListener);
DisplayMetrics displayMetrics = new DisplayMetrics();
@ -1598,6 +1627,11 @@ public class ConversationFragment extends XmppFragment
binding.requestVoice.setVisibility(View.GONE);
Toast.makeText(activity, "Your request has been sent to the moderators", Toast.LENGTH_SHORT).show();
});
binding.emojiButton.setOnClickListener(this.memojiButtonListener);
binding.emojisButton.setOnClickListener(this.memojisButtonListener);
binding.stickersButton.setOnClickListener(this.mstickersButtonListener);
binding.gifsButton.setOnClickListener(this.mgifsButtonListener);
binding.keyboardButton.setOnClickListener(this.mkeyboardButtonListener);
binding.recordVoiceButton.setOnClickListener(this.mRecordVoiceButtonListener);
binding.timer.setOnClickListener(this.mTimerClickListener);
binding.takePictureButton.setOnClickListener(this.mtakePictureButtonListener);
@ -2393,6 +2427,14 @@ public class ConversationFragment extends XmppFragment
updateThreadFromLastMessage();
return true;
}
if (binding.emojisStickerLayout.getHeight() > 100){
LinearLayout emojipickerview = binding.emojisStickerLayout;
ViewGroup.LayoutParams params = emojipickerview.getLayoutParams();
params.height = 0;
emojipickerview.setLayoutParams(params);
hideSoftKeyboard(activity);
return false;
}
if (binding.recordingVoiceActivity.getVisibility()==VISIBLE){
mHandler.removeCallbacks(mTickExecutor);
stopRecording(false);
@ -3566,7 +3608,10 @@ public class ConversationFragment extends XmppFragment
activity.onConversationArchived(this.conversation);
return false;
}
updateinputfield();
if (activity != null) {
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
}
final var cursord = activity.getDrawable(R.drawable.cursor_on_tertiary_container);
if (activity.xmppConnectionService != null && activity.xmppConnectionService.getAccounts().size() > 1) {
final var bg = MaterialColors.getColor(binding.textinput, com.google.android.material.R.attr.colorSurface);
@ -5335,4 +5380,378 @@ public class ConversationFragment extends XmppFragment
}
}
public void updateinputfield() {
LinearLayout emojipickerview = binding.emojisStickerLayout;
ViewGroup.LayoutParams params = emojipickerview.getLayoutParams();
Fragment secondaryFragment = activity.getFragmentManager().findFragmentById(R.id.secondary_fragment);
if (Build.VERSION.SDK_INT > 29) {
ViewCompat.setOnApplyWindowInsetsListener(activity.getWindow().getDecorView(), (v, insets) -> {
boolean isKeyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
int keyboardHeight = 0;
if (activity != null && ViewConfiguration.get(activity).hasPermanentMenuKey()) {
keyboardHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom - insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom - 25;
} else if (activity != null) {
keyboardHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom - insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom - 25;
}
if (keyboardHeight > 100 && !(secondaryFragment instanceof ConversationFragment)) {
binding.keyboardButton.setVisibility(GONE);
binding.emojiButton.setVisibility(VISIBLE);
params.height = keyboardHeight;
emojipickerview.setLayoutParams(params);
} else if (keyboardHeight > 100) {
binding.keyboardButton.setVisibility(GONE);
binding.emojiButton.setVisibility(VISIBLE);
params.height = keyboardHeight - 142;
emojipickerview.setLayoutParams(params);
} else if (binding.emojiButton.getVisibility() == VISIBLE) {
binding.keyboardButton.setVisibility(GONE);
params.height = 0;
emojipickerview.setLayoutParams(params);
} else if (binding.keyboardButton.getVisibility() == VISIBLE && keyboardHeight == 0) {
binding.emojiButton.setVisibility(GONE);
params.height = 800;
emojipickerview.setLayoutParams(params);
} else if (binding.keyboardButton.getVisibility() == VISIBLE && keyboardHeight > 100) {
binding.emojiButton.setVisibility(GONE);
params.height = keyboardHeight;
emojipickerview.setLayoutParams(params);
}
return ViewCompat.onApplyWindowInsets(v, insets);
});
} else {
if (keyboardHeightProvider != null) {
return;
}
RelativeLayout llRoot = binding.conversationsFragment; //The root layout (Linear, Relative, Contraint, etc...)
keyboardHeightListener = (int keyboardHeight, boolean keyboardOpen, boolean isLandscape) -> {
Log.i("keyboard listener", "keyboardHeight: " + keyboardHeight + " keyboardOpen: " + keyboardOpen + " isLandscape: " + isLandscape);
if (keyboardOpen && !(secondaryFragment instanceof ConversationFragment)) {
binding.keyboardButton.setVisibility(GONE);
binding.emojiButton.setVisibility(VISIBLE);
params.height = keyboardHeight - 25;
emojipickerview.setLayoutParams(params);
} else if (keyboardOpen) {
binding.keyboardButton.setVisibility(GONE);
binding.emojiButton.setVisibility(VISIBLE);
params.height = keyboardHeight - 150;
emojipickerview.setLayoutParams(params);
} else if (binding.emojiButton.getVisibility() == VISIBLE) {
binding.keyboardButton.setVisibility(GONE);
params.height = 0;
emojipickerview.setLayoutParams(params);
} else if (binding.keyboardButton.getVisibility() == VISIBLE && keyboardHeight == 0) {
binding.emojiButton.setVisibility(GONE);
params.height = 600;
emojipickerview.setLayoutParams(params);
} else if (binding.keyboardButton.getVisibility() == VISIBLE && keyboardHeight > 100) {
binding.emojiButton.setVisibility(GONE);
params.height = keyboardHeight;
emojipickerview.setLayoutParams(params);
}
};
keyboardHeightProvider = new KeyboardHeightProvider(activity, activity.getWindowManager(), llRoot, keyboardHeightListener);
}
}
private final OnClickListener memojiButtonListener = new OnClickListener() {
@Override
public void onClick(View v) {
if (binding.emojiButton.getVisibility() == VISIBLE && binding.emojisStickerLayout.getHeight() > 100) {
binding.emojiButton.setVisibility(GONE);
binding.keyboardButton.setVisibility(VISIBLE);
hideSoftKeyboard(activity);
EmojiPickerView emojiPickerView = binding.emojiPicker;
backPressedLeaveEmojiPicker.setEnabled(true);
binding.textinput.requestFocus();
emojiPickerView.setOnEmojiPickedListener(emojiViewItem -> {
int start = binding.textinput.getSelectionStart(); //this is to get the the cursor position
binding.textinput.getText().insert(start, emojiViewItem.getEmoji()); //this will get the text and insert the emoji into the current position
});
if (binding.emojiPicker.getVisibility() == VISIBLE) {
binding.emojisButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.emojisButton.setTypeface(null, Typeface.BOLD);
} else {
binding.emojisButton.setBackgroundColor(0);
binding.emojisButton.setTypeface(null, Typeface.NORMAL);
}
if (binding.stickersview.getVisibility() == VISIBLE) {
binding.stickersButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.stickersButton.setTypeface(null, Typeface.BOLD);
} else {
binding.stickersButton.setBackgroundColor(0);
binding.stickersButton.setTypeface(null, Typeface.NORMAL);
}
if (binding.gifsview.getVisibility() == VISIBLE) {
binding.gifsButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.gifsButton.setTypeface(null, Typeface.BOLD);
} else {
binding.gifsButton.setBackgroundColor(0);
binding.gifsButton.setTypeface(null, Typeface.NORMAL);
}
} else if (binding.emojiButton.getVisibility() == VISIBLE && binding.emojisStickerLayout.getHeight() < 100) {
LinearLayout emojipickerview = binding.emojisStickerLayout;
ViewGroup.LayoutParams params = emojipickerview.getLayoutParams();
params.height = 800;
emojipickerview.setLayoutParams(params);
binding.emojiButton.setVisibility(GONE);
binding.keyboardButton.setVisibility(VISIBLE);
hideSoftKeyboard(activity);
EmojiPickerView emojiPickerView = binding.emojiPicker;
backPressedLeaveEmojiPicker.setEnabled(true);
binding.textinput.requestFocus();
emojiPickerView.setOnEmojiPickedListener(emojiViewItem -> {
int start = binding.textinput.getSelectionStart(); //this is to get the the cursor position
binding.textinput.getText().insert(start, emojiViewItem.getEmoji()); //this will get the text and insert the emoji into the current position
});
if (binding.emojiPicker.getVisibility() == VISIBLE) {
binding.emojisButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.emojisButton.setTypeface(null, Typeface.BOLD);
} else {
binding.emojisButton.setBackgroundColor(0);
binding.emojisButton.setTypeface(null, Typeface.NORMAL);
}
if (binding.stickersview.getVisibility() == VISIBLE) {
binding.stickersButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.stickersButton.setTypeface(null, Typeface.BOLD);
} else {
binding.stickersButton.setBackgroundColor(0);
binding.stickersButton.setTypeface(null, Typeface.NORMAL);
}
if (binding.gifsview.getVisibility() == VISIBLE) {
binding.gifsButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.gifsButton.setTypeface(null, Typeface.BOLD);
} else {
binding.gifsButton.setBackgroundColor(0);
binding.gifsButton.setTypeface(null, Typeface.NORMAL);
}
}
}
};
private final OnClickListener memojisButtonListener = new OnClickListener() {
@Override
public void onClick(View v) {
binding.emojiPicker.setVisibility(VISIBLE);
binding.stickersview.setVisibility(GONE);
binding.gifsview.setVisibility(GONE);
EmojiPickerView emojiPickerView = binding.emojiPicker;
backPressedLeaveEmojiPicker.setEnabled(true);
binding.textinput.requestFocus();
emojiPickerView.setOnEmojiPickedListener(emojiViewItem -> {
int start = binding.textinput.getSelectionStart(); //this is to get the the cursor position
binding.textinput.getText().insert(start, emojiViewItem.getEmoji()); //this will get the text and insert the emoji into the current position
});
if (binding.emojiPicker.getVisibility() == VISIBLE) {
binding.emojisButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.emojisButton.setTypeface(null, Typeface.BOLD);
} else {
binding.emojisButton.setBackgroundColor(0);
binding.emojisButton.setTypeface(null, Typeface.NORMAL);
}
if (binding.stickersview.getVisibility() == VISIBLE) {
binding.stickersButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.stickersButton.setTypeface(null, Typeface.BOLD);
} else {
binding.stickersButton.setBackgroundColor(0);
binding.stickersButton.setTypeface(null, Typeface.NORMAL);
}
if (binding.gifsview.getVisibility() == VISIBLE) {
binding.gifsButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.gifsButton.setTypeface(null, Typeface.BOLD);
} else {
binding.gifsButton.setBackgroundColor(0);
binding.gifsButton.setTypeface(null, Typeface.NORMAL);
}
}
};
private final OnClickListener mstickersButtonListener = new OnClickListener() {
@Override
public void onClick(View v) {
binding.emojiPicker.setVisibility(GONE);
binding.stickersview.setVisibility(VISIBLE);
binding.gifsview.setVisibility(GONE);
backPressedLeaveEmojiPicker.setEnabled(true);
binding.textinput.requestFocus();
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isEmpty(dirStickers.toPath())) {
Toast.makeText(activity, R.string.update_default_stickers, Toast.LENGTH_LONG).show();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
if (binding.emojiPicker.getVisibility() == VISIBLE) {
binding.emojisButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.emojisButton.setTypeface(null, Typeface.BOLD);
} else {
binding.emojisButton.setBackgroundColor(0);
binding.emojisButton.setTypeface(null, Typeface.NORMAL);
}
if (binding.stickersview.getVisibility() == VISIBLE) {
binding.stickersButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.stickersButton.setTypeface(null, Typeface.BOLD);
} else {
binding.stickersButton.setBackgroundColor(0);
binding.stickersButton.setTypeface(null, Typeface.NORMAL);
}
if (binding.gifsview.getVisibility() == VISIBLE) {
binding.gifsButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.gifsButton.setTypeface(null, Typeface.BOLD);
} else {
binding.gifsButton.setBackgroundColor(0);
binding.gifsButton.setTypeface(null, Typeface.NORMAL);
}
}
};
public boolean isEmpty(Path path) throws IOException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (Files.isDirectory(path)) {
try (Stream<Path> entries = Files.list(path)) {
return !entries.findFirst().isPresent();
}
}
}
return false;
}
private final OnClickListener mgifsButtonListener = new OnClickListener() {
@Override
public void onClick(View v) {
binding.emojiPicker.setVisibility(GONE);
binding.stickersview.setVisibility(GONE);
binding.gifsview.setVisibility(VISIBLE);
backPressedLeaveEmojiPicker.setEnabled(true);
binding.textinput.requestFocus();
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isEmpty(dirGifs.toPath())) {
Toast.makeText(activity, R.string.copy_GIFs_to_GIFs_folder, Toast.LENGTH_LONG).show();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
if (binding.emojiPicker.getVisibility() == VISIBLE) {
binding.emojisButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.emojisButton.setTypeface(null, Typeface.BOLD);
} else {
binding.emojisButton.setBackgroundColor(0);
binding.emojisButton.setTypeface(null, Typeface.NORMAL);
}
if (binding.stickersview.getVisibility() == VISIBLE) {
binding.stickersButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.stickersButton.setTypeface(null, Typeface.BOLD);
} else {
binding.stickersButton.setBackgroundColor(0);
binding.stickersButton.setTypeface(null, Typeface.NORMAL);
}
if (binding.gifsview.getVisibility() == VISIBLE) {
binding.gifsButton.setBackground(ContextCompat.getDrawable(activity, R.drawable.selector_bubble));
binding.gifsButton.setTypeface(null, Typeface.BOLD);
} else {
binding.gifsButton.setBackgroundColor(0);
binding.gifsButton.setTypeface(null, Typeface.NORMAL);
}
}
};
private final OnClickListener mkeyboardButtonListener = new OnClickListener() {
@Override
public void onClick(View v) {
if (binding.keyboardButton.getVisibility() == VISIBLE) {
binding.keyboardButton.setVisibility(GONE);
binding.emojiButton.setVisibility(VISIBLE);
InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
binding.textinput.requestFocus();
inputMethodManager.showSoftInput(binding.textinput, InputMethodManager.SHOW_IMPLICIT);
}
}
}
};
private final OnBackPressedCallback backPressedLeaveEmojiPicker = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
if (binding.emojisStickerLayout.getHeight() > 100) {
LinearLayout emojipickerview = binding.emojisStickerLayout;
ViewGroup.LayoutParams params = emojipickerview.getLayoutParams();
params.height = 0;
emojipickerview.setLayoutParams(params);
binding.keyboardButton.setVisibility(GONE);
binding.emojiButton.setVisibility(VISIBLE);
}
this.setEnabled(false);
refresh();
}
};
public void LoadStickers() {
final Pattern lastColonPattern = Pattern.compile("");
binding.stickersview.setOnItemClickListener((parent, view, position, id) -> {
EmojiSearchOld.EmojiSearchAdapter adapter = ((EmojiSearchOld.EmojiSearchAdapter) binding.stickersview.getAdapter());
Editable toInsert = adapter.getItem(position).toInsert();
toInsert.append(" ");
Editable s = binding.textinput.getText();
Matcher lastColonMatcher = lastColonPattern.matcher(s);
int lastColon = 0;
while(lastColonMatcher.find()) lastColon = lastColonMatcher.end();
if (lastColon >= 0) {
int start = binding.textinput.getSelectionStart(); //this is to get the the cursor position
binding.textinput.getText().insert(start, toInsert); //this will get the text and insert the emoji into the current position
}
});
setupEmojiSearch();
}
public void LoadGifs() {
if (!hasStoragePermission(activity)) return;
// Load and show GIFs
if (!dirGifs.exists()) {
dirGifs.mkdir();
}
if (dirGifs.listFiles() != null) {
if (dirGifs.isDirectory() && dirGifs.listFiles() != null) {
files = dirGifs.listFiles();
filesPaths = new String[files.length];
filesNames = new String[files.length];
for (int i = 0; i < files.length; i++) {
filesPaths[i] = files[i].getAbsolutePath();
filesNames[i] = files[i].getName();
}
}
}
de.monocles.chat.GridView GifsGrid = binding.gifsview; // init GridView
// Create an object of CustomAdapter and set Adapter to GirdView
GifsGrid.setAdapter(new GifsAdapter(activity, filesNames, filesPaths));
// implement setOnItemClickListener event on GridView
GifsGrid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (activity == null) return;
String filePath = filesPaths[position];
mediaPreviewAdapter.addMediaPreviews(Attachment.of(activity, Uri.fromFile(new File(filePath)), Attachment.Type.IMAGE));
toggleInputMethod();
}
});
GifsGrid.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (activity != null && filesPaths[position] != null) {
File file = new File(filesPaths[position]);
if (file.delete()) {
Toast.makeText(activity, R.string.gif_deleted, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(activity, R.string.failed_to_delete_gif, Toast.LENGTH_LONG).show();
}
}
return true;
}
});
}
}

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?colorControlNormal" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M15.5,9.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
<path android:fillColor="@android:color/white" android:pathData="M8.5,9.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
<path android:fillColor="@android:color/white" android:pathData="M12,18c2.28,0 4.22,-1.66 5,-4H7C7.78,16.34 9.72,18 12,18z"/>
<path android:fillColor="@android:color/white" android:pathData="M11.99,2C6.47,2 2,6.48 2,12c0,5.52 4.47,10 9.99,10C17.52,22 22,17.52 22,12C22,6.48 17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8c0,-4.42 3.58,-8 8,-8s8,3.58 8,8C20,16.42 16.42,20 12,20z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?colorControlNormal" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM240,503L372,371Q384,359 400,359Q416,359 428,371L560,503L692,371Q704,359 720,359Q736,359 748,371L760,383L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,463L240,503ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,496L720,456L588,588Q576,600 560,600Q544,600 532,588L400,456L268,588Q256,600 240,600Q224,600 212,588L200,576L200,760Q200,760 200,760Q200,760 200,760ZM200,760L200,760Q200,760 200,760Q200,760 200,760L200,496L200,576L200,463L200,383L200,200Q200,200 200,200Q200,200 200,200L200,200Q200,200 200,200Q200,200 200,200L200,463L200,463L200,576L200,576L200,760Q200,760 200,760Q200,760 200,760Z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?colorControlNormal" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M160,760Q127,760 103.5,736.5Q80,713 80,680L80,280Q80,247 103.5,223.5Q127,200 160,200L800,200Q833,200 856.5,223.5Q880,247 880,280L880,680Q880,713 856.5,736.5Q833,760 800,760L160,760ZM160,680L800,680Q800,680 800,680Q800,680 800,680L800,280Q800,280 800,280Q800,280 800,280L160,280Q160,280 160,280Q160,280 160,280L160,680Q160,680 160,680Q160,680 160,680ZM360,640L600,640Q617,640 628.5,628.5Q640,617 640,600Q640,583 628.5,571.5Q617,560 600,560L360,560Q343,560 331.5,571.5Q320,583 320,600Q320,617 331.5,628.5Q343,640 360,640ZM160,680Q160,680 160,680Q160,680 160,680L160,280Q160,280 160,280Q160,280 160,280L160,280Q160,280 160,280Q160,280 160,280L160,680Q160,680 160,680Q160,680 160,680ZM240,400Q257,400 268.5,388.5Q280,377 280,360Q280,343 268.5,331.5Q257,320 240,320Q223,320 211.5,331.5Q200,343 200,360Q200,377 211.5,388.5Q223,400 240,400ZM360,400Q377,400 388.5,388.5Q400,377 400,360Q400,343 388.5,331.5Q377,320 360,320Q343,320 331.5,331.5Q320,343 320,360Q320,377 331.5,388.5Q343,400 360,400ZM480,400Q497,400 508.5,388.5Q520,377 520,360Q520,343 508.5,331.5Q497,320 480,320Q463,320 451.5,331.5Q440,343 440,360Q440,377 451.5,388.5Q463,400 480,400ZM600,400Q617,400 628.5,388.5Q640,377 640,360Q640,343 628.5,331.5Q617,320 600,320Q583,320 571.5,331.5Q560,343 560,360Q560,377 571.5,388.5Q583,400 600,400ZM720,400Q737,400 748.5,388.5Q760,377 760,360Q760,343 748.5,331.5Q737,320 720,320Q703,320 691.5,331.5Q680,343 680,360Q680,377 691.5,388.5Q703,400 720,400ZM240,520Q257,520 268.5,508.5Q280,497 280,480Q280,463 268.5,451.5Q257,440 240,440Q223,440 211.5,451.5Q200,463 200,480Q200,497 211.5,508.5Q223,520 240,520ZM360,520Q377,520 388.5,508.5Q400,497 400,480Q400,463 388.5,451.5Q377,440 360,440Q343,440 331.5,451.5Q320,463 320,480Q320,497 331.5,508.5Q343,520 360,520ZM480,520Q497,520 508.5,508.5Q520,497 520,480Q520,463 508.5,451.5Q497,440 480,440Q463,440 451.5,451.5Q440,463 440,480Q440,497 451.5,508.5Q463,520 480,520ZM600,520Q617,520 628.5,508.5Q640,497 640,480Q640,463 628.5,451.5Q617,440 600,440Q583,440 571.5,451.5Q560,463 560,480Q560,497 571.5,508.5Q583,520 600,520ZM720,520Q737,520 748.5,508.5Q760,497 760,480Q760,463 748.5,451.5Q737,440 720,440Q703,440 691.5,451.5Q680,463 680,480Q680,497 691.5,508.5Q703,520 720,520Z"/>
</vector>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners
android:topLeftRadius="30dp"
android:topRightRadius="30dp"
android:bottomRightRadius="30dp"
android:bottomLeftRadius="30dp" />
<padding
android:bottom="2dp"
android:left="10dp"
android:right="10dp"
android:top="2dp" />
<solid android:color="?attr/colorAccent" />
</shape>

View file

@ -18,7 +18,7 @@
</com.google.android.material.appbar.AppBarLayout>
<androidx.emoji2.emojipicker.EmojiPickerView
android:id="@+id/emoji_picker"
android:id="@+id/reaction_picker"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="1dp"
android:orientation="vertical">
<ImageView
android:id="@+id/grid_item"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="4dp"
android:scaleType="fitCenter"
android:layout_gravity="center_horizontal" />
</LinearLayout>

View file

@ -3,8 +3,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/conversations_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent" >
<ImageView
android:id="@+id/background_image"
@ -215,12 +216,39 @@
</androidx.recyclerview.widget.RecyclerView>
<ImageButton
android:id="@+id/keyboardButton"
android:layout_width="38dp"
android:layout_height="32dp"
android:layout_marginStart="-6dp"
android:layout_alignParentStart="true"
android:layout_alignBottom="@+id/textinput"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/show_keyboard"
android:src="@drawable/rounded_keyboard_24"
android:visibility="gone" />
<ImageButton
android:id="@+id/emojiButton"
android:layout_width="38dp"
android:layout_height="32dp"
android:layout_marginStart="-6dp"
android:layout_toEndOf="@+id/keyboardButton"
android:layout_alignBottom="@+id/textinput"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/choose_emoji"
android:src="@drawable/outline_emoji_emotions_24"
android:visibility="visible" />
<eu.siacs.conversations.ui.widget.EditMessage
android:id="@+id/textinput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:ems="10"
android:layout_toEndOf="@+id/emojiButton"
android:imeOptions="flagNoExtractUi|actionSend"
android:inputType="textShortMessage|textMultiLine|textCapSentences"
android:maxLines="8"
@ -320,6 +348,94 @@
android:layout_centerVertical="true"
android:text="@string/request_to_speak" />
</RelativeLayout>
<LinearLayout
android:id="@+id/emojis_sticker_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
android:visibility="visible"
android:animateLayoutChanges="true" >
<androidx.emoji2.emojipicker.EmojiPickerView
android:id="@+id/emoji_picker"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:visibility="visible" />
<de.monocles.chat.GridView
android:id="@+id/stickersview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="4dp"
android:divider="@android:color/transparent"
android:numColumns="6"
android:dividerHeight="0dp"
android:layout_weight="1"
android:visibility="gone" />
<de.monocles.chat.GridView
android:id="@+id/gifsview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="4dp"
android:divider="@android:color/transparent"
android:numColumns="4"
android:dividerHeight="0dp"
android:layout_weight="1"
android:visibility="gone" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="62dp"
android:layout_weight="1"
android:gravity="bottom|center_horizontal"
android:orientation="horizontal"
android:animateLayoutChanges="true" >
<TextView
android:id="@+id/emojis_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="bottom|center_horizontal"
android:text="@string/emojis"
android:textSize="16sp" />
<View
android:layout_width="20dp"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/stickers_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="bottom|center_horizontal"
android:text="@string/stickers"
android:textSize="16sp" />
<View
android:layout_width="20dp"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/gifs_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="bottom|center_horizontal"
android:text="@string/gifs"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton

View file

@ -1168,4 +1168,13 @@
<string name="related_chats">Related chats</string>
<string name="add_reaction_title">Add reaction</string>
<string name="more_reactions">More reactions</string>
<string name="choose_emoji">Choose emojis</string>
<string name="show_keyboard">Show keyboard</string>
<string name="emojis">Emojis</string>
<string name="stickers">Stickers</string>
<string name="gifs">GIFs</string>
<string name="update_default_stickers">Update default stickers</string>
<string name="copy_GIFs_to_GIFs_folder">Copy GIFs to GIFs folder</string>
<string name="gif_deleted">GIF deleted</string>
<string name="failed_to_delete_gif">Failed to delete GIF</string>
</resources>

View file

@ -0,0 +1,237 @@
package de.monocles.chat;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import androidx.databinding.DataBindingUtil;
import com.google.common.collect.Lists;
import com.google.common.io.CharStreams;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.Comparable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.TreeSet;
import me.xdrop.fuzzywuzzy.FuzzySearch;
import me.xdrop.fuzzywuzzy.model.BoundExtractedResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.EmojiSearchRowBinding;
import eu.siacs.conversations.utils.ReplacingSerialSingleThreadExecutor;
public class EmojiSearchOld {
protected final Set<Emoji> emoji = new TreeSet<>();
public EmojiSearchOld(Context context) {
/* TODO: No emoji search needed since there already is an emoji keyboard
try {
final JSONArray data = new JSONArray(CharStreams.toString(new InputStreamReader(context.getResources().openRawResource(R.raw.emoji), "UTF-8")));
for (int i = 0; i < data.length(); i++) {
emoji.add(new Emoji(data.getJSONObject(i)));
}
} catch (final JSONException | IOException e) {
throw new IllegalStateException("emoji.json invalid: " + e);
}
*/
}
public synchronized void addEmoji(final Emoji one) {
emoji.add(one);
}
public synchronized List<Emoji> find(final String q) {
final ResultPQ pq = new ResultPQ();
for (Emoji e : emoji) {
if (e.emoticonMatch(q)) {
pq.addTopK(e, 999999, 999);
}
int shortcodeScore = e.shortcodes.isEmpty() ? 0 : Collections.max(Lists.transform(e.shortcodes, (shortcode) -> FuzzySearch.ratio(q, shortcode)));
int tagScore = e.tags.isEmpty() ? 0 : Collections.max(Lists.transform(e.tags, (tag) -> FuzzySearch.ratio(q, tag))) - 2;
pq.addTopK(e, Math.max(shortcodeScore, tagScore), 999);
}
for (BoundExtractedResult<Emoji> r : new ArrayList<>(pq)) {
for (Emoji e : emoji) {
if (e.shortcodeMatch(r.getReferent().uniquePart())) {
// hack see https://stackoverflow.com/questions/76880072/imagespan-with-emojicompat
e.shortcodes.clear();
e.shortcodes.addAll(r.getReferent().shortcodes);
pq.addTopK(e, r.getScore() - 1, 999);
}
}
}
List<Emoji> result = new ArrayList<>();
for (int i = 0; i < 999; i++) {
BoundExtractedResult<Emoji> e = pq.poll();
if (e != null) result.add(e.getReferent());
}
Collections.reverse(result);
return result;
}
public EmojiSearchAdapter makeAdapter(Activity context) {
return new EmojiSearchAdapter(context);
}
public static class ResultPQ extends PriorityQueue<BoundExtractedResult<Emoji>> {
public void addTopK(Emoji e, int score, int k) {
BoundExtractedResult r = new BoundExtractedResult(e, null, score, 0);
if (size() < k) {
add(r);
} else if (r.compareTo(peek()) > 0) {
poll();
add(r);
}
}
}
public static class Emoji implements Comparable<Emoji> {
protected final String unicode;
protected final int order;
protected final List<String> tags = new ArrayList<>();
protected final List<String> emoticon = new ArrayList<>();
protected final List<String> shortcodes = new ArrayList<>();
public Emoji(final String unicode, final int order) {
this.unicode = unicode;
this.order = order;
}
public Emoji(JSONObject o) throws JSONException {
unicode = o.getString("unicode");
order = o.getInt("order");
final JSONArray rawTags = o.getJSONArray("tags");
for (int i = 0; i < rawTags.length(); i++) {
tags.add(rawTags.getString(i));
}
final JSONArray rawEmoticon = o.getJSONArray("emoticon");
for (int i = 0; i < rawEmoticon.length(); i++) {
emoticon.add(rawEmoticon.getString(i));
}
final JSONArray rawShortcodes = o.getJSONArray("shortcodes");
for (int i = 0; i < rawShortcodes.length(); i++) {
shortcodes.add(rawShortcodes.getString(i));
}
}
public boolean emoticonMatch(final String q) {
for (final String emote : emoticon) {
if (emote.equals(q) || emote.equals(":" + q)) return true;
}
return false;
}
public boolean shortcodeMatch(final String q) {
for (final String shortcode : shortcodes) {
if (shortcode.equals(q)) return true;
}
return false;
}
public SpannableStringBuilder toInsert() {
return new SpannableStringBuilder(unicode);
}
public String uniquePart() {
return unicode;
}
@Override
public int compareTo(Emoji o) {
if (equals(o)) return 0;
if (order == o.order) return uniquePart().compareTo(o.uniquePart());
return order - o.order;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Emoji)) return false;
return uniquePart().equals(((Emoji) o).uniquePart());
}
}
public static class CustomEmoji extends Emoji {
protected final String source;
protected final Drawable icon;
public CustomEmoji(final String shortcode, final String source, final Drawable icon, final String tag) {
super(null, 999);
shortcodes.add(shortcode);
if (tag != null) tags.add(tag);
this.source = source;
this.icon = icon;
if (icon == null) {
throw new IllegalArgumentException("icon must not be null");
}
}
public SpannableStringBuilder toInsert() {
SpannableStringBuilder builder = new SpannableStringBuilder(":" + shortcodes.get(0) + ":");
builder.setSpan(new InlineImageSpan(icon, source), 0, builder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return builder;
}
@Override
public String uniquePart() {
return source;
}
}
public class EmojiSearchAdapter extends ArrayAdapter<Emoji> {
ReplacingSerialSingleThreadExecutor executor = new ReplacingSerialSingleThreadExecutor("EmojiSearchAdapter");
public EmojiSearchAdapter(Activity context) {
super(context, 0);
}
@Override
public View getView(int position, View view, ViewGroup parent) {
EmojiSearchRowBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.emoji_search_row, parent, false);
if (getItem(position) instanceof CustomEmoji) {
binding.nonunicode.setText(getItem(position).toInsert());
binding.nonunicode.setVisibility(View.VISIBLE);
binding.unicode.setVisibility(View.GONE);
} else {
binding.unicode.setText(getItem(position).toInsert());
binding.unicode.setVisibility(View.VISIBLE);
binding.nonunicode.setVisibility(View.GONE);
}
binding.shortcode.setText(getItem(position).shortcodes.get(0));
return binding.getRoot();
}
public void search(final String q) {
executor.execute(() -> {
final List<Emoji> results = find(q);
((Activity) getContext()).runOnUiThread(() -> {
clear();
addAll(results);
notifyDataSetChanged();
});
});
}
}
}

View file

@ -0,0 +1,58 @@
package de.monocles.chat;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import eu.siacs.conversations.R;
public class GifsAdapter extends BaseAdapter {
private Context ctx;
private final String[] filesNames;
private final String[] filesPaths;
public GifsAdapter(Context ctx, String[] filesNames, String[] filesPaths) {
this.ctx = ctx;
this.filesNames = filesNames;
this.filesPaths = filesPaths;
}
@Override
public int getCount() {
if (filesNames != null) {
return filesNames.length;
} else {
return 0;
}
}
@Override
public Object getItem(int pos) {
return pos;
}
@Override
public long getItemId(int pos) {
return pos;
}
public View getView(int position, View convertView, ViewGroup parent) {
View v;
if (convertView == null) { // if it's not recycled, initialize some attributes
LayoutInflater inflater = (LayoutInflater) ctx.getSystemService( Context.LAYOUT_INFLATER_SERVICE );
v = inflater.inflate(R.layout.activity_gridview_gifs, parent, false);
} else {
v = (View) convertView;
}
ImageView image = (ImageView)v.findViewById(R.id.grid_item);
Glide.with(ctx).load(filesPaths[position]).into(image);
return v;
}
}

View file

@ -0,0 +1,69 @@
package de.monocles.chat;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
public class KeyboardHeightProvider extends PopupWindow {
LinearLayout popupView;
ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener;
public KeyboardHeightProvider(Context context, WindowManager windowManager, View parentView, KeyboardHeightListener listener) {
super(context);
popupView = new LinearLayout(context);
popupView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
globalLayoutListener = () -> {
DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(metrics);
Rect rect = new Rect();
popupView.getWindowVisibleDisplayFrame(rect);
int keyboardHeight = metrics.heightPixels - (rect.bottom - rect.top);
int resourceID = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceID > 0) {
keyboardHeight -= context.getResources().getDimensionPixelSize(resourceID);
}
if (keyboardHeight < 100) {
keyboardHeight = 0;
}
boolean isLandscape = metrics.widthPixels > metrics.heightPixels;
boolean keyboardOpen = keyboardHeight > 0;
if (listener != null) {
listener.onKeyboardHeightChanged(keyboardHeight, keyboardOpen, isLandscape);
}
};
popupView.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);
setContentView(popupView);
setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
setWidth(0);
setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
setBackgroundDrawable(new ColorDrawable(0));
parentView.post(() -> showAtLocation(parentView, Gravity.NO_GRAVITY, 0, 0));
}
@Override
public void dismiss() {
if (globalLayoutListener != null) {
popupView.getViewTreeObserver().removeOnGlobalLayoutListener(globalLayoutListener);
globalLayoutListener = null;
}
super.dismiss();
}
public interface KeyboardHeightListener {
void onKeyboardHeightChanged(int keyboardHeight, boolean keyboardOpen, boolean isLandscape);
}
}