Minor UI improvements

- added outbox label
- notification is now always removed when on main activity
- send status is now shown in message list and detail view
This commit is contained in:
Christian Basler 2016-10-27 17:37:34 +02:00
parent 7332886786
commit e249c86b79
18 changed files with 246 additions and 51 deletions

View File

@ -0,0 +1 @@
INSERT INTO Label(label, type, ord) VALUES ('Outbox', 'OUTBOX', 15);

View File

@ -22,6 +22,7 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
@ -101,10 +102,11 @@ public class AddressDetailFragment extends Fragment {
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.address, menu);
Drawables.addIcon(getActivity(), menu, R.id.write_message, GoogleMaterial.Icon.gmd_mail);
Drawables.addIcon(getActivity(), menu, R.id.share, GoogleMaterial.Icon.gmd_share);
Drawables.addIcon(getActivity(), menu, R.id.delete, GoogleMaterial.Icon.gmd_delete);
Drawables.addIcon(getActivity(), menu, R.id.export,
FragmentActivity activity = getActivity();
Drawables.addIcon(activity, menu, R.id.write_message, GoogleMaterial.Icon.gmd_mail);
Drawables.addIcon(activity, menu, R.id.share, GoogleMaterial.Icon.gmd_share);
Drawables.addIcon(activity, menu, R.id.delete, GoogleMaterial.Icon.gmd_delete);
Drawables.addIcon(activity, menu, R.id.export,
CommunityMaterial.Icon.cmd_export)
.setVisible(item != null && item.getPrivateKey() != null);
@ -185,6 +187,17 @@ public class AddressDetailFragment extends Fragment {
// Show the dummy content as text in a TextView.
if (item != null) {
FragmentActivity activity = getActivity();
if (item.isChan()) {
activity.setTitle(R.string.title_chan_detail);
} else if (item.getPrivateKey() != null) {
activity.setTitle(R.string.title_identity_detail);
} else if (item.isSubscribed()) {
activity.setTitle(R.string.title_subscription_detail);
} else {
activity.setTitle(R.string.title_contact_detail);
}
((ImageView) rootView.findViewById(R.id.avatar)).setImageDrawable(new Identicon(item));
TextView name = (TextView) rootView.findViewById(R.id.name);
name.setText(item.toString());
@ -207,8 +220,8 @@ public class AddressDetailFragment extends Fragment {
TextView address = (TextView) rootView.findViewById(R.id.address);
address.setText(item.getAddress());
address.setSelected(true);
((TextView) rootView.findViewById(R.id.stream_number)).setText(getActivity()
.getString(R.string.stream_number, item.getStream()));
((TextView) rootView.findViewById(R.id.stream_number)).setText(
getString(R.string.stream_number, item.getStream()));
if (item.getPrivateKey() == null) {
Switch active = (Switch) rootView.findViewById(R.id.active);
active.setChecked(item.isSubscribed());

View File

@ -93,13 +93,14 @@ public class AddressListFragment extends AbstractItemListFragment<BitmessageAddr
convertView = inflater.inflate(R.layout.subscription_row, parent, false);
}
BitmessageAddress item = getItem(position);
assert item != null;
((ImageView) convertView.findViewById(R.id.avatar)).setImageDrawable(new
Identicon(item));
TextView name = (TextView) convertView.findViewById(R.id.name);
name.setText(item.toString());
TextView streamNumber = (TextView) convertView.findViewById(R.id.stream_number);
streamNumber.setText(getContext().getString(R.string.stream_number, item
.getStream()));
streamNumber.setText(getContext().getString(R.string.stream_number,
item.getStream()));
convertView.findViewById(R.id.subscribed).setVisibility(item.isSubscribed() ?
View.VISIBLE : View.INVISIBLE);
return convertView;

View File

@ -37,7 +37,7 @@ public class Identicon extends Drawable {
private final boolean chan;
private final TextPaint textPaint;
public Identicon(BitmessageAddress input) {
public Identicon(@NonNull BitmessageAddress input) {
paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);

View File

@ -151,8 +151,6 @@ public class MainActivity extends AppCompatActivity
createDrawer(toolbar, labels);
Singleton.getMessageListener(this).resetNotification();
// handle intents
Intent intent = getIntent();
if (intent.hasExtra(EXTRA_SHOW_MESSAGE)) {
@ -283,7 +281,8 @@ public class MainActivity extends AppCompatActivity
.withName(label.toString())
.withTag(label);
if (label.getType() == null) {
item.withIcon(CommunityMaterial.Icon.cmd_label);
item.withIcon(CommunityMaterial.Icon.cmd_label)
.withIconColor(label.getColor());
} else {
switch (label.getType()) {
case INBOX:
@ -292,6 +291,9 @@ public class MainActivity extends AppCompatActivity
case DRAFT:
item.withIcon(CommunityMaterial.Icon.cmd_file);
break;
case OUTBOX:
item.withIcon(CommunityMaterial.Icon.cmd_outbox);
break;
case SENT:
item.withIcon(CommunityMaterial.Icon.cmd_send);
break;
@ -386,6 +388,7 @@ public class MainActivity extends AppCompatActivity
protected void onResume() {
updateUnread();
updateNodeSwitch();
Singleton.getMessageListener(this).resetNotification();
super.onResume();
}
@ -397,13 +400,10 @@ public class MainActivity extends AppCompatActivity
.withEmail(identity.getAddress())
.withTag(identity);
if (accountHeader.getProfiles() != null) {
// we know that there are 2 setting
// elements.
// we know that there are 2 setting elements.
// Set the new profile above them ;)
accountHeader.addProfile(
newProfile, accountHeader
.getProfiles().size()
- 2);
newProfile, accountHeader.getProfiles().size() - 2);
} else {
accountHeader.addProfiles(newProfile);
}

View File

@ -34,6 +34,7 @@ import java.util.Iterator;
import ch.dissem.apps.abit.listener.ActionBarListener;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.util.Assets;
import ch.dissem.apps.abit.util.Drawables;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
@ -92,9 +93,12 @@ public class MessageDetailFragment extends Fragment {
// Show the dummy content as text in a TextView.
if (item != null) {
((TextView) rootView.findViewById(R.id.subject)).setText(item.getSubject());
ImageView status = (ImageView) rootView.findViewById(R.id.status);
status.setImageResource(Assets.getStatusDrawable(item.getStatus()));
status.setContentDescription(getString(Assets.getStatusString(item.getStatus())));
BitmessageAddress sender = item.getFrom();
((ImageView) rootView.findViewById(R.id.avatar)).setImageDrawable(new Identicon
(sender));
((ImageView) rootView.findViewById(R.id.avatar))
.setImageDrawable(new Identicon(sender));
((TextView) rootView.findViewById(R.id.sender)).setText(sender.toString());
if (item.getTo() != null) {
((TextView) rootView.findViewById(R.id.recipient)).setText(item.getTo().toString());

View File

@ -41,6 +41,7 @@ import java.util.List;
import ch.dissem.apps.abit.Identicon;
import ch.dissem.apps.abit.R;
import ch.dissem.apps.abit.util.Assets;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label;
@ -82,6 +83,7 @@ public class SwipeableMessageAdapter
static class ViewHolder extends AbstractSwipeableItemViewHolder {
public final FrameLayout container;
public final ImageView avatar;
public final ImageView status;
public final TextView sender;
public final TextView subject;
public final TextView extract;
@ -90,6 +92,7 @@ public class SwipeableMessageAdapter
super(v);
container = (FrameLayout) v.findViewById(R.id.container);
avatar = (ImageView) v.findViewById(R.id.avatar);
status = (ImageView) v.findViewById(R.id.status);
sender = (TextView) v.findViewById(R.id.sender);
subject = (TextView) v.findViewById(R.id.subject);
extract = (TextView) v.findViewById(R.id.text);
@ -164,6 +167,9 @@ public class SwipeableMessageAdapter
// set data
holder.avatar.setImageDrawable(new Identicon(item.getFrom()));
holder.status.setImageResource(Assets.getStatusDrawable(item.getStatus()));
holder.status.setContentDescription(
holder.status.getContext().getString(Assets.getStatusString(item.getStatus())));
holder.sender.setText(item.getFrom().toString());
holder.subject.setText(normalizeWhitespaces(item.getSubject()));
holder.extract.setText(normalizeWhitespaces(item.getText()));

View File

@ -20,6 +20,8 @@ import android.content.Context;
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import ch.dissem.apps.abit.MainActivity;
import ch.dissem.apps.abit.notification.NewMessageNotification;
@ -38,6 +40,7 @@ public class MessageListener implements BitmessageContext.Listener {
private final Deque<Plaintext> unacknowledged = new LinkedList<>();
private int numberOfUnacknowledgedMessages = 0;
private final NewMessageNotification notification;
private final ExecutorService pool = Executors.newSingleThreadExecutor();
public MessageListener(Context ctx) {
this.notification = new NewMessageNotification(ctx);
@ -45,33 +48,32 @@ public class MessageListener implements BitmessageContext.Listener {
@Override
public void receive(final Plaintext plaintext) {
synchronized (unacknowledged) {
pool.submit(() -> {
unacknowledged.addFirst(plaintext);
numberOfUnacknowledgedMessages++;
if (unacknowledged.size() > 5) {
unacknowledged.removeLast();
}
}
if (numberOfUnacknowledgedMessages == 1) {
notification.singleNotification(plaintext);
} else {
notification.multiNotification(unacknowledged, numberOfUnacknowledgedMessages);
}
notification.show();
if (numberOfUnacknowledgedMessages == 1) {
notification.singleNotification(plaintext);
} else {
notification.multiNotification(unacknowledged, numberOfUnacknowledgedMessages);
}
notification.show();
// If MainActivity is shown, update the sidebar badges
MainActivity main = MainActivity.getInstance();
if (main != null) {
main.updateUnread();
}
// If MainActivity is shown, update the sidebar badges
MainActivity main = MainActivity.getInstance();
if (main != null) {
main.updateUnread();
}
});
}
public void resetNotification() {
notification.hide();
synchronized (unacknowledged) {
pool.submit(() -> {
notification.hide();
unacknowledged.clear();
numberOfUnacknowledgedMessages = 0;
}
});
}
}

View File

@ -123,6 +123,9 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
case DRAFT:
text = context.getString(R.string.draft);
break;
case OUTBOX:
text = context.getString(R.string.outbox);
break;
case SENT:
text = context.getString(R.string.sent);
break;
@ -197,7 +200,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
TABLE_NAME, projection,
where,
null, null, null,
COLUMN_RECEIVED + " DESC"
COLUMN_RECEIVED + " DESC, " + COLUMN_SENT + " DESC"
)) {
while (c.moveToNext()) {
byte[] iv = c.getBlob(c.getColumnIndex(COLUMN_IV));

View File

@ -27,7 +27,7 @@ import ch.dissem.apps.abit.util.Assets;
*/
public class SqlHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version.
private static final int DATABASE_VERSION = 5;
private static final int DATABASE_VERSION = 6;
private static final String DATABASE_NAME = "jabit.db";
private final Context ctx;
@ -59,6 +59,8 @@ public class SqlHelper extends SQLiteOpenHelper {
executeMigration(db, "V3.2__Update_table_message");
case 4:
executeMigration(db, "V3.3__Create_table_node");
case 5:
executeMigration(db, "V3.4__Add_label_outbox");
default:
// Nothing to do. Let's assume we won't upgrade from a version that's newer than
// DATABASE_VERSION.

View File

@ -17,6 +17,8 @@
package ch.dissem.apps.abit.util;
import android.content.Context;
import android.support.annotation.DrawableRes;
import android.support.annotation.StringRes;
import java.io.IOException;
import java.io.InputStream;
@ -24,6 +26,9 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
import ch.dissem.apps.abit.R;
import ch.dissem.bitmessage.entity.Plaintext;
/**
* Helper class to work with Assets.
*/
@ -44,4 +49,44 @@ public class Assets {
throw new RuntimeException(e);
}
}
@DrawableRes
public static int getStatusDrawable(Plaintext.Status status) {
switch (status) {
case RECEIVED:
return 0;
case DRAFT:
return R.drawable.draft;
case PUBKEY_REQUESTED:
return R.drawable.public_key;
case DOING_PROOF_OF_WORK:
return R.drawable.ic_notification_proof_of_work;
case SENT:
return R.drawable.sent;
case SENT_ACKNOWLEDGED:
return R.drawable.sent_acknowledged;
default:
return 0;
}
}
@StringRes
public static int getStatusString(Plaintext.Status status) {
switch (status) {
case RECEIVED:
return R.string.status_received;
case DRAFT:
return R.string.status_draft;
case PUBKEY_REQUESTED:
return R.string.status_public_key;
case DOING_PROOF_OF_WORK:
return R.string.proof_of_work_title;
case SENT:
return R.string.status_sent;
case SENT_ACKNOWLEDGED:
return R.string.status_sent_acknowledged;
default:
return 0;
}
}
}

View File

@ -0,0 +1,25 @@
<!--
~ Copyright 2016 Christian Basler
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View File

@ -0,0 +1,25 @@
<!--
~ Copyright 2016 Christian Basler
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
</vector>

View File

@ -0,0 +1,25 @@
<!--
~ Copyright 2016 Christian Basler
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zM22.24,5.59L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/>
</vector>

View File

@ -2,34 +2,47 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:focusableInTouchMode="true"
android:orientation="vertical">
<TextView
android:id="@+id/subject"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_toStartOf="@+id/status"
android:elegantTextHeight="false"
android:enabled="false"
android:gravity="center_vertical"
android:padding="16dp"
tools:text="Subject"
android:textAppearance="?android:attr/textAppearanceLarge"
tools:ignore="UnusedAttribute"/>
tools:ignore="UnusedAttribute"
tools:text="Subject"/>
<ImageView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:tint="@color/colorAccent"
tools:src="@drawable/ic_notification_proof_of_work"
android:padding="16dp"
tools:ignore="ContentDescription"/>
<View
android:id="@+id/divider"
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_below="@id/subject"
android:background="@color/divider" />
android:background="@color/divider"/>
<ImageView
android:id="@+id/avatar"
@ -50,8 +63,8 @@
android:gravity="center_vertical"
android:paddingLeft="8dp"
android:paddingRight="8dp"
tools:text="Sender"
android:textStyle="bold" />
android:textStyle="bold"
tools:text="Sender"/>
<TextView
android:id="@+id/recipient"
@ -62,7 +75,7 @@
android:gravity="center_vertical"
android:paddingLeft="8dp"
android:paddingRight="8dp"
tools:text="Recipient" />
tools:text="Recipient"/>
<TextView
android:id="@+id/text"
@ -75,6 +88,6 @@
android:layout_marginTop="32dp"
android:paddingBottom="64dp"
android:text="New Text"
android:textIsSelectable="true" />
android:textIsSelectable="true"/>
</RelativeLayout>
</ScrollView>

View File

@ -44,7 +44,7 @@
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_margin="16dp"
android:src="@color/colorAccent"
android:src="@color/colorPrimaryDark"
tools:ignore="ContentDescription"/>
<TextView
@ -96,6 +96,18 @@
android:textAppearance="?android:attr/textAppearanceSmall"
tools:text="Text"/>
<ImageView
android:id="@+id/status"
android:layout_width="24dp"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/avatar"
android:layout_alignEnd="@+id/avatar"
android:layout_marginBottom="-8dp"
android:layout_marginEnd="-8dp"
android:tint="@color/colorAccent"
tools:ignore="ContentDescription"
tools:src="@drawable/ic_notification_proof_of_work"/>
</RelativeLayout>
</FrameLayout>

View File

@ -101,4 +101,13 @@ Als Alternative kann in den Einstellungen ein vertrauenswürdiger Knoten konfigu
<string name="startup_node">Knoten starten</string>
<string name="personal_message">Nachricht</string>
<string name="no_identity_warning">Bitte versuchs nochmals wenn eine Identität verfügbar ist.</string>
<string name="title_chan_detail">Chan</string>
<string name="title_contact_detail">Kontakt</string>
<string name="title_identity_detail">Identität</string>
<string name="outbox">Postausgang</string>
<string name="status_draft">Entwurf</string>
<string name="status_public_key">öffentlicher Schlüssel angefordert</string>
<string name="status_received">empfangen</string>
<string name="status_sent">gesendet</string>
<string name="status_sent_acknowledged">Empfang bestätigt</string>
</resources>

View File

@ -1,8 +1,11 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">Abit</string>
<string name="about_app">A Bitmessage client for Android</string>
<string name="title_message_detail">Message Detail</string>
<string name="title_subscription_detail">Subscription Detail</string>
<string name="title_message_detail">Message</string>
<string name="title_subscription_detail">Subscription</string>
<string name="title_chan_detail">Chan</string>
<string name="title_identity_detail">Identity</string>
<string name="title_contact_detail">Contact</string>
<string name="bitmessage_full_node">Bitmessage Node</string>
<string name="settings">Settings</string>
<string name="wifi_only">Wi-Fi only</string>
@ -100,4 +103,10 @@ As an alternative you could configure a trusted node in the settings, but as of
<string name="startup_node">Startup node</string>
<string name="personal_message">Message</string>
<string name="no_identity_warning">Please try again once an identity is available.</string>
<string name="status_public_key">public key requested</string>
<string name="status_sent_acknowledged">acknowledged</string>
<string name="status_draft">draft</string>
<string name="status_sent">sent</string>
<string name="status_received">received</string>
<string name="outbox">Outbox</string>
</resources>