diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e94bbb0..ce67b34 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,8 @@ + + ) { Nameable ni = (Nameable) item; switch (ni.getNameRes()) { @@ -237,7 +245,6 @@ public class MessageListActivity extends AppCompatActivity getSupportFragmentManager().beginTransaction() .replace(R.id.message_detail_container, fragment) .commit(); - } else { // In single-pane mode, simply start the detail activity // for the selected item ID. diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.java b/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.java index 2dca634..abbd60d 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.java +++ b/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.java @@ -82,6 +82,11 @@ public class MessageListFragment extends ListFragment { super.onCreate(savedInstanceState); bmc = Singleton.getBitmessageContext(getActivity()); + } + + @Override + public void onResume() { + super.onResume(); updateList(((MessageListActivity) getActivity()).getSelectedLabel()); } diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageListener.java b/app/src/main/java/ch/dissem/apps/abit/MessageListener.java index 6b533f8..c9adc4f 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MessageListener.java +++ b/app/src/main/java/ch/dissem/apps/abit/MessageListener.java @@ -16,54 +16,134 @@ package ch.dissem.apps.abit; +import android.annotation.TargetApi; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.Build; +import android.provider.ContactsContract; import android.support.v7.app.NotificationCompat; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.style.StyleSpan; import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.entity.Plaintext; +import java.util.LinkedList; + /** - * Created by chris on 22.08.15. + * Listens for decrypted Bitmessage messages. Does show a notification. + *

+ * Should show a notification when the app isn't running, but update the message list when it is. Also, + * notifications should be combined. + *

*/ public class MessageListener implements BitmessageContext.Listener { + private static final StyleSpan SPAN_EMPHASIS = new StyleSpan(Typeface.BOLD); private final Context ctx; private final NotificationManager manager; + private final LinkedList unacknowledged = new LinkedList<>(); + private final int pictureSize; + private int numberOfUnacknowledgedMessages = 0; public MessageListener(Context ctx) { this.ctx = ctx.getApplicationContext(); this.manager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); + + this.pictureSize = getMaxContactPhotoSize(ctx); + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + public static int getMaxContactPhotoSize(final Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + // Note that this URI is safe to call on the UI thread. + final Uri uri = ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI; + final String[] projection = new String[]{ContactsContract.DisplayPhoto.DISPLAY_MAX_DIM}; + final Cursor c = context.getContentResolver().query(uri, projection, null, null, null); + try { + c.moveToFirst(); + return c.getInt(0); + } finally { + c.close(); + } + } + // fallback: 96x96 is the max contact photo size for pre-ICS versions + return 96; } @Override public void receive(final Plaintext plaintext) { - // TODO -// ctx.runOnUiThread(new Runnable() { -// @Override -// public void run() { + synchronized (unacknowledged) { + unacknowledged.addFirst(plaintext); + numberOfUnacknowledgedMessages++; + if (unacknowledged.size() > 5) { + unacknowledged.removeLast(); + } + } + NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx); - builder.setSmallIcon(R.drawable.ic_notification_new_message) - .setContentTitle(plaintext.getFrom().toString()) - .setContentText(plaintext.getSubject()); + if (numberOfUnacknowledgedMessages == 1) { + Spannable bigText = new SpannableString(plaintext.getSubject() + "\n" + plaintext.getText()); + bigText.setSpan(SPAN_EMPHASIS, 0, plaintext.getSubject().length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + builder.setSmallIcon(R.drawable.ic_notification_new_message) + .setLargeIcon(toBitmap(new Identicon(plaintext.getFrom()))) + .setContentTitle(plaintext.getFrom().toString()) + .setContentText(plaintext.getSubject()) + .setStyle(new NotificationCompat.BigTextStyle().bigText(bigText)) + .setContentInfo("Info"); - NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); - inboxStyle.setBigContentTitle(plaintext.getFrom().toString()); - inboxStyle.setSummaryText(plaintext.getSubject()); - String text = plaintext.getText(); - if (text.length() > 100) - inboxStyle.addLine(text.substring(0, 100) + "…"); - else - inboxStyle.addLine(text); - builder.setStyle(inboxStyle); + Intent showMessageIntent = new Intent(ctx, MessageListActivity.class); + showMessageIntent.putExtra(MessageListActivity.EXTRA_SHOW_MESSAGE, plaintext); + PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT); + builder.setContentIntent(pendingIntent); - Intent intent = new Intent(ctx, MessageListActivity.class); - intent.putExtra(MessageListActivity.EXTRA_SHOW_MESSAGE, plaintext); - PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, intent, 0); - builder.setContentIntent(pendingIntent); + builder.addAction(R.drawable.ic_action_reply, ctx.getString(R.string.reply), pendingIntent); + builder.addAction(R.drawable.ic_action_delete, ctx.getString(R.string.delete), pendingIntent); + } else { + builder.setSmallIcon(R.drawable.ic_notification_new_message) + .setContentTitle(ctx.getString(R.string.n_new_messages, this.unacknowledged.size())) + .setContentText(ctx.getString(R.string.app_name)); + + NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); + synchronized (unacknowledged) { + inboxStyle.setBigContentTitle(ctx.getString(R.string.n_new_messages, numberOfUnacknowledgedMessages)); + for (Plaintext msg : unacknowledged) { + Spannable sb = new SpannableString(msg.getFrom() + " " + msg.getSubject()); + sb.setSpan(SPAN_EMPHASIS, 0, String.valueOf(msg.getFrom()).length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + inboxStyle.addLine(sb); + } + } + builder.setStyle(inboxStyle); + + Intent intent = new Intent(ctx, MessageListActivity.class); + intent.setAction(MessageListActivity.ACTION_SHOW_INBOX); + PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, intent, 0); + builder.setContentIntent(pendingIntent); + } manager.notify(0, builder.build()); -// } -// }); + } + + private Bitmap toBitmap(Identicon identicon) { + Bitmap bitmap = Bitmap.createBitmap(pictureSize, pictureSize, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + identicon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + identicon.draw(canvas); + return bitmap; + } + + public void resetNotification() { + manager.cancel(0); + synchronized (unacknowledged) { + unacknowledged.clear(); + numberOfUnacknowledgedMessages = 0; + } } } diff --git a/app/src/main/java/ch/dissem/apps/abit/repositories/AndroidMessageRepository.java b/app/src/main/java/ch/dissem/apps/abit/repositories/AndroidMessageRepository.java index 8fa1bd6..b290834 100644 --- a/app/src/main/java/ch/dissem/apps/abit/repositories/AndroidMessageRepository.java +++ b/app/src/main/java/ch/dissem/apps/abit/repositories/AndroidMessageRepository.java @@ -19,6 +19,7 @@ package ch.dissem.apps.abit.repositories; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; import ch.dissem.apps.abit.R; import ch.dissem.bitmessage.InternalContext; @@ -151,6 +152,24 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont return label; } + @Override + public int countUnread(Label label) { + String where; + if (label != null) { + where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ") AND "; + } else { + where = ""; + } + SQLiteDatabase db = sql.getReadableDatabase(); + Cursor c = db.query( + TABLE_NAME, new String[]{COLUMN_ID}, + where + "id IN (SELECT message_id FROM Message_Label WHERE label_id IN (" + + "SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))", + null, null, null, null + ); + return c.getColumnCount(); + } + @Override public List<Plaintext> findMessages(Label label) { if (label != null) { @@ -258,6 +277,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont db.insertOrThrow(JOIN_TABLE_NAME, null, values); } db.setTransactionSuccessful(); + } catch (SQLiteConstraintException e) { + LOG.trace(e.getMessage(), e); } catch (IOException e) { LOG.error(e.getMessage(), e); } finally { diff --git a/app/src/main/res/drawable-hdpi/ic_action_delete.png b/app/src/main/res/drawable-hdpi/ic_action_delete.png new file mode 100644 index 0000000..4bb5259 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_delete.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_reply.png b/app/src/main/res/drawable-hdpi/ic_action_reply.png new file mode 100644 index 0000000..be372f5 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_reply.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_delete.png b/app/src/main/res/drawable-mdpi/ic_action_delete.png new file mode 100644 index 0000000..95258c3 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_delete.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_reply.png b/app/src/main/res/drawable-mdpi/ic_action_reply.png new file mode 100644 index 0000000..f93102a Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_reply.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_delete.png b/app/src/main/res/drawable-xhdpi/ic_action_delete.png new file mode 100644 index 0000000..35c97c1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_delete.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_reply.png b/app/src/main/res/drawable-xhdpi/ic_action_reply.png new file mode 100644 index 0000000..ea7c4ea Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_reply.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_delete.png b/app/src/main/res/drawable-xxhdpi/ic_action_delete.png new file mode 100644 index 0000000..cfb99e1 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_delete.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_reply.png b/app/src/main/res/drawable-xxhdpi/ic_action_reply.png new file mode 100644 index 0000000..9b6d9ef Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_reply.png differ diff --git a/app/src/main/res/layout/fragment_message_list.xml b/app/src/main/res/layout/fragment_message_list.xml index 6e7982b..cd73e5b 100644 --- a/app/src/main/res/layout/fragment_message_list.xml +++ b/app/src/main/res/layout/fragment_message_list.xml @@ -2,18 +2,21 @@ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" - android:layout_height="match_parent" - xmlns:fab="http://schemas.android.com/tools"> + android:layout_height="match_parent"> <ListView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@id/android:list" + android:paddingBottom="88dp" + android:clipToPadding="false" + android:scrollbarStyle="outsideOverlay" + android:layout_alignParentTop="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" - android:layout_alignParentBottom="true" /> + android:layout_alignParentBottom="true"/> <android.support.design.widget.FloatingActionButton android:id="@+id/fab_compose_message" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index be41919..9dad53f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,4 +24,7 @@ <string name="do_import">Import</string> <string name="cancel">Cancel</string> <string name="broadcast">Broadcast</string> + <string name="n_new_messages">%d new messages</string> + <string name="reply">Reply</string> + <string name="delete">Delete</string> </resources>