diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 257b2d8752..32f780434a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -173,6 +173,16 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
diff --git a/res/drawable-v21/conversation_list_item_background.xml b/res/drawable-v21/conversation_list_item_read_background_dark.xml
similarity index 82%
rename from res/drawable-v21/conversation_list_item_background.xml
rename to res/drawable-v21/conversation_list_item_read_background_dark.xml
index 642879178e..19f004e729 100644
--- a/res/drawable-v21/conversation_list_item_background.xml
+++ b/res/drawable-v21/conversation_list_item_read_background_dark.xml
@@ -5,6 +5,7 @@
-
+
diff --git a/res/drawable-xhdpi/ic_archive_white_24dp.png b/res/drawable-xhdpi/ic_archive_white_24dp.png
new file mode 100644
index 0000000000..3513bd9fef
Binary files /dev/null and b/res/drawable-xhdpi/ic_archive_white_24dp.png differ
diff --git a/res/drawable-xhdpi/ic_unarchive_white_24dp.png b/res/drawable-xhdpi/ic_unarchive_white_24dp.png
new file mode 100644
index 0000000000..a0a1509a16
Binary files /dev/null and b/res/drawable-xhdpi/ic_unarchive_white_24dp.png differ
diff --git a/res/drawable-xhdpi/ic_unarchive_white_36dp.png b/res/drawable-xhdpi/ic_unarchive_white_36dp.png
new file mode 100644
index 0000000000..20d015751f
Binary files /dev/null and b/res/drawable-xhdpi/ic_unarchive_white_36dp.png differ
diff --git a/res/drawable-xxhdpi/ic_archive_white_24dp.png b/res/drawable-xxhdpi/ic_archive_white_24dp.png
new file mode 100644
index 0000000000..00e04e42bf
Binary files /dev/null and b/res/drawable-xxhdpi/ic_archive_white_24dp.png differ
diff --git a/res/drawable-xxhdpi/ic_unarchive_white_24dp.png b/res/drawable-xxhdpi/ic_unarchive_white_24dp.png
new file mode 100644
index 0000000000..20d015751f
Binary files /dev/null and b/res/drawable-xxhdpi/ic_unarchive_white_24dp.png differ
diff --git a/res/drawable-xxhdpi/ic_unarchive_white_36dp.png b/res/drawable-xxhdpi/ic_unarchive_white_36dp.png
new file mode 100644
index 0000000000..fefcb36e84
Binary files /dev/null and b/res/drawable-xxhdpi/ic_unarchive_white_36dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_archive_white_24dp.png b/res/drawable-xxxhdpi/ic_archive_white_24dp.png
new file mode 100644
index 0000000000..34cd3fd805
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_archive_white_24dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_archive_white_36dp.png b/res/drawable-xxxhdpi/ic_archive_white_36dp.png
new file mode 100644
index 0000000000..5e1e2af823
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_archive_white_36dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_archive_white_48dp.png b/res/drawable-xxxhdpi/ic_archive_white_48dp.png
new file mode 100644
index 0000000000..6fb64c404b
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_archive_white_48dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_unarchive_white_24dp.png b/res/drawable-xxxhdpi/ic_unarchive_white_24dp.png
new file mode 100644
index 0000000000..a789520baa
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_unarchive_white_24dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_unarchive_white_36dp.png b/res/drawable-xxxhdpi/ic_unarchive_white_36dp.png
new file mode 100644
index 0000000000..5e4dd3ddf1
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_unarchive_white_36dp.png differ
diff --git a/res/drawable/conversation_list_item_read_background.xml b/res/drawable/conversation_list_item_read_background.xml
new file mode 100644
index 0000000000..45108a5b76
--- /dev/null
+++ b/res/drawable/conversation_list_item_read_background.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/conversation_list_item_background.xml b/res/drawable/conversation_list_item_read_background_dark.xml
similarity index 78%
rename from res/drawable/conversation_list_item_background.xml
rename to res/drawable/conversation_list_item_read_background_dark.xml
index 8a28201a1a..c5153ed188 100644
--- a/res/drawable/conversation_list_item_background.xml
+++ b/res/drawable/conversation_list_item_read_background_dark.xml
@@ -2,4 +2,5 @@
+
\ No newline at end of file
diff --git a/res/drawable/rounded_rectangle.xml b/res/drawable/rounded_rectangle.xml
new file mode 100644
index 0000000000..4af7432fd4
--- /dev/null
+++ b/res/drawable/rounded_rectangle.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/contact_selection_list_item.xml b/res/layout/contact_selection_list_item.xml
index 4f9d121420..2f842b03e5 100644
--- a/res/layout/contact_selection_list_item.xml
+++ b/res/layout/contact_selection_list_item.xml
@@ -8,7 +8,6 @@
android:layout_height="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:gravity="center_vertical"
- android:background="@drawable/conversation_list_item_background"
android:paddingLeft="48dp"
android:paddingRight="20dp">
@@ -18,7 +17,7 @@
android:layout_height="40dp"
android:foreground="@drawable/contact_photo_background"
android:cropToPadding="true"
- tools:src="@color/md_material_blue_600"
+ tools:src="@color/blue_600"
android:layout_marginRight="10dp"
android:contentDescription="@string/SingleContactSelectionActivity_contact_photo" />
diff --git a/res/layout/conversation_list_fragment.xml b/res/layout/conversation_list_fragment.xml
index 9dcc31fa54..961349a88b 100644
--- a/res/layout/conversation_list_fragment.xml
+++ b/res/layout/conversation_list_fragment.xml
@@ -1,6 +1,7 @@
-
-
+ android:contentDescription="@string/conversation_list_fragment__fab_content_description"/>
-
+
diff --git a/res/layout/conversation_list_item_action.xml b/res/layout/conversation_list_item_action.xml
new file mode 100644
index 0000000000..77d2bc8375
--- /dev/null
+++ b/res/layout/conversation_list_item_action.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/conversation_list_item_view.xml b/res/layout/conversation_list_item_view.xml
index 1965dfeda4..94624a9e04 100644
--- a/res/layout/conversation_list_item_view.xml
+++ b/res/layout/conversation_list_item_view.xml
@@ -4,7 +4,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:background="@drawable/conversation_list_item_background"
android:layout_height="70dp">
+
+
+
diff --git a/res/menu/conversation.xml b/res/menu/conversation.xml
index d57a0beaf5..81587e84c1 100644
--- a/res/menu/conversation.xml
+++ b/res/menu/conversation.xml
@@ -7,9 +7,6 @@
-
-
diff --git a/res/menu/conversation_list_batch.xml b/res/menu/conversation_list_batch.xml
index a61042f5d5..3df65d81de 100644
--- a/res/menu/conversation_list_batch.xml
+++ b/res/menu/conversation_list_batch.xml
@@ -5,10 +5,11 @@
+ app:showAsAction="always" />
+ android:icon="?menu_selectall_icon"
+ app:showAsAction="always"/>
\ No newline at end of file
diff --git a/res/menu/conversation_list_batch_archive.xml b/res/menu/conversation_list_batch_archive.xml
new file mode 100644
index 0000000000..1c91c1b358
--- /dev/null
+++ b/res/menu/conversation_list_batch_archive.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/res/menu/conversation_list_batch_unarchive.xml b/res/menu/conversation_list_batch_unarchive.xml
new file mode 100644
index 0000000000..ecb7bb310b
--- /dev/null
+++ b/res/menu/conversation_list_batch_unarchive.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index bcf175759f..e2de5006b3 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -24,7 +24,7 @@
#7F111111
- #ffffffff
+ @color/gray5
#ffffffff
#ff000000
#ff333333
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5a99e42358..c1937aea22 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -37,6 +37,7 @@
150dp
70dp
+ 16dp
135dip
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 59f88cd492..4933f80e9a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -174,10 +174,18 @@
Deleting
Deleting selected threads...
+ Archived conversations
+ UNDO
+ Moved conversation to inbox
+ Archived conversation
+ Moved conversations to inbox
Key exchange message...
+
+ Archived conversations (%d)
+
Using custom: %s
Using default: %s
@@ -867,6 +875,7 @@
Message details
Manage linked devices
Invite friends
+ Conversations archive
Import / export
@@ -1048,6 +1057,7 @@
Delete selected
Select all
+ Archive selected
Search
@@ -1055,6 +1065,7 @@
Contact Photo Image
Error alert
+ Archived
New conversation
@@ -1151,6 +1162,7 @@
Transport icon
+
diff --git a/res/values/themes.xml b/res/values/themes.xml
index c8b7f4f380..4e746c6e04 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -95,7 +95,7 @@
- @color/white
- @drawable/list_selected_holo_light
- @drawable/conversation_list_item_unread_background
- - @drawable/conversation_list_item_background
+ - @drawable/conversation_list_item_read_background
- #66333333
- #FF333333
- #FF444444
@@ -205,12 +205,13 @@
- @style/ThemeOverlay.AppCompat.Dark
- @color/text_color_dark_theme
- @color/text_color_secondary_dark_theme
+ - @color/textsecure_primary_dark
- @color/signal_primary_dark
- @color/signal_primary_dark
- @color/black
- @drawable/list_selected_holo_dark
- @drawable/conversation_list_item_unread_background_dark
- - @drawable/conversation_list_item_background
+ - @drawable/conversation_list_item_read_background_dark
- #66dddddd
- #ffdddddd
- #ffdddddd
diff --git a/src/org/thoughtcrime/securesms/BindableConversationListItem.java b/src/org/thoughtcrime/securesms/BindableConversationListItem.java
new file mode 100644
index 0000000000..1da1ac2864
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/BindableConversationListItem.java
@@ -0,0 +1,15 @@
+package org.thoughtcrime.securesms;
+
+import android.support.annotation.NonNull;
+
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.database.model.ThreadRecord;
+
+import java.util.Locale;
+import java.util.Set;
+
+public interface BindableConversationListItem extends Unbindable {
+
+ public void bind(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread,
+ @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode);
+}
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index d07eace41c..12bb463ad9 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -392,7 +392,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
switch (item.getItemId()) {
case R.id.menu_call_secure:
case R.id.menu_call_insecure: handleDial(getRecipients().getPrimaryRecipient()); return true;
- case R.id.menu_delete_thread: handleDeleteThread(); return true;
case R.id.menu_add_attachment: handleAddAttachment(); return true;
case R.id.menu_view_media: handleViewMedia(); return true;
case R.id.menu_add_to_contacts: handleAddToContacts(); return true;
@@ -650,28 +649,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
new GroupMembersDialog(this, getRecipients()).display();
}
- private void handleDeleteThread() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.ConversationActivity_delete_thread_question);
- builder.setIconAttribute(R.attr.dialog_alert_icon);
- builder.setCancelable(true);
- builder.setMessage(R.string.ConversationActivity_this_will_permanently_delete_all_messages_in_this_conversation);
- builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (threadId > 0) {
- DatabaseFactory.getThreadDatabase(ConversationActivity.this).deleteConversation(threadId);
- }
- composeText.getText().clear();
- threadId = -1;
- finish();
- }
- });
-
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.show();
- }
-
private void handleAddToContacts() {
final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipients.getPrimaryRecipient().getNumber());
@@ -1089,9 +1066,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
draftDatabase.insertDrafts(new MasterCipher(thisMasterSecret), threadId, drafts);
threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this),
drafts.getUriSnippet(ConversationActivity.this),
- System.currentTimeMillis(), Types.BASE_DRAFT_TYPE);
+ System.currentTimeMillis(), Types.BASE_DRAFT_TYPE, true);
} else if (threadId > 0) {
- threadDatabase.update(threadId);
+ threadDatabase.update(threadId, false);
}
return threadId;
diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java
index 56fcadc332..a2297fc270 100644
--- a/src/org/thoughtcrime/securesms/ConversationFragment.java
+++ b/src/org/thoughtcrime/securesms/ConversationFragment.java
@@ -57,7 +57,7 @@ import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.MessageSender;
-import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
+import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
import org.thoughtcrime.securesms.util.ViewUtil;
diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java
index 0428eb4ff1..cff90af531 100644
--- a/src/org/thoughtcrime/securesms/ConversationListActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java
@@ -161,15 +161,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
@Override
public void onCreateConversation(long threadId, Recipients recipients, int distributionType) {
- createConversation(threadId, recipients, distributionType);
- }
-
- private void createGroup() {
- Intent intent = new Intent(this, GroupCreateActivity.class);
- startActivity(intent);
- }
-
- private void createConversation(long threadId, Recipients recipients, int distributionType) {
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients.getIds());
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
@@ -179,6 +170,17 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out);
}
+ @Override
+ public void onSwitchToArchive() {
+ Intent intent = new Intent(this, ConversationListArchiveActivity.class);
+ startActivity(intent);
+ }
+
+ private void createGroup() {
+ Intent intent = new Intent(this, GroupCreateActivity.class);
+ startActivity(intent);
+ }
+
private void handleDisplaySettings() {
Intent preferencesIntent = new Intent(this, ApplicationPreferencesActivity.class);
startActivity(preferencesIntent);
diff --git a/src/org/thoughtcrime/securesms/ConversationListAdapter.java b/src/org/thoughtcrime/securesms/ConversationListAdapter.java
index 3325b699db..e29737ce82 100644
--- a/src/org/thoughtcrime/securesms/ConversationListAdapter.java
+++ b/src/org/thoughtcrime/securesms/ConversationListAdapter.java
@@ -49,6 +49,9 @@ import java.util.Set;
*/
public class ConversationListAdapter extends CursorRecyclerViewAdapter {
+ private static final int MESSAGE_TYPE_SWITCH_ARCHIVE = 1;
+ private static final int MESSAGE_TYPE_THREAD = 2;
+
private final ThreadDatabase threadDatabase;
private final MasterSecret masterSecret;
private final MasterCipher masterCipher;
@@ -61,37 +64,25 @@ public class ConversationListAdapter extends CursorRecyclerViewAdapter ViewHolder(final @NonNull V itemView)
{
super(itemView);
- itemView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- if (clickListener != null) clickListener.onItemClick(itemView);
- }
- });
- itemView.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(View view) {
- if (clickListener != null) clickListener.onItemLongClick(itemView);
- return true;
- }
- });
}
- public ConversationListItem getItem() {
- return (ConversationListItem)itemView;
+ public BindableConversationListItem getItem() {
+ return (BindableConversationListItem)itemView;
}
}
@Override
public long getItemId(@NonNull Cursor cursor) {
- ThreadRecord record = getThreadRecord(cursor);
- StringBuilder builder = new StringBuilder(""+record.getThreadId());
+ ThreadRecord record = getThreadRecord(cursor);
+ StringBuilder builder = new StringBuilder("" + record.getThreadId());
+
for (long recipientId : record.getRecipients().getIds()) {
builder.append("::").append(recipientId);
}
+
return Conversions.byteArrayToLong(digest.digest(builder.toString().getBytes()));
}
@@ -116,10 +107,51 @@ public class ConversationListAdapter extends CursorRecyclerViewAdapter, ActionMode.Callback, ItemClickListener
{
+
+ public static final String ARCHIVE = "archive";
+
private MasterSecret masterSecret;
private ActionMode actionMode;
private RecyclerView list;
@@ -76,27 +91,39 @@ public class ConversationListFragment extends Fragment
private FloatingActionButton fab;
private Locale locale;
private String queryFilter = "";
+ private boolean archive;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
masterSecret = getArguments().getParcelable("master_secret");
locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA);
+ archive = getArguments().getBoolean(ARCHIVE, false);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
final View view = inflater.inflate(R.layout.conversation_list_fragment, container, false);
- reminderView = (ReminderView) view.findViewById(R.id.reminder);
- list = (RecyclerView) view.findViewById(R.id.list);
- fab = (FloatingActionButton) view.findViewById(R.id.fab);
+
+ reminderView = ViewUtil.findById(view, R.id.reminder);
+ list = ViewUtil.findById(view, R.id.list);
+ fab = ViewUtil.findById(view, R.id.fab);
+
+ if (archive) fab.setVisibility(View.GONE);
+ else fab.setVisibility(View.VISIBLE);
+
reminderView.setOnDismissListener(new OnDismissListener() {
- @Override public void onDismiss() {
+ @Override
+ public void onDismiss() {
updateReminders();
}
});
+
list.setHasFixedSize(true);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
+
+ new ItemTouchHelper(new ArchiveListenerCallback()).attachToRecyclerView(list);
+
return view;
}
@@ -170,6 +197,49 @@ public class ConversationListFragment extends Fragment
getLoaderManager().restartLoader(0, null, this);
}
+ private void handleArchiveAllSelected() {
+ final Set selectedConversations = new HashSet<>(getListAdapter().getBatchSelections());
+ final boolean archive = this.archive;
+
+ String snackBarTitle;
+
+ if (archive) snackBarTitle = getString(R.string.ConversationListFragment_moved_conversations_to_inbox);
+ else snackBarTitle = getString(R.string.ConversationListFragment_archived_conversations);
+
+ new SnackbarAsyncTask(getView(), snackBarTitle,
+ getString(R.string.ConversationListFragment_undo),
+ getResources().getColor(R.color.amber_500),
+ Snackbar.LENGTH_LONG, true)
+ {
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+
+ if (actionMode != null) {
+ actionMode.finish();
+ actionMode = null;
+ }
+ }
+
+ @Override
+ protected void executeAction(@Nullable Void parameter) {
+ for (long threadId : selectedConversations) {
+ if (!archive) DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId);
+ else DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId);
+ }
+ }
+
+ @Override
+ protected void reverseAction(@Nullable Void parameter) {
+ for (long threadId : selectedConversations) {
+ if (!archive) DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId);
+ else DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId);
+ }
+ }
+ }.execute();
+ }
+
private void handleDeleteAllSelected() {
int conversationsCount = getListAdapter().getBatchSelections().size();
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
@@ -225,7 +295,7 @@ public class ConversationListFragment extends Fragment
private void handleSelectAllThreads() {
getListAdapter().selectAllThreads();
actionMode.setSubtitle(getString(R.string.conversation_fragment_cab__batch_selection_amount,
- getListAdapter().getBatchSelections().size()));
+ getListAdapter().getBatchSelections().size()));
}
private void handleCreateConversation(long threadId, Recipients recipients, int distributionType) {
@@ -234,7 +304,7 @@ public class ConversationListFragment extends Fragment
@Override
public Loader onCreateLoader(int arg0, Bundle arg1) {
- return new ConversationListLoader(getActivity(), queryFilter);
+ return new ConversationListLoader(getActivity(), queryFilter, archive);
}
@Override
@@ -276,21 +346,30 @@ public class ConversationListFragment extends Fragment
getListAdapter().notifyDataSetChanged();
}
+ @Override
+ public void onSwitchToArchive() {
+ ((ConversationSelectedListener)getActivity()).onSwitchToArchive();
+ }
+
public interface ConversationSelectedListener {
void onCreateConversation(long threadId, Recipients recipients, int distributionType);
+ void onSwitchToArchive();
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = getActivity().getMenuInflater();
+
+ if (archive) inflater.inflate(R.menu.conversation_list_batch_unarchive, menu);
+ else inflater.inflate(R.menu.conversation_list_batch_archive, menu);
+
inflater.inflate(R.menu.conversation_list_batch, menu);
mode.setTitle(R.string.conversation_fragment_cab__batch_selection_mode);
mode.setSubtitle(getString(R.string.conversation_fragment_cab__batch_selection_amount, 1));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- getActivity().getWindow()
- .setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
+ getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
}
return true;
@@ -304,8 +383,9 @@ public class ConversationListFragment extends Fragment
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
- case R.id.menu_select_all: handleSelectAllThreads(); return true;
- case R.id.menu_delete_selected: handleDeleteAllSelected(); return true;
+ case R.id.menu_select_all: handleSelectAllThreads(); return true;
+ case R.id.menu_delete_selected: handleDeleteAllSelected(); return true;
+ case R.id.menu_archive_selected: handleArchiveAllSelected(); return true;
}
return false;
@@ -316,8 +396,7 @@ public class ConversationListFragment extends Fragment
getListAdapter().initializeBatchMode(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- TypedArray color = getActivity().getTheme()
- .obtainStyledAttributes(new int[] { android.R.attr.statusBarColor });
+ TypedArray color = getActivity().getTheme().obtainStyledAttributes(new int[] {android.R.attr.statusBarColor});
getActivity().getWindow().setStatusBarColor(color.getColor(0, Color.BLACK));
color.recycle();
}
@@ -325,6 +404,114 @@ public class ConversationListFragment extends Fragment
actionMode = null;
}
+ private class ArchiveListenerCallback extends ItemTouchHelper.SimpleCallback {
+
+ public ArchiveListenerCallback() {
+ super(0, ItemTouchHelper.RIGHT);
+ }
+
+ @Override
+ public boolean onMove(RecyclerView recyclerView,
+ RecyclerView.ViewHolder viewHolder,
+ RecyclerView.ViewHolder target)
+ {
+ return false;
+ }
+
+ @Override
+ public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+ if (viewHolder.itemView instanceof ConversationListItemAction) {
+ return 0;
+ }
+
+ if (actionMode != null) {
+ return 0;
+ }
+
+ return super.getSwipeDirs(recyclerView, viewHolder);
+ }
+
+ @Override
+ public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
+ final long threadId = ((ConversationListItem)viewHolder.itemView).getThreadId();
+
+ if (archive) {
+ new SnackbarAsyncTask(getView(),
+ getString(R.string.ConversationListFragment_moved_conversation_to_inbox),
+ getString(R.string.ConversationListFragment_undo),
+ getResources().getColor(R.color.amber_500),
+ Snackbar.LENGTH_SHORT, false)
+ {
+ @Override
+ protected void executeAction(@Nullable Long parameter) {
+ DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId);
+ }
+
+ @Override
+ protected void reverseAction(@Nullable Long parameter) {
+ DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId);
+ }
+ }.execute(threadId);
+ } else {
+ new SnackbarAsyncTask(getView(),
+ getString(R.string.ConversationListFragment_archived_conversation),
+ getString(R.string.ConversationListFragment_undo),
+ getResources().getColor(R.color.amber_500),
+ Snackbar.LENGTH_SHORT, false)
+ {
+ @Override
+ protected void executeAction(@Nullable Long parameter) {
+ DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId);
+ }
+
+ @Override
+ protected void reverseAction(@Nullable Long parameter) {
+ DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId);
+ }
+ }.execute(threadId);
+ }
+ }
+
+ @Override
+ public void onChildDraw(Canvas c, RecyclerView recyclerView,
+ RecyclerView.ViewHolder viewHolder,
+ float dX, float dY, int actionState,
+ boolean isCurrentlyActive)
+ {
+
+ if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
+ View itemView = viewHolder.itemView;
+ Paint p = new Paint();
+
+ if (dX > 0) {
+ Bitmap icon;
+
+ if (archive) icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_unarchive_white_36dp);
+ else icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_archive_white_36dp);
+
+ p.setColor(getResources().getColor(R.color.green_500));
+
+ c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX,
+ (float) itemView.getBottom(), p);
+
+ c.drawBitmap(icon,
+ (float) itemView.getLeft() + getResources().getDimension(R.dimen.conversation_list_fragment_archive_padding),
+ (float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - icon.getHeight())/2,
+ p);
+ }
+
+ if (Build.VERSION.SDK_INT >= 11) {
+ float alpha = 1.0f - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
+ viewHolder.itemView.setAlpha(alpha);
+ viewHolder.itemView.setTranslationX(dX);
+ }
+
+ } else {
+ super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
+ }
+ }
+ }
+
}
diff --git a/src/org/thoughtcrime/securesms/ConversationListItem.java b/src/org/thoughtcrime/securesms/ConversationListItem.java
index 0f51dce854..366b09158c 100644
--- a/src/org/thoughtcrime/securesms/ConversationListItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationListItem.java
@@ -39,6 +39,7 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.ResUtil;
+import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Locale;
import java.util.Set;
@@ -53,7 +54,8 @@ import static org.thoughtcrime.securesms.util.SpanUtil.color;
*/
public class ConversationListItem extends RelativeLayout
- implements Recipients.RecipientsModifiedListener, Unbindable
+ implements Recipients.RecipientsModifiedListener,
+ BindableConversationListItem, Unbindable
{
private final static String TAG = ConversationListItem.class.getSimpleName();
@@ -66,6 +68,7 @@ public class ConversationListItem extends RelativeLayout
private TextView subjectView;
private FromTextView fromView;
private TextView dateView;
+ private TextView archivedView;
private boolean read;
private AvatarImageView contactPhotoImage;
private ThumbnailView thumbnailView;
@@ -94,11 +97,12 @@ public class ConversationListItem extends RelativeLayout
this.dateView = (TextView) findViewById(R.id.date);
this.contactPhotoImage = (AvatarImageView) findViewById(R.id.contact_photo_image);
this.thumbnailView = (ThumbnailView) findViewById(R.id.thumbnail);
+ this.archivedView = ViewUtil.findById(this, R.id.archived);
thumbnailView.setClickable(false);
}
- public void set(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread,
- @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode)
+ public void bind(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread,
+ @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode)
{
this.selectedThreads = selectedThreads;
this.recipients = thread.getRecipients();
@@ -118,6 +122,12 @@ public class ConversationListItem extends RelativeLayout
dateView.setTypeface(read ? LIGHT_TYPEFACE : BOLD_TYPEFACE);
}
+ if (thread.isArchived()) {
+ this.archivedView.setVisibility(View.VISIBLE);
+ } else {
+ this.archivedView.setVisibility(View.GONE);
+ }
+
setThumbnailSnippet(masterSecret, thread);
setBatchState(batchMode);
setBackground(thread);
@@ -158,7 +168,7 @@ public class ConversationListItem extends RelativeLayout
this.thumbnailView.setVisibility(View.GONE);
LayoutParams subjectParams = (RelativeLayout.LayoutParams)this.subjectView.getLayoutParams();
- subjectParams.addRule(RelativeLayout.LEFT_OF, 0);
+ subjectParams.addRule(RelativeLayout.LEFT_OF, R.id.archived);
this.subjectView.setLayoutParams(subjectParams);
}
}
@@ -187,4 +197,5 @@ public class ConversationListItem extends RelativeLayout
}
});
}
+
}
diff --git a/src/org/thoughtcrime/securesms/ConversationListItemAction.java b/src/org/thoughtcrime/securesms/ConversationListItemAction.java
new file mode 100644
index 0000000000..6b49f7360e
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/ConversationListItemAction.java
@@ -0,0 +1,50 @@
+package org.thoughtcrime.securesms;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.database.model.ThreadRecord;
+import org.thoughtcrime.securesms.util.ViewUtil;
+
+import java.util.Locale;
+import java.util.Set;
+
+public class ConversationListItemAction extends LinearLayout implements BindableConversationListItem {
+
+ private TextView description;
+
+ public ConversationListItemAction(Context context) {
+ super(context);
+ }
+
+ public ConversationListItemAction(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public ConversationListItemAction(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public void onFinishInflate() {
+ super.onFinishInflate();
+ this.description = ViewUtil.findById(this, R.id.description);
+ }
+
+ @Override
+ public void bind(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread, @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode) {
+ this.description.setText(getContext().getString(R.string.ConversationListItemAction_archived_conversations_d, thread.getCount()));
+ }
+
+ @Override
+ public void unbind() {
+
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/DeviceActivity.java b/src/org/thoughtcrime/securesms/DeviceActivity.java
index d92e6a7d5e..0e2c8cf686 100644
--- a/src/org/thoughtcrime/securesms/DeviceActivity.java
+++ b/src/org/thoughtcrime/securesms/DeviceActivity.java
@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.push.TextSecureCommunicationFactory;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
-import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
+import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.IdentityKeyPair;
diff --git a/src/org/thoughtcrime/securesms/DeviceListFragment.java b/src/org/thoughtcrime/securesms/DeviceListFragment.java
index 23d8b19eff..762742066d 100644
--- a/src/org/thoughtcrime/securesms/DeviceListFragment.java
+++ b/src/org/thoughtcrime/securesms/DeviceListFragment.java
@@ -22,7 +22,7 @@ import com.melnykov.fab.FloatingActionButton;
import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
import org.thoughtcrime.securesms.dependencies.InjectableType;
-import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
+import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.textsecure.api.TextSecureAccountManager;
diff --git a/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java b/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java
index 167a8ca212..0cf49f65f0 100644
--- a/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java
+++ b/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java
@@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.push.TextSecureCommunicationFactory;
import org.thoughtcrime.securesms.util.Base64;
-import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
+import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyException;
diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java
index c189ce46b8..511283aab6 100644
--- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java
+++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java
@@ -65,7 +65,7 @@ import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.GroupUtil;
-import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
+import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter;
import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter.OnRecipientDeletedListener;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
diff --git a/src/org/thoughtcrime/securesms/InviteActivity.java b/src/org/thoughtcrime/securesms/InviteActivity.java
index 0b83cbe1a3..bf3b79754a 100644
--- a/src/org/thoughtcrime/securesms/InviteActivity.java
+++ b/src/org/thoughtcrime/securesms/InviteActivity.java
@@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
-import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
+import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
diff --git a/src/org/thoughtcrime/securesms/ShareFragment.java b/src/org/thoughtcrime/securesms/ShareFragment.java
index 0577077dfa..9a0a7b1472 100644
--- a/src/org/thoughtcrime/securesms/ShareFragment.java
+++ b/src/org/thoughtcrime/securesms/ShareFragment.java
@@ -90,7 +90,7 @@ public class ShareFragment extends ListFragment implements LoaderManager.LoaderC
@Override
public Loader onCreateLoader(int arg0, Bundle arg1) {
- return new ConversationListLoader(getActivity(), null);
+ return new ConversationListLoader(getActivity(), null, false);
}
@Override
diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
index 18cbd066cb..4d16f89676 100644
--- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
+++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
@@ -68,7 +68,8 @@ public class DatabaseFactory {
private static final int INTRODUCED_DB_OPTIMIZATIONS_VERSION = 21;
private static final int INTRODUCED_INVITE_REMINDERS_VERSION = 22;
private static final int INTRODUCED_CONVERSATION_LIST_THUMBNAILS_VERSION = 23;
- private static final int DATABASE_VERSION = 23;
+ private static final int INTRODUCED_ARCHIVE_VERSION = 24;
+ private static final int DATABASE_VERSION = 24;
private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object();
@@ -778,6 +779,11 @@ public class DatabaseFactory {
db.execSQL("ALTER TABLE thread ADD COLUMN snippet_uri TEXT DEFAULT NULL");
}
+ if (oldVersion < INTRODUCED_ARCHIVE_VERSION) {
+ db.execSQL("ALTER TABLE thread ADD COLUMN archived INTEGER DEFAULT 0");
+ db.execSQL("CREATE INDEX IF NOT EXISTS archived_index ON thread (archived)");
+ }
+
db.setTransactionSuccessful();
db.endTransaction();
}
diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
index 037052accb..a942341746 100644
--- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
@@ -449,7 +449,7 @@ public class MmsDatabase extends MessagingDatabase {
long threadId = getThreadIdForMessage(messageId);
- DatabaseFactory.getThreadDatabase(context).update(threadId);
+ DatabaseFactory.getThreadDatabase(context).update(threadId, true);
notifyConversationListeners(threadId);
notifyConversationListListeners();
@@ -604,7 +604,7 @@ public class MmsDatabase extends MessagingDatabase {
contentValues);
DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
- DatabaseFactory.getThreadDatabase(context).update(threadId);
+ DatabaseFactory.getThreadDatabase(context).update(threadId, true);
notifyConversationListeners(threadId);
jobManager.add(new TrimThreadJob(context, threadId));
@@ -692,7 +692,7 @@ public class MmsDatabase extends MessagingDatabase {
public void markIncomingNotificationReceived(long threadId) {
notifyConversationListeners(threadId);
- DatabaseFactory.getThreadDatabase(context).update(threadId);
+ DatabaseFactory.getThreadDatabase(context).update(threadId, true);
if (org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context)) {
DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
@@ -808,7 +808,7 @@ public class MmsDatabase extends MessagingDatabase {
db.endTransaction();
notifyConversationListeners(contentValues.getAsLong(THREAD_ID));
- DatabaseFactory.getThreadDatabase(context).update(contentValues.getAsLong(THREAD_ID));
+ DatabaseFactory.getThreadDatabase(context).update(contentValues.getAsLong(THREAD_ID), true);
}
}
@@ -821,7 +821,7 @@ public class MmsDatabase extends MessagingDatabase {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
- boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId);
+ boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false);
notifyConversationListeners(threadId);
return threadDeleted;
}
diff --git a/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java b/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java
index ac56bc95d0..c1cf58c30d 100644
--- a/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java
+++ b/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java
@@ -83,7 +83,7 @@ public class PlaintextBackupImporter {
}
for (long threadId : modifiedThreads) {
- threads.update(threadId);
+ threads.update(threadId, true);
}
Log.w("PlaintextBackupImporter", "Exited loop");
diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
index 0f17ebab66..365c471fae 100644
--- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
@@ -118,7 +118,7 @@ public class SmsDatabase extends MessagingDatabase {
long threadId = getThreadIdForMessage(id);
- DatabaseFactory.getThreadDatabase(context).update(threadId);
+ DatabaseFactory.getThreadDatabase(context).update(threadId, false);
notifyConversationListeners(threadId);
notifyConversationListListeners();
}
@@ -310,7 +310,7 @@ public class SmsDatabase extends MessagingDatabase {
long threadId = getThreadIdForMessage(messageId);
- DatabaseFactory.getThreadDatabase(context).update(threadId);
+ DatabaseFactory.getThreadDatabase(context).update(threadId, true);
notifyConversationListeners(threadId);
notifyConversationListListeners();
@@ -335,7 +335,7 @@ public class SmsDatabase extends MessagingDatabase {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long newMessageId = db.insert(TABLE_NAME, null, contentValues);
- DatabaseFactory.getThreadDatabase(context).update(record.getThreadId());
+ DatabaseFactory.getThreadDatabase(context).update(record.getThreadId(), true);
notifyConversationListeners(record.getThreadId());
jobManager.add(new TrimThreadJob(context, record.getThreadId()));
@@ -372,7 +372,7 @@ public class SmsDatabase extends MessagingDatabase {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long messageId = db.insert(TABLE_NAME, null, values);
- DatabaseFactory.getThreadDatabase(context).update(threadId);
+ DatabaseFactory.getThreadDatabase(context).update(threadId, true);
notifyConversationListeners(threadId);
jobManager.add(new TrimThreadJob(context, threadId));
@@ -450,7 +450,7 @@ public class SmsDatabase extends MessagingDatabase {
DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
}
- DatabaseFactory.getThreadDatabase(context).update(threadId);
+ DatabaseFactory.getThreadDatabase(context).update(threadId, true);
notifyConversationListeners(threadId);
jobManager.add(new TrimThreadJob(context, threadId));
@@ -481,7 +481,7 @@ public class SmsDatabase extends MessagingDatabase {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues);
- DatabaseFactory.getThreadDatabase(context).update(threadId);
+ DatabaseFactory.getThreadDatabase(context).update(threadId, true);
notifyConversationListeners(threadId);
jobManager.add(new TrimThreadJob(context, threadId));
@@ -526,7 +526,7 @@ public class SmsDatabase extends MessagingDatabase {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long threadId = getThreadIdForMessage(messageId);
db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
- boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId);
+ boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false);
notifyConversationListeners(threadId);
return threadDeleted;
}
diff --git a/src/org/thoughtcrime/securesms/database/SmsMigrator.java b/src/org/thoughtcrime/securesms/database/SmsMigrator.java
index 499219b9bf..f78318b7b0 100644
--- a/src/org/thoughtcrime/securesms/database/SmsMigrator.java
+++ b/src/org/thoughtcrime/securesms/database/SmsMigrator.java
@@ -197,7 +197,7 @@ public class SmsMigrator {
}
ourSmsDatabase.endTransaction(transaction);
- DatabaseFactory.getThreadDatabase(context).update(ourThreadId);
+ DatabaseFactory.getThreadDatabase(context).update(ourThreadId, true);
DatabaseFactory.getThreadDatabase(context).notifyConversationListeners(ourThreadId);
} finally {
diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java
index c16ab17b01..b468159661 100644
--- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java
@@ -59,19 +59,21 @@ public class ThreadDatabase extends Database {
public static final String SNIPPET = "snippet";
private static final String SNIPPET_CHARSET = "snippet_cs";
public static final String READ = "read";
- private static final String TYPE = "type";
+ public static final String TYPE = "type";
private static final String ERROR = "error";
public static final String SNIPPET_TYPE = "snippet_type";
- private static final String SNIPPET_URI = "snippet_uri";
+ public static final String SNIPPET_URI = "snippet_uri";
+ public static final String ARCHIVED = "archived";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
DATE + " INTEGER DEFAULT 0, " + MESSAGE_COUNT + " INTEGER DEFAULT 0, " +
RECIPIENT_IDS + " TEXT, " + SNIPPET + " TEXT, " + 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);";
+ SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " + ARCHIVED + " INTEGER DEFAULT 0);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + RECIPIENT_IDS + ");",
+ "CREATE INDEX IF NOT EXISTS archived_index ON " + TABLE_NAME + " (" + ARCHIVED + ");",
};
public ThreadDatabase(Context context, SQLiteOpenHelper databaseHelper) {
@@ -124,27 +126,36 @@ public class ThreadDatabase extends Database {
return db.insert(TABLE_NAME, null, contentValues);
}
- private void updateThread(long threadId, long count, String body, @Nullable Uri attachment, long date, long type)
+ private void updateThread(long threadId, long count, String body, @Nullable Uri attachment, long date, long type, boolean unarchive)
{
- ContentValues contentValues = new ContentValues(4);
+ ContentValues contentValues = new ContentValues(5);
contentValues.put(DATE, date - date % 1000);
contentValues.put(MESSAGE_COUNT, count);
contentValues.put(SNIPPET, body);
contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString());
contentValues.put(SNIPPET_TYPE, type);
+ if (unarchive) {
+ contentValues.put(ARCHIVED, 0);
+ }
+
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""});
notifyConversationListListeners();
}
- public void updateSnippet(long threadId, String snippet, @Nullable Uri attachment, long date, long type) {
+ public void updateSnippet(long threadId, String snippet, @Nullable Uri attachment, long date, long type, boolean unarchive) {
ContentValues contentValues = new ContentValues(3);
contentValues.put(DATE, date - date % 1000);
contentValues.put(SNIPPET, snippet);
contentValues.put(SNIPPET_TYPE, type);
contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString());
+
+ if (unarchive) {
+ contentValues.put(ARCHIVED, 0);
+ }
+
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""});
notifyConversationListListeners();
@@ -217,7 +228,7 @@ public class ThreadDatabase extends Database {
DatabaseFactory.getSmsDatabase(context).deleteMessagesInThreadBeforeDate(threadId, lastTweetDate);
DatabaseFactory.getMmsDatabase(context).deleteMessagesInThreadBeforeDate(threadId, lastTweetDate);
- update(threadId);
+ update(threadId, false);
notifyConversationListeners(threadId);
}
} finally {
@@ -302,12 +313,60 @@ public class ThreadDatabase extends Database {
}
public Cursor getConversationList() {
- SQLiteDatabase db = databaseHelper.getReadableDatabase();
- Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, DATE + " DESC");
+ SQLiteDatabase db = databaseHelper.getReadableDatabase();
+ Cursor cursor = db.query(TABLE_NAME, null, ARCHIVED + " = ?", new String[] {"0"}, null, null, DATE + " DESC");
+
setNotifyConverationListListeners(cursor);
+
return cursor;
}
+ public Cursor getArchivedConversationList() {
+ SQLiteDatabase db = databaseHelper.getReadableDatabase();
+ Cursor cursor = db.query(TABLE_NAME, null, ARCHIVED + " = ?", new String[] {"1"}, null, null, DATE + " DESC");
+
+ setNotifyConverationListListeners(cursor);
+
+ return cursor;
+ }
+
+ public int getArchivedConversationListCount() {
+ SQLiteDatabase db = databaseHelper.getReadableDatabase();
+ Cursor cursor = null;
+
+ try {
+ cursor = db.query(TABLE_NAME, new String[] {"COUNT(*)"}, ARCHIVED + " = ?",
+ new String[] {"1"}, null, null, null);
+
+ if (cursor != null && cursor.moveToFirst()) {
+ return cursor.getInt(0);
+ }
+
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+
+ return 0;
+ }
+
+ public void archiveConversation(long threadId) {
+ SQLiteDatabase db = databaseHelper.getWritableDatabase();
+ ContentValues contentValues = new ContentValues(1);
+ contentValues.put(ARCHIVED, 1);
+
+ db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId + ""});
+ notifyConversationListListeners();
+ }
+
+ public void unarchiveConversation(long threadId) {
+ SQLiteDatabase db = databaseHelper.getWritableDatabase();
+ ContentValues contentValues = new ContentValues(1);
+ contentValues.put(ARCHIVED, 0);
+
+ db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId + ""});
+ notifyConversationListListeners();
+ }
+
public void deleteConversation(long threadId) {
DatabaseFactory.getSmsDatabase(context).deleteThread(threadId);
DatabaseFactory.getMmsDatabase(context).deleteThread(threadId);
@@ -317,7 +376,6 @@ public class ThreadDatabase extends Database {
notifyConversationListListeners();
}
-
public void deleteConversations(Set selectedConversations) {
DatabaseFactory.getSmsDatabase(context).deleteThreads(selectedConversations);
DatabaseFactory.getMmsDatabase(context).deleteThreads(selectedConversations);
@@ -399,7 +457,7 @@ public class ThreadDatabase extends Database {
return null;
}
- public boolean update(long threadId) {
+ public boolean update(long threadId, boolean unarchive) {
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context);
long count = mmsSmsDatabase.getConversationCount(threadId);
@@ -421,7 +479,10 @@ public class ThreadDatabase extends Database {
if (record.isPush()) timestamp = record.getDateSent();
else timestamp = record.getDateReceived();
- updateThread(threadId, count, record.getBody().getBody(), getAttachmentUriFor(record), timestamp, record.getType());
+ updateThread(threadId, count, record.getBody().getBody(),
+ getAttachmentUriFor(record), timestamp,
+ record.getType(), unarchive);
+
notifyConversationListListeners();
return false;
} else {
@@ -456,6 +517,7 @@ public class ThreadDatabase extends Database {
public static final int DEFAULT = 2;
public static final int BROADCAST = 1;
public static final int CONVERSATION = 2;
+ public static final int ARCHIVE = 3;
}
public class Reader {
@@ -486,10 +548,11 @@ public class ThreadDatabase extends Database {
long read = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.READ));
long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE));
int distributionType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.TYPE));
+ boolean archived = cursor.getInt(cursor.getColumnIndex(ThreadDatabase.ARCHIVED)) != 0;
Uri snippetUri = getSnippetUri(cursor);
return new ThreadRecord(context, body, snippetUri, recipients, date, count,
- read == 1, threadId, type, distributionType);
+ read == 1, threadId, type, distributionType, archived);
}
private DisplayRecord.Body getPlaintextBody(Cursor cursor) {
diff --git a/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java b/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java
index d4a34755b2..f0749c40f8 100644
--- a/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java
+++ b/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java
@@ -2,30 +2,64 @@ package org.thoughtcrime.securesms.database.loaders;
import android.content.Context;
import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MergeCursor;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.util.AbstractCursorLoader;
+import java.util.LinkedList;
import java.util.List;
public class ConversationListLoader extends AbstractCursorLoader {
private final String filter;
+ private final boolean archived;
- public ConversationListLoader(Context context, String filter) {
+ public ConversationListLoader(Context context, String filter, boolean archived) {
super(context);
- this.filter = filter;
+ this.filter = filter;
+ this.archived = archived;
}
@Override
public Cursor getCursor() {
- if (filter != null && filter.trim().length() != 0) {
- List numbers = ContactAccessor.getInstance().getNumbersForThreadSearchFilter(context, filter);
+ if (filter != null && filter.trim().length() != 0) return getFilteredConversationList(filter);
+ else if (!archived) return getUnarchivedConversationList();
+ else return getArchivedConversationList();
+ }
- return DatabaseFactory.getThreadDatabase(context).getFilteredConversationList(numbers);
- } else {
- return DatabaseFactory.getThreadDatabase(context).getConversationList();
+ private Cursor getUnarchivedConversationList() {
+ List cursorList = new LinkedList<>();
+ cursorList.add(DatabaseFactory.getThreadDatabase(context).getConversationList());
+
+ int archivedCount = DatabaseFactory.getThreadDatabase(context)
+ .getArchivedConversationListCount();
+
+ if (archivedCount > 0) {
+ MatrixCursor switchToArchiveCursor = new MatrixCursor(new String[] {
+ ThreadDatabase.ID, ThreadDatabase.DATE, ThreadDatabase.MESSAGE_COUNT,
+ ThreadDatabase.RECIPIENT_IDS, ThreadDatabase.SNIPPET, ThreadDatabase.READ,
+ ThreadDatabase.TYPE, ThreadDatabase.SNIPPET_TYPE, ThreadDatabase.SNIPPET_URI,
+ ThreadDatabase.ARCHIVED}, 1);
+
+ switchToArchiveCursor.addRow(new Object[] {-1L, System.currentTimeMillis(), archivedCount,
+ "-1", null, 1, ThreadDatabase.DistributionTypes.ARCHIVE, 0, null, 0});
+
+ cursorList.add(switchToArchiveCursor);
}
+
+ return new MergeCursor(cursorList.toArray(new Cursor[0]));
+ }
+
+ private Cursor getArchivedConversationList() {
+ return DatabaseFactory.getThreadDatabase(context).getArchivedConversationList();
+ }
+
+ private Cursor getFilteredConversationList(String filter) {
+ List numbers = ContactAccessor.getInstance().getNumbersForThreadSearchFilter(context, filter);
+ return DatabaseFactory.getThreadDatabase(context).getFilteredConversationList(numbers);
}
}
diff --git a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java
index 539167646b..471d26da6b 100644
--- a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java
@@ -44,10 +44,11 @@ public class ThreadRecord extends DisplayRecord {
private final long count;
private final boolean read;
private final int distributionType;
+ private final boolean archived;
public ThreadRecord(@NonNull Context context, @NonNull Body body, @Nullable Uri snippetUri,
@NonNull Recipients recipients, long date, long count, boolean read,
- long threadId, long snippetType, int distributionType)
+ long threadId, long snippetType, int distributionType, boolean archived)
{
super(context, body, recipients, date, date, threadId, snippetType);
this.context = context.getApplicationContext();
@@ -55,6 +56,7 @@ public class ThreadRecord extends DisplayRecord {
this.count = count;
this.read = read;
this.distributionType = distributionType;
+ this.archived = archived;
}
public @Nullable Uri getSnippetUri() {
@@ -124,6 +126,10 @@ public class ThreadRecord extends DisplayRecord {
return getDateReceived();
}
+ public boolean isArchived() {
+ return archived;
+ }
+
public int getDistributionType() {
return distributionType;
}
diff --git a/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java
index 6e874bf8f3..87be7a2a27 100644
--- a/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java
+++ b/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java
@@ -31,7 +31,7 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.push.TextSecureCommunicationFactory;
-import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
+import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.TextSecureAccountManager;
diff --git a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java
index 1216b4ff1e..6072414b1f 100644
--- a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java
+++ b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java
@@ -13,6 +13,7 @@ import android.widget.Toast;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.mms.PartAuthority;
+import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import java.io.File;
import java.io.FileOutputStream;
diff --git a/src/org/thoughtcrime/securesms/util/ProgressDialogAsyncTask.java b/src/org/thoughtcrime/securesms/util/task/ProgressDialogAsyncTask.java
similarity index 96%
rename from src/org/thoughtcrime/securesms/util/ProgressDialogAsyncTask.java
rename to src/org/thoughtcrime/securesms/util/task/ProgressDialogAsyncTask.java
index 6ea6d02f1a..e862d5d4f5 100644
--- a/src/org/thoughtcrime/securesms/util/ProgressDialogAsyncTask.java
+++ b/src/org/thoughtcrime/securesms/util/task/ProgressDialogAsyncTask.java
@@ -1,4 +1,4 @@
-package org.thoughtcrime.securesms.util;
+package org.thoughtcrime.securesms.util.task;
import android.app.ProgressDialog;
import android.content.Context;
@@ -7,6 +7,7 @@ import android.os.AsyncTask;
import java.lang.ref.WeakReference;
public abstract class ProgressDialogAsyncTask extends AsyncTask {
+
private final WeakReference contextReference;
private ProgressDialog progress;
private final String title;
diff --git a/src/org/thoughtcrime/securesms/util/task/SnackbarAsyncTask.java b/src/org/thoughtcrime/securesms/util/task/SnackbarAsyncTask.java
new file mode 100644
index 0000000000..db2fd0fc19
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/util/task/SnackbarAsyncTask.java
@@ -0,0 +1,94 @@
+package org.thoughtcrime.securesms.util.task;
+
+import android.app.ProgressDialog;
+import android.os.AsyncTask;
+import android.support.annotation.Nullable;
+import android.support.design.widget.Snackbar;
+import android.view.View;
+
+public abstract class SnackbarAsyncTask
+ extends AsyncTask
+ implements View.OnClickListener
+{
+
+ private final View view;
+ private final String snackbarText;
+ private final String snackbarActionText;
+ private final int snackbarActionColor;
+ private final int snackbarDuration;
+ private final boolean showProgress;
+
+ private @Nullable Params reversibleParameter;
+ private @Nullable ProgressDialog progressDialog;
+
+ public SnackbarAsyncTask(View view,
+ String snackbarText,
+ String snackbarActionText,
+ int snackbarActionColor,
+ int snackbarDuration,
+ boolean showProgress)
+ {
+ this.view = view;
+ this.snackbarText = snackbarText;
+ this.snackbarActionText = snackbarActionText;
+ this.snackbarActionColor = snackbarActionColor;
+ this.snackbarDuration = snackbarDuration;
+ this.showProgress = showProgress;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ if (this.showProgress) this.progressDialog = ProgressDialog.show(view.getContext(), "", "", true);
+ else this.progressDialog = null;
+ }
+
+ @SafeVarargs
+ @Override
+ protected final Void doInBackground(Params... params) {
+ this.reversibleParameter = params != null && params.length > 0 ?params[0] : null;
+ executeAction(reversibleParameter);
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (this.showProgress && this.progressDialog != null) {
+ this.progressDialog.dismiss();
+ this.progressDialog = null;
+ }
+
+ Snackbar.make(view, snackbarText, snackbarDuration)
+ .setAction(snackbarActionText, this)
+ .setActionTextColor(snackbarActionColor)
+ .show();
+ }
+
+ @Override
+ public void onClick(View v) {
+ new AsyncTask() {
+ @Override
+ protected void onPreExecute() {
+ if (showProgress) progressDialog = ProgressDialog.show(view.getContext(), "", "", true);
+ else progressDialog = null;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ reverseAction(reversibleParameter);
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (showProgress && progressDialog != null) {
+ progressDialog.dismiss();
+ progressDialog = null;
+ }
+ }
+ }.execute();
+ }
+
+ protected abstract void executeAction(@Nullable Params parameter);
+ protected abstract void reverseAction(@Nullable Params parameter);
+
+}