Added ongoing notification showing network status

- renamed packages to be more consistent
- somewhate refactored the way notifications are made
This commit is contained in:
Christian Basler 2015-10-18 13:40:17 +02:00
parent 1659aaa1ee
commit 13cb804fc2
19 changed files with 262 additions and 82 deletions

View File

@ -21,7 +21,7 @@ import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.ListView;
import ch.dissem.apps.abit.listeners.ListSelectionListener;
import ch.dissem.apps.abit.listener.ListSelectionListener;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.valueobject.Label;

View File

@ -7,7 +7,7 @@ import android.view.*;
import android.widget.ImageView;
import android.widget.TextView;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.utils.Drawables;
import ch.dissem.apps.abit.util.Drawables;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;

View File

@ -10,13 +10,13 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import ch.dissem.apps.abit.listeners.ActionBarListener;
import ch.dissem.apps.abit.listeners.ListSelectionListener;
import ch.dissem.apps.abit.listener.ActionBarListener;
import ch.dissem.apps.abit.listener.ListSelectionListener;
import ch.dissem.apps.abit.notification.NetworkNotification;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.entity.valueobject.Label;
import com.mikepenz.community_material_typeface_library.CommunityMaterial;
@ -39,6 +39,8 @@ import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
/**
@ -288,6 +290,7 @@ public class MessageListActivity extends AppCompatActivity
switch (item.getItemId()) {
case R.id.sync_disabled:
bmc.startup();
new NetworkNotification(this).show();
updateMenu();
return true;
case R.id.sync_enabled:

View File

@ -1,21 +1,15 @@
package ch.dissem.apps.abit;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.ListFragment;
import android.view.*;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import ch.dissem.apps.abit.listeners.ActionBarListener;
import ch.dissem.apps.abit.listeners.ListSelectionListener;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.apps.abit.listener.ActionBarListener;
import ch.dissem.apps.abit.listener.ListSelectionListener;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.MessageRepository;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package ch.dissem.apps.abit.listeners;
package ch.dissem.apps.abit.listener;
/**
* Created by chris on 06.09.15.

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package ch.dissem.apps.abit.listeners;
package ch.dissem.apps.abit.listener;
/**
* A callback interface that all activities containing this fragment must

View File

@ -14,10 +14,9 @@
* limitations under the License.
*/
package ch.dissem.apps.abit.listeners;
package ch.dissem.apps.abit.listener;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
@ -34,9 +33,11 @@ import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.StyleSpan;
import ch.dissem.apps.abit.Identicon;
import ch.dissem.apps.abit.MessageListActivity;
import ch.dissem.apps.abit.R;
import ch.dissem.apps.abit.notification.NewMessageNotification;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.Plaintext;
@ -50,18 +51,17 @@ import java.util.LinkedList;
* </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;
private final NewMessageNotification notification;
public MessageListener(Context ctx) {
this.ctx = ctx.getApplicationContext();
this.manager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);
this.pictureSize = getMaxContactPhotoSize(ctx);
this.notification = new NewMessageNotification(ctx);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@ -94,57 +94,15 @@ public class MessageListener implements BitmessageContext.Listener {
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())
.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);
notification.singleNotification(plaintext);
} 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);
notification.multiNotification(unacknowledged, numberOfUnacknowledgedMessages);
}
}
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;
notification.show();
}
public void resetNotification() {
manager.cancel(0);
notification.hide();
synchronized (unacknowledged) {
unacknowledged.clear();
numberOfUnacknowledgedMessages = 0;

View File

@ -0,0 +1,33 @@
package ch.dissem.apps.abit.notification;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
/**
* Some base class to create and handle notifications.
*/
public abstract class AbstractNotification {
protected final Context ctx;
protected final NotificationManager manager;
public Notification notification;
public AbstractNotification(Context ctx) {
this.ctx = ctx;
this.manager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);
}
/**
* @return an id unique to this notification class
*/
protected abstract int getNotificationId();
public void show() {
manager.notify(getNotificationId(), notification);
}
public void hide() {
manager.cancel(getNotificationId());
}
}

View File

@ -0,0 +1,87 @@
package ch.dissem.apps.abit.notification;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v7.app.NotificationCompat;
import java.util.Timer;
import java.util.TimerTask;
import ch.dissem.apps.abit.MessageListActivity;
import ch.dissem.apps.abit.R;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.utils.Property;
/**
* Shows the network status (as long as the client is connected as a full node)
*/
public class NetworkNotification extends AbstractNotification {
private final BitmessageContext bmc;
private NotificationCompat.Builder builder;
public NetworkNotification(Context ctx) {
super(ctx);
builder = new NotificationCompat.Builder(ctx);
builder.setSmallIcon(R.drawable.ic_notification_new_message)
.setContentTitle(ctx.getString(R.string.bitmessage_active));
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
bmc = Singleton.getBitmessageContext(ctx);
}
@SuppressLint("StringFormatMatches")
private boolean update() {
boolean running = bmc.isRunning();
builder.setOngoing(running);
Property connections = bmc.status().getProperty("network").getProperty("connections");
if (!running) {
builder.setContentText(ctx.getString(R.string.connection_info_disconnected));
} else if (connections.getProperties().length == 0) {
builder.setContentText(ctx.getString(R.string.connection_info_pending));
} else {
StringBuilder info = new StringBuilder();
for (Property stream : connections.getProperties()) {
int streamNumber = Integer.parseInt(stream.getName().substring("stream ".length()));
Integer nodeCount = (Integer) stream.getProperty("nodes").getValue();
if (nodeCount == 1) {
info.append(ctx.getString(R.string.connection_info_1,
streamNumber));
} else {
info.append(ctx.getString(R.string.connection_info_n,
streamNumber, nodeCount));
}
info.append('\n');
}
builder.setContentText(info);
}
Intent showMessageIntent = new Intent(ctx, MessageListActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, showMessageIntent, 0);
builder.setContentIntent(pendingIntent);
notification = builder.build();
return running;
}
@Override
public void show() {
update();
super.show();
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if (!update()) {
cancel();
}
NetworkNotification.super.show();
}
}, 10_000, 10_000);
}
@Override
protected int getNotificationId() {
return 2;
}
}

View File

@ -0,0 +1,81 @@
package ch.dissem.apps.abit.notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.support.v7.app.NotificationCompat;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.StyleSpan;
import java.util.LinkedList;
import ch.dissem.apps.abit.Identicon;
import ch.dissem.apps.abit.MessageListActivity;
import ch.dissem.apps.abit.R;
import ch.dissem.bitmessage.entity.Plaintext;
import static ch.dissem.apps.abit.util.Drawables.toBitmap;
public class NewMessageNotification extends AbstractNotification {
private static final StyleSpan SPAN_EMPHASIS = new StyleSpan(Typeface.BOLD);
public NewMessageNotification(Context ctx) {
super(ctx);
}
public NewMessageNotification singleNotification(Plaintext plaintext) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx);
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()), 192))
.setContentTitle(plaintext.getFrom().toString())
.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);
notification = builder.build();
return this;
}
public NewMessageNotification multiNotification(LinkedList<Plaintext> unacknowledged, int numberOfUnacknowledgedMessages) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx);
builder.setSmallIcon(R.drawable.ic_notification_new_message)
.setContentTitle(ctx.getString(R.string.n_new_messages, 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);
notification = builder.build();
return this;
}
@Override
protected int getNotificationId() {
return 1;
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package ch.dissem.apps.abit.repositories;
package ch.dissem.apps.abit.repository;
import android.content.ContentValues;
import android.database.Cursor;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package ch.dissem.apps.abit.repositories;
package ch.dissem.apps.abit.repository;
import android.content.ContentValues;
import android.database.Cursor;
@ -36,7 +36,7 @@ import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import static ch.dissem.apps.abit.repositories.SqlHelper.join;
import static ch.dissem.apps.abit.repository.SqlHelper.join;
import static ch.dissem.bitmessage.utils.UnixTime.now;
/**

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package ch.dissem.apps.abit.repositories;
package ch.dissem.apps.abit.repository;
import android.content.ContentValues;
import android.content.Context;
@ -40,7 +40,7 @@ import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import static ch.dissem.apps.abit.repositories.SqlHelper.join;
import static ch.dissem.apps.abit.repository.SqlHelper.join;
/**
* {@link MessageRepository} implementation using the Android SQL API.

View File

@ -14,12 +14,12 @@
* limitations under the License.
*/
package ch.dissem.apps.abit.repositories;
package ch.dissem.apps.abit.repository;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import ch.dissem.apps.abit.utils.Assets;
import ch.dissem.apps.abit.util.Assets;
/**
* Handles database migration and provides access.

View File

@ -1,13 +1,12 @@
package ch.dissem.apps.abit.service;
import android.app.NotificationManager;
import android.content.Context;
import ch.dissem.apps.abit.listeners.MessageListener;
import ch.dissem.apps.abit.repositories.AndroidAddressRepository;
import ch.dissem.apps.abit.repositories.AndroidInventory;
import ch.dissem.apps.abit.repositories.AndroidMessageRepository;
import ch.dissem.apps.abit.repositories.SqlHelper;
import ch.dissem.apps.abit.listener.MessageListener;
import ch.dissem.apps.abit.repository.AndroidAddressRepository;
import ch.dissem.apps.abit.repository.AndroidInventory;
import ch.dissem.apps.abit.repository.AndroidMessageRepository;
import ch.dissem.apps.abit.repository.SqlHelper;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.ports.MemoryNodeRegistry;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package ch.dissem.apps.abit.utils;
package ch.dissem.apps.abit.util;
import android.content.Context;

View File

@ -14,11 +14,16 @@
* limitations under the License.
*/
package ch.dissem.apps.abit.utils;
package ch.dissem.apps.abit.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.view.Menu;
import ch.dissem.apps.abit.Identicon;
import ch.dissem.apps.abit.R;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.iconics.IconicsDrawable;
@ -29,4 +34,16 @@ public class Drawables {
public static void addIcon(Context ctx, Menu menu, int menuItem, GoogleMaterial.Icon icon) {
menu.findItem(menuItem).setIcon(new IconicsDrawable(ctx, icon).colorRes(R.color.primary_text_default_material_dark).actionBar());
}
public static Bitmap toBitmap(Identicon identicon, int size) {
return toBitmap(identicon, size, size);
}
public static Bitmap toBitmap(Identicon identicon, int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
identicon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
identicon.draw(canvas);
return bitmap;
}
}

View File

@ -30,7 +30,11 @@
<string name="archive">Archiv</string>
<string name="empty_trash">Papierkorb leeren</string>
<string name="mark_unread">Als ungelesen markieren</string>
<string name="stream_number">Stream #%d</string>
<string name="stream_number">Stream %d</string>
<string name="enabled">Aktiv</string>
<string name="title_subscription_detail">Abonnement</string>
<string name="connection_info_1">Stream %1$d: eine Verbindung</string>
<string name="connection_info_n">Stream %1$d: %2$d Verbindungen</string>
<string name="connection_info_disconnected">Getrennt</string>
<string name="connection_info_pending">Verbindung wird aufgebaut…</string>
</resources>

View File

@ -18,6 +18,8 @@
<string name="title_activity_open_bitmessage_link">Import Contact</string>
<string name="action_settings">Settings</string>
<string name="connection_info_1">Stream #%1$d: one connection</string>
<string name="connection_info_n">Stream #%1$d: %2$d connections</string>
<string name="import_address">Import Address</string>
<string name="import_contact">Add to contacts</string>
<string name="label">Label</string>
@ -33,4 +35,6 @@
<string name="empty_trash">Empty Trash</string>
<string name="stream_number">Stream #%d</string>
<string name="enabled">Enabled</string>
<string name="connection_info_disconnected">Disconnected</string>
<string name="connection_info_pending">Connecting…</string>
</resources>