Notifications could still use some fine tuning, but should work fine for now
@ -6,6 +6,8 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<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
|
||||
android:allowBackup="true"
|
||||
|
@ -29,6 +29,9 @@ public class Identicon extends Drawable {
|
||||
private static final int CENTER_COLUMN = 5;
|
||||
|
||||
private final Paint paint;
|
||||
private float width;
|
||||
private float height;
|
||||
|
||||
private float cellWidth;
|
||||
private float cellHeight;
|
||||
private byte[] hash;
|
||||
@ -54,15 +57,11 @@ public class Identicon extends Drawable {
|
||||
}
|
||||
}
|
||||
|
||||
protected byte getByte(int index) {
|
||||
return hash[index % hash.length];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
float x, y;
|
||||
paint.setColor(background);
|
||||
canvas.drawPaint(paint);
|
||||
canvas.drawCircle(width/2, height/2, width/2, paint);
|
||||
paint.setColor(color);
|
||||
for (int row = 0; row < SIZE; row++) {
|
||||
for (int column = 0; column < SIZE; column++) {
|
||||
@ -88,13 +87,16 @@ public class Identicon extends Drawable {
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.OPAQUE;
|
||||
return PixelFormat.TRANSPARENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
|
||||
width = bounds.width();
|
||||
height = bounds.height();
|
||||
|
||||
cellWidth = bounds.width() / (float) SIZE;
|
||||
cellHeight = bounds.height() / (float) SIZE;
|
||||
}
|
||||
|
@ -52,7 +52,8 @@ import java.util.ArrayList;
|
||||
*/
|
||||
public class MessageListActivity extends AppCompatActivity
|
||||
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 int ADD_IDENTITY = 1;
|
||||
@ -95,7 +96,12 @@ public class MessageListActivity extends AppCompatActivity
|
||||
|
||||
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) {
|
||||
@ -103,6 +109,7 @@ public class MessageListActivity extends AppCompatActivity
|
||||
for (BitmessageAddress identity : bmc.addresses().getIdentities()) {
|
||||
LOG.info("Adding identity " + identity.getAddress());
|
||||
profiles.add(new ProfileDrawerItem()
|
||||
.withIcon(new Identicon(identity))
|
||||
.withName(identity.toString())
|
||||
.withEmail(identity.getAddress())
|
||||
.withTag(identity)
|
||||
@ -173,6 +180,7 @@ public class MessageListActivity extends AppCompatActivity
|
||||
selectedLabel = (Label) item.getTag();
|
||||
((MessageListFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.message_list)).updateList(selectedLabel);
|
||||
return true;
|
||||
} else if (item instanceof Nameable<?>) {
|
||||
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.
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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.
|
||||
* <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 {
|
||||
private static final StyleSpan SPAN_EMPHASIS = new StyleSpan(Typeface.BOLD);
|
||||
private final Context ctx;
|
||||
private final NotificationManager manager;
|
||||
private final LinkedList<Plaintext> 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);
|
||||
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());
|
||||
.setContentText(plaintext.getSubject())
|
||||
.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText))
|
||||
.setContentInfo("Info");
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
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);
|
||||
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.putExtra(MessageListActivity.EXTRA_SHOW_MESSAGE, plaintext);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, intent, 0);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
BIN
app/src/main/res/drawable-hdpi/ic_action_delete.png
Normal file
After Width: | Height: | Size: 642 B |
BIN
app/src/main/res/drawable-hdpi/ic_action_reply.png
Normal file
After Width: | Height: | Size: 486 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_delete.png
Normal file
After Width: | Height: | Size: 428 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_reply.png
Normal file
After Width: | Height: | Size: 330 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_delete.png
Normal file
After Width: | Height: | Size: 775 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_reply.png
Normal file
After Width: | Height: | Size: 623 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_delete.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_reply.png
Normal file
After Width: | Height: | Size: 929 B |
@ -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"
|
||||
|
@ -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>
|
||||
|