in-conversation invite reminder

Closes #4250
// FREEBIE
master
Jake McGinty 2015-10-13 21:44:01 -07:00 committed by Moxie Marlinspike
parent 38d0b5caa8
commit 4ffb1ea95e
20 changed files with 366 additions and 240 deletions

View File

@ -20,6 +20,11 @@
android:paddingTop="?attr/actionBarSize"
android:gravity="bottom">
<org.thoughtcrime.securesms.components.reminder.ReminderView
android:id="@+id/reminder"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<FrameLayout android:id="@+id/fragment_content"
android:layout_width="match_parent"
android:layout_height="0dp"

View File

@ -11,7 +11,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
<org.thoughtcrime.securesms.components.ReminderView
<org.thoughtcrime.securesms.components.reminder.ReminderView
android:id="@+id/reminder"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

View File

@ -1,59 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<LinearLayout android:id="@+id/container"
android:orientation="horizontal"
android:layout_width="match_parent"
<LinearLayout android:id="@+id/container"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:visibility="gone"
android:background="?reminder_header_background">
<LinearLayout android:id="@+id/reminder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:background="?reminder_header_background">
android:layout_weight="1"
android:layout_margin="10dp"
android:orientation="vertical">
<ImageView android:id="@+id/icon"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/sms_selection_icon"
android:padding="5dp"/>
<TextView android:id="@+id/reminder_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="16sp"
tools:text="@string/reminder_header_push_title" />
<LinearLayout android:id="@+id/reminder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_margin="10dp"
android:orientation="vertical">
<TextView android:id="@+id/reminder_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:textColor="@color/white"
android:textSize="18sp"/>
<TextView android:id="@+id/reminder_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:textColor="@color/white"
android:textSize="16sp"/>
</LinearLayout>
<LinearLayout android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="right|top"
android:layout_marginLeft="15dp"
android:orientation="vertical">
<ImageButton android:id="@+id/cancel"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="10dp"
android:background="@drawable/touch_highlight_background"
android:src="@drawable/ic_cancel_white_24dp"/>
</LinearLayout>
<TextView android:id="@+id/reminder_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:textColor="@color/white"
android:textSize="14sp"
tools:text="@string/reminder_header_push_text" />
</LinearLayout>
<TextView android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:fontFamily="sans-serif-medium"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:textColor="#99ffffff"
android:clickable="true"
android:text="@string/reminder_header_close_button" />
<TextView android:id="@+id/accept"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:fontFamily="sans-serif-medium"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:textColor="@color/white"
tools:text="ENABLE" />
</LinearLayout>

View File

@ -1037,12 +1037,20 @@
<!-- reminder_header -->
<string name="reminder_header_expired_build">Your build of Signal has expired!</string>
<string name="reminder_header_expired_build_details">Messages will no longer send successfully, please update to the most recent version.</string>
<string name="reminder_header_expired_build_button">UPGRADE</string>
<string name="reminder_header_sms_default_title">Use as default SMS app?</string>
<string name="reminder_header_sms_default_text">Tap to make Signal your default SMS app.</string>
<string name="reminder_header_sms_default_button">SET</string>
<string name="reminder_header_sms_import_title">Import system SMS?</string>
<string name="reminder_header_sms_import_text">Tap to copy your phone\'s SMS messages into its encrypted database.</string>
<string name="reminder_header_push_title">Enable Signal messages?</string>
<string name="reminder_header_push_text">Tap for instant delivery, stronger privacy, and no SMS fees.</string>
<string name="reminder_header_sms_import_button">IMPORT</string>
<string name="reminder_header_push_title">Enable Signal?</string>
<string name="reminder_header_push_text">Upgrade your messaging experience.</string>
<string name="reminder_header_push_button">ENABLE</string>
<string name="reminder_header_invite_title">Invite to Signal?</string>
<string name="reminder_header_invite_text">Take your conversation with %1$s to the next level.</string>
<string name="reminder_header_invite_button">INVITE</string>
<string name="reminder_header_close_button">CLOSE</string>
<!-- MediaPreviewActivity -->
<string name="MediaPreviewActivity_you">You</string>

View File

@ -66,6 +66,8 @@ import org.thoughtcrime.securesms.components.AnimatingToggle;
import org.thoughtcrime.securesms.components.ComposeText;
import org.thoughtcrime.securesms.components.InputAwareLayout;
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener;
import org.thoughtcrime.securesms.components.reminder.InviteReminder;
import org.thoughtcrime.securesms.components.reminder.ReminderView;
import org.thoughtcrime.securesms.components.SendButton;
import org.thoughtcrime.securesms.components.camera.HidingImageButton;
import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer;
@ -85,6 +87,7 @@ import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
import org.thoughtcrime.securesms.database.DraftDatabase.Drafts;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.mms.AttachmentManager;
import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType;
@ -106,6 +109,7 @@ import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DirectoryHelper;
@ -117,16 +121,17 @@ import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
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;
import org.whispersystems.textsecure.api.util.InvalidNumberException;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.List;
import java.util.concurrent.ExecutionException;
import static org.thoughtcrime.securesms.TransportOption.Type;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
@ -175,6 +180,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private InputAwareLayout container;
private View composePanel;
private View composeBubble;
private ReminderView reminderView;
private AttachmentTypeSelectorAdapter attachmentAdapter;
private AttachmentManager attachmentManager;
@ -216,16 +222,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
initializeActionBar();
initializeViews();
initializeResources();
initializeSecurity(false, false).addListener(new ListenableFuture.Listener<Boolean>() {
initializeSecurity(false, false).addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
initializeDraft();
}
@Override
public void onFailure(ExecutionException e) {
throw new AssertionError(e);
}
});
}
@ -241,15 +242,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
setIntent(intent);
initializeResources();
initializeSecurity(false, false).addListener(new ListenableFuture.Listener<Boolean>() {
initializeSecurity(false, false).addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
initializeDraft();
}
@Override
public void onFailure(ExecutionException e) {
throw new AssertionError(e);
}
});
if (fragment != null) {
@ -804,14 +801,25 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (result.first != currentSecureText || result.second != currentSecureVoice) {
handleSecurityChange(result.first, result.second);
}
future.set(true);
onSecurityUpdated();
}
}.execute(recipients);
return future;
}
private void onSecurityUpdated() {
updateInviteReminder();
}
private void updateInviteReminder() {
if (TextSecurePreferences.isPushRegistered(this) && !isSecureText && recipients.isSingleRecipient()) {
new ShowInviteReminderTask().execute(recipients);
} else {
reminderView.hide();
}
}
private void initializeMmsEnabledCheck() {
new AsyncTask<Void, Void, Boolean>() {
@ -828,21 +836,21 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void initializeViews() {
titleView = (ConversationTitleView) getSupportActionBar().getCustomView();
buttonToggle = (AnimatingToggle) findViewById(R.id.button_toggle);
sendButton = (SendButton) findViewById(R.id.send_button);
attachButton = (ImageButton) findViewById(R.id.attach_button);
composeText = (ComposeText) findViewById(R.id.embedded_text_editor);
charactersLeft = (TextView) findViewById(R.id.space_left);
emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle);
emojiDrawer = (EmojiDrawer) findViewById(R.id.emoji_drawer);
unblockButton = (Button) findViewById(R.id.unblock_button);
composePanel = findViewById(R.id.bottom_panel);
composeBubble = findViewById(R.id.compose_bubble);
container = (InputAwareLayout) findViewById(R.id.layout_container);
quickAttachmentDrawer = (QuickAttachmentDrawer) findViewById(R.id.quick_attachment_drawer);
quickAttachmentToggle = (HidingImageButton) findViewById(R.id.quick_attachment_toggle);
titleView = (ConversationTitleView) getSupportActionBar().getCustomView();
buttonToggle = ViewUtil.findById(this, R.id.button_toggle);
sendButton = ViewUtil.findById(this, R.id.send_button);
attachButton = ViewUtil.findById(this, R.id.attach_button);
composeText = ViewUtil.findById(this, R.id.embedded_text_editor);
charactersLeft = ViewUtil.findById(this, R.id.space_left);
emojiToggle = ViewUtil.findById(this, R.id.emoji_toggle);
emojiDrawer = ViewUtil.findById(this, R.id.emoji_drawer);
unblockButton = ViewUtil.findById(this, R.id.unblock_button);
composePanel = ViewUtil.findById(this, R.id.bottom_panel);
composeBubble = ViewUtil.findById(this, R.id.compose_bubble);
container = ViewUtil.findById(this, R.id.layout_container);
reminderView = ViewUtil.findById(this, R.id.reminder);
quickAttachmentDrawer = ViewUtil.findById(this, R.id.quick_attachment_drawer);
quickAttachmentToggle = ViewUtil.findById(this, R.id.quick_attachment_toggle);
container.addOnKeyboardShownListener(this);
@ -944,6 +952,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
titleView.setTitle(recipients);
setBlockedUserState(recipients);
setActionBarColor(recipients.getColor());
updateInviteReminder();
}
});
}
@ -1441,4 +1450,31 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateToggleButtonState();
}
private class ShowInviteReminderTask extends AsyncTask<Recipients, Void, Pair<Recipients,Boolean>> {
@Override
protected Pair<Recipients, Boolean> doInBackground(Recipients... recipients) {
if (recipients.length != 1 || recipients[0] == null) throw new AssertionError("task needs exactly one Recipients object");
Optional<RecipientsPreferences> prefs = DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this)
.getRecipientsPreferences(recipients[0].getIds());
return new Pair<>(recipients[0], prefs.isPresent() && prefs.get().hasSeenInviteReminder());
}
@Override
protected void onPostExecute(Pair<Recipients, Boolean> result) {
if (!result.second && result.first == recipients) {
InviteReminder reminder = new InviteReminder(ConversationActivity.this, result.first);
reminder.setOkListener(new OnClickListener() {
@Override
public void onClick(View v) {
handleInviteLink();
reminderView.requestDismiss();
}
});
reminderView.showReminder(reminder);
} else {
reminderView.hide();
}
}
}
}

View File

@ -33,8 +33,6 @@ import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.RecyclerListener;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
@ -48,12 +46,12 @@ import com.afollestad.materialdialogs.AlertDialogWrapper;
import com.melnykov.fab.FloatingActionButton;
import org.thoughtcrime.securesms.ConversationListAdapter.ItemClickListener;
import org.thoughtcrime.securesms.components.DefaultSmsReminder;
import org.thoughtcrime.securesms.components.ExpiredBuildReminder;
import org.thoughtcrime.securesms.components.PushRegistrationReminder;
import org.thoughtcrime.securesms.components.Reminder;
import org.thoughtcrime.securesms.components.ReminderView;
import org.thoughtcrime.securesms.components.SystemSmsImportReminder;
import org.thoughtcrime.securesms.components.reminder.DefaultSmsReminder;
import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder;
import org.thoughtcrime.securesms.components.reminder.PushRegistrationReminder;
import org.thoughtcrime.securesms.components.reminder.Reminder;
import org.thoughtcrime.securesms.components.reminder.ReminderView;
import org.thoughtcrime.securesms.components.reminder.SystemSmsImportReminder;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.loaders.ConversationListLoader;
@ -137,7 +135,7 @@ public class ConversationListFragment extends Fragment
@Override protected Optional<? extends Reminder> doInBackground(Context... params) {
final Context context = params[0];
if (ExpiredBuildReminder.isEligible(context)) {
return Optional.of(new ExpiredBuildReminder());
return Optional.of(new ExpiredBuildReminder(context));
} else if (DefaultSmsReminder.isEligible(context)) {
return Optional.of(new DefaultSmsReminder(context));
} else if (SystemSmsImportReminder.isEligible(context)) {

View File

@ -57,6 +57,7 @@ public class ComposeText extends EmojiEditText {
}
append(invite);
setSelection(getText().length());
}
private boolean isLandscape() {

View File

@ -1,35 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.provider.Telephony;
import android.util.Log;
import android.view.View;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.TextSecureExpiredException;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
public class ExpiredBuildReminder extends Reminder {
private static final String TAG = ExpiredBuildReminder.class.getSimpleName();
public ExpiredBuildReminder() {
super(R.drawable.ic_warning_dark,
R.string.reminder_header_expired_build,
R.string.reminder_header_expired_build_details);
}
@Override
public boolean isDismissable() {
return false;
}
public static boolean isEligible(Context context) {
return !Util.isBuildFresh();
}
}

View File

@ -1,53 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import org.thoughtcrime.securesms.R;
public abstract class Reminder {
private int iconResId;
private int titleResId;
private int textResId;
private OnClickListener okListener;
private OnClickListener cancelListener;
public Reminder(int iconResId, int titleResId, int textResId) {
this.iconResId = iconResId;
this.titleResId = titleResId;
this.textResId = textResId;
}
public int getIconResId() {
return iconResId;
}
public int getTitleResId() {
return titleResId;
}
public int getTextResId() {
return textResId;
}
public OnClickListener getOkListener() {
return okListener;
}
public OnClickListener getCancelListener() {
return cancelListener;
}
public void setOkListener(OnClickListener okListener) {
this.okListener = okListener;
}
public void setCancelListener(OnClickListener cancelListener) {
this.cancelListener = cancelListener;
}
public boolean isDismissable() {
return true;
}
}

View File

@ -1,9 +1,8 @@
package org.thoughtcrime.securesms.components;
package org.thoughtcrime.securesms.components.reminder;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.provider.Telephony;
import android.view.View;
@ -17,9 +16,9 @@ public class DefaultSmsReminder extends Reminder {
@TargetApi(VERSION_CODES.KITKAT)
public DefaultSmsReminder(final Context context) {
super(R.drawable.sms_selection_icon,
R.string.reminder_header_sms_default_title,
R.string.reminder_header_sms_default_text);
super(context.getString(R.string.reminder_header_sms_default_title),
context.getString(R.string.reminder_header_sms_default_text),
context.getString(R.string.reminder_header_sms_default_button));
final OnClickListener okListener = new OnClickListener() {
@Override
@ -30,14 +29,14 @@ public class DefaultSmsReminder extends Reminder {
context.startActivity(intent);
}
};
final OnClickListener cancelListener = new OnClickListener() {
final OnClickListener dismissListener = new OnClickListener() {
@Override
public void onClick(View v) {
TextSecurePreferences.setPromptedDefaultSmsProvider(context, true);
}
};
setOkListener(okListener);
setCancelListener(cancelListener);
setDismissListener(dismissListener);
}
public static boolean isEligible(Context context) {

View File

@ -0,0 +1,39 @@
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.view.View;
import android.view.View.OnClickListener;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.Util;
public class ExpiredBuildReminder extends Reminder {
private static final String TAG = ExpiredBuildReminder.class.getSimpleName();
public ExpiredBuildReminder(final Context context) {
super(context.getString(R.string.reminder_header_expired_build),
context.getString(R.string.reminder_header_expired_build_details),
context.getString(R.string.reminder_header_expired_build_button));
setOkListener(new OnClickListener() {
@Override public void onClick(View v) {
try {
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + context.getPackageName())));
} catch (android.content.ActivityNotFoundException anfe) {
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + context.getPackageName())));
}
}
});
}
@Override
public boolean isDismissable() {
return false;
}
public static boolean isEligible(Context context) {
return !Util.isBuildFresh();
}
}

View File

@ -0,0 +1,34 @@
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.View.OnClickListener;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
public class InviteReminder extends Reminder {
public InviteReminder(final @NonNull Context context,
final @NonNull Recipients recipients)
{
super(context.getString(R.string.reminder_header_invite_title),
context.getString(R.string.reminder_header_invite_text, recipients.toShortString()),
context.getString(R.string.reminder_header_invite_button));
setDismissListener(new OnClickListener() {
@Override public void onClick(View v) {
new AsyncTask<Void,Void,Void>() {
@Override protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientPreferenceDatabase(context).setSeenInviteReminder(recipients, true);
return null;
}
}.execute();
}
});
}
}

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.components;
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import android.content.Intent;
@ -13,9 +13,9 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
public class PushRegistrationReminder extends Reminder {
public PushRegistrationReminder(final Context context, final MasterSecret masterSecret) {
super(R.drawable.ic_push_registration_reminder,
R.string.reminder_header_push_title,
R.string.reminder_header_push_text);
super(context.getString(R.string.reminder_header_push_title),
context.getString(R.string.reminder_header_push_text),
context.getString(R.string.reminder_header_push_button));
final OnClickListener okListener = new OnClickListener() {
@Override

View File

@ -0,0 +1,56 @@
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.view.View.OnClickListener;
public abstract class Reminder {
private CharSequence buttonText;
private CharSequence title;
private CharSequence text;
private OnClickListener okListener;
private OnClickListener dismissListener;
public Reminder(@NonNull CharSequence title,
@NonNull CharSequence text,
@NonNull CharSequence buttonText)
{
this.title = title;
this.text = text;
this.buttonText = buttonText;
}
public CharSequence getTitle() {
return title;
}
public CharSequence getText() {
return text;
}
public CharSequence getButtonText() {
return buttonText;
}
public OnClickListener getOkListener() {
return okListener;
}
public OnClickListener getDismissListener() {
return dismissListener;
}
public void setOkListener(OnClickListener okListener) {
this.okListener = okListener;
}
public void setDismissListener(OnClickListener dismissListener) {
this.dismissListener = dismissListener;
}
public boolean isDismissable() {
return true;
}
}

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.components;
package org.thoughtcrime.securesms.components.reminder;
import android.annotation.TargetApi;
import android.content.Context;
@ -7,23 +7,21 @@ import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ViewUtil;
/**
* View to display actionable reminders to the user
*/
public class ReminderView extends LinearLayout {
private ViewGroup container;
private ImageButton cancel;
private TextView acceptButton;
private TextView closeButton;
private TextView title;
private TextView text;
private ImageView icon;
public ReminderView(Context context) {
super(context);
@ -43,35 +41,39 @@ public class ReminderView extends LinearLayout {
private void initialize() {
LayoutInflater.from(getContext()).inflate(R.layout.reminder_header, this, true);
container = (ViewGroup ) findViewById(R.id.container);
cancel = (ImageButton) findViewById(R.id.cancel);
title = (TextView ) findViewById(R.id.reminder_title);
text = (TextView ) findViewById(R.id.reminder_text);
icon = (ImageView ) findViewById(R.id.icon);
container = ViewUtil.findById(this, R.id.container);
acceptButton = ViewUtil.findById(this, R.id.accept);
closeButton = ViewUtil.findById(this, R.id.cancel);
title = ViewUtil.findById(this, R.id.reminder_title);
text = ViewUtil.findById(this, R.id.reminder_text);
}
public void showReminder(final Reminder reminder) {
icon.setImageResource(reminder.getIconResId());
title.setText(reminder.getTitleResId());
text.setText(reminder.getTextResId());
title.setText(reminder.getTitle());
text.setText(reminder.getText());
acceptButton.setText(reminder.getButtonText());
this.setOnClickListener(reminder.getOkListener());
acceptButton.setOnClickListener(reminder.getOkListener());
if (reminder.isDismissable()) {
cancel.setOnClickListener(new OnClickListener() {
closeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
hide();
if (reminder.getCancelListener() != null) reminder.getCancelListener().onClick(v);
if (reminder.getDismissListener() != null) reminder.getDismissListener().onClick(v);
}
});
} else {
cancel.setVisibility(View.GONE);
closeButton.setVisibility(View.GONE);
}
container.setVisibility(View.VISIBLE);
}
public void requestDismiss() {
closeButton.performClick();
}
public void hide() {
container.setVisibility(View.GONE);
}

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.components;
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import android.content.Intent;
@ -14,9 +14,9 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
public class SystemSmsImportReminder extends Reminder {
public SystemSmsImportReminder(final Context context, final MasterSecret masterSecret) {
super(R.drawable.sms_system_import_icon,
R.string.reminder_header_sms_import_title,
R.string.reminder_header_sms_import_text);
super(context.getString(R.string.reminder_header_sms_import_title),
context.getString(R.string.reminder_header_sms_import_text),
context.getString(R.string.reminder_header_sms_import_button));
final OnClickListener okListener = new OnClickListener() {
@Override
@ -42,7 +42,7 @@ public class SystemSmsImportReminder extends Reminder {
}
};
setOkListener(okListener);
setCancelListener(cancelListener);
setDismissListener(cancelListener);
}
public static boolean isEligible(Context context) {

View File

@ -66,7 +66,8 @@ public class DatabaseFactory {
private static final int INTRODUCED_ENVELOPE_CONTENT_VERSION = 19;
private static final int INTRODUCED_COLOR_PREFERENCE_VERSION = 20;
private static final int INTRODUCED_DB_OPTIMIZATIONS_VERSION = 21;
private static final int DATABASE_VERSION = 21;
private static final int INTRODUCED_INVITE_REMINDERS_VERSION = 22;
private static final int DATABASE_VERSION = 22;
private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object();
@ -768,6 +769,10 @@ public class DatabaseFactory {
db.execSQL("CREATE INDEX IF NOT EXISTS mms_thread_date_index ON mms (thread_id, date_received);");
}
if (oldVersion < INTRODUCED_INVITE_REMINDERS_VERSION) {
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN seen_invite_reminder INTEGER DEFAULT 0");
}
db.setTransactionSuccessful();
db.endTransaction();
}

View File

@ -23,14 +23,15 @@ public class RecipientPreferenceDatabase extends Database {
private static final String TAG = RecipientPreferenceDatabase.class.getSimpleName();
private static final String RECIPIENT_PREFERENCES_URI = "content://textsecure/recipients/";
private static final String TABLE_NAME = "recipient_preferences";
private static final String ID = "_id";
private static final String RECIPIENT_IDS = "recipient_ids";
private static final String BLOCK = "block";
private static final String NOTIFICATION = "notification";
private static final String VIBRATE = "vibrate";
private static final String MUTE_UNTIL = "mute_until";
private static final String COLOR = "color";
private static final String TABLE_NAME = "recipient_preferences";
private static final String ID = "_id";
private static final String RECIPIENT_IDS = "recipient_ids";
private static final String BLOCK = "block";
private static final String NOTIFICATION = "notification";
private static final String VIBRATE = "vibrate";
private static final String MUTE_UNTIL = "mute_until";
private static final String COLOR = "color";
private static final String SEEN_INVITE_REMINDER = "seen_invite_reminder";
public enum VibrateState {
DEFAULT(0), ENABLED(1), DISABLED(2);
@ -58,7 +59,8 @@ public class RecipientPreferenceDatabase extends Database {
NOTIFICATION + " TEXT DEFAULT NULL, " +
VIBRATE + " INTEGER DEFAULT " + VibrateState.DEFAULT.getId() + ", " +
MUTE_UNTIL + " INTEGER DEFAULT 0, " +
COLOR + " TEXT DEFAULT NULL);";
COLOR + " TEXT DEFAULT NULL, " +
SEEN_INVITE_REMINDER + " INTEGER DEFAULT 0);";
public RecipientPreferenceDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
@ -86,12 +88,13 @@ public class RecipientPreferenceDatabase extends Database {
null, null, null);
if (cursor != null && cursor.moveToNext()) {
boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCK)) == 1;
String notification = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION));
int vibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(VIBRATE));
long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL));
String serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR));
Uri notificationUri = notification == null ? null : Uri.parse(notification);
boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCK)) == 1;
String notification = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION));
int vibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(VIBRATE));
long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL));
String serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR));
Uri notificationUri = notification == null ? null : Uri.parse(notification);
boolean seenInviteReminder = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_INVITE_REMINDER)) == 1;
MaterialColor color;
@ -106,7 +109,7 @@ public class RecipientPreferenceDatabase extends Database {
return Optional.of(new RecipientsPreferences(blocked, muteUntil,
VibrateState.fromId(vibrateState),
notificationUri, color));
notificationUri, color, seenInviteReminder));
}
return Optional.absent();
@ -146,6 +149,12 @@ public class RecipientPreferenceDatabase extends Database {
updateOrInsert(recipients, values);
}
public void setSeenInviteReminder(Recipients recipients, boolean seen) {
ContentValues values = new ContentValues(1);
values.put(SEEN_INVITE_REMINDER, seen ? 1 : 0);
updateOrInsert(recipients, values);
}
private void updateOrInsert(Recipients recipients, ContentValues contentValues) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
@ -171,17 +180,20 @@ public class RecipientPreferenceDatabase extends Database {
private final VibrateState vibrateState;
private final Uri notification;
private final MaterialColor color;
private final boolean seenInviteReminder;
public RecipientsPreferences(boolean blocked, long muteUntil,
@NonNull VibrateState vibrateState,
@Nullable Uri notification,
@Nullable MaterialColor color)
@Nullable MaterialColor color,
boolean seenInviteReminder)
{
this.blocked = blocked;
this.muteUntil = muteUntil;
this.vibrateState = vibrateState;
this.notification = notification;
this.color = color;
this.blocked = blocked;
this.muteUntil = muteUntil;
this.vibrateState = vibrateState;
this.notification = notification;
this.color = color;
this.seenInviteReminder = seenInviteReminder;
}
public @Nullable MaterialColor getColor() {
@ -203,5 +215,9 @@ public class RecipientPreferenceDatabase extends Database {
public @Nullable Uri getRingtone() {
return notification;
}
public boolean hasSeenInviteReminder() {
return seenInviteReminder;
}
}
}

View File

@ -0,0 +1,12 @@
package org.thoughtcrime.securesms.util.concurrent;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
import java.util.concurrent.ExecutionException;
public abstract class AssertedSuccessListener<T> implements Listener<T> {
@Override
public void onFailure(ExecutionException e) {
throw new AssertionError(e);
}
}

View File

@ -23,10 +23,10 @@ import android.widget.TextView;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.thoughtcrime.securesms.components.DefaultSmsReminder;
import org.thoughtcrime.securesms.components.ExpiredBuildReminder;
import org.thoughtcrime.securesms.components.PushRegistrationReminder;
import org.thoughtcrime.securesms.components.SystemSmsImportReminder;
import org.thoughtcrime.securesms.components.reminder.DefaultSmsReminder;
import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder;
import org.thoughtcrime.securesms.components.reminder.PushRegistrationReminder;
import org.thoughtcrime.securesms.components.reminder.SystemSmsImportReminder;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import static android.support.test.espresso.Espresso.onView;