Added widget to display the unread count for an account

This commit is contained in:
cketti 2012-02-07 01:00:16 +01:00
parent 328701e87e
commit 29e1a68288
13 changed files with 513 additions and 12 deletions

View File

@ -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"
/>
<receiver
android:name=".provider.UnreadWidgetProvider"
android:label="@string/unread_widget_label"
android:icon="@drawable/icon">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/unread_widget_info" />
</receiver>
<activity android:name=".activity.UnreadWidgetConfiguration">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<activity
android:name=".activity.AccountList">
</activity>
</application>
</manifest>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#a0000000"/>
<corners android:radius="7dp"/>
</shape>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#ffcc0000"/>
<corners android:radius="17dp"/>
</shape>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="fill_parent"
android:layout_width="fill_parent">
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
<LinearLayout
android:id="@android:id/empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:gravity="center_vertical|center_horizontal">
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"/>
</LinearLayout>
</LinearLayout>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="fill_parent"
android:layout_width="fill_parent">
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/unread_widget_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="2dp"
android:orientation="vertical"
android:gravity="bottom|center_horizontal">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon" />
<TextView
android:id="@+id/unread_count"
android:textSize="10sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|bottom"
android:paddingTop="0.5dp"
android:paddingBottom="0.5dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:background="@drawable/unread_count_background"
android:textColor="#ffffff"/>
</FrameLayout>
<TextView
android:id="@+id/account_name"
android:ellipsize="none"
android:textSize="11sp"
android:shadowColor="#000000"
android:singleLine="true"
android:paddingTop="1dp"
android:paddingBottom="1dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="2dp"
android:background="@drawable/rounded_corners"
android:textColor="#ffffff"/>
</LinearLayout>

View File

@ -1126,4 +1126,6 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="manage_accounts_move_down_action">Move down</string>
<string name="manage_accounts_moving_message">Moving account...</string>
<string name="unread_widget_label">Unread count</string>
<string name="unread_widget_select_account">Show unread count for…</string>
</resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/unread_widget_layout"
android:minHeight="40dp"
android:minWidth="40dp"
android:configure="com.fsck.k9.activity.UnreadWidgetConfiguration"
android:updatePeriodMillis="0">
</appwidget-provider>

View File

@ -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.
*
* <p>
* Classes extending this abstract class have to provide an {@link #onAccountSelected(Account)}
* method to perform an action when an account is selected.
* </p>
*/
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<Account> {
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<Void, Void, Account[]> {
@Override
protected Account[] doInBackground(Void... params) {
Account[] accounts = Preferences.getPreferences(getApplicationContext()).getAccounts();
return accounts;
}
@Override
protected void onPostExecute(Account[] accounts) {
populateListView(accounts);
}
}
}

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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;
/**
* <pre>
@ -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 {

View File

@ -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);
}
}
}