mirror of
https://github.com/moparisthebest/k-9
synced 2024-10-31 23:55:08 -04:00
Provide folder threading support. When threading is on, messages in
folders are sorted first by subject, then by date. Subject sorting is case insensitive and disregards leading re:, fw: and fwd: prefixes. Threading can be toggled via the T hotkey or the option menu. The icons could use improvement. Threading state is maintained during a run of K-9, among all accounts.
This commit is contained in:
parent
f184350aec
commit
3dfabbbd0c
BIN
res/drawable/ic_menu_thread.png
Normal file
BIN
res/drawable/ic_menu_thread.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable/ic_menu_unthread.png
Normal file
BIN
res/drawable/ic_menu_unthread.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
@ -28,10 +28,10 @@
|
|||||||
android:title="@string/account_settings_action"
|
android:title="@string/account_settings_action"
|
||||||
android:icon="@android:drawable/ic_menu_preferences"
|
android:icon="@android:drawable/ic_menu_preferences"
|
||||||
/>
|
/>
|
||||||
<!--
|
<item
|
||||||
<item android:id="@+id/search"
|
android:id="@+id/thread"
|
||||||
android:title="@string/search_action" />
|
android:title="@string/thread_action"
|
||||||
<item android:id="@+id/preferences"
|
android:icon="@drawable/ic_menu_thread"
|
||||||
android:title="@string/preferences_action" />
|
/>
|
||||||
-->
|
|
||||||
</menu>
|
</menu>
|
||||||
|
@ -60,6 +60,8 @@
|
|||||||
<string name="add_attachment_action">Add attachment</string>
|
<string name="add_attachment_action">Add attachment</string>
|
||||||
<string name="dump_settings_action">Dump settings</string>
|
<string name="dump_settings_action">Dump settings</string>
|
||||||
<string name="empty_trash_action">Empty Trash</string>
|
<string name="empty_trash_action">Empty Trash</string>
|
||||||
|
<string name="thread_action">Thread</string>
|
||||||
|
<string name="unthread_action">Unthread</string>
|
||||||
<string name="about_action">About</string>
|
<string name="about_action">About</string>
|
||||||
|
|
||||||
<string name="accounts_context_menu_title">Account options</string>
|
<string name="accounts_context_menu_title">Account options</string>
|
||||||
@ -111,6 +113,8 @@ Welcome to K-9 Mail setup. K-9 is an open source email client for Android based
|
|||||||
* Keyboard shortcuts
|
* Keyboard shortcuts
|
||||||
* Better IMAP support
|
* Better IMAP support
|
||||||
* Saving attachments to SD
|
* Saving attachments to SD
|
||||||
|
* Empty Trash
|
||||||
|
* Threading
|
||||||
* ...and more
|
* ...and more
|
||||||
\nPlease note that K-9 does not support most free hotmail accounts and, like many email clients, has some quirks when talking to Microsoft Exchange.
|
\nPlease note that K-9 does not support most free hotmail accounts and, like many email clients, has some quirks when talking to Microsoft Exchange.
|
||||||
\nSubmit bug reports, contribute new features and ask questions at http://code.google.com/p/k9mail
|
\nSubmit bug reports, contribute new features and ask questions at http://code.google.com/p/k9mail
|
||||||
@ -371,6 +375,6 @@ Welcome to K-9 Mail setup. K-9 is an open source email client for Android based
|
|||||||
Message\u000AK, N - Next Message\u000AZ - Zoom Out\u000AShift-Z -
|
Message\u000AK, N - Next Message\u000AZ - Zoom Out\u000AShift-Z -
|
||||||
Zoom In\u000aG - Flag</string>
|
Zoom In\u000aG - Flag</string>
|
||||||
<string name="message_list_help_key">Del (or D) - Delete\u000AR -
|
<string name="message_list_help_key">Del (or D) - Delete\u000AR -
|
||||||
Reply\u000AA - Reply All\u000AC - Compose\u000AF - Forward\u000aG - Flag\u000AQ
|
Reply\u000AA - Reply All\u000AC - Compose\u000AF - Forward\u000aG - Flag\u000AT - Thread\u000AQ
|
||||||
- Return to Accounts</string>
|
- Return to Accounts</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -108,6 +108,8 @@ public class MessagingController implements Runnable {
|
|||||||
//private Set<MessagingListener> mListeners = Collections.synchronizedSet(new HashSet<MessagingListener>());
|
//private Set<MessagingListener> mListeners = Collections.synchronizedSet(new HashSet<MessagingListener>());
|
||||||
private Set<MessagingListener> mListeners = new CopyOnWriteArraySet<MessagingListener>();
|
private Set<MessagingListener> mListeners = new CopyOnWriteArraySet<MessagingListener>();
|
||||||
|
|
||||||
|
private boolean threading = false;
|
||||||
|
|
||||||
private MessagingListener checkMailListener = null;
|
private MessagingListener checkMailListener = null;
|
||||||
|
|
||||||
private boolean mBusy;
|
private boolean mBusy;
|
||||||
@ -2477,4 +2479,14 @@ s * critical data as fast as possible, and then we'll fill in the de
|
|||||||
addListener(this.checkMailListener);
|
addListener(this.checkMailListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isThreading()
|
||||||
|
{
|
||||||
|
return threading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThreading(boolean threading)
|
||||||
|
{
|
||||||
|
this.threading = threading;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ import java.util.Collections;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import android.app.ExpandableListActivity;
|
import android.app.ExpandableListActivity;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
@ -126,6 +128,9 @@ public class FolderMessageList extends ExpandableListActivity
|
|||||||
|
|
||||||
private Account mAccount;
|
private Account mAccount;
|
||||||
|
|
||||||
|
private Menu optionsMenu = null;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the name of the folder that we want to open as soon as possible
|
* Stores the name of the folder that we want to open as soon as possible
|
||||||
* after load. It is set to null once the folder has been opened once.
|
* after load. It is set to null once the folder has been opened once.
|
||||||
@ -144,6 +149,8 @@ public class FolderMessageList extends ExpandableListActivity
|
|||||||
|
|
||||||
private DateFormat timeFormat = null;
|
private DateFormat timeFormat = null;
|
||||||
|
|
||||||
|
private boolean thread = false;
|
||||||
|
|
||||||
private DateFormat getDateFormat()
|
private DateFormat getDateFormat()
|
||||||
{
|
{
|
||||||
if (dateFormat == null)
|
if (dateFormat == null)
|
||||||
@ -527,6 +534,7 @@ public class FolderMessageList extends ExpandableListActivity
|
|||||||
{
|
{
|
||||||
final int expandedGroup = savedInstanceState.getInt(
|
final int expandedGroup = savedInstanceState.getInt(
|
||||||
STATE_KEY_EXPANDED_GROUP, -1);
|
STATE_KEY_EXPANDED_GROUP, -1);
|
||||||
|
|
||||||
if (expandedGroup >= 0 && mAdapter.getGroupCount() > expandedGroup)
|
if (expandedGroup >= 0 && mAdapter.getGroupCount() > expandedGroup)
|
||||||
{
|
{
|
||||||
mListView.expandGroup(expandedGroup);
|
mListView.expandGroup(expandedGroup);
|
||||||
@ -573,6 +581,8 @@ public class FolderMessageList extends ExpandableListActivity
|
|||||||
|
|
||||||
NotificationManager notifMgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
NotificationManager notifMgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
notifMgr.cancel(mAccount.getAccountNumber());
|
notifMgr.cancel(mAccount.getAccountNumber());
|
||||||
|
thread = MessagingController.getInstance(getApplication()).isThreading();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -630,6 +640,7 @@ public class FolderMessageList extends ExpandableListActivity
|
|||||||
case KeyEvent.KEYCODE_C: { onCompose(); return true;}
|
case KeyEvent.KEYCODE_C: { onCompose(); return true;}
|
||||||
case KeyEvent.KEYCODE_Q: { onAccounts(); return true; }
|
case KeyEvent.KEYCODE_Q: { onAccounts(); return true; }
|
||||||
case KeyEvent.KEYCODE_S: { onEditAccount(); return true; }
|
case KeyEvent.KEYCODE_S: { onEditAccount(); return true; }
|
||||||
|
case KeyEvent.KEYCODE_T: { onToggleThread(); return true; }
|
||||||
case KeyEvent.KEYCODE_H: {
|
case KeyEvent.KEYCODE_H: {
|
||||||
Toast toast = Toast.makeText(this, R.string.message_list_help_key, Toast.LENGTH_LONG);
|
Toast toast = Toast.makeText(this, R.string.message_list_help_key, Toast.LENGTH_LONG);
|
||||||
toast.show();
|
toast.show();
|
||||||
@ -770,6 +781,20 @@ public class FolderMessageList extends ExpandableListActivity
|
|||||||
MessageCompose.actionCompose(this, mAccount);
|
MessageCompose.actionCompose(this, mAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onToggleThread()
|
||||||
|
{
|
||||||
|
thread = !thread;
|
||||||
|
|
||||||
|
for (FolderInfoHolder folder : mAdapter.mFolders)
|
||||||
|
{
|
||||||
|
Collections.sort(folder.messages);
|
||||||
|
}
|
||||||
|
mAdapter.notifyDataSetChanged();
|
||||||
|
setMenuThread();
|
||||||
|
MessagingController.getInstance(getApplication()).setThreading(thread);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void onDelete(MessageInfoHolder holder)
|
private void onDelete(MessageInfoHolder holder)
|
||||||
{
|
{
|
||||||
if (holder.read == false && holder.folder.unreadMessageCount > 0)
|
if (holder.read == false && holder.folder.unreadMessageCount > 0)
|
||||||
@ -879,6 +904,9 @@ public class FolderMessageList extends ExpandableListActivity
|
|||||||
case R.id.account_settings:
|
case R.id.account_settings:
|
||||||
onEditAccount();
|
onEditAccount();
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.thread:
|
||||||
|
onToggleThread();
|
||||||
|
return true;
|
||||||
case R.id.empty_trash:
|
case R.id.empty_trash:
|
||||||
onEmptyTrash(mAccount);
|
onEmptyTrash(mAccount);
|
||||||
return true;
|
return true;
|
||||||
@ -893,9 +921,25 @@ public class FolderMessageList extends ExpandableListActivity
|
|||||||
{
|
{
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
getMenuInflater().inflate(R.menu.folder_message_list_option, menu);
|
getMenuInflater().inflate(R.menu.folder_message_list_option, menu);
|
||||||
|
optionsMenu = menu;
|
||||||
|
setMenuThread();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setMenuThread()
|
||||||
|
{
|
||||||
|
Menu menu = optionsMenu;
|
||||||
|
if (menu != null)
|
||||||
|
{
|
||||||
|
MenuItem threadItem = menu.findItem(R.id.thread);
|
||||||
|
if (threadItem != null)
|
||||||
|
{
|
||||||
|
threadItem.setTitle(thread ? R.string.unthread_action : R.string.thread_action);
|
||||||
|
threadItem.setIcon(thread ? R.drawable.ic_menu_unthread : R.drawable.ic_menu_thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onContextItemSelected(MenuItem item)
|
public boolean onContextItemSelected(MenuItem item)
|
||||||
{
|
{
|
||||||
@ -1865,6 +1909,8 @@ public class FolderMessageList extends ExpandableListActivity
|
|||||||
|
|
||||||
public Date compareDate;
|
public Date compareDate;
|
||||||
|
|
||||||
|
public String compareSubject;
|
||||||
|
|
||||||
public String sender;
|
public String sender;
|
||||||
|
|
||||||
public String[] recipients;
|
public String[] recipients;
|
||||||
@ -1929,8 +1975,52 @@ public class FolderMessageList extends ExpandableListActivity
|
|||||||
|
|
||||||
public int compareTo(MessageInfoHolder o)
|
public int compareTo(MessageInfoHolder o)
|
||||||
{
|
{
|
||||||
return this.compareDate.compareTo(o.compareDate) * -1;
|
if (thread)
|
||||||
|
{
|
||||||
|
if (compareSubject == null)
|
||||||
|
{
|
||||||
|
compareSubject = stripPrefixes(subject).toLowerCase();
|
||||||
|
}
|
||||||
|
if (o.compareSubject == null)
|
||||||
|
{
|
||||||
|
o.compareSubject = stripPrefixes(o.subject).toLowerCase();
|
||||||
|
}
|
||||||
|
int subjCompare = this.compareSubject.compareTo(o.compareSubject);
|
||||||
|
if (subjCompare != 0)
|
||||||
|
{
|
||||||
|
return subjCompare;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.compareDate.compareTo(o.compareDate) * -1;
|
||||||
}
|
}
|
||||||
|
Pattern pattern = null;
|
||||||
|
String patternString = "^ *(re|fw|fwd): *";
|
||||||
|
private String stripPrefixes(String in)
|
||||||
|
{
|
||||||
|
synchronized(patternString)
|
||||||
|
{
|
||||||
|
if (pattern == null)
|
||||||
|
{
|
||||||
|
pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Matcher matcher = pattern.matcher(in);
|
||||||
|
int lastPrefix = -1;
|
||||||
|
while (matcher.find())
|
||||||
|
{
|
||||||
|
lastPrefix = matcher.end();
|
||||||
|
}
|
||||||
|
if (lastPrefix > -1 && lastPrefix < in.length() - 1)
|
||||||
|
{
|
||||||
|
return in.substring(lastPrefix);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FolderViewHolder
|
class FolderViewHolder
|
||||||
|
Loading…
Reference in New Issue
Block a user