Added setting to enable/disable conversation threading

pull/146/head
M66B 7 years ago
parent 15b9351cd8
commit b5b0e56fea

@ -52,7 +52,6 @@ Anything on this list is in random order and *might* be added in the near future
* Better design: please let me know what you have in mind [in this forum](https://forum.xda-developers.com/android/apps-games/source-email-t3824168). * Better design: please let me know what you have in mind [in this forum](https://forum.xda-developers.com/android/apps-games/source-email-t3824168).
* Hide archived messages: hiding archived messages which exists in other folders too would have a performance impact. * Hide archived messages: hiding archived messages which exists in other folders too would have a performance impact.
* Save all attachments: there is no [Storage Access Framework](https://developer.android.com/guide/topics/providers/document-provider) API to selected multiple files to save. * Save all attachments: there is no [Storage Access Framework](https://developer.android.com/guide/topics/providers/document-provider) API to selected multiple files to save.
* Show single messages: listing single messages just clutters the list without much benefit, so this will not be added.
* S/MIME encryption: only PGP encryption will be supported, see [this FAQ](#user-content-faq12) for more information. * S/MIME encryption: only PGP encryption will be supported, see [this FAQ](#user-content-faq12) for more information.
Since FairEmail is meant to be privacy friendly, the following will not be added: Since FairEmail is meant to be privacy friendly, the following will not be added:

@ -92,7 +92,8 @@ abstract class ActivityBase extends AppCompatActivity implements SharedPreferenc
if (this.getClass().equals(ActivitySetup.class)) if (this.getClass().equals(ActivitySetup.class))
startActivity(getIntent()); startActivity(getIntent());
} else if (!this.getClass().equals(ActivitySetup.class) && } else if (!this.getClass().equals(ActivitySetup.class) &&
("compact".equals(key) || ("threading".equals(key) ||
"compact".equals(key) ||
"avatars".equals(key) || "avatars".equals(key) ||
"identicons".equals(key) || "identicons".equals(key) ||
"preview".equals(key) || "preview".equals(key) ||

@ -1077,6 +1077,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong("account", intent.getLongExtra("account", -1)); args.putLong("account", intent.getLongExtra("account", -1));
args.putString("thread", intent.getStringExtra("thread")); args.putString("thread", intent.getStringExtra("thread"));
args.putLong("id", intent.getLongExtra("id", -1));
args.putBoolean("found", intent.getBooleanExtra("found", false)); args.putBoolean("found", intent.getBooleanExtra("found", false));
FragmentMessages fragment = new FragmentMessages(); FragmentMessages fragment = new FragmentMessages();

@ -113,6 +113,7 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
private boolean outgoing; private boolean outgoing;
private IProperties properties; private IProperties properties;
private boolean threading;
private boolean compact; private boolean compact;
private boolean contacts; private boolean contacts;
private boolean avatars; private boolean avatars;
@ -353,7 +354,7 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
vwColor.setBackgroundColor(message.accountColor == null ? Color.TRANSPARENT : message.accountColor); vwColor.setBackgroundColor(message.accountColor == null ? Color.TRANSPARENT : message.accountColor);
ivExpander.setImageResource(show_expanded ? R.drawable.baseline_expand_less_24 : R.drawable.baseline_expand_more_24); ivExpander.setImageResource(show_expanded ? R.drawable.baseline_expand_less_24 : R.drawable.baseline_expand_more_24);
if (viewType == ViewType.THREAD) if (viewType == ViewType.THREAD && threading)
ivExpander.setVisibility(EntityFolder.DRAFTS.equals(message.folderType) ? View.INVISIBLE : View.VISIBLE); ivExpander.setVisibility(EntityFolder.DRAFTS.equals(message.folderType) ? View.INVISIBLE : View.VISIBLE);
else else
ivExpander.setVisibility(View.GONE); ivExpander.setVisibility(View.GONE);
@ -386,7 +387,7 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
tvPreview.setText(message.preview); tvPreview.setText(message.preview);
tvPreview.setVisibility(preview && !TextUtils.isEmpty(message.preview) ? View.VISIBLE : View.GONE); tvPreview.setVisibility(preview && !TextUtils.isEmpty(message.preview) ? View.VISIBLE : View.GONE);
if (viewType == ViewType.THREAD) { if (viewType == ViewType.THREAD || !threading) {
tvCount.setVisibility(View.GONE); tvCount.setVisibility(View.GONE);
ivThread.setVisibility(View.GONE); ivThread.setVisibility(View.GONE);
} else { } else {
@ -565,6 +566,7 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
new Intent(ActivityView.ACTION_VIEW_THREAD) new Intent(ActivityView.ACTION_VIEW_THREAD)
.putExtra("account", message.account) .putExtra("account", message.account)
.putExtra("thread", message.thread) .putExtra("thread", message.thread)
.putExtra("id", message.id)
.putExtra("found", message.ui_found)); .putExtra("found", message.ui_found));
} }
} }
@ -1519,6 +1521,7 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
this.threading = prefs.getBoolean("threading", true);
this.compact = prefs.getBoolean("compact", false); this.compact = prefs.getBoolean("compact", false);
this.contacts = (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) this.contacts = (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
== PackageManager.PERMISSION_GRANTED); == PackageManager.PERMISSION_GRANTED);

@ -58,7 +58,7 @@ public interface DaoMessage {
" WHERE account.`synchronize`" + " WHERE account.`synchronize`" +
" AND (NOT message.ui_hide OR :debug)" + " AND (NOT message.ui_hide OR :debug)" +
" AND NOT ui_found" + " AND NOT ui_found" +
" GROUP BY account.id, CASE WHEN message.thread IS NULL THEN message.id ELSE message.thread END" + " GROUP BY account.id, CASE WHEN message.thread IS NULL OR NOT :threading THEN message.id ELSE message.thread END" +
" HAVING SUM(unified) > 0" + " HAVING SUM(unified) > 0" +
" ORDER BY CASE" + " ORDER BY CASE" +
" WHEN 'unread' = :sort THEN NOT message.ui_seen" + " WHEN 'unread' = :sort THEN NOT message.ui_seen" +
@ -66,7 +66,7 @@ public interface DaoMessage {
" ELSE 0" + " ELSE 0" +
" END DESC, message.received DESC") " END DESC, message.received DESC")
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
DataSource.Factory<Integer, TupleMessageEx> pagedUnifiedInbox(String sort, boolean debug); DataSource.Factory<Integer, TupleMessageEx> pagedUnifiedInbox(boolean threading, String sort, boolean debug);
@Query("SELECT message.*" + @Query("SELECT message.*" +
", account.name AS accountName, identity.color AS accountColor, account.notify AS accountNotify" + ", account.name AS accountName, identity.color AS accountColor, account.notify AS accountNotify" +
@ -91,7 +91,7 @@ public interface DaoMessage {
" WHERE (message.account = f.account OR folder.type = '" + EntityFolder.OUTBOX + "')" + " WHERE (message.account = f.account OR folder.type = '" + EntityFolder.OUTBOX + "')" +
" AND (NOT message.ui_hide OR :debug)" + " AND (NOT message.ui_hide OR :debug)" +
" AND ui_found = :found" + " AND ui_found = :found" +
" GROUP BY CASE WHEN message.thread IS NULL THEN message.id ELSE message.thread END" + " GROUP BY CASE WHEN message.thread IS NULL OR NOT :threading THEN message.id ELSE message.thread END" +
" HAVING SUM(CASE WHEN folder.id = :folder THEN 1 ELSE 0 END) > 0" + " HAVING SUM(CASE WHEN folder.id = :folder THEN 1 ELSE 0 END) > 0" +
" ORDER BY CASE" + " ORDER BY CASE" +
" WHEN 'unread' = :sort THEN NOT message.ui_seen" + " WHEN 'unread' = :sort THEN NOT message.ui_seen" +
@ -99,7 +99,7 @@ public interface DaoMessage {
" ELSE 0" + " ELSE 0" +
" END DESC, message.received DESC") " END DESC, message.received DESC")
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
DataSource.Factory<Integer, TupleMessageEx> pagedFolder(long folder, String sort, boolean found, boolean debug); DataSource.Factory<Integer, TupleMessageEx> pagedFolder(long folder, boolean threading, String sort, boolean found, boolean debug);
@Query("SELECT message.*" + @Query("SELECT message.*" +
", account.name AS accountName, identity.color AS accountColor, account.notify AS accountNotify" + ", account.name AS accountName, identity.color AS accountColor, account.notify AS accountNotify" +
@ -125,6 +125,7 @@ public interface DaoMessage {
" JOIN folder ON folder.id = message.folder" + " JOIN folder ON folder.id = message.folder" +
" WHERE message.account = :account" + " WHERE message.account = :account" +
" AND message.thread = :thread" + " AND message.thread = :thread" +
" AND (:id IS NULL OR message.id = :id)" +
" AND ui_found = :found" + " AND ui_found = :found" +
" AND (NOT message.ui_hide OR :debug)" + " AND (NOT message.ui_hide OR :debug)" +
" ORDER BY CASE" + " ORDER BY CASE" +
@ -132,7 +133,7 @@ public interface DaoMessage {
" WHEN 'starred' = :sort THEN message.ui_flagged" + " WHEN 'starred' = :sort THEN message.ui_flagged" +
" ELSE 0" + " ELSE 0" +
" END DESC, message.received DESC") " END DESC, message.received DESC")
DataSource.Factory<Integer, TupleMessageEx> pagedThread(long account, String thread, boolean found, String sort, boolean debug); DataSource.Factory<Integer, TupleMessageEx> pagedThread(long account, String thread, Long id, boolean found, String sort, boolean debug);
@Query("SELECT COUNT(id)" + @Query("SELECT COUNT(id)" +
" FROM message" + " FROM message" +

@ -93,9 +93,11 @@ public class FragmentMessages extends FragmentEx {
private long folder = -1; private long folder = -1;
private boolean outgoing = false; private boolean outgoing = false;
private String thread = null; private String thread = null;
private long id = -1;
private boolean found = false; private boolean found = false;
private String search = null; private String search = null;
private boolean threading = true;
private boolean actionbar = false; private boolean actionbar = false;
private boolean autoclose = false; private boolean autoclose = false;
@ -136,10 +138,12 @@ public class FragmentMessages extends FragmentEx {
folder = args.getLong("folder", -1); folder = args.getLong("folder", -1);
outgoing = args.getBoolean("outgoing", false); outgoing = args.getBoolean("outgoing", false);
thread = args.getString("thread"); thread = args.getString("thread");
id = args.getLong("id", -1);
found = args.getBoolean("found", false); found = args.getBoolean("found", false);
search = args.getString("search"); search = args.getString("search");
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
threading = prefs.getBoolean("threading", true);
actionbar = prefs.getBoolean("actionbar", true); actionbar = prefs.getBoolean("actionbar", true);
autoclose = prefs.getBoolean("autoclose", false); autoclose = prefs.getBoolean("autoclose", false);
@ -1222,7 +1226,7 @@ public class FragmentMessages extends FragmentEx {
switch (viewType) { switch (viewType) {
case UNIFIED: case UNIFIED:
messages = new LivePagedListBuilder<>(db.message().pagedUnifiedInbox(sort, debug), LOCAL_PAGE_SIZE).build(); messages = new LivePagedListBuilder<>(db.message().pagedUnifiedInbox(threading, sort, debug), LOCAL_PAGE_SIZE).build();
break; break;
case FOLDER: case FOLDER:
if (searchCallback == null) if (searchCallback == null)
@ -1255,14 +1259,15 @@ public class FragmentMessages extends FragmentEx {
.setPrefetchDistance(REMOTE_PAGE_SIZE) .setPrefetchDistance(REMOTE_PAGE_SIZE)
.build(); .build();
LivePagedListBuilder<Integer, TupleMessageEx> builder = new LivePagedListBuilder<>( LivePagedListBuilder<Integer, TupleMessageEx> builder = new LivePagedListBuilder<>(
db.message().pagedFolder(folder, sort, false, debug), config); db.message().pagedFolder(folder, threading, sort, false, debug), config);
if (browse) if (browse)
builder.setBoundaryCallback(searchCallback); builder.setBoundaryCallback(searchCallback);
messages = builder.build(); messages = builder.build();
break; break;
case THREAD: case THREAD:
messages = new LivePagedListBuilder<>(db.message().pagedThread(account, thread, found, sort, debug), LOCAL_PAGE_SIZE).build(); messages = new LivePagedListBuilder<>(
db.message().pagedThread(account, thread, threading ? null : id, found, sort, debug), LOCAL_PAGE_SIZE).build();
break; break;
} }
} else { } else {
@ -1299,7 +1304,7 @@ public class FragmentMessages extends FragmentEx {
.setPrefetchDistance(REMOTE_PAGE_SIZE) .setPrefetchDistance(REMOTE_PAGE_SIZE)
.build(); .build();
LivePagedListBuilder<Integer, TupleMessageEx> builder = new LivePagedListBuilder<>( LivePagedListBuilder<Integer, TupleMessageEx> builder = new LivePagedListBuilder<>(
db.message().pagedFolder(folder, "time", true, false), config); db.message().pagedFolder(folder, threading, "time", true, false), config);
builder.setBoundaryCallback(searchCallback); builder.setBoundaryCallback(searchCallback);
messages = builder.build(); messages = builder.build();
} }
@ -1497,6 +1502,7 @@ public class FragmentMessages extends FragmentEx {
new Intent(ActivityView.ACTION_VIEW_THREAD) new Intent(ActivityView.ACTION_VIEW_THREAD)
.putExtra("account", target.account) .putExtra("account", target.account)
.putExtra("thread", target.thread) .putExtra("thread", target.thread)
.putExtra("id", target.id)
.putExtra("found", target.found)); .putExtra("found", target.found));
} }

@ -43,9 +43,10 @@ import androidx.appcompat.widget.SwitchCompat;
public class FragmentOptions extends FragmentEx implements SharedPreferences.OnSharedPreferenceChangeListener { public class FragmentOptions extends FragmentEx implements SharedPreferences.OnSharedPreferenceChangeListener {
private SwitchCompat swEnabled; private SwitchCompat swEnabled;
private SwitchCompat swMetered; private SwitchCompat swMetered;
private SwitchCompat swThreading;
private SwitchCompat swCompact;
private SwitchCompat swAvatars; private SwitchCompat swAvatars;
private SwitchCompat swIdenticons; private SwitchCompat swIdenticons;
private SwitchCompat swCompact;
private SwitchCompat swPreview; private SwitchCompat swPreview;
private SwitchCompat swLight; private SwitchCompat swLight;
private SwitchCompat swBrowse; private SwitchCompat swBrowse;
@ -69,6 +70,7 @@ public class FragmentOptions extends FragmentEx implements SharedPreferences.OnS
// Get controls // Get controls
swEnabled = view.findViewById(R.id.swEnabled); swEnabled = view.findViewById(R.id.swEnabled);
swMetered = view.findViewById(R.id.swMetered); swMetered = view.findViewById(R.id.swMetered);
swThreading = view.findViewById(R.id.swThreading);
swCompact = view.findViewById(R.id.swCompact); swCompact = view.findViewById(R.id.swCompact);
swAvatars = view.findViewById(R.id.swAvatars); swAvatars = view.findViewById(R.id.swAvatars);
swIdenticons = view.findViewById(R.id.swIdenticons); swIdenticons = view.findViewById(R.id.swIdenticons);
@ -107,6 +109,14 @@ public class FragmentOptions extends FragmentEx implements SharedPreferences.OnS
} }
}); });
swThreading.setChecked(prefs.getBoolean("threading", true));
swThreading.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("threading", checked).apply();
}
});
swCompact.setChecked(prefs.getBoolean("compact", false)); swCompact.setChecked(prefs.getBoolean("compact", false));
swCompact.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { swCompact.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override @Override

@ -56,18 +56,20 @@ public class ViewModelMessages extends ViewModel {
next = item; next = item;
} }
return new Target[]{ return new Target[]{
prev == null ? null : new Target(prev.account, prev.thread, prev.ui_found), prev == null ? null : new Target(prev.account, prev.thread, prev.id, prev.ui_found),
next == null ? null : new Target(next.account, next.thread, next.ui_found)}; next == null ? null : new Target(next.account, next.thread, next.id, next.ui_found)};
} }
class Target { class Target {
long account; long account;
String thread; String thread;
long id;
boolean found; boolean found;
Target(long account, String thread, boolean found) { Target(long account, String thread, long id, boolean found) {
this.account = account; this.account = account;
this.thread = thread; this.thread = thread;
this.id = id;
this.found = found; this.found = found;
} }
} }

@ -29,6 +29,15 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swEnabled" /> app:layout_constraintTop_toBottomOf="@id/swEnabled" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swThreading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_advanced_threading"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swMetered" />
<androidx.appcompat.widget.SwitchCompat <androidx.appcompat.widget.SwitchCompat
android:id="@+id/swCompact" android:id="@+id/swCompact"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -36,7 +45,7 @@
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:text="@string/title_advanced_compact" android:text="@string/title_advanced_compact"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swMetered" /> app:layout_constraintTop_toBottomOf="@id/swThreading" />
<androidx.appcompat.widget.SwitchCompat <androidx.appcompat.widget.SwitchCompat
android:id="@+id/swAvatars" android:id="@+id/swAvatars"

@ -98,6 +98,7 @@
<string name="title_advanced">Advanced options</string> <string name="title_advanced">Advanced options</string>
<string name="title_advanced_enabled">Synchronize</string> <string name="title_advanced_enabled">Synchronize</string>
<string name="title_advanced_metered">Use metered connections</string> <string name="title_advanced_metered">Use metered connections</string>
<string name="title_advanced_threading">Conversation threading</string>
<string name="title_advanced_compact">Compact message view</string> <string name="title_advanced_compact">Compact message view</string>
<string name="title_advanced_avatars">Show contact photos</string> <string name="title_advanced_avatars">Show contact photos</string>
<string name="title_advanced_identicons">Show identicons</string> <string name="title_advanced_identicons">Show identicons</string>

Loading…
Cancel
Save