diff --git a/res/drawable-hdpi/ic_block_white_24dp.png b/res/drawable-hdpi/ic_block_white_24dp.png new file mode 100644 index 0000000000..2ccc89d246 Binary files /dev/null and b/res/drawable-hdpi/ic_block_white_24dp.png differ diff --git a/res/drawable-hdpi/ic_face_white_24dp.png b/res/drawable-hdpi/ic_face_white_24dp.png new file mode 100644 index 0000000000..dcb6ee9663 Binary files /dev/null and b/res/drawable-hdpi/ic_face_white_24dp.png differ diff --git a/res/drawable-hdpi/ic_person_add_white_24dp.png b/res/drawable-hdpi/ic_person_add_white_24dp.png new file mode 100644 index 0000000000..10ae5a70c4 Binary files /dev/null and b/res/drawable-hdpi/ic_person_add_white_24dp.png differ diff --git a/res/drawable-mdpi/ic_block_white_24dp.png b/res/drawable-mdpi/ic_block_white_24dp.png new file mode 100644 index 0000000000..ec1b33f0ea Binary files /dev/null and b/res/drawable-mdpi/ic_block_white_24dp.png differ diff --git a/res/drawable-mdpi/ic_face_white_24dp.png b/res/drawable-mdpi/ic_face_white_24dp.png new file mode 100644 index 0000000000..4daa988771 Binary files /dev/null and b/res/drawable-mdpi/ic_face_white_24dp.png differ diff --git a/res/drawable-mdpi/ic_person_add_white_24dp.png b/res/drawable-mdpi/ic_person_add_white_24dp.png new file mode 100644 index 0000000000..38e0a2882a Binary files /dev/null and b/res/drawable-mdpi/ic_person_add_white_24dp.png differ diff --git a/res/drawable-xhdpi/ic_block_white_24dp.png b/res/drawable-xhdpi/ic_block_white_24dp.png new file mode 100644 index 0000000000..7aba97b659 Binary files /dev/null and b/res/drawable-xhdpi/ic_block_white_24dp.png differ diff --git a/res/drawable-xhdpi/ic_face_white_24dp.png b/res/drawable-xhdpi/ic_face_white_24dp.png new file mode 100644 index 0000000000..430be5e84a Binary files /dev/null and b/res/drawable-xhdpi/ic_face_white_24dp.png differ diff --git a/res/drawable-xhdpi/ic_person_add_white_24dp.png b/res/drawable-xhdpi/ic_person_add_white_24dp.png new file mode 100644 index 0000000000..7e7c289d49 Binary files /dev/null and b/res/drawable-xhdpi/ic_person_add_white_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_block_white_24dp.png b/res/drawable-xxhdpi/ic_block_white_24dp.png new file mode 100644 index 0000000000..fddfa54b85 Binary files /dev/null and b/res/drawable-xxhdpi/ic_block_white_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_face_white_24dp.png b/res/drawable-xxhdpi/ic_face_white_24dp.png new file mode 100644 index 0000000000..7e329149f0 Binary files /dev/null and b/res/drawable-xxhdpi/ic_face_white_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_person_add_white_24dp.png b/res/drawable-xxhdpi/ic_person_add_white_24dp.png new file mode 100644 index 0000000000..8f744f0391 Binary files /dev/null and b/res/drawable-xxhdpi/ic_person_add_white_24dp.png differ diff --git a/res/drawable-xxxhdpi/ic_block_white_24dp.png b/res/drawable-xxxhdpi/ic_block_white_24dp.png new file mode 100644 index 0000000000..0378d1bedc Binary files /dev/null and b/res/drawable-xxxhdpi/ic_block_white_24dp.png differ diff --git a/res/drawable-xxxhdpi/ic_face_white_24dp.png b/res/drawable-xxxhdpi/ic_face_white_24dp.png new file mode 100644 index 0000000000..393047f59b Binary files /dev/null and b/res/drawable-xxxhdpi/ic_face_white_24dp.png differ diff --git a/res/drawable-xxxhdpi/ic_person_add_white_24dp.png b/res/drawable-xxxhdpi/ic_person_add_white_24dp.png new file mode 100644 index 0000000000..2fa2cca80c Binary files /dev/null and b/res/drawable-xxxhdpi/ic_person_add_white_24dp.png differ diff --git a/res/layout/unknown_sender_view.xml b/res/layout/unknown_sender_view.xml new file mode 100644 index 0000000000..9756e5a2ac --- /dev/null +++ b/res/layout/unknown_sender_view.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index d98b3bb676..186edc38d0 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -617,6 +617,14 @@ Signal update A new version of Signal is available, tap to update + + Block %s? + Blocked contacts will no longer be able to send you messages or call you. + Block + Share profile with %s? + The easiest way to share your profile information is to add the sender to your contacts. If you do not wish to, you can still share your profile information this way. + Share profile + Send message? Send @@ -1065,6 +1073,12 @@ Enter a name or number Add members + + The sender is not in your contact list + BLOCK + ADD TO CONTACTS + DON\'T ADD, BUT MAKE MY PROFILE VISIBLE + Learn more.]]> Tap to scan @@ -1424,7 +1438,6 @@ Transport icon - diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java index f2f9c1bef2..a9290f8b8d 100644 --- a/src/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationFragment.java @@ -61,6 +61,7 @@ import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.Slide; +import org.thoughtcrime.securesms.profiles.UnknownSenderView; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.sms.MessageSender; @@ -100,6 +101,7 @@ public class ConversationFragment extends Fragment private RecyclerView list; private RecyclerView.ItemDecoration lastSeenDecoration; private View loadMoreView; + private UnknownSenderView unknownSenderView; private View composeDivider; private View scrollToBottomButton; private TextView scrollDateHeader; @@ -132,14 +134,12 @@ public class ConversationFragment extends Fragment list.setItemAnimator(null); loadMoreView = inflater.inflate(R.layout.load_more_header, container, false); - loadMoreView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Bundle args = new Bundle(); - args.putLong("limit", 0); - getLoaderManager().restartLoader(0, args, ConversationFragment.this); - } + loadMoreView.setOnClickListener(v -> { + Bundle args = new Bundle(); + args.putLong("limit", 0); + getLoaderManager().restartLoader(0, args, ConversationFragment.this); }); + return view; } @@ -184,10 +184,11 @@ public class ConversationFragment extends Fragment } private void initializeResources() { - this.recipient = RecipientFactory.getRecipientFor(getActivity(), (Address)getActivity().getIntent().getParcelableExtra(ConversationActivity.ADDRESS_EXTRA), true); - this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1); - this.lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1); - this.firstLoad = true; + this.recipient = RecipientFactory.getRecipientFor(getActivity(), (Address) getActivity().getIntent().getParcelableExtra(ConversationActivity.ADDRESS_EXTRA), true); + this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1); + this.lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1); + this.firstLoad = true; + this.unknownSenderView = new UnknownSenderView(getActivity(), recipient, threadId); OnScrollListener scrollListener = new ConversationScrollListener(getActivity()); list.addOnScrollListener(scrollListener); @@ -430,6 +431,12 @@ public class ConversationFragment extends Fragment setLastSeen(loader.getLastSeen()); } + if (!loader.hasSent() && recipient.getName() == null) { + getListAdapter().setHeaderView(unknownSenderView); + } else { + getListAdapter().setHeaderView(null); + } + getListAdapter().changeCursor(cursor); int lastSeenPosition = getListAdapter().findLastSeenPosition(lastSeen); @@ -456,6 +463,8 @@ public class ConversationFragment extends Fragment MessageRecord messageRecord = DatabaseFactory.getMmsDatabase(getContext()).readerFor(message, threadId).getCurrent(); if (getListAdapter() != null) { + getListAdapter().setHeaderView(null); + setLastSeen(0); getListAdapter().addFastRecord(messageRecord); } @@ -466,6 +475,8 @@ public class ConversationFragment extends Fragment MessageRecord messageRecord = DatabaseFactory.getSmsDatabase(getContext()).readerFor(message, threadId).getCurrent(); if (getListAdapter() != null) { + getListAdapter().setHeaderView(null); + setLastSeen(0); getListAdapter().addFastRecord(messageRecord); } diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java index 5cbe9e599b..c907350b3e 100644 --- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -105,7 +105,8 @@ public class DatabaseFactory { private static final int INTERNAL_SYSTEM_DISPLAY_NAME = 40; private static final int PROFILES = 41; private static final int PROFILE_SHARING_APPROVAL = 42; - private static final int DATABASE_VERSION = 42; + private static final int UNSEEN_NUMBER_OFFER = 43; + private static final int DATABASE_VERSION = 43; private static final String DATABASE_NAME = "messages.db"; private static final Object lock = new Object(); @@ -1312,6 +1313,10 @@ public class DatabaseFactory { db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN profile_sharing_approval INTEGER DEFAULT 0"); } + if (oldVersion < UNSEEN_NUMBER_OFFER) { + db.execSQL("ALTER TABLE thread ADD COLUMN has_sent INTEGER DEFAULT 0"); + } + db.setTransactionSuccessful(); db.endTransaction(); } diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 6df470108d..ca16c8455c 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -808,6 +808,7 @@ public class MmsDatabase extends MessagingDatabase { long messageId = insertMediaMessage(masterSecret, message.getBody(), message.getAttachments(), contentValues, insertListener); DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId); + DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true); jobManager.add(new TrimThreadJob(context, threadId)); return messageId; diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 5defe8f154..7e043fa4b7 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -615,6 +615,8 @@ public class SmsDatabase extends MessagingDatabase { DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId); } + DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true); + notifyConversationListeners(threadId); if (!message.isIdentityVerified() && !message.isIdentityDefault()) { diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java index 820734aa03..b8769df859 100644 --- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -46,6 +46,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.util.DelimiterUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.InvalidMessageException; +import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.guava.Optional; import java.util.LinkedList; @@ -73,6 +74,7 @@ public class ThreadDatabase extends Database { public static final String RECEIPT_COUNT = "delivery_receipt_count"; public static final String EXPIRES_IN = "expires_in"; public static final String LAST_SEEN = "last_seen"; + private static final String HAS_SENT = "has_sent"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " + @@ -82,7 +84,7 @@ public class ThreadDatabase extends Database { SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " + ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " + RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " + - LAST_SEEN + " INTEGER DEFAULT 0);"; + LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " INTEGER DEFAULT 0);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + ADDRESS + ");", @@ -417,20 +419,19 @@ public class ThreadDatabase extends Database { notifyConversationListListeners(); } - public long getLastSeen(long threadId) { + public Pair getLastSeenAndHasSent(long threadId) { SQLiteDatabase db = databaseHelper.getReadableDatabase(); - Cursor cursor = db.query(TABLE_NAME, new String[]{LAST_SEEN}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null); + Cursor cursor = db.query(TABLE_NAME, new String[]{LAST_SEEN, HAS_SENT}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null); try { if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(0); + return new Pair<>(cursor.getLong(0), cursor.getLong(1) == 1); } - return -1; + return new Pair<>(-1L, false); } finally { if (cursor != null) cursor.close(); } - } public void deleteConversation(long threadId) { @@ -520,6 +521,16 @@ public class ThreadDatabase extends Database { return null; } + public void setHasSent(long threadId, boolean hasSent) { + ContentValues contentValues = new ContentValues(1); + contentValues.put(HAS_SENT, hasSent ? 1 : 0); + + databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, ID_WHERE, + new String[] {String.valueOf(threadId)}); + + notifyConversationListeners(threadId); + } + public void updateReadState(long threadId) { int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId); diff --git a/src/org/thoughtcrime/securesms/database/loaders/ConversationLoader.java b/src/org/thoughtcrime/securesms/database/loaders/ConversationLoader.java index 21cace94e3..3f8866b81d 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/ConversationLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/ConversationLoader.java @@ -5,17 +5,20 @@ import android.database.Cursor; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.util.AbstractCursorLoader; +import org.whispersystems.libsignal.util.Pair; public class ConversationLoader extends AbstractCursorLoader { - private final long threadId; - private long limit; - private long lastSeen; + private final long threadId; + private long limit; + private long lastSeen; + private boolean hasSent; public ConversationLoader(Context context, long threadId, long limit, long lastSeen) { super(context); this.threadId = threadId; this.limit = limit; this.lastSeen = lastSeen; + this.hasSent = true; } public boolean hasLimit() { @@ -26,10 +29,18 @@ public class ConversationLoader extends AbstractCursorLoader { return lastSeen; } + public boolean hasSent() { + return hasSent; + } + @Override public Cursor getCursor() { + Pair lastSeenAndHasSent = DatabaseFactory.getThreadDatabase(context).getLastSeenAndHasSent(threadId); + + this.hasSent = lastSeenAndHasSent.second(); + if (lastSeen == -1) { - this.lastSeen = DatabaseFactory.getThreadDatabase(context).getLastSeen(threadId); + this.lastSeen = lastSeenAndHasSent.first(); } return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId, limit); diff --git a/src/org/thoughtcrime/securesms/profiles/UnknownSenderView.java b/src/org/thoughtcrime/securesms/profiles/UnknownSenderView.java new file mode 100644 index 0000000000..7af27e76e3 --- /dev/null +++ b/src/org/thoughtcrime/securesms/profiles/UnknownSenderView.java @@ -0,0 +1,106 @@ +package org.thoughtcrime.securesms.profiles; + + +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.provider.ContactsContract; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.text.TextUtils; +import android.view.View; +import android.widget.FrameLayout; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.util.ViewUtil; + +public class UnknownSenderView extends FrameLayout { + + private final @NonNull Recipient recipient; + private final long threadId; + + public UnknownSenderView(@NonNull Context context, @NonNull Recipient recipient, long threadId) { + super(context); + this.recipient = recipient; + this.threadId = threadId; + + inflate(context, R.layout.unknown_sender_view, this); + + View block = ViewUtil.findById(this, R.id.block); + View add = ViewUtil.findById(this, R.id.add_to_contacts); + View profileAccess = ViewUtil.findById(this, R.id.share_profile); + + block.setOnClickListener(v -> handleBlock()); + add.setOnClickListener(v -> handleAdd()); + profileAccess.setOnClickListener(v -> handleProfileAccess()); + } + + private void handleBlock() { + final Context context = getContext(); + + new AlertDialog.Builder(getContext()) + .setIconAttribute(R.attr.dialog_alert_icon) + .setTitle(getContext().getString(R.string.UnknownSenderView_block_s, recipient.toShortString())) + .setMessage(R.string.UnknownSenderView_blocked_contacts_will_no_longer_be_able_to_send_you_messages_or_call_you) + .setPositiveButton(R.string.UnknownSenderView_block, (dialog, which) -> { + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + DatabaseFactory.getRecipientPreferenceDatabase(context).setBlocked(recipient, true); + if (threadId != -1) DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true); + return null; + } + + @Override + protected void onPostExecute(Void result) { + recipient.setBlocked(true); + } + }.execute(); + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + private void handleAdd() { + Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); + intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); + + if (!TextUtils.isEmpty(recipient.getProfileName())) { + intent.putExtra(ContactsContract.Intents.Insert.NAME, recipient.getProfileName()); + } + + if (recipient.getAddress().isEmail()) { + intent.putExtra(ContactsContract.Intents.Insert.EMAIL, recipient.getAddress().toEmailString()); + } + + if (recipient.getAddress().isPhone()) { + intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipient.getAddress().toPhoneString()); + } + + getContext().startActivity(intent); + if (threadId != -1) DatabaseFactory.getThreadDatabase(getContext()).setHasSent(threadId, true); + } + + private void handleProfileAccess() { + final Context context = getContext(); + + new AlertDialog.Builder(getContext()) + .setIconAttribute(R.attr.dialog_info_icon) + .setTitle(getContext().getString(R.string.UnknownSenderView_share_profile_with_s, recipient.toShortString())) + .setMessage(R.string.UnknownSenderView_the_easiest_way_to_share_your_profile_information_is_to_add_the_sender_to_your_contacts) + .setPositiveButton(R.string.UnknownSenderView_share_profile, (dialog, which) -> { + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + DatabaseFactory.getRecipientPreferenceDatabase(context).setProfileSharing(recipient.getAddress(), true); + if (threadId != -1) DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true); + return null; + } + }.execute(); + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } +} diff --git a/src/org/thoughtcrime/securesms/util/StickyHeaderDecoration.java b/src/org/thoughtcrime/securesms/util/StickyHeaderDecoration.java index f2115acadd..e72bb20cd1 100644 --- a/src/org/thoughtcrime/securesms/util/StickyHeaderDecoration.java +++ b/src/org/thoughtcrime/securesms/util/StickyHeaderDecoration.java @@ -132,7 +132,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration { { int headerHeight = getHeaderHeightForLayout(header); int top = getChildY(parent, child) - headerHeight; - if (layoutPos == 0) { + if (sticky && layoutPos == 0) { final int count = parent.getChildCount(); final long currentId = adapter.getHeaderId(adapterPos); // find next view with header and compute the offscreen push if needed