Notifications could still use some fine tuning, but should work fine for now

This commit is contained in:
Christian Basler 2015-09-04 08:19:07 +02:00
parent e5b00c7453
commit 496fffe6ee
16 changed files with 158 additions and 35 deletions

View File

@ -6,6 +6,8 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<application <application
android:allowBackup="true" android:allowBackup="true"

View File

@ -29,6 +29,9 @@ public class Identicon extends Drawable {
private static final int CENTER_COLUMN = 5; private static final int CENTER_COLUMN = 5;
private final Paint paint; private final Paint paint;
private float width;
private float height;
private float cellWidth; private float cellWidth;
private float cellHeight; private float cellHeight;
private byte[] hash; private byte[] hash;
@ -54,15 +57,11 @@ public class Identicon extends Drawable {
} }
} }
protected byte getByte(int index) {
return hash[index % hash.length];
}
@Override @Override
public void draw(Canvas canvas) { public void draw(Canvas canvas) {
float x, y; float x, y;
paint.setColor(background); paint.setColor(background);
canvas.drawPaint(paint); canvas.drawCircle(width/2, height/2, width/2, paint);
paint.setColor(color); paint.setColor(color);
for (int row = 0; row < SIZE; row++) { for (int row = 0; row < SIZE; row++) {
for (int column = 0; column < SIZE; column++) { for (int column = 0; column < SIZE; column++) {
@ -88,13 +87,16 @@ public class Identicon extends Drawable {
@Override @Override
public int getOpacity() { public int getOpacity() {
return PixelFormat.OPAQUE; return PixelFormat.TRANSPARENT;
} }
@Override @Override
protected void onBoundsChange(Rect bounds) { protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds); super.onBoundsChange(bounds);
width = bounds.width();
height = bounds.height();
cellWidth = bounds.width() / (float) SIZE; cellWidth = bounds.width() / (float) SIZE;
cellHeight = bounds.height() / (float) SIZE; cellHeight = bounds.height() / (float) SIZE;
} }

View File

@ -52,7 +52,8 @@ import java.util.ArrayList;
*/ */
public class MessageListActivity extends AppCompatActivity public class MessageListActivity extends AppCompatActivity
implements MessageListFragment.Callbacks { implements MessageListFragment.Callbacks {
public static final String EXTRA_SHOW_MESSAGE = "show_message"; public static final String EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage";
public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox";
private static final Logger LOG = LoggerFactory.getLogger(MessageListActivity.class); private static final Logger LOG = LoggerFactory.getLogger(MessageListActivity.class);
private static final int ADD_IDENTITY = 1; private static final int ADD_IDENTITY = 1;
@ -95,7 +96,12 @@ public class MessageListActivity extends AppCompatActivity
createDrawer(toolbar); createDrawer(toolbar);
// TODO: If exposing deep links into your app, handle intents here. Singleton.getMessageListener(this).resetNotification();
// handle intents
if (getIntent().hasExtra(EXTRA_SHOW_MESSAGE)) {
onItemSelected((Plaintext) getIntent().getSerializableExtra(EXTRA_SHOW_MESSAGE));
}
} }
private void createDrawer(Toolbar toolbar) { private void createDrawer(Toolbar toolbar) {
@ -103,6 +109,7 @@ public class MessageListActivity extends AppCompatActivity
for (BitmessageAddress identity : bmc.addresses().getIdentities()) { for (BitmessageAddress identity : bmc.addresses().getIdentities()) {
LOG.info("Adding identity " + identity.getAddress()); LOG.info("Adding identity " + identity.getAddress());
profiles.add(new ProfileDrawerItem() profiles.add(new ProfileDrawerItem()
.withIcon(new Identicon(identity))
.withName(identity.toString()) .withName(identity.toString())
.withEmail(identity.getAddress()) .withEmail(identity.getAddress())
.withTag(identity) .withTag(identity)
@ -173,6 +180,7 @@ public class MessageListActivity extends AppCompatActivity
selectedLabel = (Label) item.getTag(); selectedLabel = (Label) item.getTag();
((MessageListFragment) getSupportFragmentManager() ((MessageListFragment) getSupportFragmentManager()
.findFragmentById(R.id.message_list)).updateList(selectedLabel); .findFragmentById(R.id.message_list)).updateList(selectedLabel);
return true;
} else if (item instanceof Nameable<?>) { } else if (item instanceof Nameable<?>) {
Nameable<?> ni = (Nameable<?>) item; Nameable<?> ni = (Nameable<?>) item;
switch (ni.getNameRes()) { switch (ni.getNameRes()) {
@ -237,7 +245,6 @@ public class MessageListActivity extends AppCompatActivity
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.replace(R.id.message_detail_container, fragment) .replace(R.id.message_detail_container, fragment)
.commit(); .commit();
} else { } else {
// In single-pane mode, simply start the detail activity // In single-pane mode, simply start the detail activity
// for the selected item ID. // for the selected item ID.

View File

@ -82,6 +82,11 @@ public class MessageListFragment extends ListFragment {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
bmc = Singleton.getBitmessageContext(getActivity()); bmc = Singleton.getBitmessageContext(getActivity());
}
@Override
public void onResume() {
super.onResume();
updateList(((MessageListActivity) getActivity()).getSelectedLabel()); updateList(((MessageListActivity) getActivity()).getSelectedLabel());
} }

View File

@ -16,54 +16,134 @@
package ch.dissem.apps.abit; package ch.dissem.apps.abit;
import android.annotation.TargetApi;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; 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.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.BitmessageContext;
import ch.dissem.bitmessage.entity.Plaintext; 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.
* <p>
* Should show a notification when the app isn't running, but update the message list when it is. Also,
* notifications should be combined.
* </p>
*/ */
public class MessageListener implements BitmessageContext.Listener { public class MessageListener implements BitmessageContext.Listener {
private static final StyleSpan SPAN_EMPHASIS = new StyleSpan(Typeface.BOLD);
private final Context ctx; private final Context ctx;
private final NotificationManager manager; private final NotificationManager manager;
private final LinkedList<Plaintext> unacknowledged = new LinkedList<>();
private final int pictureSize;
private int numberOfUnacknowledgedMessages = 0;
public MessageListener(Context ctx) { public MessageListener(Context ctx) {
this.ctx = ctx.getApplicationContext(); this.ctx = ctx.getApplicationContext();
this.manager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); 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 @Override
public void receive(final Plaintext plaintext) { public void receive(final Plaintext plaintext) {
// TODO synchronized (unacknowledged) {
// ctx.runOnUiThread(new Runnable() { unacknowledged.addFirst(plaintext);
// @Override numberOfUnacknowledgedMessages++;
// public void run() { if (unacknowledged.size() > 5) {
unacknowledged.removeLast();
}
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx); NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx);
builder.setSmallIcon(R.drawable.ic_notification_new_message) if (numberOfUnacknowledgedMessages == 1) {
.setContentTitle(plaintext.getFrom().toString()) Spannable bigText = new SpannableString(plaintext.getSubject() + "\n" + plaintext.getText());
.setContentText(plaintext.getSubject()); 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(); Intent showMessageIntent = new Intent(ctx, MessageListActivity.class);
inboxStyle.setBigContentTitle(plaintext.getFrom().toString()); showMessageIntent.putExtra(MessageListActivity.EXTRA_SHOW_MESSAGE, plaintext);
inboxStyle.setSummaryText(plaintext.getSubject()); PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT);
String text = plaintext.getText(); builder.setContentIntent(pendingIntent);
if (text.length() > 100)
inboxStyle.addLine(text.substring(0, 100) + "");
else
inboxStyle.addLine(text);
builder.setStyle(inboxStyle);
Intent intent = new Intent(ctx, MessageListActivity.class); builder.addAction(R.drawable.ic_action_reply, ctx.getString(R.string.reply), pendingIntent);
intent.putExtra(MessageListActivity.EXTRA_SHOW_MESSAGE, plaintext); builder.addAction(R.drawable.ic_action_delete, ctx.getString(R.string.delete), pendingIntent);
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, intent, 0); } else {
builder.setContentIntent(pendingIntent); 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()); 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;
}
} }
} }

View File

@ -19,6 +19,7 @@ package ch.dissem.apps.abit.repositories;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import ch.dissem.apps.abit.R; import ch.dissem.apps.abit.R;
import ch.dissem.bitmessage.InternalContext; import ch.dissem.bitmessage.InternalContext;
@ -151,6 +152,24 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
return label; 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 @Override
public List<Plaintext> findMessages(Label label) { public List<Plaintext> findMessages(Label label) {
if (label != null) { if (label != null) {
@ -258,6 +277,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
db.insertOrThrow(JOIN_TABLE_NAME, null, values); db.insertOrThrow(JOIN_TABLE_NAME, null, values);
} }
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} catch (SQLiteConstraintException e) {
LOG.trace(e.getMessage(), e);
} catch (IOException e) { } catch (IOException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
} finally { } finally {

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 B

View File

@ -2,18 +2,21 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
xmlns:fab="http://schemas.android.com/tools">
<ListView <ListView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@id/android:list" android:id="@id/android:list"
android:paddingBottom="88dp"
android:clipToPadding="false"
android:scrollbarStyle="outsideOverlay"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentBottom="true" /> android:layout_alignParentBottom="true"/>
<android.support.design.widget.FloatingActionButton <android.support.design.widget.FloatingActionButton
android:id="@+id/fab_compose_message" android:id="@+id/fab_compose_message"

View File

@ -24,4 +24,7 @@
<string name="do_import">Import</string> <string name="do_import">Import</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
<string name="broadcast">Broadcast</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> </resources>