From c11d72b95b88ded848e172b8e6780d128f250b65 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 13 Feb 2017 22:35:47 -0800 Subject: [PATCH 01/12] Support for a "new messages" divider in conversations // FREEBIE Upstream commit: https://github.com/WhisperSystems/Signal-Android/commit/d9b42c436907fbf5d0c34b1b5b25baac65c1d628 --- .../last_seen_background.9.png | Bin 0 -> 187 bytes .../last_seen_divider_text_background.xml | 7 ++ res/layout/conversation_item_last_seen.xml | 23 +++++ res/values/strings.xml | 7 +- .../smssecure/ConversationActivity.java | 15 +++ .../smssecure/ConversationAdapter.java | 95 +++++++++++++++++- .../smssecure/ConversationFragment.java | 65 +++++++++--- .../smssecure/ConversationListActivity.java | 3 +- .../ConversationListArchiveActivity.java | 3 +- .../smssecure/ConversationListFragment.java | 8 +- .../smssecure/ConversationListItem.java | 6 ++ .../smssecure/database/DatabaseFactory.java | 14 ++- .../smssecure/database/ThreadDatabase.java | 32 +++++- .../database/loaders/ConversationLoader.java | 14 ++- .../database/model/ThreadRecord.java | 9 +- .../notifications/MarkReadReceiver.java | 1 + .../notifications/RemoteReplyReceiver.java | 2 + .../util/StickyHeaderDecoration.java | 4 +- 18 files changed, 275 insertions(+), 33 deletions(-) create mode 100644 res/drawable-xxhdpi/last_seen_background.9.png create mode 100644 res/drawable/last_seen_divider_text_background.xml create mode 100644 res/layout/conversation_item_last_seen.xml diff --git a/res/drawable-xxhdpi/last_seen_background.9.png b/res/drawable-xxhdpi/last_seen_background.9.png new file mode 100644 index 0000000000000000000000000000000000000000..e84de6dd3d24d770d7f7510b8488a0898f9fd0e8 GIT binary patch literal 187 zcmeAS@N?(olHy`uVBq!ia0vp^JU}eQ!3HG1Sky`ZDYhhUcNd2LAh=-f^2tCE&H|6f zVg?3oVGw3ym^DWND9B#o>Fdh=kcppF(W>5DpBpIT;pyTSVsSb-#ewy|5x!|mEu_Y;%xdhpqUJwu6{1-oD!M + + + + + + \ No newline at end of file diff --git a/res/layout/conversation_item_last_seen.xml b/res/layout/conversation_item_last_seen.xml new file mode 100644 index 000000000..b125457b2 --- /dev/null +++ b/res/layout/conversation_item_last_seen.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index c7cab7c89..ec7c18fae 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -124,6 +124,12 @@ Please choose a contact Attachment exceeds size limits. + + + %d unread message + %d unread messages + + Message details @@ -877,7 +883,6 @@ Transport icon - diff --git a/src/org/smssecure/smssecure/ConversationActivity.java b/src/org/smssecure/smssecure/ConversationActivity.java index 6b5cb844f..319c3597f 100644 --- a/src/org/smssecure/smssecure/ConversationActivity.java +++ b/src/org/smssecure/smssecure/ConversationActivity.java @@ -156,6 +156,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity public static final String TEXT_EXTRA = "draft_text"; public static final String DISTRIBUTION_TYPE_EXTRA = "distribution_type"; public static final String TIMING_EXTRA = "timing"; + public static final String LAST_SEEN_EXTRA = "last_seen"; private static final int PICK_IMAGE = 1; private static final int PICK_VIDEO = 2; @@ -274,6 +275,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity super.onPause(); MessageNotifier.setVisibleThread(-1L); if (isFinishing()) overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right); + fragment.setLastSeen(System.currentTimeMillis()); + markLastSeen(); AudioSlidePlayer.stopAll(); } @@ -1171,6 +1174,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity }.execute(threadId); } + private void markLastSeen() { + new AsyncTask() { + @Override + protected Void doInBackground(Long... params) { + DatabaseFactory.getThreadDatabase(ConversationActivity.this).setLastSeen(params[0]); + return null; + } + }.execute(threadId); + } + protected void sendComplete(long threadId) { boolean refreshFragment = (threadId != this.threadId); this.threadId = threadId; @@ -1179,6 +1192,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity return; } + fragment.setLastSeen(0); + if (refreshFragment) { fragment.reload(recipients, threadId); diff --git a/src/org/smssecure/smssecure/ConversationAdapter.java b/src/org/smssecure/smssecure/ConversationAdapter.java index 5d601685c..9cf8346d6 100644 --- a/src/org/smssecure/smssecure/ConversationAdapter.java +++ b/src/org/smssecure/smssecure/ConversationAdapter.java @@ -18,6 +18,8 @@ package org.smssecure.smssecure; import android.content.Context; import android.database.Cursor; +import android.graphics.Canvas; +import android.graphics.Rect; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -105,15 +107,15 @@ public class ConversationAdapter } } - protected static class HeaderViewHolder extends RecyclerView.ViewHolder { - protected TextView textView; + static class HeaderViewHolder extends RecyclerView.ViewHolder { + TextView textView; - public HeaderViewHolder(View itemView) { + HeaderViewHolder(View itemView) { super(itemView); textView = ViewUtil.findById(itemView, R.id.text); } - public HeaderViewHolder(TextView textView) { + HeaderViewHolder(TextView textView) { super(textView); this.textView = textView; } @@ -265,6 +267,24 @@ public class ConversationAdapter getCursor().close(); } + public int findLastSeenPosition(long lastSeen) { + if (lastSeen <= 0) return -1; + if (!isActiveCursor()) return -1; + + int count = getItemCount(); + + for (int i=0;i return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR)); } + public long getTimestamp(int position) { + if (!isActiveCursor()) return 0; + if (isHeaderPosition(position)) return 0; + if (isFooterPosition(position)) return 0; + if (position >= getItemCount()) return 0; + if (position < 0) return 0; + + Cursor cursor = getCursorAtPositionOrThrow(position); + MessageRecord messageRecord = getMessageRecord(cursor); + + return messageRecord.getTimestamp(); + } + @Override public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) { return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.conversation_item_header, parent, false)); } + public HeaderViewHolder onCreateLastSeenViewHolder(ViewGroup parent) { + return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.conversation_item_last_seen, parent, false)); + } + @Override public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) { Cursor cursor = getCursorAtPositionOrThrow(position); viewHolder.setText(DateUtils.getRelativeDate(getContext(), locale, getMessageRecord(cursor).getDateReceived())); } + + public void onBindLastSeenViewHolder(HeaderViewHolder viewHolder, int position) { + viewHolder.setText(getContext().getResources().getQuantityString(R.plurals.ConversationAdapter_n_unread_messages, (position + 1), (position + 1))); + } + + public static class LastSeenHeader extends StickyHeaderDecoration { + + private final ConversationAdapter adapter; + private final long lastSeenTimestamp; + + public LastSeenHeader(ConversationAdapter adapter, long lastSeenTimestamp) { + super(adapter, false, false); + this.adapter = adapter; + this.lastSeenTimestamp = lastSeenTimestamp; + } + + @Override + protected boolean hasHeader(RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) { + if (!adapter.isActiveCursor()) { + return false; + } + + if (lastSeenTimestamp <= 0) { + return false; + } + + long currentRecordTimestamp = adapter.getTimestamp(position); + long previousRecordTimestamp = adapter.getTimestamp(position + 1); + + return (currentRecordTimestamp > lastSeenTimestamp) && (previousRecordTimestamp < lastSeenTimestamp); + } + + @Override + protected HeaderViewHolder getHeader(RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) { + HeaderViewHolder viewHolder = adapter.onCreateLastSeenViewHolder(parent); + adapter.onBindLastSeenViewHolder(viewHolder, position); + + int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY); + int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED); + + int childWidth = ViewGroup.getChildMeasureSpec(widthSpec, parent.getPaddingLeft() + parent.getPaddingRight(), viewHolder.itemView.getLayoutParams().width); + int childHeight = ViewGroup.getChildMeasureSpec(heightSpec, parent.getPaddingTop() + parent.getPaddingBottom(), viewHolder.itemView.getLayoutParams().height); + + viewHolder.itemView.measure(childWidth, childHeight); + viewHolder.itemView.layout(0, 0, viewHolder.itemView.getMeasuredWidth(), viewHolder.itemView.getMeasuredHeight()); + + return viewHolder; + } + } + } diff --git a/src/org/smssecure/smssecure/ConversationFragment.java b/src/org/smssecure/smssecure/ConversationFragment.java index adfd2ad5e..58ab8e87e 100644 --- a/src/org/smssecure/smssecure/ConversationFragment.java +++ b/src/org/smssecure/smssecure/ConversationFragment.java @@ -88,16 +88,19 @@ public class ConversationFragment extends Fragment private ConversationFragmentListener listener; - private MasterSecret masterSecret; - private Recipients recipients; - private long threadId; - private ActionMode actionMode; - private Locale locale; - private RecyclerView list; - private View loadMoreView; - private View composeDivider; - private View scrollToBottomButton; - private TextView scrollDateHeader; + private MasterSecret masterSecret; + private Recipients recipients; + private long threadId; + private long lastSeen; + private boolean firstLoad; + private ActionMode actionMode; + private Locale locale; + private RecyclerView list; + private RecyclerView.ItemDecoration lastSeenDecoration; + private View loadMoreView; + private View composeDivider; + private View scrollToBottomButton; + private TextView scrollDateHeader; @Override public void onCreate(Bundle icicle) { @@ -180,6 +183,8 @@ public class ConversationFragment extends Fragment private void initializeResources() { this.recipients = RecipientFactory.getRecipientsForIds(getActivity(), getActivity().getIntent().getLongArrayExtra("recipients"), true); this.threadId = this.getActivity().getIntent().getLongExtra("thread_id", -1); + this.lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1); + this.firstLoad = true; OnScrollListener scrollListener = new ConversationScrollListener(getActivity()); list.addOnScrollListener(scrollListener); @@ -191,6 +196,7 @@ public class ConversationFragment extends Fragment list.setAdapter(adapter); list.addItemDecoration(new StickyHeaderDecoration(adapter, false, false)); + setLastSeen(lastSeen); getLoaderManager().restartLoader(0, Bundle.EMPTY, this); list.getItemAnimator().setMoveDuration(120); } @@ -253,6 +259,16 @@ public class ConversationFragment extends Fragment }); } + public void setLastSeen(long lastSeen) { + this.lastSeen = lastSeen; + if (lastSeenDecoration != null) { + list.removeItemDecoration(lastSeenDecoration); + } + + lastSeenDecoration = new ConversationAdapter.LastSeenHeader(getListAdapter(), lastSeen); + list.addItemDecoration(lastSeenDecoration); + } + private void handleCopyMessage(final Set messageRecords) { List messageList = new LinkedList<>(messageRecords); Collections.sort(messageList, new Comparator() { @@ -383,18 +399,31 @@ public class ConversationFragment extends Fragment @Override public Loader onCreateLoader(int id, Bundle args) { - return new ConversationLoader(getActivity(), threadId, args.getLong("limit", PARTIAL_CONVERSATION_LIMIT)); + return new ConversationLoader(getActivity(), threadId, args.getLong("limit", PARTIAL_CONVERSATION_LIMIT), lastSeen); } + @Override - public void onLoadFinished(Loader loader, Cursor cursor) { + public void onLoadFinished(Loader cursorLoader, Cursor cursor) { + ConversationLoader loader = (ConversationLoader)cursorLoader; + if (list.getAdapter() != null) { - if (cursor.getCount() >= PARTIAL_CONVERSATION_LIMIT && ((ConversationLoader)loader).hasLimit()) { + if (cursor.getCount() >= PARTIAL_CONVERSATION_LIMIT && loader.hasLimit()) { getListAdapter().setFooterView(loadMoreView); } else { getListAdapter().setFooterView(null); } + + if (lastSeen == -1) { + setLastSeen(loader.getLastSeen()); + } + getListAdapter().changeCursor(cursor); + + if (firstLoad) { + scrollToLastSeenPosition(lastSeen); + firstLoad = false; + } } } @@ -405,6 +434,16 @@ public class ConversationFragment extends Fragment } } + private void scrollToLastSeenPosition(long lastSeen) { + int lastSeenPosition = getListAdapter().findLastSeenPosition(lastSeen); + + if (lastSeenPosition > 0) { + ((LinearLayoutManager)list.getLayoutManager()).scrollToPositionWithOffset(lastSeenPosition, list.getHeight()); + } else { + setLastSeen(0); + } + } + public interface ConversationFragmentListener { void setThreadId(long threadId); } diff --git a/src/org/smssecure/smssecure/ConversationListActivity.java b/src/org/smssecure/smssecure/ConversationListActivity.java index dbaba7ef1..12d289a1a 100644 --- a/src/org/smssecure/smssecure/ConversationListActivity.java +++ b/src/org/smssecure/smssecure/ConversationListActivity.java @@ -154,12 +154,13 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit } @Override - public void onCreateConversation(long threadId, Recipients recipients, int distributionType) { + public void onCreateConversation(long threadId, Recipients recipients, int distributionType, long lastSeen) { Intent intent = new Intent(this, ConversationActivity.class); intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients.getIds()); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType); intent.putExtra(ConversationActivity.TIMING_EXTRA, System.currentTimeMillis()); + intent.putExtra(ConversationActivity.LAST_SEEN_EXTRA, lastSeen); startActivity(intent); overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out); diff --git a/src/org/smssecure/smssecure/ConversationListArchiveActivity.java b/src/org/smssecure/smssecure/ConversationListArchiveActivity.java index 690e66520..c87a4397f 100644 --- a/src/org/smssecure/smssecure/ConversationListArchiveActivity.java +++ b/src/org/smssecure/smssecure/ConversationListArchiveActivity.java @@ -54,12 +54,13 @@ public class ConversationListArchiveActivity extends PassphraseRequiredActionBar } @Override - public void onCreateConversation(long threadId, Recipients recipients, int distributionType) { + public void onCreateConversation(long threadId, Recipients recipients, int distributionType, long lastSeenTime) { Intent intent = new Intent(this, ConversationActivity.class); intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients.getIds()); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); intent.putExtra(ConversationActivity.IS_ARCHIVED_EXTRA, true); intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType); + intent.putExtra(ConversationActivity.LAST_SEEN_EXTRA, lastSeenTime); startActivity(intent); overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out); diff --git a/src/org/smssecure/smssecure/ConversationListFragment.java b/src/org/smssecure/smssecure/ConversationListFragment.java index 45e7e2fa0..99ec6a8e4 100644 --- a/src/org/smssecure/smssecure/ConversationListFragment.java +++ b/src/org/smssecure/smssecure/ConversationListFragment.java @@ -304,8 +304,8 @@ public class ConversationListFragment extends Fragment getListAdapter().getBatchSelections().size())); } - private void handleCreateConversation(long threadId, Recipients recipients, int distributionType) { - ((ConversationSelectedListener)getActivity()).onCreateConversation(threadId, recipients, distributionType); + private void handleCreateConversation(long threadId, Recipients recipients, int distributionType, long lastSeen) { + ((ConversationSelectedListener)getActivity()).onCreateConversation(threadId, recipients, distributionType, lastSeen); } private void handleSendDrafts() { @@ -435,7 +435,7 @@ public class ConversationListFragment extends Fragment public void onItemClick(ConversationListItem item) { if (actionMode == null) { handleCreateConversation(item.getThreadId(), item.getRecipients(), - item.getDistributionType()); + item.getDistributionType(), item.getLastSeen()); } else { ConversationListAdapter adapter = (ConversationListAdapter)list.getAdapter(); adapter.toggleThreadInBatchSet(item.getThreadId()); @@ -468,7 +468,7 @@ public class ConversationListFragment extends Fragment } public interface ConversationSelectedListener { - void onCreateConversation(long threadId, Recipients recipients, int distributionType); + void onCreateConversation(long threadId, Recipients recipients, int distributionType, long lastSeen); void onSwitchToArchive(); } diff --git a/src/org/smssecure/smssecure/ConversationListItem.java b/src/org/smssecure/smssecure/ConversationListItem.java index 5449ec629..bde6a119b 100644 --- a/src/org/smssecure/smssecure/ConversationListItem.java +++ b/src/org/smssecure/smssecure/ConversationListItem.java @@ -73,6 +73,7 @@ public class ConversationListItem extends RelativeLayout private TextView archivedView; private DeliveryStatusView deliveryStatusIndicator; private AlertView alertView; + private long lastSeen; private boolean read; private AvatarImageView contactPhotoImage; @@ -119,6 +120,7 @@ public class ConversationListItem extends RelativeLayout this.threadId = thread.getThreadId(); this.read = thread.isRead(); this.distributionType = thread.getDistributionType(); + this.lastSeen = thread.getLastSeen(); this.recipients.addListener(this); this.fromView.setText(recipients, read); @@ -171,6 +173,10 @@ public class ConversationListItem extends RelativeLayout return distributionType; } + public long getLastSeen() { + return lastSeen; + } + private void setThumbnailSnippet(MasterSecret masterSecret, ThreadRecord thread) { if (thread.getSnippetUri() != null) { this.thumbnailView.setVisibility(View.VISIBLE); diff --git a/src/org/smssecure/smssecure/database/DatabaseFactory.java b/src/org/smssecure/smssecure/database/DatabaseFactory.java index 9040e011b..a57650151 100644 --- a/src/org/smssecure/smssecure/database/DatabaseFactory.java +++ b/src/org/smssecure/smssecure/database/DatabaseFactory.java @@ -73,8 +73,14 @@ public class DatabaseFactory { private static final int INTRODUCED_CONVERSATION_LIST_STATUS_VERSION = 25; private static final int MIGRATED_CONVERSATION_LIST_STATUS_VERSION = 26; private static final int INTRODUCED_SUBSCRIPTION_ID_VERSION = 28; - private static final int INTRODUCED_XMPP_TRANSPORT = 29; - private static final int DATABASE_VERSION = 28; + private static final int INTRODUCED_LAST_SEEN = 29; + + /* + * Yes, INTRODUCED_XMPP_TRANSPORT > DATABASE_VERSION to allow database + * downgrade when XMPP transport will be included in unstable branch. + */ + private static final int INTRODUCED_XMPP_TRANSPORT = 30; + private static final int DATABASE_VERSION = 29; private static final String DATABASE_NAME = "messages.db"; private static final Object lock = new Object(); @@ -828,6 +834,10 @@ public class DatabaseFactory { db.execSQL("ALTER TABLE mms ADD COLUMN subscription_id INTEGER DEFAULT -1"); } + if (oldVersion < INTRODUCED_LAST_SEEN) { + db.execSQL("ALTER TABLE thread ADD COLUMN last_seen INTEGER DEFAULT 0"); + } + db.setTransactionSuccessful(); db.endTransaction(); } diff --git a/src/org/smssecure/smssecure/database/ThreadDatabase.java b/src/org/smssecure/smssecure/database/ThreadDatabase.java index 12540630a..de2f15212 100644 --- a/src/org/smssecure/smssecure/database/ThreadDatabase.java +++ b/src/org/smssecure/smssecure/database/ThreadDatabase.java @@ -65,6 +65,7 @@ public class ThreadDatabase extends Database { public static final String SNIPPET_URI = "snippet_uri"; public static final String ARCHIVED = "archived"; public static final String STATUS = "status"; + public static final String LAST_SEEN = "last_seen"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " + @@ -72,7 +73,8 @@ public class ThreadDatabase extends Database { SNIPPET_CHARSET + " INTEGER DEFAULT 0, " + READ + " INTEGER DEFAULT 1, " + TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " + SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " + - ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0);"; + ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " + + LAST_SEEN + " INTEGER DEFAULT 0);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + RECIPIENT_IDS + ");", @@ -376,6 +378,31 @@ public class ThreadDatabase extends Database { notifyConversationListListeners(); } + public void setLastSeen(long threadId) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + ContentValues contentValues = new ContentValues(1); + contentValues.put(LAST_SEEN, System.currentTimeMillis()); + + db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(threadId)}); + notifyConversationListListeners(); + } + + public long getLastSeen(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); + + try { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(0); + } + + return -1; + } finally { + if (cursor != null) cursor.close(); + } + + } + public void deleteConversation(long threadId) { DatabaseFactory.getSmsDatabase(context).deleteThread(threadId); DatabaseFactory.getMmsDatabase(context).deleteThread(threadId); @@ -552,10 +579,11 @@ public class ThreadDatabase extends Database { int distributionType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.TYPE)); boolean archived = cursor.getInt(cursor.getColumnIndex(ThreadDatabase.ARCHIVED)) != 0; int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS)); + long lastSeen = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.LAST_SEEN)); Uri snippetUri = getSnippetUri(cursor); return new ThreadRecord(context, body, snippetUri, recipients, date, count, read == 1, - threadId, status, type, distributionType, archived); + threadId, status, type, distributionType, archived, lastSeen); } private DisplayRecord.Body getPlaintextBody(Cursor cursor) { diff --git a/src/org/smssecure/smssecure/database/loaders/ConversationLoader.java b/src/org/smssecure/smssecure/database/loaders/ConversationLoader.java index 7b7f612ca..6d4e2600a 100644 --- a/src/org/smssecure/smssecure/database/loaders/ConversationLoader.java +++ b/src/org/smssecure/smssecure/database/loaders/ConversationLoader.java @@ -9,19 +9,29 @@ import org.smssecure.smssecure.util.AbstractCursorLoader; public class ConversationLoader extends AbstractCursorLoader { private final long threadId; private long limit; + private long lastSeen; - public ConversationLoader(Context context, long threadId, long limit) { + public ConversationLoader(Context context, long threadId, long limit, long lastSeen) { super(context); this.threadId = threadId; - this.limit = limit; + this.limit = limit; + this.lastSeen = lastSeen; } public boolean hasLimit() { return limit > 0; } + public long getLastSeen() { + return lastSeen; + } + @Override public Cursor getCursor() { + if (lastSeen == -1) { + this.lastSeen = DatabaseFactory.getThreadDatabase(context).getLastSeen(threadId); + } + return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId, limit); } } diff --git a/src/org/smssecure/smssecure/database/model/ThreadRecord.java b/src/org/smssecure/smssecure/database/model/ThreadRecord.java index 703cb9dbb..eb518e84a 100644 --- a/src/org/smssecure/smssecure/database/model/ThreadRecord.java +++ b/src/org/smssecure/smssecure/database/model/ThreadRecord.java @@ -46,10 +46,12 @@ public class ThreadRecord extends DisplayRecord { private final boolean read; private final int distributionType; private final boolean archived; + private final long lastSeen; public ThreadRecord(@NonNull Context context, @NonNull Body body, @Nullable Uri snippetUri, @NonNull Recipients recipients, long date, long count, boolean read, - long threadId, int status, long snippetType, int distributionType, boolean archived) + long threadId, int status, long snippetType, int distributionType, boolean archived, + long lastSeen) { super(context, body, recipients, date, date, date, threadId, status, snippetType); this.context = context.getApplicationContext(); @@ -58,6 +60,7 @@ public class ThreadRecord extends DisplayRecord { this.read = read; this.distributionType = distributionType; this.archived = archived; + this.lastSeen = lastSeen; } public @Nullable Uri getSnippetUri() { @@ -134,4 +137,8 @@ public class ThreadRecord extends DisplayRecord { public int getDistributionType() { return distributionType; } + + public long getLastSeen() { + return lastSeen; + } } diff --git a/src/org/smssecure/smssecure/notifications/MarkReadReceiver.java b/src/org/smssecure/smssecure/notifications/MarkReadReceiver.java index 3d0dbfe8e..dd2d4469f 100644 --- a/src/org/smssecure/smssecure/notifications/MarkReadReceiver.java +++ b/src/org/smssecure/smssecure/notifications/MarkReadReceiver.java @@ -36,6 +36,7 @@ public class MarkReadReceiver extends MasterSecretBroadcastReceiver { for (long threadId : threadIds) { Log.w(TAG, "Marking as read: " + threadId); DatabaseFactory.getThreadDatabase(context).setRead(threadId); + DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId); } MessageNotifier.updateNotification(context, masterSecret); diff --git a/src/org/smssecure/smssecure/notifications/RemoteReplyReceiver.java b/src/org/smssecure/smssecure/notifications/RemoteReplyReceiver.java index d15fddc70..690b6c4f2 100644 --- a/src/org/smssecure/smssecure/notifications/RemoteReplyReceiver.java +++ b/src/org/smssecure/smssecure/notifications/RemoteReplyReceiver.java @@ -88,6 +88,8 @@ public class RemoteReplyReceiver extends MasterSecretBroadcastReceiver { } DatabaseFactory.getThreadDatabase(context).setRead(threadId); + DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId); + MessageNotifier.updateNotification(context, masterSecret); return null; diff --git a/src/org/smssecure/smssecure/util/StickyHeaderDecoration.java b/src/org/smssecure/smssecure/util/StickyHeaderDecoration.java index 967e03cf6..e351dec20 100644 --- a/src/org/smssecure/smssecure/util/StickyHeaderDecoration.java +++ b/src/org/smssecure/smssecure/util/StickyHeaderDecoration.java @@ -57,7 +57,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration { outRect.set(0, headerHeight, 0, 0); } - private boolean hasHeader(RecyclerView parent, StickyHeaderAdapter adapter, int adapterPos) { + protected boolean hasHeader(RecyclerView parent, StickyHeaderAdapter adapter, int adapterPos) { boolean isReverse = isReverseLayout(parent); int itemCount = ((RecyclerView.Adapter)adapter).getItemCount(); @@ -74,7 +74,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration { return headerId != NO_HEADER_ID && previousHeaderId != NO_HEADER_ID && headerId != previousHeaderId; } - private ViewHolder getHeader(RecyclerView parent, StickyHeaderAdapter adapter, int position) { + protected ViewHolder getHeader(RecyclerView parent, StickyHeaderAdapter adapter, int position) { final long key = adapter.getHeaderId(position); if (headerCache.containsKey(key)) { -- GitLab From c409767dc335b83d00bc0f6c51bc1fdf4df91a21 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Wed, 15 Feb 2017 22:39:53 -0800 Subject: [PATCH 02/12] Make header decorations play well together // FREEBIE Upstream commit: https://github.com/WhisperSystems/Signal-Android/commit/143fb1fe2152a42af9c1436127b2f1be9e68ce40 --- src/org/smssecure/smssecure/ConversationAdapter.java | 9 +++++++-- .../smssecure/smssecure/util/StickyHeaderDecoration.java | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/org/smssecure/smssecure/ConversationAdapter.java b/src/org/smssecure/smssecure/ConversationAdapter.java index 9cf8346d6..f07368230 100644 --- a/src/org/smssecure/smssecure/ConversationAdapter.java +++ b/src/org/smssecure/smssecure/ConversationAdapter.java @@ -352,12 +352,12 @@ public class ConversationAdapter viewHolder.setText(getContext().getResources().getQuantityString(R.plurals.ConversationAdapter_n_unread_messages, (position + 1), (position + 1))); } - public static class LastSeenHeader extends StickyHeaderDecoration { + static class LastSeenHeader extends StickyHeaderDecoration { private final ConversationAdapter adapter; private final long lastSeenTimestamp; - public LastSeenHeader(ConversationAdapter adapter, long lastSeenTimestamp) { + LastSeenHeader(ConversationAdapter adapter, long lastSeenTimestamp) { super(adapter, false, false); this.adapter = adapter; this.lastSeenTimestamp = lastSeenTimestamp; @@ -379,6 +379,11 @@ public class ConversationAdapter return (currentRecordTimestamp > lastSeenTimestamp) && (previousRecordTimestamp < lastSeenTimestamp); } + @Override + protected int getHeaderTop(RecyclerView parent, View child, View header, int adapterPos, int layoutPos) { + return parent.getLayoutManager().getDecoratedTop(child); + } + @Override protected HeaderViewHolder getHeader(RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) { HeaderViewHolder viewHolder = adapter.onCreateLastSeenViewHolder(parent); diff --git a/src/org/smssecure/smssecure/util/StickyHeaderDecoration.java b/src/org/smssecure/smssecure/util/StickyHeaderDecoration.java index e351dec20..c4d30d085 100644 --- a/src/org/smssecure/smssecure/util/StickyHeaderDecoration.java +++ b/src/org/smssecure/smssecure/util/StickyHeaderDecoration.java @@ -127,7 +127,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration { } } - private int getHeaderTop(RecyclerView parent, View child, View header, int adapterPos, + protected int getHeaderTop(RecyclerView parent, View child, View header, int adapterPos, int layoutPos) { int headerHeight = getHeaderHeightForLayout(header); @@ -172,7 +172,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration { } } - private int getHeaderHeightForLayout(View header) { + protected int getHeaderHeightForLayout(View header) { return renderInline ? 0 : header.getHeight(); } -- GitLab From 4807ea7362e7edc407bd890696417c1609f70bef Mon Sep 17 00:00:00 2001 From: Christian Ascheberg Date: Thu, 24 Dec 2015 15:27:10 +0100 Subject: [PATCH 03/12] enable silent per recipient ringtone preference Closes https://github.com/WhisperSystems/TextSecure/pull/4957 // FREEBIE Upstream commit: https://github.com/WhisperSystems/Signal-Android/commit/1aa2d546d2ffcf64108136fddca4468cf580feb7 --- res/xml/recipient_preferences.xml | 2 +- .../RecipientPreferenceActivity.java | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/res/xml/recipient_preferences.xml b/res/xml/recipient_preferences.xml index d8d8a69b8..7b5f54f55 100644 --- a/res/xml/recipient_preferences.xml +++ b/res/xml/recipient_preferences.xml @@ -14,7 +14,7 @@ android:key="pref_key_recipient_ringtone" android:title="@string/recipient_preferences__notification_sound" android:ringtoneType="notification" - android:showSilent="false" + android:showSilent="true" android:showDefault="true" android:persistent="false"/> diff --git a/src/org/smssecure/smssecure/RecipientPreferenceActivity.java b/src/org/smssecure/smssecure/RecipientPreferenceActivity.java index 75ad3a5dd..8a9377ade 100644 --- a/src/org/smssecure/smssecure/RecipientPreferenceActivity.java +++ b/src/org/smssecure/smssecure/RecipientPreferenceActivity.java @@ -18,7 +18,6 @@ import android.support.v4.app.Fragment; import android.support.v4.preference.PreferenceFragment; import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; -import android.text.TextUtils; import android.view.MenuItem; import android.view.View; import android.view.Window; @@ -198,15 +197,21 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi mutePreference.setChecked(recipients.isMuted()); - if (recipients.getRingtone() != null) { - Ringtone tone = RingtoneManager.getRingtone(getActivity(), recipients.getRingtone()); + final Uri toneUri = recipients.getRingtone(); + + if (toneUri == null) { + ringtonePreference.setSummary(R.string.preferences__default); + ringtonePreference.setCurrentRingtone(Settings.System.DEFAULT_NOTIFICATION_URI); + } else if (toneUri.toString().isEmpty()) { + ringtonePreference.setSummary(R.string.preferences__silent); + ringtonePreference.setCurrentRingtone(null); + } else { + Ringtone tone = RingtoneManager.getRingtone(getActivity(), toneUri); if (tone != null) { ringtonePreference.setSummary(tone.getTitle(getActivity())); - ringtonePreference.setCurrentRingtone(recipients.getRingtone()); + ringtonePreference.setCurrentRingtone(toneUri); } - } else { - ringtonePreference.setSummary(R.string.preferences__default); } if (recipients.getVibrate() == VibrateState.DEFAULT) { @@ -249,7 +254,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi final Uri uri; - if (TextUtils.isEmpty(value) || Settings.System.DEFAULT_NOTIFICATION_URI.toString().equals(value)) { + if (Settings.System.DEFAULT_NOTIFICATION_URI.toString().equals(value)) { uri = null; } else { uri = Uri.parse(value); -- GitLab From 060fa24d63f602e701862376b2dda696decabbf5 Mon Sep 17 00:00:00 2001 From: FeuRenard Date: Fri, 10 Feb 2017 15:02:48 +0100 Subject: [PATCH 04/12] Don't display date for drafts in media preview Closes https://github.com/WhisperSystems/TextSecure/pull/6208 // FREEBIE Upstream commit: https://github.com/WhisperSystems/Signal-Android/commit/98d223f09406f3ea5b6db6538701d6545c70e6c5 --- res/values/strings.xml | 1 + src/org/smssecure/smssecure/MediaPreviewActivity.java | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index ec7c18fae..82f4bce08 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -863,6 +863,7 @@ You Failed to preview this image Unsupported media type + Draft Save diff --git a/src/org/smssecure/smssecure/MediaPreviewActivity.java b/src/org/smssecure/smssecure/MediaPreviewActivity.java index 29a5b802c..ac9ed2a40 100644 --- a/src/org/smssecure/smssecure/MediaPreviewActivity.java +++ b/src/org/smssecure/smssecure/MediaPreviewActivity.java @@ -106,7 +106,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im if (date > 0) { relativeTimeSpan = DateUtils.getExtendedRelativeTimeSpanString(this,dynamicLanguage.getCurrentLocale(),date); } else { - relativeTimeSpan = null; + relativeTimeSpan = getString(R.string.MediaPreviewActivity_draft); } getSupportActionBar().setTitle(recipient == null ? getString(R.string.MediaPreviewActivity_you) : recipient.toShortString()); @@ -147,7 +147,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im mediaUri = getIntent().getData(); mediaType = getIntent().getType(); - date = getIntent().getLongExtra(DATE_EXTRA, System.currentTimeMillis()); + date = getIntent().getLongExtra(DATE_EXTRA, -1); size = getIntent().getLongExtra(SIZE_EXTRA, 0); threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1); @@ -208,7 +208,8 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im @Override public void onClick(DialogInterface dialogInterface, int i) { SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this, masterSecret); - saveTask.execute(new Attachment(mediaUri, mediaType, date)); + long saveDate = (date > 0) ? date : System.currentTimeMillis(); + saveTask.execute(new Attachment(mediaUri, mediaType, saveDate)); } }); } -- GitLab From 839238e39004b41ac5ed12fca3815de0e0a6a398 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Fri, 17 Feb 2017 19:20:11 -0800 Subject: [PATCH 05/12] Fix for last_seen crash when archive placeholder is visible // FREEBIE Upstream commit: https://github.com/WhisperSystems/Signal-Android/commit/d5b7ff58e2f305e40f38d4aa47265c251a433e27 --- .../smssecure/database/loaders/ConversationListLoader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/smssecure/smssecure/database/loaders/ConversationListLoader.java b/src/org/smssecure/smssecure/database/loaders/ConversationListLoader.java index a580fed43..4a820d129 100644 --- a/src/org/smssecure/smssecure/database/loaders/ConversationListLoader.java +++ b/src/org/smssecure/smssecure/database/loaders/ConversationListLoader.java @@ -43,11 +43,11 @@ public class ConversationListLoader extends AbstractCursorLoader { ThreadDatabase.ID, ThreadDatabase.DATE, ThreadDatabase.MESSAGE_COUNT, ThreadDatabase.RECIPIENT_IDS, ThreadDatabase.SNIPPET, ThreadDatabase.READ, ThreadDatabase.TYPE, ThreadDatabase.SNIPPET_TYPE, ThreadDatabase.SNIPPET_URI, - ThreadDatabase.ARCHIVED, ThreadDatabase.STATUS}, 1); + ThreadDatabase.ARCHIVED, ThreadDatabase.STATUS, ThreadDatabase.LAST_SEEN}, 1); switchToArchiveCursor.addRow(new Object[] {-1L, System.currentTimeMillis(), archivedCount, "-1", null, 1, ThreadDatabase.DistributionTypes.ARCHIVE, - 0, null, 0, -1}); + 0, null, 0, -1, 0}); cursorList.add(switchToArchiveCursor); } -- GitLab From 2d7b755553a1b9c0688afe60ad343ff194736f75 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Sat, 18 Feb 2017 14:32:22 -0800 Subject: [PATCH 06/12] Don't scroll to last seen until the recyclerview has been measured // FREEBIE Upstream commit: https://github.com/WhisperSystems/Signal-Android/commit/a075bf750530f160d59a9c2cdc65e2732e635d3a --- src/org/smssecure/smssecure/ConversationFragment.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/org/smssecure/smssecure/ConversationFragment.java b/src/org/smssecure/smssecure/ConversationFragment.java index 58ab8e87e..5fa0f61e0 100644 --- a/src/org/smssecure/smssecure/ConversationFragment.java +++ b/src/org/smssecure/smssecure/ConversationFragment.java @@ -435,10 +435,15 @@ public class ConversationFragment extends Fragment } private void scrollToLastSeenPosition(long lastSeen) { - int lastSeenPosition = getListAdapter().findLastSeenPosition(lastSeen); + final int lastSeenPosition = getListAdapter().findLastSeenPosition(lastSeen); if (lastSeenPosition > 0) { - ((LinearLayoutManager)list.getLayoutManager()).scrollToPositionWithOffset(lastSeenPosition, list.getHeight()); + list.post(new Runnable() { + @Override + public void run() { + ((LinearLayoutManager)list.getLayoutManager()).scrollToPositionWithOffset(lastSeenPosition, list.getHeight()); + } + }); } else { setLastSeen(0); } -- GitLab From 73ac47e4667245be500146beb1623787cf1ad5ad Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Tue, 21 Feb 2017 12:37:40 -0800 Subject: [PATCH 07/12] Fix unread message conversation separator for dark theme Fixes https://github.com/WhisperSystems/TextSecure/issues/6265 // FREEBIE Upstream commit: https://github.com/WhisperSystems/Signal-Android/commit/4ee607878c39d56c5f66dc5e9396bedceee93667 --- res/drawable/last_seen_divider_text_background.xml | 2 +- res/layout/conversation_item_last_seen.xml | 2 +- res/values/attrs.xml | 2 ++ res/values/themes.xml | 4 ++++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/res/drawable/last_seen_divider_text_background.xml b/res/drawable/last_seen_divider_text_background.xml index 8814e2a0a..ccf22d163 100644 --- a/res/drawable/last_seen_divider_text_background.xml +++ b/res/drawable/last_seen_divider_text_background.xml @@ -1,6 +1,6 @@ - + diff --git a/res/layout/conversation_item_last_seen.xml b/res/layout/conversation_item_last_seen.xml index b125457b2..d2dd402e4 100644 --- a/res/layout/conversation_item_last_seen.xml +++ b/res/layout/conversation_item_last_seen.xml @@ -6,7 +6,7 @@ android:paddingTop="10dp" android:paddingBottom="10dp" android:layout_marginBottom="5dp" - android:background="@drawable/last_seen_background"> + android:background="?conversation_item_last_seen_background"> + + diff --git a/res/values/themes.xml b/res/values/themes.xml index 04b925353..64c34b620 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -144,6 +144,8 @@ @color/white #BFffffff @drawable/conversation_item_header_background + @drawable/last_seen_background + @color/white @drawable/conversation_item_sent_indicator_text_shape @@ -225,6 +227,8 @@ #BFffffff @drawable/conversation_item_sent_indicator_text_shape_dark @drawable/conversation_item_header_background_dark + #66333333 + #ff333333 @drawable/ic_info_outline_dark @drawable/ic_warning_dark -- GitLab From 578eee63e8112d58a2f12dde80c2afc3da27f41a Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Tue, 21 Feb 2017 12:55:44 -0800 Subject: [PATCH 08/12] Reduce size of new message divider, adjust style slightly // FREEBIE Upstream commit: https://github.com/WhisperSystems/Signal-Android/commit/b43beaab752f1a1cb72ef3d464ccecfe8da9f593 --- res/drawable/last_seen_divider_text_background.xml | 2 +- res/layout/conversation_item_last_seen.xml | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/res/drawable/last_seen_divider_text_background.xml b/res/drawable/last_seen_divider_text_background.xml index ccf22d163..ef89bff4e 100644 --- a/res/drawable/last_seen_divider_text_background.xml +++ b/res/drawable/last_seen_divider_text_background.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/res/layout/conversation_item_last_seen.xml b/res/layout/conversation_item_last_seen.xml index d2dd402e4..daa37f4ec 100644 --- a/res/layout/conversation_item_last_seen.xml +++ b/res/layout/conversation_item_last_seen.xml @@ -3,8 +3,8 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingTop="10dp" - android:paddingBottom="10dp" + android:paddingTop="5dp" + android:paddingBottom="5dp" android:layout_marginBottom="5dp" android:background="?conversation_item_last_seen_background"> @@ -12,12 +12,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:paddingLeft="6dp" - android:paddingRight="6dp" - android:paddingTop="3dp" - android:paddingBottom="3dp" android:textSize="12sp" - android:padding="12dp" + android:textAllCaps="true" + android:textStyle="bold" android:background="@drawable/last_seen_divider_text_background" tools:text="3 unread messages" /> \ No newline at end of file -- GitLab From 8ca196a8831175abf144f375d64c4b521c17ab74 Mon Sep 17 00:00:00 2001 From: haffenloher Date: Wed, 22 Feb 2017 16:47:52 +0100 Subject: [PATCH 09/12] Fix last seen divider crash on Android <5.0 Apparently, it's not possible to reference an attribute in an xml drawable on API <21, so we have to use separate light and dark theme drawables instead. Closes https://github.com/WhisperSystems/TextSecure/pull/6285 Upstream commit: https://github.com/WhisperSystems/Signal-Android/commit/76ac95756fb5e7208f4e5e86578da7ca2f9699d1 --- ...ound.xml => last_seen_divider_text_background_dark.xml} | 2 +- res/drawable/last_seen_divider_text_background_light.xml | 7 +++++++ res/layout/conversation_item_last_seen.xml | 2 +- res/values/attrs.xml | 2 +- res/values/themes.xml | 4 ++-- 5 files changed, 12 insertions(+), 5 deletions(-) rename res/drawable/{last_seen_divider_text_background.xml => last_seen_divider_text_background_dark.xml} (78%) create mode 100644 res/drawable/last_seen_divider_text_background_light.xml diff --git a/res/drawable/last_seen_divider_text_background.xml b/res/drawable/last_seen_divider_text_background_dark.xml similarity index 78% rename from res/drawable/last_seen_divider_text_background.xml rename to res/drawable/last_seen_divider_text_background_dark.xml index ef89bff4e..ca657c96b 100644 --- a/res/drawable/last_seen_divider_text_background.xml +++ b/res/drawable/last_seen_divider_text_background_dark.xml @@ -1,6 +1,6 @@ - + diff --git a/res/drawable/last_seen_divider_text_background_light.xml b/res/drawable/last_seen_divider_text_background_light.xml new file mode 100644 index 000000000..63ce36656 --- /dev/null +++ b/res/drawable/last_seen_divider_text_background_light.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/layout/conversation_item_last_seen.xml b/res/layout/conversation_item_last_seen.xml index daa37f4ec..327d104ff 100644 --- a/res/layout/conversation_item_last_seen.xml +++ b/res/layout/conversation_item_last_seen.xml @@ -15,6 +15,6 @@ android:textSize="12sp" android:textAllCaps="true" android:textStyle="bold" - android:background="@drawable/last_seen_divider_text_background" + android:background="?conversation_item_last_seen_text_background" tools:text="3 unread messages" /> \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml index a6d8b5a30..1a1dd743a 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -68,7 +68,7 @@ - + diff --git a/res/values/themes.xml b/res/values/themes.xml index 64c34b620..3e50ae116 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -145,7 +145,7 @@ #BFffffff @drawable/conversation_item_header_background @drawable/last_seen_background - @color/white + @drawable/last_seen_divider_text_background_light @drawable/conversation_item_sent_indicator_text_shape @@ -228,7 +228,7 @@ @drawable/conversation_item_sent_indicator_text_shape_dark @drawable/conversation_item_header_background_dark #66333333 - #ff333333 + @drawable/last_seen_divider_text_background_dark @drawable/ic_info_outline_dark @drawable/ic_warning_dark -- GitLab From b941e389ce22059957b26a7ce4d293c840a48a85 Mon Sep 17 00:00:00 2001 From: Christian Ascheberg Date: Thu, 23 Feb 2017 17:28:34 +0100 Subject: [PATCH 10/12] Do not call Notification.setSound with empty ringtone Closes https://github.com/WhisperSystems/TextSecure/pull/6293 // FREEBIE Upstream commit: https://github.com/WhisperSystems/Signal-Android/commit/44bb2c7c72c54e0bbcd7b044bfa0b41b0bf7669f --- .../smssecure/notifications/AbstractNotificationBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/smssecure/smssecure/notifications/AbstractNotificationBuilder.java b/src/org/smssecure/smssecure/notifications/AbstractNotificationBuilder.java index 0bff4bce4..1810f69d3 100644 --- a/src/org/smssecure/smssecure/notifications/AbstractNotificationBuilder.java +++ b/src/org/smssecure/smssecure/notifications/AbstractNotificationBuilder.java @@ -42,8 +42,8 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui String defaultRingtoneName = SilencePreferences.getNotificationRingtone(context); boolean defaultVibrate = SilencePreferences.isNotificationVibrateEnabled(context); - if (ringtone != null) setSound(ringtone); - else if (!TextUtils.isEmpty(defaultRingtoneName)) setSound(Uri.parse(defaultRingtoneName)); + if (ringtone == null && !TextUtils.isEmpty(defaultRingtoneName)) setSound(Uri.parse(defaultRingtoneName)); + else if (ringtone != null && !ringtone.toString().isEmpty()) setSound(ringtone); if (vibrate != null && (vibrate == RecipientPreferenceDatabase.VibrateState.ENABLED || -- GitLab From d74b06920262f0c66f3fec3fafb3af894fd1346d Mon Sep 17 00:00:00 2001 From: Christian Ascheberg Date: Sat, 25 Feb 2017 16:13:28 +0100 Subject: [PATCH 11/12] Fix unread messages timestamp comparison Closes https://github.com/WhisperSystems/TextSecure/pull/6302 // FREEBIE Upstream commit: https://github.com/WhisperSystems/Signal-Android/commit/25daf01307a96c825064dacc3f4670b1a7834ac3 --- src/org/smssecure/smssecure/ConversationAdapter.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/org/smssecure/smssecure/ConversationAdapter.java b/src/org/smssecure/smssecure/ConversationAdapter.java index f07368230..84c23bc65 100644 --- a/src/org/smssecure/smssecure/ConversationAdapter.java +++ b/src/org/smssecure/smssecure/ConversationAdapter.java @@ -277,7 +277,7 @@ public class ConversationAdapter Cursor cursor = getCursorAtPositionOrThrow(i); MessageRecord messageRecord = getMessageRecord(cursor); - if (messageRecord.getTimestamp() < lastSeen) { + if (messageRecord.getDateReceived() <= lastSeen) { return i; } } @@ -320,7 +320,7 @@ public class ConversationAdapter return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR)); } - public long getTimestamp(int position) { + public long getReceivedTimestamp(int position) { if (!isActiveCursor()) return 0; if (isHeaderPosition(position)) return 0; if (isFooterPosition(position)) return 0; @@ -330,7 +330,7 @@ public class ConversationAdapter Cursor cursor = getCursorAtPositionOrThrow(position); MessageRecord messageRecord = getMessageRecord(cursor); - return messageRecord.getTimestamp(); + return messageRecord.getDateReceived(); } @Override @@ -373,8 +373,8 @@ public class ConversationAdapter return false; } - long currentRecordTimestamp = adapter.getTimestamp(position); - long previousRecordTimestamp = adapter.getTimestamp(position + 1); + long currentRecordTimestamp = adapter.getReceivedTimestamp(position); + long previousRecordTimestamp = adapter.getReceivedTimestamp(position + 1); return (currentRecordTimestamp > lastSeenTimestamp) && (previousRecordTimestamp < lastSeenTimestamp); } -- GitLab From 8703bf7e791d9ac4bc4da726f311ab0a9f292eb5 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Sun, 26 Feb 2017 10:49:48 -0800 Subject: [PATCH 12/12] Don't let outgoing messages trigger last seen divider Closes https://github.com/WhisperSystems/TextSecure/pull/6306 // FREEBIE Upstream commit: https://github.com/WhisperSystems/Signal-Android/commit/8f96cff1043ea57ac429e9044790e6bd18c2a7f4 --- .../smssecure/smssecure/ConversationAdapter.java | 5 +++-- .../smssecure/smssecure/ConversationFragment.java | 15 +++++++++------ .../smssecure/smssecure/database/MmsDatabase.java | 1 + .../smssecure/smssecure/database/SmsDatabase.java | 1 + 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/org/smssecure/smssecure/ConversationAdapter.java b/src/org/smssecure/smssecure/ConversationAdapter.java index 84c23bc65..01d485917 100644 --- a/src/org/smssecure/smssecure/ConversationAdapter.java +++ b/src/org/smssecure/smssecure/ConversationAdapter.java @@ -277,7 +277,7 @@ public class ConversationAdapter Cursor cursor = getCursorAtPositionOrThrow(i); MessageRecord messageRecord = getMessageRecord(cursor); - if (messageRecord.getDateReceived() <= lastSeen) { + if (messageRecord.isOutgoing() || messageRecord.getDateReceived() <= lastSeen) { return i; } } @@ -330,7 +330,8 @@ public class ConversationAdapter Cursor cursor = getCursorAtPositionOrThrow(position); MessageRecord messageRecord = getMessageRecord(cursor); - return messageRecord.getDateReceived(); + if (messageRecord.isOutgoing()) return 0; + else return messageRecord.getDateReceived(); } @Override diff --git a/src/org/smssecure/smssecure/ConversationFragment.java b/src/org/smssecure/smssecure/ConversationFragment.java index 5fa0f61e0..b90522a66 100644 --- a/src/org/smssecure/smssecure/ConversationFragment.java +++ b/src/org/smssecure/smssecure/ConversationFragment.java @@ -405,6 +405,7 @@ public class ConversationFragment extends Fragment @Override public void onLoadFinished(Loader cursorLoader, Cursor cursor) { + Log.w(TAG, "onLoadFinished"); ConversationLoader loader = (ConversationLoader)cursorLoader; if (list.getAdapter() != null) { @@ -420,10 +421,16 @@ public class ConversationFragment extends Fragment getListAdapter().changeCursor(cursor); + int lastSeenPosition = getListAdapter().findLastSeenPosition(lastSeen); + if (firstLoad) { - scrollToLastSeenPosition(lastSeen); + scrollToLastSeenPosition(lastSeenPosition); firstLoad = false; } + + if (lastSeenPosition <= 0) { + setLastSeen(0); + } } } @@ -434,9 +441,7 @@ public class ConversationFragment extends Fragment } } - private void scrollToLastSeenPosition(long lastSeen) { - final int lastSeenPosition = getListAdapter().findLastSeenPosition(lastSeen); - + private void scrollToLastSeenPosition(final int lastSeenPosition) { if (lastSeenPosition > 0) { list.post(new Runnable() { @Override @@ -444,8 +449,6 @@ public class ConversationFragment extends Fragment ((LinearLayoutManager)list.getLayoutManager()).scrollToPositionWithOffset(lastSeenPosition, list.getHeight()); } }); - } else { - setLastSeen(0); } } diff --git a/src/org/smssecure/smssecure/database/MmsDatabase.java b/src/org/smssecure/smssecure/database/MmsDatabase.java index 1f43b599a..89a71b1f2 100644 --- a/src/org/smssecure/smssecure/database/MmsDatabase.java +++ b/src/org/smssecure/smssecure/database/MmsDatabase.java @@ -685,6 +685,7 @@ public class MmsDatabase extends MessagingDatabase { long messageId = insertMediaMessage(masterSecret, addresses, message.getBody(), message.getAttachments(), contentValues); + DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId); jobManager.add(new TrimThreadJob(context, threadId)); return messageId; diff --git a/src/org/smssecure/smssecure/database/SmsDatabase.java b/src/org/smssecure/smssecure/database/SmsDatabase.java index 365d4de06..1164092ea 100644 --- a/src/org/smssecure/smssecure/database/SmsDatabase.java +++ b/src/org/smssecure/smssecure/database/SmsDatabase.java @@ -455,6 +455,7 @@ public class SmsDatabase extends MessagingDatabase { long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues); DatabaseFactory.getThreadDatabase(context).update(threadId, true); + DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId); notifyConversationListeners(threadId); jobManager.add(new TrimThreadJob(context, threadId)); -- GitLab