mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-25 05:12:15 +00:00
Support for quick reply from notifications.
Fixes #483 Closes #3455 // FREEBIE
This commit is contained in:
@@ -97,6 +97,8 @@ import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
|
||||
@@ -138,16 +140,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private static final int GROUP_EDIT = 5;
|
||||
private static final int CAPTURE_PHOTO = 6;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private ComposeText composeText;
|
||||
private AnimatingToggle buttonToggle;
|
||||
private SendButton sendButton;
|
||||
private ImageButton attachButton;
|
||||
private ConversationTitleView titleView;
|
||||
private TextView charactersLeft;
|
||||
private ConversationFragment fragment;
|
||||
private Button unblockButton;
|
||||
private View composePanel;
|
||||
private MasterSecret masterSecret;
|
||||
protected ComposeText composeText;
|
||||
private AnimatingToggle buttonToggle;
|
||||
private SendButton sendButton;
|
||||
private ImageButton attachButton;
|
||||
private ConversationTitleView titleView;
|
||||
private TextView charactersLeft;
|
||||
private ConversationFragment fragment;
|
||||
private Button unblockButton;
|
||||
private View composePanel;
|
||||
|
||||
private AttachmentTypeSelectorAdapter attachmentAdapter;
|
||||
private AttachmentManager attachmentManager;
|
||||
@@ -169,7 +171,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
protected void onPreCreate() {
|
||||
dynamicTheme.onCreate(this);
|
||||
dynamicLanguage.onCreate(this);
|
||||
overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -768,7 +769,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
emojiToggle.setOnClickListener(new EmojiToggleListener());
|
||||
}
|
||||
|
||||
private void initializeActionBar() {
|
||||
protected void initializeActionBar() {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setCustomView(R.layout.conversation_title_view);
|
||||
getSupportActionBar().setDisplayShowCustomEnabled(true);
|
||||
@@ -950,18 +951,22 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
return drafts;
|
||||
}
|
||||
|
||||
private void saveDraft() {
|
||||
if (this.recipients == null || this.recipients.isEmpty())
|
||||
return;
|
||||
protected ListenableFuture<Long> saveDraft() {
|
||||
final SettableFuture<Long> future = new SettableFuture<>();
|
||||
|
||||
if (this.recipients == null || this.recipients.isEmpty()) {
|
||||
future.set(threadId);
|
||||
return future;
|
||||
}
|
||||
|
||||
final Drafts drafts = getDraftsForCurrentState();
|
||||
final long thisThreadId = this.threadId;
|
||||
final MasterSecret thisMasterSecret = this.masterSecret.parcelClone();
|
||||
final int thisDistributionType = this.distributionType;
|
||||
|
||||
new AsyncTask<Long, Void, Void>() {
|
||||
new AsyncTask<Long, Void, Long>() {
|
||||
@Override
|
||||
protected Void doInBackground(Long... params) {
|
||||
protected Long doInBackground(Long... params) {
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(ConversationActivity.this);
|
||||
DraftDatabase draftDatabase = DatabaseFactory.getDraftDatabase(ConversationActivity.this);
|
||||
long threadId = params[0];
|
||||
@@ -974,9 +979,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
} else if (threadId > 0) {
|
||||
threadDatabase.update(threadId);
|
||||
}
|
||||
return null;
|
||||
|
||||
return threadId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Long result) {
|
||||
future.set(result);
|
||||
}
|
||||
|
||||
}.execute(thisThreadId);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
private void setBlockedUserState(Recipients recipients) {
|
||||
@@ -1031,10 +1045,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
return getRecipients() != null && getRecipients().isGroupRecipient();
|
||||
}
|
||||
|
||||
private Recipients getRecipients() {
|
||||
protected Recipients getRecipients() {
|
||||
return this.recipients;
|
||||
}
|
||||
|
||||
protected long getThreadId() {
|
||||
return this.threadId;
|
||||
}
|
||||
|
||||
private String getMessage() throws InvalidMessageException {
|
||||
String rawText = composeText.getText().toString();
|
||||
|
||||
@@ -1055,7 +1073,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}.execute(threadId);
|
||||
}
|
||||
|
||||
private void sendComplete(long threadId) {
|
||||
protected void sendComplete(long threadId) {
|
||||
boolean refreshFragment = (threadId != this.threadId);
|
||||
this.threadId = threadId;
|
||||
|
||||
|
||||
@@ -174,6 +174,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType);
|
||||
|
||||
startActivity(intent);
|
||||
overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out);
|
||||
}
|
||||
|
||||
private void handleDisplaySettings() {
|
||||
|
||||
117
src/org/thoughtcrime/securesms/ConversationPopupActivity.java
Normal file
117
src/org/thoughtcrime/securesms/ConversationPopupActivity.java
Normal file
@@ -0,0 +1,117 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.Gravity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class ConversationPopupActivity extends ConversationActivity {
|
||||
|
||||
private static final String TAG = ConversationPopupActivity.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
super.onPreCreate();
|
||||
overridePendingTransition(R.anim.slide_from_top, R.anim.slide_to_top);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND,
|
||||
WindowManager.LayoutParams.FLAG_DIM_BEHIND);
|
||||
|
||||
WindowManager.LayoutParams params = getWindow().getAttributes();
|
||||
params.alpha = 1.0f;
|
||||
params.dimAmount = 0.1f;
|
||||
params.gravity = Gravity.TOP;
|
||||
getWindow().setAttributes(params);
|
||||
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
int width = display.getWidth();
|
||||
int height = display.getHeight();
|
||||
|
||||
if (height > width) getWindow().setLayout((int) (width * .85), (int) (height * .5));
|
||||
else getWindow().setLayout((int) (width * .7), (int) (height * .75));
|
||||
|
||||
super.onCreate(bundle, masterSecret);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
composeText.requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (isFinishing()) overridePendingTransition(R.anim.slide_from_top, R.anim.slide_to_top);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = this.getMenuInflater();
|
||||
menu.clear();
|
||||
|
||||
inflater.inflate(R.menu.conversation_popup, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_expand:
|
||||
saveDraft().addListener(new ListenableFuture.Listener<Long>() {
|
||||
@Override
|
||||
public void onSuccess(Long result) {
|
||||
ActivityOptionsCompat transition = ActivityOptionsCompat.makeScaleUpAnimation(getWindow().getDecorView(), 0, 0, getWindow().getAttributes().width, getWindow().getAttributes().height);
|
||||
Intent intent = new Intent(ConversationPopupActivity.this, ConversationActivity.class);
|
||||
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, getRecipients().getIds());
|
||||
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, result);
|
||||
|
||||
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
startActivity(intent, transition.toBundle());
|
||||
} else {
|
||||
startActivity(intent);
|
||||
overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(ExecutionException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeActionBar() {
|
||||
super.initializeActionBar();
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendComplete(long threadId) {
|
||||
super.sendComplete(threadId);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,6 @@ public class MessageNotifier {
|
||||
sendInThreadNotification(context, recipients);
|
||||
} else {
|
||||
Intent intent = new Intent(context, ConversationActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
intent.putExtra("recipients", recipients.getIds());
|
||||
intent.putExtra("thread_id", threadId);
|
||||
intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
|
||||
@@ -221,6 +220,7 @@ public class MessageNotifier {
|
||||
context.getString(R.string.MessageNotifier_mark_as_read),
|
||||
notificationState.getMarkAsReadIntent(context, masterSecret));
|
||||
builder.addAction(markAsReadAction);
|
||||
builder.addAction(new Action(R.drawable.ic_reply_white_36dp, context.getString(R.string.MessageNotifier_reply), notifications.get(0).getReplyIntent(context)));
|
||||
builder.extend(new NotificationCompat.WearableExtender().addAction(markAsReadAction));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.support.annotation.Nullable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import org.thoughtcrime.securesms.ConversationActivity;
|
||||
import org.thoughtcrime.securesms.ConversationPopupActivity;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@@ -81,8 +82,7 @@ public class NotificationItem {
|
||||
}
|
||||
|
||||
public PendingIntent getPendingIntent(Context context) {
|
||||
Intent intent = new Intent(context, ConversationActivity.class);
|
||||
|
||||
Intent intent = new Intent(context, ConversationActivity.class);
|
||||
Recipients notifyRecipients = threadRecipients != null ? threadRecipients : recipients;
|
||||
if (notifyRecipients != null) intent.putExtra("recipients", notifyRecipients.getIds());
|
||||
|
||||
@@ -92,4 +92,16 @@ public class NotificationItem {
|
||||
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
public PendingIntent getReplyIntent(Context context) {
|
||||
Intent intent = new Intent(context, ConversationPopupActivity.class);
|
||||
Recipients notifyRecipients = threadRecipients != null ? threadRecipients : recipients;
|
||||
if (notifyRecipients != null) intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, notifyRecipients.getIds());
|
||||
|
||||
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
|
||||
intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
|
||||
|
||||
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.thoughtcrime.securesms.util.concurrent;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public interface ListenableFuture<T> {
|
||||
void addListener(Listener<T> listener);
|
||||
|
||||
public interface Listener<T> {
|
||||
public void onSuccess(T result);
|
||||
public void onFailure(ExecutionException e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.thoughtcrime.securesms.util.concurrent;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public class SettableFuture<T> implements Future<T>, ListenableFuture<T> {
|
||||
|
||||
private final List<Listener<T>> listeners = new LinkedList<>();
|
||||
|
||||
private boolean completed;
|
||||
private boolean canceled;
|
||||
private volatile T result;
|
||||
private volatile Throwable exception;
|
||||
|
||||
@Override
|
||||
public synchronized boolean cancel(boolean mayInterruptIfRunning) {
|
||||
if (!completed && !canceled) {
|
||||
canceled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isCancelled() {
|
||||
return canceled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isDone() {
|
||||
return completed;
|
||||
}
|
||||
|
||||
public boolean set(T result) {
|
||||
synchronized (this) {
|
||||
if (completed || canceled) return false;
|
||||
|
||||
this.result = result;
|
||||
this.completed = true;
|
||||
}
|
||||
|
||||
notifyAllListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean setException(Throwable throwable) {
|
||||
synchronized (this) {
|
||||
if (completed || canceled) return false;
|
||||
|
||||
this.exception = throwable;
|
||||
this.completed = true;
|
||||
}
|
||||
|
||||
notifyAllListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized T get() throws InterruptedException, ExecutionException {
|
||||
while (!completed) wait();
|
||||
|
||||
if (exception != null) throw new ExecutionException(exception);
|
||||
else return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized T get(long timeout, TimeUnit unit)
|
||||
throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
while (!completed && System.currentTimeMillis() - startTime > unit.toMillis(timeout)) {
|
||||
wait(unit.toMillis(timeout));
|
||||
}
|
||||
|
||||
if (!completed) throw new TimeoutException();
|
||||
else return get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Listener<T> listener) {
|
||||
synchronized (this) {
|
||||
listeners.add(listener);
|
||||
|
||||
if (!completed) return;
|
||||
}
|
||||
|
||||
notifyListener(listener);
|
||||
}
|
||||
|
||||
private void notifyAllListeners() {
|
||||
List<Listener<T>> localListeners;
|
||||
|
||||
synchronized (this) {
|
||||
localListeners = new LinkedList<>(listeners);
|
||||
}
|
||||
|
||||
for (Listener<T> listener : localListeners) {
|
||||
notifyListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyListener(Listener<T> listener) {
|
||||
if (exception != null) listener.onFailure(new ExecutionException(exception));
|
||||
else listener.onSuccess(result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user