mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
[android] support adding image attachments to private messages
This commit is contained in:
@@ -105,6 +105,7 @@ dependencies {
|
||||
implementation "com.android.support:cardview-v7:$supportVersion"
|
||||
implementation "com.android.support:support-annotations:$supportVersion"
|
||||
implementation "com.android.support:exifinterface:$supportVersion"
|
||||
implementation "com.android.support:palette-v7:$supportVersion"
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
implementation "android.arch.lifecycle:extensions:1.1.1"
|
||||
|
||||
|
||||
@@ -30,4 +30,10 @@ public interface TestingConstants {
|
||||
long EXPIRY_DATE = IS_DEBUG_BUILD || IS_BETA_BUILD ?
|
||||
BuildConfig.BuildTimestamp + 90 * 24 * 60 * 60 * 1000L :
|
||||
Long.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* Feature flag for enabling image attachments.
|
||||
*/
|
||||
boolean FEATURE_FLAG_IMAGE_ATTACHMENTS = IS_DEBUG_BUILD;
|
||||
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@ public interface RequestCodes {
|
||||
int REQUEST_BLUETOOTH_DISCOVERABLE = 10;
|
||||
int REQUEST_UNLOCK = 11;
|
||||
int REQUEST_KEYGUARD_UNLOCK = 12;
|
||||
int REQUEST_ATTACH_IMAGE = 13;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -20,6 +21,8 @@ import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.view.TextInputView;
|
||||
import org.briarproject.briar.android.view.TextInputView.TextInputListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -118,10 +121,9 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendClick(String text) {
|
||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
||||
ui.input.hideSoftKeyboard();
|
||||
String comment = getComment();
|
||||
feedController.repeatPost(item, comment,
|
||||
feedController.repeatPost(item, text,
|
||||
new UiExceptionHandler<DbException>(this) {
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
@@ -131,12 +133,6 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getComment() {
|
||||
if (ui.input.getText().length() == 0) return null;
|
||||
return ui.input.getText().toString();
|
||||
}
|
||||
|
||||
private void showProgressBar() {
|
||||
ui.progressBar.setVisibility(VISIBLE);
|
||||
ui.input.setVisibility(GONE);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
@@ -14,8 +16,9 @@ import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
@@ -27,6 +30,7 @@ import org.briarproject.briar.api.blog.BlogPost;
|
||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -35,8 +39,12 @@ import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
import static org.briarproject.bramble.util.StringUtils.truncateUtf8;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class WriteBlogPostActivity extends BriarActivity
|
||||
implements OnEditorActionListener, TextInputListener {
|
||||
|
||||
@@ -58,9 +66,8 @@ public class WriteBlogPostActivity extends BriarActivity
|
||||
@Inject
|
||||
volatile BlogManager blogManager;
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
Intent i = getIntent();
|
||||
@@ -128,17 +135,19 @@ public class WriteBlogPostActivity extends BriarActivity
|
||||
}
|
||||
|
||||
private void enableOrDisablePublishButton() {
|
||||
input.setSendButtonEnabled(input.getText().length() > 0);
|
||||
input.setSendButtonEnabled(!input.isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendClick(String text) {
|
||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
||||
if (isNullOrEmpty(text)) return;
|
||||
|
||||
// hide publish button, show progress bar
|
||||
input.hideSoftKeyboard();
|
||||
input.setVisibility(GONE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
|
||||
text = StringUtils.truncateUtf8(text, MAX_BLOG_POST_TEXT_LENGTH);
|
||||
text = truncateUtf8(text, MAX_BLOG_POST_TEXT_LENGTH);
|
||||
storePost(text);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.arch.lifecycle.ViewModelProvider;
|
||||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
@@ -52,7 +53,6 @@ import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
@@ -64,6 +64,7 @@ import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.android.view.TextInputView;
|
||||
import org.briarproject.briar.android.view.TextInputView.AttachImageListener;
|
||||
import org.briarproject.briar.android.view.TextInputView.TextInputListener;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||
@@ -106,6 +107,7 @@ import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAn
|
||||
import static android.support.v4.view.ViewCompat.setTransitionName;
|
||||
import static android.support.v7.util.SortedList.INVALID_POSITION;
|
||||
import static android.view.Gravity.RIGHT;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.sort;
|
||||
@@ -115,6 +117,10 @@ import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
import static org.briarproject.bramble.util.StringUtils.truncateUtf8;
|
||||
import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_IMAGE_ATTACHMENTS;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ATTACH_IMAGE;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
|
||||
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT;
|
||||
import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
|
||||
@@ -131,7 +137,7 @@ import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.S
|
||||
@ParametersNotNullByDefault
|
||||
public class ConversationActivity extends BriarActivity
|
||||
implements EventListener, ConversationListener, TextInputListener,
|
||||
TextCache, AttachmentCache {
|
||||
TextCache, AttachmentCache, AttachImageListener {
|
||||
|
||||
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
||||
|
||||
@@ -251,6 +257,9 @@ public class ConversationActivity extends BriarActivity
|
||||
|
||||
textInputView = findViewById(R.id.text_input_container);
|
||||
textInputView.setListener(this);
|
||||
if (FEATURE_FLAG_IMAGE_ATTACHMENTS) {
|
||||
textInputView.setAttachImageListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -267,6 +276,8 @@ public class ConversationActivity extends BriarActivity
|
||||
Snackbar.LENGTH_SHORT);
|
||||
snackbar.getView().setBackgroundResource(R.color.briar_primary);
|
||||
snackbar.show();
|
||||
} else if (request == REQUEST_ATTACH_IMAGE && result == RESULT_OK) {
|
||||
textInputView.onImageReceived(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,9 +584,19 @@ public class ConversationActivity extends BriarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendClick(String text) {
|
||||
if (text.isEmpty()) return;
|
||||
text = StringUtils.truncateUtf8(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
||||
public void onAttachImage(Intent intent) {
|
||||
startActivityForResult(intent, REQUEST_ATTACH_IMAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
||||
if (!imageUris.isEmpty()) {
|
||||
Toast.makeText(this, "Not yet implemented.", LENGTH_LONG).show();
|
||||
textInputView.setText("");
|
||||
return;
|
||||
}
|
||||
if (isNullOrEmpty(text)) return;
|
||||
text = truncateUtf8(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
|
||||
if (messagingGroupId == null) loadGroupId(text, timestamp);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.briarproject.briar.android.introduction;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -17,7 +17,8 @@ import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
@@ -25,6 +26,7 @@ import org.briarproject.briar.android.view.TextInputView;
|
||||
import org.briarproject.briar.android.view.TextInputView.TextInputListener;
|
||||
import org.briarproject.briar.api.introduction.IntroductionManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -38,9 +40,12 @@ import static android.view.View.VISIBLE;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.StringUtils.truncateUtf8;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class IntroductionMessageFragment extends BaseFragment
|
||||
implements TextInputListener {
|
||||
|
||||
@@ -84,8 +89,9 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
// change toolbar text
|
||||
ActionBar actionBar = introductionActivity.getSupportActionBar();
|
||||
@@ -184,14 +190,14 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendClick(@NonNull String text) {
|
||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
||||
// disable button to prevent accidental double invitations
|
||||
ui.message.setSendButtonEnabled(false);
|
||||
|
||||
String txt = ui.message.getText().toString();
|
||||
if (txt.isEmpty()) txt = null;
|
||||
else txt = StringUtils.truncateUtf8(txt, MAX_INTRODUCTION_TEXT_LENGTH);
|
||||
makeIntroduction(contact1, contact2, txt);
|
||||
if (text != null) {
|
||||
text = truncateUtf8(text, MAX_INTRODUCTION_TEXT_LENGTH);
|
||||
}
|
||||
makeIntroduction(contact1, contact2, text);
|
||||
|
||||
// don't wait for the introduction to be made before finishing activity
|
||||
introductionActivity.hideSoftKeyboard(ui.message);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.briarproject.briar.android.sharing;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.design.widget.Snackbar;
|
||||
@@ -10,17 +12,23 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.view.LargeTextInputView;
|
||||
import org.briarproject.briar.android.view.TextInputView.TextInputListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static android.support.design.widget.Snackbar.LENGTH_SHORT;
|
||||
import static org.briarproject.bramble.util.StringUtils.truncateUtf8;
|
||||
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
|
||||
import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_TEXT_LENGTH;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public abstract class BaseMessageFragment extends BaseFragment
|
||||
implements TextInputListener {
|
||||
|
||||
@@ -34,8 +42,9 @@ public abstract class BaseMessageFragment extends BaseFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
public View onCreateView(@Nullable LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
// inflate view
|
||||
View v = inflater.inflate(R.layout.fragment_message, container,
|
||||
@@ -76,7 +85,8 @@ public abstract class BaseMessageFragment extends BaseFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendClick(String text) {
|
||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
||||
if (text == null) return;
|
||||
if (utf8IsTooLong(text, listener.getMaximumTextLength())) {
|
||||
Snackbar.make(message, R.string.text_too_long, LENGTH_SHORT).show();
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.StringRes;
|
||||
@@ -33,6 +34,7 @@ import org.briarproject.briar.api.client.NamedGroup;
|
||||
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -348,8 +350,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendClick(String text) {
|
||||
if (text.trim().length() == 0)
|
||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
||||
if (text == null || text.trim().length() == 0)
|
||||
return;
|
||||
if (utf8IsTooLong(text, getMaxTextLength())) {
|
||||
displaySnackbar(R.string.text_too_long);
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v7.graphics.Palette;
|
||||
import android.support.v7.widget.AppCompatImageButton;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.load.DataSource;
|
||||
import com.bumptech.glide.load.engine.GlideException;
|
||||
import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.vanniktech.emoji.EmojiEditText;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||
import org.briarproject.briar.android.view.TextInputView.AttachImageListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static android.content.Intent.ACTION_GET_CONTENT;
|
||||
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
|
||||
import static android.content.Intent.CATEGORY_OPENABLE;
|
||||
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
|
||||
import static android.graphics.Color.BLACK;
|
||||
import static android.graphics.Color.WHITE;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_YES;
|
||||
import static android.support.v7.app.AppCompatDelegate.getDefaultNightMode;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
||||
import static com.bumptech.glide.load.resource.bitmap.DownsampleStrategy.FIT_CENTER;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
class TextInputAttachmentController implements TextWatcher {
|
||||
|
||||
private final EmojiEditText editText;
|
||||
private final View sendButton;
|
||||
private final AppCompatImageButton imageButton;
|
||||
private final ViewGroup imageLayout;
|
||||
private final ImageView imageView;
|
||||
|
||||
private final AttachImageListener listener;
|
||||
|
||||
private String textHint;
|
||||
private List<Uri> imageUris = emptyList();
|
||||
|
||||
public TextInputAttachmentController(View v, EmojiEditText editText,
|
||||
View sendButton, AttachImageListener listener) {
|
||||
|
||||
imageLayout = v.findViewById(R.id.imageLayout);
|
||||
imageView = v.findViewById(R.id.imageView);
|
||||
FloatingActionButton imageCancelButton =
|
||||
v.findViewById(R.id.imageCancelButton);
|
||||
imageButton = v.findViewById(R.id.imageButton);
|
||||
|
||||
this.listener = listener;
|
||||
this.sendButton = sendButton;
|
||||
this.editText = editText;
|
||||
this.textHint = editText.getHint().toString();
|
||||
|
||||
editText.addTextChangedListener(this);
|
||||
imageButton.setOnClickListener(view -> onImageButtonClicked());
|
||||
imageCancelButton.setOnClickListener(view -> afterSendButtonClicked());
|
||||
showImageButton(true);
|
||||
}
|
||||
|
||||
private void onImageButtonClicked() {
|
||||
Intent intent = new Intent(SDK_INT >= 19 ?
|
||||
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
|
||||
intent.addCategory(CATEGORY_OPENABLE);
|
||||
intent.setType("image/*");
|
||||
if (SDK_INT >= 18)
|
||||
intent.putExtra(EXTRA_ALLOW_MULTIPLE, false);
|
||||
listener.onAttachImage(intent);
|
||||
}
|
||||
|
||||
void onImageReceived(@Nullable Intent resultData) {
|
||||
if (resultData == null) return;
|
||||
if (resultData.getData() != null) {
|
||||
imageUris = singletonList(resultData.getData());
|
||||
} else if (SDK_INT >= 18 && resultData.getClipData() != null) {
|
||||
ClipData clipData = resultData.getClipData();
|
||||
imageUris = new ArrayList<>(clipData.getItemCount());
|
||||
for (int i = 0; i < clipData.getItemCount(); i++) {
|
||||
imageUris.add(clipData.getItemAt(i).getUri());
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
showImageButton(false);
|
||||
editText.setHint(R.string.image_caption_hint);
|
||||
imageLayout.setVisibility(VISIBLE);
|
||||
GlideApp.with(imageView)
|
||||
.asBitmap()
|
||||
.load(imageUris.get(0)) // TODO show more than the first
|
||||
.diskCacheStrategy(NONE)
|
||||
.downsample(FIT_CENTER)
|
||||
.addListener(new RequestListener<Bitmap>() {
|
||||
@Override
|
||||
public boolean onLoadFailed(@Nullable GlideException e,
|
||||
Object model, Target<Bitmap> target,
|
||||
boolean isFirstResource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourceReady(Bitmap resource,
|
||||
Object model, Target<Bitmap> target,
|
||||
DataSource dataSource, boolean isFirstResource) {
|
||||
Palette.from(resource).generate(
|
||||
TextInputAttachmentController.this::onPaletteGenerated);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void onPaletteGenerated(@Nullable Palette palette) {
|
||||
int color;
|
||||
if (palette == null) {
|
||||
color = getDefaultNightMode() == MODE_NIGHT_YES ? BLACK : WHITE;
|
||||
} else {
|
||||
color = getDefaultNightMode() == MODE_NIGHT_YES ?
|
||||
palette.getDarkMutedColor(BLACK) :
|
||||
palette.getLightMutedColor(WHITE);
|
||||
}
|
||||
imageView.setBackgroundColor(color);
|
||||
}
|
||||
|
||||
private void showImageButton(boolean showImageButton) {
|
||||
if (showImageButton) {
|
||||
imageButton.setVisibility(VISIBLE);
|
||||
if (SDK_INT <= 15) {
|
||||
sendButton.setVisibility(INVISIBLE);
|
||||
} else {
|
||||
sendButton.clearAnimation();
|
||||
sendButton.animate().alpha(0f).withEndAction(
|
||||
() -> sendButton.setVisibility(INVISIBLE)
|
||||
).start();
|
||||
imageButton.clearAnimation();
|
||||
imageButton.animate().alpha(1f).start();
|
||||
}
|
||||
} else {
|
||||
sendButton.setVisibility(VISIBLE);
|
||||
if (SDK_INT <= 15) {
|
||||
imageButton.setVisibility(INVISIBLE);
|
||||
} else {
|
||||
sendButton.clearAnimation();
|
||||
sendButton.animate().alpha(1f).start();
|
||||
imageButton.clearAnimation();
|
||||
imageButton.animate().alpha(0f).withEndAction(
|
||||
() -> imageButton.setVisibility(INVISIBLE)
|
||||
).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
||||
int after) {
|
||||
// noop
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before,
|
||||
int count) {
|
||||
if (start != 0 || !imageUris.isEmpty()) return;
|
||||
if (s.length() > 0) showImageButton(false);
|
||||
else if (s.length() == 0) showImageButton(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
// noop
|
||||
}
|
||||
|
||||
public List<Uri> getUris() {
|
||||
return imageUris;
|
||||
}
|
||||
|
||||
public void saveHint(String hint) {
|
||||
textHint = hint;
|
||||
}
|
||||
|
||||
void afterSendButtonClicked() {
|
||||
// restore hint
|
||||
editText.setHint(textHint);
|
||||
// hide image layout
|
||||
imageLayout.setVisibility(GONE);
|
||||
// reset image URIs
|
||||
imageUris = emptyList();
|
||||
// show the image button again, so images can get attached
|
||||
showImageButton(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,10 +2,13 @@ package org.briarproject.briar.android.view;
|
||||
|
||||
import android.animation.LayoutTransition;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.widget.AppCompatImageButton;
|
||||
@@ -26,13 +29,16 @@ import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.BriarApplication;
|
||||
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
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_ENTER;
|
||||
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@UiThread
|
||||
@MethodsNotNullByDefault
|
||||
@@ -44,6 +50,8 @@ public class TextInputView extends KeyboardAwareLinearLayout {
|
||||
|
||||
@Nullable
|
||||
TextInputListener listener;
|
||||
@Nullable
|
||||
TextInputAttachmentController attachmentController;
|
||||
|
||||
AppCompatImageButton emojiToggle;
|
||||
EmojiEditText editText;
|
||||
@@ -69,12 +77,12 @@ public class TextInputView extends KeyboardAwareLinearLayout {
|
||||
setOrientation(VERTICAL);
|
||||
setLayoutTransition(new LayoutTransition());
|
||||
inflateLayout(context);
|
||||
setUpViews(context, attrs);
|
||||
if (!isInEditMode()) setUpViews(context, attrs);
|
||||
}
|
||||
|
||||
protected void inflateLayout(Context context) {
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(LAYOUT_INFLATER_SERVICE);
|
||||
LayoutInflater inflater = (LayoutInflater) requireNonNull(
|
||||
context.getSystemService(LAYOUT_INFLATER_SERVICE));
|
||||
inflater.inflate(R.layout.text_input_view, this, true);
|
||||
}
|
||||
|
||||
@@ -96,18 +104,33 @@ public class TextInputView extends KeyboardAwareLinearLayout {
|
||||
String hint = attributes.getString(R.styleable.TextInputView_hint);
|
||||
attributes.recycle();
|
||||
|
||||
if (hint != null) editText.setHint(hint);
|
||||
if (hint != null) setHint(hint);
|
||||
|
||||
emojiToggle.setOnClickListener(v -> emojiPopup.toggle());
|
||||
editText.setOnClickListener(v -> showSoftKeyboard());
|
||||
editText.setOnKeyListener((v, keyCode, event) -> {
|
||||
if (keyCode == KEYCODE_ENTER && event.isCtrlPressed()) {
|
||||
trySendMessage();
|
||||
onSendButtonClicked();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
sendButton.setOnClickListener(v -> trySendMessage());
|
||||
sendButton.setOnClickListener(v -> onSendButtonClicked());
|
||||
}
|
||||
|
||||
public void setListener(TextInputListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this during onCreate() to enable image attachment support.
|
||||
* Do not call it twice!
|
||||
*/
|
||||
public void setAttachImageListener(AttachImageListener imageListener) {
|
||||
if (attachmentController != null) throw new IllegalStateException();
|
||||
attachmentController = new TextInputAttachmentController(getRootView(),
|
||||
editText, sendButton, imageListener
|
||||
);
|
||||
}
|
||||
|
||||
private void showEmojiIcon() {
|
||||
@@ -118,10 +141,23 @@ public class TextInputView extends KeyboardAwareLinearLayout {
|
||||
emojiToggle.setImageResource(R.drawable.ic_keyboard);
|
||||
}
|
||||
|
||||
private void trySendMessage() {
|
||||
private void onSendButtonClicked() {
|
||||
if (listener != null) {
|
||||
listener.onSendClick(editText.getText().toString());
|
||||
Editable editable = editText.getText();
|
||||
String text = editable == null || editable.length() == 0 ?
|
||||
null : editable.toString();
|
||||
List<Uri> imageUris = attachmentController == null ? emptyList() :
|
||||
attachmentController.getUris();
|
||||
listener.onSendClick(text, imageUris);
|
||||
}
|
||||
if (attachmentController != null) {
|
||||
attachmentController.afterSendButtonClicked();
|
||||
}
|
||||
}
|
||||
|
||||
public void onImageReceived(@Nullable Intent resultData) {
|
||||
if (attachmentController == null) throw new IllegalStateException();
|
||||
attachmentController.onImageReceived(resultData);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,12 +175,17 @@ public class TextInputView extends KeyboardAwareLinearLayout {
|
||||
editText.setText(text);
|
||||
}
|
||||
|
||||
public Editable getText() {
|
||||
return editText.getText();
|
||||
public boolean isEmpty() {
|
||||
return editText.getText() == null || editText.getText().length() == 0;
|
||||
}
|
||||
|
||||
public void setHint(@StringRes int res) {
|
||||
editText.setHint(res);
|
||||
setHint(getContext().getString(res));
|
||||
}
|
||||
|
||||
public void setHint(String hint) {
|
||||
if (attachmentController != null) attachmentController.saveHint(hint);
|
||||
editText.setHint(hint);
|
||||
}
|
||||
|
||||
public void setSendButtonEnabled(boolean enabled) {
|
||||
@@ -155,24 +196,26 @@ public class TextInputView extends KeyboardAwareLinearLayout {
|
||||
editText.addTextChangedListener(watcher);
|
||||
}
|
||||
|
||||
public void setListener(TextInputListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void showSoftKeyboard() {
|
||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
((InputMethodManager) o).showSoftInput(editText, SHOW_IMPLICIT);
|
||||
InputMethodManager imm = (InputMethodManager) requireNonNull(o);
|
||||
imm.showSoftInput(editText, SHOW_IMPLICIT);
|
||||
}
|
||||
|
||||
public void hideSoftKeyboard() {
|
||||
if (emojiPopup.isShowing()) emojiPopup.dismiss();
|
||||
IBinder token = editText.getWindowToken();
|
||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
|
||||
InputMethodManager imm = (InputMethodManager) requireNonNull(o);
|
||||
imm.hideSoftInputFromWindow(token, 0);
|
||||
}
|
||||
|
||||
public interface AttachImageListener {
|
||||
void onAttachImage(Intent intent);
|
||||
}
|
||||
|
||||
public interface TextInputListener {
|
||||
void onSendClick(String text);
|
||||
void onSendClick(@Nullable String text, List<Uri> imageUris);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
9
briar-android/src/main/res/drawable/ic_image.xml
Normal file
9
briar-android/src/main/res/drawable/ic_image.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
|
||||
</vector>
|
||||
@@ -5,12 +5,43 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"
|
||||
tools:showIn="@layout/activity_conversation">
|
||||
|
||||
<View
|
||||
style="@style/Divider.Horizontal"
|
||||
android:layout_alignParentTop="true"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/imageLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/text_input_image_height"
|
||||
tools:background="@color/msg_status_bubble_background"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:srcCompat="@tools:sample/avatars"/>
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/imageCancelButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:src="@drawable/ic_close"
|
||||
app:backgroundTint="@color/briar_red"
|
||||
app:fabCustomSize="26dp"
|
||||
app:maxImageSize="18dp"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -42,20 +73,41 @@
|
||||
tools:ignore="RtlSymmetry"
|
||||
tools:text="Line 1\nLine 2\nLine 3"/>
|
||||
|
||||
<android.support.v7.widget.AppCompatImageButton
|
||||
android:id="@+id/btn_send"
|
||||
<FrameLayout
|
||||
android:layout_width="@dimen/text_input_height"
|
||||
android:layout_height="@dimen/text_input_height"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/send"
|
||||
android:enabled="false"
|
||||
android:focusable="true"
|
||||
android:padding="4dp"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/social_send_now_white"
|
||||
app:tint="@color/briar_accent"/>
|
||||
android:layout_gravity="bottom">
|
||||
|
||||
<android.support.v7.widget.AppCompatImageButton
|
||||
android:id="@+id/btn_send"
|
||||
android:layout_width="@dimen/text_input_height"
|
||||
android:layout_height="@dimen/text_input_height"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/send"
|
||||
android:enabled="false"
|
||||
android:focusable="true"
|
||||
android:padding="4dp"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/social_send_now_white"
|
||||
app:tint="@color/briar_accent"/>
|
||||
|
||||
<android.support.v7.widget.AppCompatImageButton
|
||||
android:id="@+id/imageButton"
|
||||
android:layout_width="@dimen/text_input_height"
|
||||
android:layout_height="@dimen/text_input_height"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/image_attach"
|
||||
android:enabled="false"
|
||||
android:focusable="true"
|
||||
android:padding="4dp"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_image"
|
||||
android:visibility="invisible"
|
||||
app:tint="?attr/colorControlNormal"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:layout_marginLeft="@dimen/margin_small"
|
||||
android:layout_marginRight="@dimen/margin_small"
|
||||
android:layout_marginStart="@dimen/margin_small"/>
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
tools:text="@string/send"/>
|
||||
|
||||
</merge>
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
|
||||
<!-- Emoji -->
|
||||
<dimen name="text_input_height">42dp</dimen>
|
||||
<dimen name="text_input_image_height">150dp</dimen>
|
||||
<dimen name="conversation_item_body_text_size">16sp</dimen>
|
||||
<dimen name="emoji_drawer_size">32sp</dimen>
|
||||
<dimen name="emoji_drawer_indicator_height">2dp</dimen>
|
||||
|
||||
@@ -127,6 +127,8 @@
|
||||
<string name="date_no_private_messages">No messages.</string>
|
||||
<string name="no_private_messages">No messages to show</string>
|
||||
<string name="message_hint">Type message</string>
|
||||
<string name="image_caption_hint">Add a caption (optional)</string>
|
||||
<string name="image_attach">Attach image</string>
|
||||
<string name="set_contact_alias">Change contact name</string>
|
||||
<string name="set_contact_alias_hint">Contact name</string>
|
||||
<string name="set_alias_button">Change</string>
|
||||
|
||||
@@ -40,6 +40,7 @@ dependencyVerification {
|
||||
'com.android.support:interpolator:28.0.0:interpolator-28.0.0.aar:7bc7ee86a0db39a4b51956f3e89842d2bd962118d57d779eb6ed6b34ba0677ea',
|
||||
'com.android.support:loader:28.0.0:loader-28.0.0.aar:920b85efd72dc33e915b0f88a883fe73b88483c6df8751a741e17611f2460341',
|
||||
'com.android.support:localbroadcastmanager:28.0.0:localbroadcastmanager-28.0.0.aar:d287c823af5fdde72c099fcfc5f630efe9687af7a914343ae6fd92de32c8a806',
|
||||
'com.android.support:palette-v7:28.0.0:palette-v7-28.0.0.aar:317202dddb953d152d0677dbd8bb3b9d8ef8dcd0bdee0da4f40c98826e4960e6',
|
||||
'com.android.support:preference-v14:28.0.0:preference-v14-28.0.0.aar:8133c6e19233fa51e036a341e6d3f4adeead3375cebf777efced0fe154c3267e',
|
||||
'com.android.support:preference-v7:28.0.0:preference-v7-28.0.0.aar:75eabe936d1fc3b178450a554c4d433466036f2be6d6dccdf971eac9590fdbf5',
|
||||
'com.android.support:print:28.0.0:print-28.0.0.aar:4be8a812d73e4a80e35b91ceae127def3f0bb9726bf3bc439aa0cc81503f5728',
|
||||
|
||||
Reference in New Issue
Block a user