Commit 428501cf authored by akwizgran's avatar akwizgran

Use vanniktech emoji library.

parent d8b04edc
package org.briarproject.bramble.api.settings;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
......@@ -11,6 +12,11 @@ public interface SettingsManager {
*/
Settings getSettings(String namespace) throws DbException;
/**
* Returns all settings in the given namespace.
*/
Settings getSettings(Transaction txn, String namespace) throws DbException;
/**
* Merges the given settings with any existing settings in the given
* namespace.
......
......@@ -34,6 +34,12 @@ class SettingsManagerImpl implements SettingsManager {
return s;
}
@Override
public Settings getSettings(Transaction txn, String namespace)
throws DbException {
return db.getSettings(txn, namespace);
}
@Override
public void mergeSettings(Settings s, String namespace) throws DbException {
Transaction txn = db.startTransaction(false);
......
......@@ -31,9 +31,8 @@ dependencies {
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.google.zxing:core:3.3.0'
implementation 'com.jpardogo.materialtabstrip:library:1.1.0'
implementation 'com.github.bumptech.glide:glide:3.8.0'
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.8.0'
implementation 'com.vanniktech:emoji-google:0.5.1'
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
......@@ -80,7 +79,7 @@ android {
buildToolsVersion '27.0.3'
defaultConfig {
minSdkVersion 14
minSdkVersion 15
targetSdkVersion 26
versionCode 10013
versionName "1.0.13"
......
......@@ -27,9 +27,4 @@
-dontnote org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout
# Emoji
-keep class org.thoughtcrime.securesms.**
-keep class com.astuetz.PagerSlidingTabStrip$OnTabReselectedListener
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
-keep class com.vanniktech.emoji.**
......@@ -28,6 +28,7 @@ import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.reporting.BriarReportSender;
import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager;
......@@ -49,8 +50,6 @@ import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
import org.briarproject.briar.api.test.TestDataCreator;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel;
import java.util.concurrent.Executor;
......@@ -161,12 +160,10 @@ public interface AndroidComponent
void inject(BriarReportSender briarReportSender);
void inject(EmojiProvider emojiProvider);
void inject(RecentEmojiPageModel recentEmojiPageModel);
void inject(NotificationCleanupService notificationCleanupService);
void inject(TextInputView textInputView);
// Eager singleton load
void inject(AppModule.EagerSingletons init);
}
......@@ -5,6 +5,8 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.os.StrictMode;
import com.vanniktech.emoji.RecentEmoji;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
......@@ -65,6 +67,8 @@ public class AppModule {
NetworkUsageLogger networkUsageLogger;
@Inject
DozeWatchdog dozeWatchdog;
@Inject
RecentEmoji recentEmoji;
}
private final Application application;
......@@ -211,4 +215,11 @@ public class AppModule {
return lockManager;
}
@Provides
@Singleton
RecentEmoji provideRecentEmoji(LifecycleManager lifecycleManager,
RecentEmojiImpl recentEmoji) {
lifecycleManager.registerClient(recentEmoji);
return recentEmoji;
}
}
......@@ -9,6 +9,9 @@ import android.os.StrictMode.ThreadPolicy;
import android.os.StrictMode.VmPolicy;
import android.preference.PreferenceManager;
import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.google.GoogleEmojiProvider;
import org.acra.ACRA;
import org.acra.ReportingInteractionMode;
import org.acra.annotation.ReportsCrashes;
......@@ -89,6 +92,7 @@ public class BriarApplicationImpl extends Application
Localizer.getInstance().setLocale(base));
setTheme(base, prefs);
ACRA.init(this);
EmojiManager.install(new GoogleEmojiProvider());
}
@Override
......
package org.thoughtcrime.securesms.components.emoji;
package org.briarproject.briar.android;
import android.content.Context;
import android.support.annotation.DrawableRes;
import com.vanniktech.emoji.EmojiRange;
import com.vanniktech.emoji.EmojiUtils;
import com.vanniktech.emoji.RecentEmoji;
import com.vanniktech.emoji.emoji.Emoji;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.sync.Client;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarApplication;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
......@@ -28,85 +31,72 @@ import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class RecentEmojiPageModel implements EmojiPageModel {
class RecentEmojiImpl implements RecentEmoji, Client {
private static final Logger LOG =
Logger.getLogger(RecentEmojiPageModel.class.getName());
Logger.getLogger(RecentEmojiImpl.class.getName());
private static final String EMOJI_LRU_PREFERENCE = "pref_emoji_recent2";
private static final int EMOJI_LRU_SIZE = 50;
private final LinkedHashSet<String> recentlyUsed; // UI thread
// UI thread
private final LinkedHashSet<Emoji> recentlyUsed = new LinkedHashSet<>();
@Inject
SettingsManager settingsManager;
private final Executor dbExecutor;
private final AndroidExecutor androidExecutor;
private final SettingsManager settingsManager;
@Inject
@DatabaseExecutor
Executor dbExecutor;
RecentEmojiPageModel(Context context) {
BriarApplication app =
(BriarApplication) context.getApplicationContext();
app.getApplicationComponent().inject(this);
recentlyUsed = getPersistedCache();
}
private LinkedHashSet<String> getPersistedCache() {
String serialized;
try {
// FIXME: Don't make DB calls on the UI thread
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
serialized = settings.get(EMOJI_LRU_PREFERENCE);
} catch (DbException e) {
logException(LOG, WARNING, e);
serialized = null;
}
return deserialize(serialized);
RecentEmojiImpl(@DatabaseExecutor Executor dbExecutor,
AndroidExecutor androidExecutor, SettingsManager settingsManager) {
this.dbExecutor = dbExecutor;
this.androidExecutor = androidExecutor;
this.settingsManager = settingsManager;
}
@DrawableRes
@Override
public int getIcon() {
return R.drawable.ic_emoji_recent;
public Collection<Emoji> getRecentEmojis() {
return new ArrayList<>(recentlyUsed);
}
@Override
public String[] getEmoji() {
return toReversePrimitiveArray(recentlyUsed);
}
@Override
public boolean hasSpriteMap() {
return false;
}
@Override
public String getSprite() {
return null;
}
void onCodePointSelected(String emoji) {
public void addEmoji(Emoji emoji) {
recentlyUsed.remove(emoji);
recentlyUsed.add(emoji);
if (recentlyUsed.size() > EMOJI_LRU_SIZE) {
Iterator<String> iterator = recentlyUsed.iterator();
Iterator<Emoji> iterator = recentlyUsed.iterator();
iterator.next();
iterator.remove();
}
save(serialize(recentlyUsed));
}
private String serialize(LinkedHashSet<String> emojis) {
return StringUtils.join(emojis, "\t");
@Override
public void persist() {
if (!recentlyUsed.isEmpty()) save(serialize(recentlyUsed));
}
@Override
public void createLocalState(Transaction txn) throws DbException {
Settings settings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
String serialized = settings.get(EMOJI_LRU_PREFERENCE);
if (serialized != null) {
androidExecutor.runOnUiThread(() ->
recentlyUsed.addAll(deserialize(serialized)));
}
}
private LinkedHashSet<String> deserialize(@Nullable String serialized) {
if (serialized == null) return new LinkedHashSet<>();
String[] list = serialized.split("\t");
LinkedHashSet<String> result = new LinkedHashSet<>(list.length);
Collections.addAll(result, list);
private String serialize(Collection<Emoji> emojis) {
Collection<String> strings = new ArrayList<>(emojis.size());
for (Emoji emoji : emojis) strings.add(emoji.getUnicode());
return StringUtils.join(strings, "\t");
}
private Collection<Emoji> deserialize(String serialized) {
Collection<EmojiRange> ranges = EmojiUtils.emojis(serialized);
Collection<Emoji> result = new ArrayList<>(ranges.size());
for (EmojiRange range : ranges) result.add(range.emoji);
return result;
}
......@@ -121,13 +111,4 @@ public class RecentEmojiPageModel implements EmojiPageModel {
}
});
}
private String[] toReversePrimitiveArray(LinkedHashSet<String> emojiSet) {
String[] emojis = new String[emojiSet.size()];
int i = emojiSet.size() - 1;
for (String emoji : emojiSet) {
emojis[i--] = emoji;
}
return emojis;
}
}
......@@ -202,7 +202,6 @@ public class GroupActivity extends
if (!enabled) {
textInput.setVisibility(GONE);
if (textInput.isKeyboardOpen()) textInput.hideSoftKeyboard();
if (textInput.isEmojiDrawerOpen()) textInput.hideEmojiDrawer();
} else {
textInput.setVisibility(VISIBLE);
}
......
......@@ -8,6 +8,8 @@ import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.Button;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import javax.annotation.Nullable;
......@@ -15,6 +17,8 @@ import javax.annotation.Nullable;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@UiThread
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class LargeTextInputView extends TextInputView {
public LargeTextInputView(Context context) {
......@@ -46,30 +50,28 @@ public class LargeTextInputView extends TextInputView {
R.styleable.LargeTextInputView);
String buttonText =
attributes.getString(R.styleable.LargeTextInputView_buttonText);
int maxLines =
attributes
.getInteger(R.styleable.LargeTextInputView_maxLines, 0);
int maxLines = attributes
.getInteger(R.styleable.LargeTextInputView_maxLines, 0);
boolean fillHeight = attributes
.getBoolean(R.styleable.LargeTextInputView_fillHeight,
false);
.getBoolean(R.styleable.LargeTextInputView_fillHeight, false);
attributes.recycle();
if (buttonText != null) setButtonText(buttonText);
if (maxLines > 0) ui.editText.setMaxLines(maxLines);
if (maxLines > 0) editText.setMaxLines(maxLines);
if (fillHeight) {
ViewGroup layout = findViewById(R.id.input_layout);
LayoutParams params = (LayoutParams) layout.getLayoutParams();
params.height = 0;
params.weight = 1;
layout.setLayoutParams(params);
ViewGroup.LayoutParams editParams = ui.editText.getLayoutParams();
ViewGroup.LayoutParams editParams = editText.getLayoutParams();
editParams.height = MATCH_PARENT;
ui.editText.setLayoutParams(editParams);
editText.setLayoutParams(editParams);
}
}
public void setButtonText(String text) {
((Button) ui.sendButton).setText(text);
((Button) sendButton).setText(text);
}
}
......@@ -8,35 +8,47 @@ import android.os.IBinder;
import android.support.annotation.CallSuper;
import android.support.annotation.StringRes;
import android.support.annotation.UiThread;
import android.support.v7.widget.AppCompatImageButton;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import com.vanniktech.emoji.EmojiEditText;
import com.vanniktech.emoji.EmojiPopup;
import com.vanniktech.emoji.RecentEmoji;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarApplication;
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer;
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer.EmojiEventListener;
import org.thoughtcrime.securesms.components.emoji.EmojiEditText;
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.content.Context.INPUT_METHOD_SERVICE;
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
import static android.view.KeyEvent.KEYCODE_BACK;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
@UiThread
public class TextInputView extends KeyboardAwareLinearLayout
implements EmojiEventListener {
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class TextInputView extends KeyboardAwareLinearLayout {
@Inject
RecentEmoji recentEmoji;
@Nullable
TextInputListener listener;
protected final ViewHolder ui;
protected TextInputListener listener;
AppCompatImageButton emojiToggle;
EmojiEditText editText;
EmojiPopup emojiPopup;
View sendButton;
public TextInputView(Context context) {
this(context, null);
......@@ -49,11 +61,12 @@ public class TextInputView extends KeyboardAwareLinearLayout
public TextInputView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
BriarApplication app =
(BriarApplication) context.getApplicationContext();
app.getApplicationComponent().inject(this);
setOrientation(VERTICAL);
setLayoutTransition(new LayoutTransition());
inflateLayout(context);
ui = new ViewHolder();
setUpViews(context, attrs);
}
......@@ -65,89 +78,79 @@ public class TextInputView extends KeyboardAwareLinearLayout
@CallSuper
protected void setUpViews(Context context, @Nullable AttributeSet attrs) {
emojiToggle = findViewById(R.id.emoji_toggle);
editText = findViewById(R.id.input_text);
emojiPopup = EmojiPopup.Builder
.fromRootView(this)
.setRecentEmoji(recentEmoji)
.setOnEmojiPopupShownListener(this::showKeyboardIcon)
.setOnEmojiPopupDismissListener(this::showEmojiIcon)
.build(editText);
sendButton = findViewById(R.id.btn_send);
// get attributes
TypedArray attributes = context.obtainStyledAttributes(attrs,
R.styleable.TextInputView);
String hint = attributes.getString(R.styleable.TextInputView_hint);
attributes.recycle();
if (hint != null) {
ui.editText.setHint(hint);
}
if (hint != null) editText.setHint(hint);
ui.emojiToggle.attach(ui.emojiDrawer);
ui.emojiToggle.setOnClickListener(v -> onEmojiToggleClicked());
ui.editText.setOnClickListener(v -> showSoftKeyboard());
ui.editText.setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KEYCODE_BACK && isEmojiDrawerOpen()) {
hideEmojiDrawer();
return true;
}
emojiToggle.setOnClickListener(v -> emojiPopup.toggle());
editText.setOnClickListener(v -> showSoftKeyboard());
editText.setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KEYCODE_ENTER && event.isCtrlPressed()) {
trySendMessage();
return true;
}
return false;
});
ui.sendButton.setOnClickListener(v -> trySendMessage());
ui.emojiDrawer.setEmojiEventListener(this);
sendButton.setOnClickListener(v -> trySendMessage());
}
private void trySendMessage() {
if (listener != null) {
listener.onSendClick(ui.editText.getText().toString());
}
private void showEmojiIcon() {
emojiToggle.setImageResource(R.drawable.ic_emoji_toggle);
}
@Override
public void setVisibility(int visibility) {
if (visibility == GONE && isKeyboardOpen()) {
onKeyboardClose();
}
super.setVisibility(visibility);
private void showKeyboardIcon() {
emojiToggle.setImageResource(R.drawable.ic_keyboard);
}
@Override
public void onKeyEvent(KeyEvent keyEvent) {
ui.editText.dispatchKeyEvent(keyEvent);
}
@Override
public void onEmojiSelected(String emoji) {
ui.editText.insertEmoji(emoji);
private void trySendMessage() {
if (listener != null) {
listener.onSendClick(editText.getText().toString());
}
}
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
return ui.editText.requestFocus(direction, previouslyFocusedRect);
return editText.requestFocus(direction, previouslyFocusedRect);
}
private void onEmojiToggleClicked() {
if (isEmojiDrawerOpen()) {
showSoftKeyboard();
} else {
showEmojiDrawer();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (emojiPopup.isShowing()) emojiPopup.dismiss();
}
public void setText(String text) {
ui.editText.setText(text);
editText.setText(text);
}
public Editable getText() {
return ui.editText.getText();
return editText.getText();
}
public void setHint(@StringRes int res) {
ui.editText.setHint(res);
editText.setHint(res);
}
public void setSendButtonEnabled(boolean enabled) {
ui.sendButton.setEnabled(enabled);
sendButton.setEnabled(enabled);
}
public void addTextChangedListener(TextWatcher watcher) {
ui.editText.addTextChangedListener(watcher);
editText.addTextChangedListener(watcher);
}
public void setListener(TextInputListener listener) {
......@@ -155,58 +158,16 @@ public class TextInputView extends KeyboardAwareLinearLayout
}
public void showSoftKeyboard() {
if (isKeyboardOpen()) return;
if (ui.emojiDrawer.isShowing()) {
postOnKeyboardOpen(this::hideEmojiDrawer);
}
ui.editText.post(() -> {
ui.editText.requestFocus();
InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(INPUT_METHOD_SERVICE);
imm.showSoftInput(ui.editText, SHOW_IMPLICIT);
});
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).showSoftInput(editText, SHOW_IMPLICIT);
}
public void hideSoftKeyboard() {
IBinder token = ui.editText.getWindowToken();
IBinder token = editText.getWindowToken();
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
}
public void showEmojiDrawer() {
if (isKeyboardOpen()) {
postOnKeyboardClose(() -> ui.emojiDrawer.show(getKeyboardHeight()));
hideSoftKeyboard();
} else {
ui.emojiDrawer.show(getKeyboardHeight());
ui.editText.requestFocus();
}
}
public void hideEmojiDrawer() {
ui.emojiDrawer.hide();
}
public boolean isEmojiDrawerOpen() {
return ui.emojiDrawer.isShowing();
}
protected class ViewHolder {
private final EmojiToggle emojiToggle;
final EmojiEditText editText;
final View sendButton;
final EmojiDrawer emojiDrawer;
private ViewHolder() {
emojiToggle = findViewById(R.id.emoji_toggle);
editText = findViewById(R.id.input_text);
emojiDrawer = findViewById(R.id.emoji_drawer);
sendButton = findViewById(R.id.btn_send);
}
}
public interface TextInputListener {
void onSendClick(String text);
}
......