diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d0f09e32e..f530b902f 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -391,5 +391,25 @@ otherwise it would make K-9 start at the wrong time android:readPermission="com.fsck.k9.permission.READ_MESSAGES" android:writePermission="com.fsck.k9.permission.DELETE_MESSAGES" /> + + + + + + + + + + + + + + diff --git a/res/drawable/rounded_corners.xml b/res/drawable/rounded_corners.xml new file mode 100644 index 000000000..73f1772ef --- /dev/null +++ b/res/drawable/rounded_corners.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/res/drawable/unread_count_background.xml b/res/drawable/unread_count_background.xml new file mode 100644 index 000000000..d8504bd43 --- /dev/null +++ b/res/drawable/unread_count_background.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/res/layout/account_list.xml b/res/layout/account_list.xml new file mode 100644 index 000000000..468904618 --- /dev/null +++ b/res/layout/account_list.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/res/layout/launcher_shortcuts.xml b/res/layout/launcher_shortcuts.xml deleted file mode 100644 index f05c883c1..000000000 --- a/res/layout/launcher_shortcuts.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/res/layout/unread_widget_layout.xml b/res/layout/unread_widget_layout.xml new file mode 100644 index 000000000..2b242b1bd --- /dev/null +++ b/res/layout/unread_widget_layout.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 9fd5973aa..79af72444 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1126,4 +1126,6 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Move down Moving account... + Unread count + Show unread count for… diff --git a/res/xml/unread_widget_info.xml b/res/xml/unread_widget_info.xml new file mode 100644 index 000000000..8e060337d --- /dev/null +++ b/res/xml/unread_widget_info.xml @@ -0,0 +1,8 @@ + + + diff --git a/src/com/fsck/k9/activity/AccountList.java b/src/com/fsck/k9/activity/AccountList.java new file mode 100644 index 000000000..3e8ac0109 --- /dev/null +++ b/src/com/fsck/k9/activity/AccountList.java @@ -0,0 +1,163 @@ +package com.fsck.k9.activity; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import com.fsck.k9.Account; +import com.fsck.k9.FontSizes; +import com.fsck.k9.K9; +import com.fsck.k9.Preferences; +import com.fsck.k9.R; + + +/** + * Activity displaying the list of accounts. + * + *

+ * Classes extending this abstract class have to provide an {@link #onAccountSelected(Account)} + * method to perform an action when an account is selected. + *

+ */ +public abstract class AccountList extends K9ListActivity implements OnItemClickListener { + private FontSizes mFontSizes = K9.getFontSizes(); + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + setResult(RESULT_CANCELED); + + setContentView(R.layout.account_list); + + ListView listView = getListView(); + listView.setOnItemClickListener(this); + listView.setItemsCanFocus(false); + } + + /** + * Reload list of accounts when this activity is resumed. + */ + @Override + public void onResume() { + super.onResume(); + new LoadAccounts().execute(); + } + + public void onItemClick(AdapterView parent, View view, int position, long id) { + Account account = (Account) parent.getItemAtPosition(position); + onAccountSelected(account); + } + + /** + * Create a new {@link AccountsAdapter} instance and assign it to the {@link ListView}. + * + * @param accounts + * An array of accounts to display. + */ + public void populateListView(Account[] accounts) { + AccountsAdapter adapter = new AccountsAdapter(accounts); + ListView listView = getListView(); + listView.setAdapter(adapter); + listView.invalidate(); + } + + /** + * This method will be called when an account was selected. + * + * @param account + * The account the user selected. + */ + protected abstract void onAccountSelected(Account account); + + class AccountsAdapter extends ArrayAdapter { + private Account[] mAccounts; + + public AccountsAdapter(Account[] accounts) { + super(AccountList.this, 0, accounts); + mAccounts = accounts; + } + + public Account[] getAccounts() { + return mAccounts; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final Account account = getItem(position); + + final View view; + if (convertView != null) { + view = convertView; + } else { + view = getLayoutInflater().inflate(R.layout.accounts_item, parent, false); + view.findViewById(R.id.active_icons).setVisibility(View.GONE); + view.findViewById(R.id.folders).setVisibility(View.GONE); + view.getBackground().setAlpha(0); + } + + AccountViewHolder holder = (AccountViewHolder) view.getTag(); + if (holder == null) { + holder = new AccountViewHolder(); + holder.description = (TextView) view.findViewById(R.id.description); + holder.email = (TextView) view.findViewById(R.id.email); + holder.chip = view.findViewById(R.id.chip); + + view.setTag(holder); + } + + String description = account.getDescription(); + if (account.getEmail().equals(description)) { + holder.email.setVisibility(View.GONE); + } else { + holder.email.setVisibility(View.VISIBLE); + holder.email.setText(account.getEmail()); + } + + if (description == null || description.length() == 0) { + description = account.getEmail(); + } + + holder.description.setText(description); + + holder.chip.setBackgroundColor(account.getChipColor()); + holder.chip.getBackground().setAlpha(255); + + holder.description.setTextSize(TypedValue.COMPLEX_UNIT_SP, + mFontSizes.getAccountName()); + holder.email.setTextSize(TypedValue.COMPLEX_UNIT_SP, + mFontSizes.getAccountDescription()); + + return view; + } + + class AccountViewHolder { + public TextView description; + public TextView email; + public View chip; + } + } + + /** + * Load accounts in a background thread + */ + class LoadAccounts extends AsyncTask { + @Override + protected Account[] doInBackground(Void... params) { + Account[] accounts = Preferences.getPreferences(getApplicationContext()).getAccounts(); + return accounts; + } + + @Override + protected void onPostExecute(Account[] accounts) { + populateListView(accounts); + } + } +} diff --git a/src/com/fsck/k9/activity/LauncherShortcuts.java b/src/com/fsck/k9/activity/LauncherShortcuts.java index 580d5e342..aaf2008da 100644 --- a/src/com/fsck/k9/activity/LauncherShortcuts.java +++ b/src/com/fsck/k9/activity/LauncherShortcuts.java @@ -40,7 +40,7 @@ public class LauncherShortcuts extends K9ListActivity implements OnItemClickList return; } - setContentView(R.layout.launcher_shortcuts); + setContentView(R.layout.account_list); ListView listView = getListView(); listView.setOnItemClickListener(this); listView.setItemsCanFocus(false); diff --git a/src/com/fsck/k9/activity/UnreadWidgetConfiguration.java b/src/com/fsck/k9/activity/UnreadWidgetConfiguration.java new file mode 100644 index 000000000..482a51949 --- /dev/null +++ b/src/com/fsck/k9/activity/UnreadWidgetConfiguration.java @@ -0,0 +1,93 @@ +package com.fsck.k9.activity; + +import android.appwidget.AppWidgetManager; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.os.Bundle; + +import com.fsck.k9.Account; +import com.fsck.k9.R; +import com.fsck.k9.provider.UnreadWidgetProvider; + + +/** + * Activity to select an account for the unread widget. + */ +public class UnreadWidgetConfiguration extends AccountList { + /** + * Name of the preference file to store the widget configuration. + */ + private static final String PREFS_NAME = "unread_widget_configuration.xml"; + + /** + * Prefix for the preference keys. + */ + private static final String PREF_PREFIX_KEY = "unread_widget."; + + + /** + * The ID of the widget we are configuring. + */ + private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; + + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + // Find the widget ID from the intent. + Intent intent = getIntent(); + Bundle extras = intent.getExtras(); + if (extras != null) { + mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID); + } + + // If they gave us an intent without the widget ID, just bail. + if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { + finish(); + return; + } + + setTitle(R.string.unread_widget_select_account); + } + + @Override + protected void onAccountSelected(Account account) { + // Save widget configuration + String accountUuid = account.getUuid(); + saveAccountUuid(this, mAppWidgetId, accountUuid); + + // Update widget + Context context = getApplicationContext(); + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + UnreadWidgetProvider.updateWidget(context, appWidgetManager, mAppWidgetId, accountUuid); + + // Let the caller know that the configuration was successful + Intent resultValue = new Intent(); + resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); + setResult(RESULT_OK, resultValue); + finish(); + } + + private static void saveAccountUuid(Context context, int appWidgetId, String accountUuid) { + SharedPreferences.Editor editor = + context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit(); + editor.putString(PREF_PREFIX_KEY + appWidgetId, accountUuid); + editor.commit(); + } + + public static String getAccountUuid(Context context, int appWidgetId) { + SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + String accountUuid = prefs.getString(PREF_PREFIX_KEY + appWidgetId, null); + return accountUuid; + } + + public static void deleteWidgetConfiguration(Context context, int appWidgetId) { + Editor editor = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit(); + editor.remove(PREF_PREFIX_KEY + appWidgetId); + editor.commit(); + } +} diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index 049f4222e..ad8524090 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -60,6 +60,7 @@ import com.fsck.k9.mail.store.LockableDatabase.DbCallback; import com.fsck.k9.mail.store.LockableDatabase.WrappedException; import com.fsck.k9.mail.store.StorageManager.StorageProvider; import com.fsck.k9.provider.AttachmentProvider; +import com.fsck.k9.provider.UnreadWidgetProvider; /** *
@@ -499,6 +500,9 @@ public class LocalStore extends Store implements Serializable {
             public Void doDbWork(final SQLiteDatabase db) {
                 db.execSQL("DELETE FROM messages WHERE deleted = 0 and uid not like 'Local%'");
                 db.execSQL("update folders set flagged_count = 0, unread_count = 0");
+
+                // FIXME: hack to update unread count widget
+                UnreadWidgetProvider.updateUnreadCount(K9.app);
                 return null;
             }
         });
@@ -1311,6 +1315,9 @@ public class LocalStore extends Store implements Serializable {
         public void setUnreadMessageCount(final int unreadMessageCount) throws MessagingException {
             mUnreadMessageCount = Math.max(0, unreadMessageCount);
             updateFolderColumn("unread_count", mUnreadMessageCount);
+
+            // FIXME: hack to update unread count widget
+            UnreadWidgetProvider.updateUnreadCount(K9.app);
         }
 
         public void setFlaggedMessageCount(final int flaggedMessageCount) throws MessagingException {
diff --git a/src/com/fsck/k9/provider/UnreadWidgetProvider.java b/src/com/fsck/k9/provider/UnreadWidgetProvider.java
new file mode 100644
index 000000000..8913fb4e7
--- /dev/null
+++ b/src/com/fsck/k9/provider/UnreadWidgetProvider.java
@@ -0,0 +1,123 @@
+package com.fsck.k9.provider;
+
+import com.fsck.k9.Account;
+import com.fsck.k9.AccountStats;
+import com.fsck.k9.K9;
+import com.fsck.k9.Preferences;
+import com.fsck.k9.R;
+import com.fsck.k9.activity.UnreadWidgetConfiguration;
+import com.fsck.k9.activity.FolderList;
+import com.fsck.k9.activity.MessageList;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import android.view.View;
+import android.widget.RemoteViews;
+
+public class UnreadWidgetProvider extends AppWidgetProvider {
+
+    /**
+     * Trigger update for all of our unread widgets.
+     *
+     * @param context
+     *         The {@code Context} object to use for the broadcast intent.
+     */
+    public static void updateUnreadCount(Context context) {
+        Context appContext = context.getApplicationContext();
+        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(appContext);
+
+        ComponentName thisWidget = new ComponentName(appContext, UnreadWidgetProvider.class);
+        int[] widgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
+
+        Intent intent = new Intent(context, UnreadWidgetProvider.class);
+        intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds);
+
+        context.sendBroadcast(intent);
+    }
+
+    public static void updateWidget(Context context, AppWidgetManager appWidgetManager,
+            int appWidgetId, String accountUuid) {
+
+        RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
+                R.layout.unread_widget_layout);
+
+        int unreadCount = 0;
+        String accountName = context.getString(R.string.app_name);
+        Intent clickIntent = null;
+        try {
+            Account account = Preferences.getPreferences(context).getAccount(accountUuid);
+            if (account != null) {
+                AccountStats stats = new AccountStats();
+                account.getLocalStore().getMessageCounts(stats);
+                unreadCount = stats.unreadMessageCount;
+                accountName = account.getDescription();
+                if (K9.FOLDER_NONE.equals(account.getAutoExpandFolderName())) {
+                    clickIntent = FolderList.actionHandleAccountIntent(context, account, null);
+                } else {
+                    clickIntent = MessageList.actionHandleFolderIntent(context, account,
+                            account.getAutoExpandFolderName());
+                }
+                clickIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+            }
+        } catch (Exception e) {
+            if (K9.DEBUG) {
+                Log.e(K9.LOG_TAG, "Error getting widget configuration", e);
+            }
+        }
+
+        if (unreadCount == 0) {
+            // Hide TextView for unread count if there are no unread messages.
+            remoteViews.setViewVisibility(R.id.unread_count, View.GONE);
+        } else {
+            remoteViews.setTextViewText(R.id.unread_count, String.valueOf(unreadCount));
+        }
+
+        remoteViews.setTextViewText(R.id.account_name, accountName);
+
+        if (clickIntent == null) {
+            // If the widget configuration couldn't be loaded we open the configuration
+            // activity when the user clicks the widget.
+            clickIntent = new Intent(context, UnreadWidgetConfiguration.class);
+            clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+        }
+        clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+
+        PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId,
+                clickIntent, 0);
+
+        remoteViews.setOnClickPendingIntent(R.id.unread_widget_layout, pendingIntent);
+
+        appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
+
+    }
+
+
+    /**
+     * Called when one or more widgets need to be updated.
+     */
+    @Override
+    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+        for (int widgetId : appWidgetIds) {
+            String accountUuid = UnreadWidgetConfiguration.getAccountUuid(context, widgetId);
+
+            updateWidget(context, appWidgetManager, widgetId, accountUuid);
+        }
+    }
+
+    /**
+     * Called when a widget instance is deleted.
+     */
+    @Override
+    public void onDeleted(Context context, int[] appWidgetIds) {
+        for (int appWidgetId : appWidgetIds) {
+            UnreadWidgetConfiguration.deleteWidgetConfiguration(context, appWidgetId);
+        }
+    }
+}